198 lines
5.1 KiB
TypeScript
198 lines
5.1 KiB
TypeScript
import toml from '@iarna/toml';
|
|
|
|
// Use dynamic imports for Node.js modules to prevent client-side errors
|
|
let fs: any;
|
|
let path: any;
|
|
if (typeof window === 'undefined') {
|
|
// We're on the server
|
|
fs = require('fs');
|
|
path = require('path');
|
|
}
|
|
|
|
const configFileName = 'config.toml';
|
|
|
|
interface Config {
|
|
GENERAL: {
|
|
SIMILARITY_MEASURE: string;
|
|
KEEP_ALIVE: string;
|
|
BASE_URL?: string;
|
|
HIDDEN_MODELS: string[];
|
|
};
|
|
MODELS: {
|
|
OPENAI: {
|
|
API_KEY: string;
|
|
};
|
|
GROQ: {
|
|
API_KEY: string;
|
|
};
|
|
ANTHROPIC: {
|
|
API_KEY: string;
|
|
};
|
|
GEMINI: {
|
|
API_KEY: string;
|
|
};
|
|
OLLAMA: {
|
|
API_URL: string;
|
|
};
|
|
DEEPSEEK: {
|
|
API_KEY: string;
|
|
};
|
|
AIMLAPI: {
|
|
API_KEY: string;
|
|
};
|
|
LM_STUDIO: {
|
|
API_URL: string;
|
|
};
|
|
OPENROUTER: {
|
|
API_KEY: string;
|
|
};
|
|
CUSTOM_OPENAI: {
|
|
API_URL: string;
|
|
API_KEY: string;
|
|
MODEL_NAME: string;
|
|
};
|
|
};
|
|
API_ENDPOINTS: {
|
|
SEARXNG: string;
|
|
};
|
|
}
|
|
|
|
type RecursivePartial<T> = {
|
|
[P in keyof T]?: RecursivePartial<T[P]>;
|
|
};
|
|
|
|
const loadConfig = () => {
|
|
// Server-side only
|
|
if (typeof window === 'undefined') {
|
|
const config = toml.parse(
|
|
fs.readFileSync(path.join(process.cwd(), `${configFileName}`), 'utf-8'),
|
|
) as any as Config;
|
|
|
|
// Ensure GENERAL section exists
|
|
if (!config.GENERAL) {
|
|
config.GENERAL = {} as any;
|
|
}
|
|
|
|
// Handle HIDDEN_MODELS - fix malformed table format to proper array
|
|
if (!config.GENERAL.HIDDEN_MODELS) {
|
|
config.GENERAL.HIDDEN_MODELS = [];
|
|
} else if (
|
|
typeof config.GENERAL.HIDDEN_MODELS === 'object' &&
|
|
!Array.isArray(config.GENERAL.HIDDEN_MODELS)
|
|
) {
|
|
// Convert malformed table format to array
|
|
const hiddenModelsObj = config.GENERAL.HIDDEN_MODELS as any;
|
|
const hiddenModelsArray: string[] = [];
|
|
|
|
// Extract values from numeric keys and sort by key
|
|
const keys = Object.keys(hiddenModelsObj)
|
|
.map((k) => parseInt(k))
|
|
.filter((k) => !isNaN(k))
|
|
.sort((a, b) => a - b);
|
|
for (const key of keys) {
|
|
if (typeof hiddenModelsObj[key] === 'string') {
|
|
hiddenModelsArray.push(hiddenModelsObj[key]);
|
|
}
|
|
}
|
|
|
|
config.GENERAL.HIDDEN_MODELS = hiddenModelsArray;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
// Client-side fallback - settings will be loaded via API
|
|
return {} as Config;
|
|
};
|
|
|
|
export const getSimilarityMeasure = () =>
|
|
loadConfig().GENERAL.SIMILARITY_MEASURE;
|
|
|
|
export const getKeepAlive = () => loadConfig().GENERAL.KEEP_ALIVE;
|
|
|
|
export const getBaseUrl = () => loadConfig().GENERAL.BASE_URL;
|
|
|
|
export const getHiddenModels = () => loadConfig().GENERAL.HIDDEN_MODELS;
|
|
|
|
export const getOpenaiApiKey = () => loadConfig().MODELS.OPENAI.API_KEY;
|
|
|
|
export const getGroqApiKey = () => loadConfig().MODELS.GROQ.API_KEY;
|
|
|
|
export const getOpenrouterApiKey = () => loadConfig().MODELS.OPENROUTER.API_KEY;
|
|
|
|
export const getAnthropicApiKey = () => loadConfig().MODELS.ANTHROPIC.API_KEY;
|
|
|
|
export const getGeminiApiKey = () => loadConfig().MODELS.GEMINI.API_KEY;
|
|
|
|
export const getSearxngApiEndpoint = () =>
|
|
process.env.SEARXNG_API_URL || loadConfig().API_ENDPOINTS.SEARXNG;
|
|
|
|
export const getOllamaApiEndpoint = () => loadConfig().MODELS.OLLAMA.API_URL;
|
|
|
|
export const getDeepseekApiKey = () => loadConfig().MODELS.DEEPSEEK.API_KEY;
|
|
|
|
export const getAimlApiKey = () => loadConfig().MODELS.AIMLAPI.API_KEY;
|
|
|
|
export const getCustomOpenaiApiKey = () =>
|
|
loadConfig().MODELS.CUSTOM_OPENAI.API_KEY;
|
|
|
|
export const getCustomOpenaiApiUrl = () =>
|
|
loadConfig().MODELS.CUSTOM_OPENAI.API_URL;
|
|
|
|
export const getCustomOpenaiModelName = () =>
|
|
loadConfig().MODELS.CUSTOM_OPENAI.MODEL_NAME;
|
|
|
|
export const getLMStudioApiEndpoint = () =>
|
|
loadConfig().MODELS.LM_STUDIO.API_URL;
|
|
|
|
const mergeConfigs = (current: any, update: any): any => {
|
|
if (update === null || update === undefined) {
|
|
return current;
|
|
}
|
|
|
|
if (typeof current !== 'object' || current === null) {
|
|
return update;
|
|
}
|
|
|
|
// Handle arrays specifically - don't merge them, replace them
|
|
if (Array.isArray(update)) {
|
|
return update;
|
|
}
|
|
|
|
const result = { ...current };
|
|
|
|
for (const key in update) {
|
|
if (Object.prototype.hasOwnProperty.call(update, key)) {
|
|
const updateValue = update[key];
|
|
|
|
// Handle arrays specifically - don't merge them, replace them
|
|
if (Array.isArray(updateValue)) {
|
|
result[key] = updateValue;
|
|
} else if (
|
|
typeof updateValue === 'object' &&
|
|
updateValue !== null &&
|
|
typeof result[key] === 'object' &&
|
|
result[key] !== null &&
|
|
!Array.isArray(result[key])
|
|
) {
|
|
result[key] = mergeConfigs(result[key], updateValue);
|
|
} else if (updateValue !== undefined) {
|
|
result[key] = updateValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
export const updateConfig = (config: RecursivePartial<Config>) => {
|
|
// Server-side only
|
|
if (typeof window === 'undefined') {
|
|
const currentConfig = loadConfig();
|
|
const mergedConfig = mergeConfigs(currentConfig, config);
|
|
fs.writeFileSync(
|
|
path.join(path.join(process.cwd(), `${configFileName}`)),
|
|
toml.stringify(mergedConfig),
|
|
);
|
|
}
|
|
};
|