/** * LLM 配置组件 - 简化版 * 参考 OpenAI 等主流配置方式 */ app.component('llm-config-page', { props: ['config'], emits: ['save'], setup(props, { emit }) { const { ref, reactive, watch, computed } = Vue; // 当前激活的提供商标签 const activeProvider = ref('openai-compatible'); // 提供商配置状态 const providerStatus = reactive({}); // 测试状态 const testing = ref(null); const testResult = reactive({ provider: '', success: false, message: '', details: '' }); // 保存状态 const saving = ref(false); // 表单数据 - 简化为通用的 OpenAI 兼容格式 const form = reactive({ // 当前使用的配置 provider: 'openai-compatible', api_key: '', base_url: '', model: '', // 各提供商单独配置 configs: { 'openai-compatible': { name: 'OpenAI 兼容接口', api_key: '', base_url: 'https://api.openai.com/v1', model: 'gpt-4o', configured: false }, 'anthropic': { name: 'Anthropic Claude', api_key: '', base_url: 'https://api.anthropic.com', model: 'claude-sonnet-4-20250514', configured: false }, 'deepseek': { name: 'DeepSeek', api_key: '', base_url: 'https://api.deepseek.com/v1', model: 'deepseek-chat', configured: false }, 'gemini': { name: 'Google Gemini', api_key: '', base_url: '', model: 'gemini-2.5-flash-preview-05-20', configured: false } } }); // 提供商列表 const providers = [ { id: 'openai-compatible', name: 'OpenAI 兼容', icon: '🔌', description: '支持所有 OpenAI 兼容的 API(OpenAI、Azure、本地模型等)', defaultUrl: 'https://api.openai.com/v1', defaultModel: 'gpt-4o', keyPlaceholder: 'sk-xxx', domestic: false }, { id: 'anthropic', name: 'Anthropic', icon: '🤖', description: 'Claude 系列模型,需代理访问', defaultUrl: 'https://api.anthropic.com', defaultModel: 'claude-sonnet-4-20250514', keyPlaceholder: 'sk-ant-xxx', domestic: false }, { id: 'deepseek', name: 'DeepSeek', icon: '🔍', description: '国产高性价比模型,无需代理', defaultUrl: 'https://api.deepseek.com/v1', defaultModel: 'deepseek-chat', keyPlaceholder: 'sk-xxx', domestic: true }, { id: 'gemini', name: 'Google Gemini', icon: '✨', description: 'Google AI 模型,需代理访问', defaultUrl: '', defaultModel: 'gemini-2.5-flash-preview-05-20', keyPlaceholder: 'AIzaSy-xxx', domestic: false } ]; // 当前选中的提供商信息 const currentProvider = computed(() => { return providers.find(p => p.id === activeProvider.value) || providers[0]; }); // 当前提供商的配置 const currentConfig = computed(() => { return form.configs[activeProvider.value] || {}; }); // 监听 props 变化,加载已保存的配置 watch(() => props.config, (newVal) => { if (newVal) { // 从旧配置迁移数据 if (newVal.default_provider) { form.provider = newVal.default_provider; activeProvider.value = mapProviderName(newVal.default_provider); } if (newVal.default_model) { form.model = newVal.default_model; } // 迁移各提供商配置 migrateConfig('openai-compatible', newVal.openai_api_key, newVal.openai_base_url); migrateConfig('anthropic', newVal.anthropic_api_key, newVal.anthropic_base_url); migrateConfig('deepseek', newVal.deepseek_api_key, newVal.deepseek_base_url); migrateConfig('gemini', newVal.gemini_api_key, newVal.gemini_base_url); } }, { immediate: true, deep: true }); // 映射旧的提供商名称 function mapProviderName(name) { const map = { 'openai': 'openai-compatible', 'anthropic': 'anthropic', 'deepseek': 'deepseek', 'gemini': 'gemini', 'zhipu': 'openai-compatible', 'minimax': 'openai-compatible', 'moonshot': 'openai-compatible' }; return map[name] || 'openai-compatible'; } // 迁移旧配置 function migrateConfig(providerId, apiKey, baseUrl) { if (apiKey && apiKey !== '***') { form.configs[providerId].api_key = apiKey; form.configs[providerId].configured = true; } if (baseUrl) { form.configs[providerId].base_url = baseUrl; } } // 测试连接 async function testConnection() { const config = form.configs[activeProvider.value]; if (!config.api_key) { ElementPlus.ElMessage.warning('请先填写 API Key'); return; } testing.value = activeProvider.value; testResult.provider = activeProvider.value; testResult.success = false; testResult.message = '正在测试...'; testResult.details = ''; try { const res = await fetch('/api/llm/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: activeProvider.value, api_key: config.api_key, base_url: config.base_url || currentProvider.value.defaultUrl, model: config.model || currentProvider.value.defaultModel }) }); const data = await res.json(); if (data.success) { testResult.success = true; testResult.message = '连接成功!'; testResult.details = data.message || `模型响应正常,延迟: ${data.latency || 'N/A'}ms`; config.configured = true; ElementPlus.ElMessage.success('API 连接测试成功'); } else { testResult.success = false; testResult.message = '连接失败'; testResult.details = formatError(data.error); ElementPlus.ElMessage.error('连接失败: ' + (data.error || '未知错误')); } } catch (e) { testResult.success = false; testResult.message = '请求失败'; testResult.details = formatError(e.message); ElementPlus.ElMessage.error('测试请求失败: ' + e.message); } testing.value = null; } // 格式化错误信息 function formatError(error) { if (!error) return '未知错误'; // 常见错误映射 const errorMap = { '401': '认证失败 - API Key 无效或已过期', '403': '访问被拒绝 - 请检查 API Key 权限', '404': 'API 地址错误 - 请检查 Base URL', '429': '请求过于频繁 - 已达到速率限制', '500': '服务器错误 - API 服务暂时不可用', '502': '网关错误 - 可能需要配置代理', '503': '服务不可用 - API 服务维护中', 'ECONNREFUSED': '连接被拒绝 - 请检查网络或代理设置', 'ETIMEDOUT': '连接超时 - 请检查网络或代理设置', 'ENOTFOUND': 'DNS 解析失败 - 请检查 Base URL', 'fetch failed': '网络请求失败 - 请检查代理设置' }; for (const [key, msg] of Object.entries(errorMap)) { if (error.includes(key)) { return msg; } } return error; } // 保存配置 async function handleSave() { saving.value = true; try { // 转换为旧格式保存(兼容后端) const saveData = { default_provider: activeProvider.value === 'openai-compatible' ? 'openai' : activeProvider.value, default_model: form.configs[activeProvider.value].model, // OpenAI 兼容 openai_api_key: form.configs['openai-compatible'].api_key, openai_base_url: form.configs['openai-compatible'].base_url, // Anthropic anthropic_api_key: form.configs['anthropic'].api_key, anthropic_base_url: form.configs['anthropic'].base_url, // DeepSeek deepseek_api_key: form.configs['deepseek'].api_key, deepseek_base_url: form.configs['deepseek'].base_url, // Gemini gemini_api_key: form.configs['gemini'].api_key, gemini_base_url: form.configs['gemini'].base_url }; emit('save', saveData); } finally { saving.value = false; } } // 设为默认 function setAsDefault() { form.provider = activeProvider.value; ElementPlus.ElMessage.success(`已将 ${currentProvider.value.name} 设为默认提供商`); } // 快速配置预设 function applyPreset(preset) { const config = form.configs[activeProvider.value]; switch (preset) { case 'openai': config.base_url = 'https://api.openai.com/v1'; config.model = 'gpt-4o'; break; case 'azure': config.base_url = 'https://YOUR_RESOURCE.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT'; config.model = 'gpt-4o'; break; case 'ollama': config.base_url = 'http://localhost:11434/v1'; config.model = 'llama3'; config.api_key = 'ollama'; break; case 'oneapi': config.base_url = 'http://localhost:3000/v1'; config.model = 'gpt-4o'; break; } } return { activeProvider, providers, currentProvider, currentConfig, form, testing, testResult, saving, testConnection, handleSave, setAsDefault, applyPreset }; }, template: `