docs/archives/121-multi-custom-models-support/implementation.md
用户环境变量 → 环境变量扫描 → 动态模型生成 → 模型注册 → UI显示
↓ ↓ ↓ ↓ ↓
VITE_CUSTOM_API_* scanCustom... generateDynamic getAllModels ModelSelector
环境变量扫描器 (scanCustomModelEnvVars)
动态模型生成器 (generateDynamicModels)
模型配置管理器 (getAllModels)
// 1. 环境变量扫描
const customModels = scanCustomModelEnvVars();
// 2. 动态模型生成
const dynamicModels = generateDynamicModels();
// 3. 模型合并
const allModels = { ...staticModels, ...dynamicModels };
问题描述: 担心Electron环境中环境变量在模块加载时未就绪 诊断过程:
解决方案:
问题描述: process.env[key] 检查会忽略空字符串值
诊断过程:
// 错误的检查方式
if (process.env[key]) { // 空字符串会被忽略
return process.env[key] || '';
}
// 正确的检查方式
if (process.env[key] !== undefined) { // 正确处理空字符串
return process.env[key] || '';
}
解决方案: 修改条件检查逻辑,正确处理空字符串值
问题描述: 多个模块重复定义相同的常量和逻辑 诊断过程: 发现Desktop模块重复定义了环境变量扫描常量 解决方案: 统一从core模块导入共享常量,消除重复
问题描述: echo 和 sed 的字符转义不正确
诊断过程:
echo "$value" 会解释控制字符sed 's/\n/\\n/g' 匹配字面字符串而非实际换行符解决方案: 使用 printf '%s' 替代 echo,简化转义逻辑
问题描述: 大量 NODE_ENV !== 'production' 判断是过度设计
诊断过程: 分析日志需求和调试价值
解决方案: 移除所有过度的环境判断,保持日志简洁直接
创建环境变量扫描函数
scanCustomModelEnvVars 函数修改Core模块
defaults.ts 中的模型生成逻辑electron-config.ts 保持一致性MCP Server适配
Desktop模块适配
Docker模块适配
配置验证和容错
文档和示例
env.local.example测试验证
console.log 跟踪变量传递基础功能测试
边界条件测试
兼容性测试
环境测试
export const scanCustomModelEnvVars = (): Record<string, CustomModelEnvConfig> => {
const customModels: Record<string, CustomModelEnvConfig> = {};
const customApiPattern = /^VITE_CUSTOM_API_(KEY|BASE_URL|MODEL)_(.+)$/;
// 多环境源合并
const mergedEnv = {
...getProcessEnv(),
...getRuntimeConfig(),
...getElectronEnv()
};
// 扫描和分组
Object.entries(mergedEnv).forEach(([key, value]) => {
const match = key.match(customApiPattern);
if (match) {
const [, configType, suffix] = match;
// 配置验证和分组逻辑
}
});
return customModels;
};
export function generateDynamicModels(): Record<string, ModelConfig> {
const customModelConfigs = scanCustomModelEnvVars();
const dynamicModels: Record<string, ModelConfig> = {};
Object.entries(customModelConfigs).forEach(([suffix, envConfig]) => {
// 配置验证
if (!envConfig.apiKey || !envConfig.baseURL || !envConfig.model) {
return; // 跳过不完整配置
}
// 冲突检测
const staticModelKeys = ['openai', 'gemini', 'deepseek', 'siliconflow', 'zhipu', 'custom'];
if (staticModelKeys.includes(suffix)) {
return; // 跳过冲突配置
}
// 生成模型配置
const modelKey = `custom_${suffix}`;
dynamicModels[modelKey] = generateModelConfig(envConfig);
});
return dynamicModels;
}
// 后缀名验证
const SUFFIX_PATTERN = /^[a-zA-Z0-9_-]+$/;
const MAX_SUFFIX_LENGTH = 50;
if (!suffix || suffix.length > MAX_SUFFIX_LENGTH || !SUFFIX_PATTERN.test(suffix)) {
console.warn(`Invalid suffix: ${suffix}`);
return;
}
// 配置完整性验证
if (!envConfig.apiKey) {
console.warn(`Missing API key for ${suffix}`);
return;
}