134 lines
4.3 KiB
JavaScript
134 lines
4.3 KiB
JavaScript
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const DEFAULT_CONFIG_NAME = 'models.json';
|
||
const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '..', DEFAULT_CONFIG_NAME);
|
||
|
||
function isNonEmptyString(value) {
|
||
return typeof value === 'string' && value.trim().length > 0;
|
||
}
|
||
|
||
function parsePositiveInt(value) {
|
||
if (value === null || value === undefined || value === '') return null;
|
||
const num = Number(value);
|
||
if (!Number.isFinite(num)) return null;
|
||
const out = Math.floor(num);
|
||
return out > 0 ? out : null;
|
||
}
|
||
|
||
function normalizeModes(value) {
|
||
if (!value) return null;
|
||
const text = String(value).trim().toLowerCase();
|
||
if (!text) return null;
|
||
const parts = text.split(/[,,\s]+/).filter(Boolean);
|
||
const hasFast = parts.some((p) => p.includes('fast') || p.includes('快速'));
|
||
const hasThinking = parts.some((p) => p.includes('thinking') || p.includes('思考'));
|
||
if (hasFast && hasThinking) return 'fast+thinking';
|
||
if (hasThinking) return 'thinking';
|
||
if (hasFast) return 'fast';
|
||
return null;
|
||
}
|
||
|
||
function normalizeMultimodal(value) {
|
||
if (value === null || value === undefined) return 'none';
|
||
const text = String(value).trim().toLowerCase();
|
||
if (!text) return 'none';
|
||
if (text.includes('无') || text.includes('none') || text.includes('no')) return 'none';
|
||
const parts = text.split(/[,,\s]+/).filter(Boolean);
|
||
const hasImage = parts.some((p) => p.includes('图片') || p.includes('image'));
|
||
const hasVideo = parts.some((p) => p.includes('视频') || p.includes('video'));
|
||
if (hasVideo && hasImage) return 'image+video';
|
||
if (hasVideo) return 'image+video';
|
||
if (hasImage) return 'image';
|
||
return null;
|
||
}
|
||
|
||
function normalizeModel(raw) {
|
||
const name = String(raw.name || raw.model_name || raw.model || '').trim();
|
||
const url = String(raw.url || raw.base_url || '').trim();
|
||
const apiKey = String(raw.apikey || raw.api_key || '').trim();
|
||
const modes = normalizeModes(raw.modes || raw.mode || raw.supported_modes);
|
||
const multimodal = normalizeMultimodal(raw.multimodal || raw.multi_modal || raw.multi);
|
||
const maxOutput = parsePositiveInt(raw.max_output ?? raw.max_tokens ?? raw.max_output_tokens);
|
||
const maxContext = parsePositiveInt(raw.max_context ?? raw.context_window ?? raw.max_context_tokens);
|
||
const valid = Boolean(
|
||
isNonEmptyString(name)
|
||
&& isNonEmptyString(url)
|
||
&& isNonEmptyString(apiKey)
|
||
&& modes
|
||
&& multimodal
|
||
&& maxOutput
|
||
&& maxContext
|
||
);
|
||
return {
|
||
key: name,
|
||
name,
|
||
model_id: name,
|
||
base_url: url,
|
||
api_key: apiKey,
|
||
modes,
|
||
multimodal,
|
||
max_output: maxOutput,
|
||
max_context: maxContext,
|
||
valid,
|
||
};
|
||
}
|
||
|
||
function buildConfig(raw, filePath) {
|
||
const modelsRaw = Array.isArray(raw.models) ? raw.models : [];
|
||
const models = modelsRaw.map((item) => normalizeModel(item || {}));
|
||
const modelMap = new Map();
|
||
models.forEach((model) => {
|
||
if (model.key) modelMap.set(model.key, model);
|
||
});
|
||
const validModels = models.filter((model) => model.valid);
|
||
const defaultModelKey = String(raw.default_model || raw.default_model_key || '').trim();
|
||
const resolvedDefault = modelMap.get(defaultModelKey)?.valid
|
||
? defaultModelKey
|
||
: (validModels[0] ? validModels[0].key : '');
|
||
return {
|
||
path: filePath,
|
||
tavily_api_key: String(raw.tavily_api_key || '').trim(),
|
||
models,
|
||
valid_models: validModels,
|
||
model_map: modelMap,
|
||
default_model_key: resolvedDefault,
|
||
};
|
||
}
|
||
|
||
function ensureConfig() {
|
||
const file = DEFAULT_CONFIG_PATH;
|
||
if (!fs.existsSync(file)) {
|
||
const template = {
|
||
tavily_api_key: '',
|
||
default_model: '',
|
||
models: [],
|
||
};
|
||
fs.writeFileSync(file, JSON.stringify(template, null, 2), 'utf8');
|
||
}
|
||
const content = fs.readFileSync(file, 'utf8');
|
||
const raw = JSON.parse(content);
|
||
return buildConfig(raw, file);
|
||
}
|
||
|
||
function maskKey(key) {
|
||
if (!key) return '';
|
||
if (key.length <= 8) return key;
|
||
return `${key.slice(0, 3)}...${key.slice(-3)}`;
|
||
}
|
||
|
||
function getModelByKey(config, key) {
|
||
if (!config || !key) return null;
|
||
if (config.model_map && typeof config.model_map.get === 'function') {
|
||
return config.model_map.get(key) || null;
|
||
}
|
||
if (Array.isArray(config.models)) {
|
||
return config.models.find((m) => m && m.key === key) || null;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
module.exports = { ensureConfig, maskKey, getModelByKey };
|