feat: add light theme support and enhance LLM configuration UI
This commit is contained in:
@@ -1,231 +1,458 @@
|
||||
/**
|
||||
* LLM 配置组件
|
||||
* LLM 配置组件 - 简化版
|
||||
* 参考 OpenAI 等主流配置方式
|
||||
*/
|
||||
app.component('llm-config-page', {
|
||||
props: ['config'],
|
||||
emits: ['save'],
|
||||
setup(props, { emit }) {
|
||||
const { ref, reactive, watch } = Vue;
|
||||
const { ref, reactive, watch, computed } = Vue;
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
default_provider: '',
|
||||
default_model: '',
|
||||
anthropic_api_key: '',
|
||||
openai_api_key: '',
|
||||
deepseek_api_key: '',
|
||||
zhipu_api_key: '',
|
||||
minimax_api_key: '',
|
||||
minimax_group_id: '',
|
||||
moonshot_api_key: '',
|
||||
gemini_api_key: ''
|
||||
// 当前激活的提供商标签
|
||||
const activeProvider = ref('openai-compatible');
|
||||
|
||||
// 提供商配置状态
|
||||
const providerStatus = reactive({});
|
||||
|
||||
// 测试状态
|
||||
const testing = ref(null);
|
||||
const testResult = reactive({
|
||||
provider: '',
|
||||
success: false,
|
||||
message: '',
|
||||
details: ''
|
||||
});
|
||||
|
||||
// 监听 props 变化
|
||||
watch(() => props.config, (newVal) => {
|
||||
if (newVal) {
|
||||
Object.assign(form, newVal);
|
||||
// 保存状态
|
||||
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
|
||||
}
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
});
|
||||
|
||||
// 提供商列表
|
||||
const providers = [
|
||||
{ value: 'anthropic', label: 'Anthropic (Claude)', region: 'overseas', models: ['claude-sonnet-4-20250514', 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229'] },
|
||||
{ value: 'openai', label: 'OpenAI (GPT)', region: 'overseas', models: ['gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo'] },
|
||||
{ value: 'gemini', label: 'Google Gemini', region: 'overseas', models: ['gemini-2.0-flash', 'gemini-1.5-pro'] },
|
||||
{ value: 'deepseek', label: 'DeepSeek', region: 'domestic', models: ['deepseek-chat', 'deepseek-coder'] },
|
||||
{ value: 'zhipu', label: '智谱 (GLM)', region: 'domestic', models: ['glm-4-flash', 'glm-4', 'glm-3-turbo'] },
|
||||
{ value: 'minimax', label: 'MiniMax', region: 'domestic', models: ['abab6.5s-chat', 'abab5.5-chat'] },
|
||||
{ value: 'moonshot', label: 'Moonshot (Kimi)', region: 'domestic', models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'] }
|
||||
{
|
||||
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 currentModels = Vue.computed(() => {
|
||||
const provider = providers.find(p => p.value === form.default_provider);
|
||||
return provider ? provider.models : [];
|
||||
// 当前选中的提供商信息
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
const testing = ref(null);
|
||||
async function testConnection(provider) {
|
||||
testing.value = provider;
|
||||
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/${provider}`, { method: 'POST' });
|
||||
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) {
|
||||
ElementPlus.ElMessage.success(`${provider} 连接成功`);
|
||||
testResult.success = true;
|
||||
testResult.message = '连接成功!';
|
||||
testResult.details = data.message || `模型响应正常,延迟: ${data.latency || 'N/A'}ms`;
|
||||
config.configured = true;
|
||||
ElementPlus.ElMessage.success('API 连接测试成功');
|
||||
} else {
|
||||
ElementPlus.ElMessage.error(`${provider} 连接失败: ${data.error}`);
|
||||
testResult.success = false;
|
||||
testResult.message = '连接失败';
|
||||
testResult.details = formatError(data.error);
|
||||
ElementPlus.ElMessage.error('连接失败: ' + (data.error || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error(`测试失败: ${e.message}`);
|
||||
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;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
function handleSave() {
|
||||
emit('save', { ...form });
|
||||
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 {
|
||||
form,
|
||||
activeProvider,
|
||||
providers,
|
||||
currentModels,
|
||||
currentProvider,
|
||||
currentConfig,
|
||||
form,
|
||||
testing,
|
||||
testResult,
|
||||
saving,
|
||||
testConnection,
|
||||
handleSave
|
||||
handleSave,
|
||||
setAsDefault,
|
||||
applyPreset
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="llm-config">
|
||||
<!-- 当前状态概览 -->
|
||||
<el-card class="config-card" style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>API 配置状态</span>
|
||||
</div>
|
||||
</template>
|
||||
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
|
||||
<div v-for="p in providers" :key="p.id"
|
||||
class="status-chip"
|
||||
:class="{ active: form.configs[p.id]?.configured, selected: activeProvider === p.id }"
|
||||
@click="activeProvider = p.id">
|
||||
<span class="status-icon">{{ p.icon }}</span>
|
||||
<span class="status-name">{{ p.name }}</span>
|
||||
<el-icon v-if="form.configs[p.id]?.configured" color="#67c23a"><CircleCheck /></el-icon>
|
||||
<el-tag v-if="form.provider === p.id || activeProvider === p.id && form.provider === 'openai' && p.id === 'openai-compatible'" size="small" type="primary">默认</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 配置表单 -->
|
||||
<el-card class="config-card">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>默认提供商</span>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 20px;">{{ currentProvider.icon }}</span>
|
||||
<span>{{ currentProvider.name }}</span>
|
||||
<el-tag v-if="!currentProvider.domestic" size="small" type="warning">需代理</el-tag>
|
||||
<el-tag v-else size="small" type="success">国内直连</el-tag>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<el-button size="small" @click="setAsDefault" :disabled="form.provider === activeProvider">设为默认</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="默认提供商">
|
||||
<el-select v-model="form.default_provider" style="width: 300px">
|
||||
<el-option
|
||||
v-for="p in providers"
|
||||
:key="p.value"
|
||||
:value="p.value"
|
||||
:label="p.label"
|
||||
>
|
||||
<span>{{ p.label }}</span>
|
||||
<el-tag size="small" :type="p.region === 'domestic' ? 'success' : ''" style="margin-left: 8px">
|
||||
{{ p.region === 'domestic' ? '国内' : '境外' }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-alert
|
||||
:title="currentProvider.description"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px;"
|
||||
/>
|
||||
|
||||
<!-- OpenAI 兼容接口的快速预设 -->
|
||||
<div v-if="activeProvider === 'openai-compatible'" style="margin-bottom: 16px;">
|
||||
<span style="margin-right: 8px; color: var(--text-secondary);">快速配置:</span>
|
||||
<el-button-group size="small">
|
||||
<el-button @click="applyPreset('openai')">OpenAI</el-button>
|
||||
<el-button @click="applyPreset('azure')">Azure</el-button>
|
||||
<el-button @click="applyPreset('ollama')">Ollama 本地</el-button>
|
||||
<el-button @click="applyPreset('oneapi')">OneAPI</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<el-form label-position="top" style="max-width: 600px;">
|
||||
<el-form-item label="API Key" required>
|
||||
<el-input
|
||||
v-model="form.configs[activeProvider].api_key"
|
||||
type="password"
|
||||
show-password
|
||||
:placeholder="currentProvider.keyPlaceholder"
|
||||
size="large"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认模型">
|
||||
<el-select v-model="form.default_model" style="width: 300px">
|
||||
<el-option v-for="m in currentModels" :key="m" :value="m" :label="m" />
|
||||
</el-select>
|
||||
|
||||
<el-form-item label="API Base URL">
|
||||
<el-input
|
||||
v-model="form.configs[activeProvider].base_url"
|
||||
:placeholder="currentProvider.defaultUrl + '(留空使用默认)'"
|
||||
size="large"
|
||||
/>
|
||||
<div style="margin-top: 4px; font-size: 12px; color: var(--text-secondary);">
|
||||
留空将使用默认地址。如需使用代理或中转服务,请填写完整 URL。
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模型名称">
|
||||
<el-input
|
||||
v-model="form.configs[activeProvider].model"
|
||||
:placeholder="currentProvider.defaultModel"
|
||||
size="large"
|
||||
/>
|
||||
<div style="margin-top: 4px; font-size: 12px; color: var(--text-secondary);">
|
||||
留空将使用默认模型:{{ currentProvider.defaultModel }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 测试区域 -->
|
||||
<el-divider />
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 16px;">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="testConnection"
|
||||
:loading="testing === activeProvider"
|
||||
:disabled="!form.configs[activeProvider].api_key"
|
||||
>
|
||||
<el-icon v-if="!testing"><Connection /></el-icon>
|
||||
测试连接
|
||||
</el-button>
|
||||
|
||||
<div v-if="testResult.provider === activeProvider && testResult.message !== '正在测试...'"
|
||||
:class="['test-result', testResult.success ? 'success' : 'error']">
|
||||
<el-icon v-if="testResult.success"><CircleCheck /></el-icon>
|
||||
<el-icon v-else><CircleClose /></el-icon>
|
||||
<span>{{ testResult.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细错误信息 -->
|
||||
<el-alert
|
||||
v-if="testResult.provider === activeProvider && testResult.details && !testResult.success"
|
||||
:title="testResult.details"
|
||||
type="error"
|
||||
:closable="false"
|
||||
style="margin-top: 16px;"
|
||||
/>
|
||||
|
||||
<el-alert
|
||||
v-if="testResult.provider === activeProvider && testResult.details && testResult.success"
|
||||
:title="testResult.details"
|
||||
type="success"
|
||||
:closable="false"
|
||||
style="margin-top: 16px;"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 境外服务 -->
|
||||
<el-card class="config-card">
|
||||
<template #header>
|
||||
<span>境外服务 <el-tag size="small" type="warning">需要代理</el-tag></span>
|
||||
</template>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>🤖</span>
|
||||
<span>Anthropic (Claude)</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'anthropic'" @click="testConnection('anthropic')">测试连接</el-button>
|
||||
</div>
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.anthropic_api_key" type="password" show-password placeholder="sk-ant-xxx" style="width: 400px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>💬</span>
|
||||
<span>OpenAI (GPT)</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'openai'" @click="testConnection('openai')">测试连接</el-button>
|
||||
</div>
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.openai_api_key" type="password" show-password placeholder="sk-xxx" style="width: 400px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>✨</span>
|
||||
<span>Google Gemini</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'gemini'" @click="testConnection('gemini')">测试连接</el-button>
|
||||
</div>
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.gemini_api_key" type="password" show-password placeholder="AIzaSy-xxx" style="width: 400px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 国内服务 -->
|
||||
<el-card class="config-card">
|
||||
<template #header>
|
||||
<span>国内服务 <el-tag size="small" type="success">无需代理</el-tag></span>
|
||||
</template>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>🔍</span>
|
||||
<span>DeepSeek</span>
|
||||
<span class="provider-badge domestic">推荐</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'deepseek'" @click="testConnection('deepseek')">测试连接</el-button>
|
||||
</div>
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.deepseek_api_key" type="password" show-password placeholder="sk-xxx" style="width: 400px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>🧠</span>
|
||||
<span>智谱 (GLM)</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'zhipu'" @click="testConnection('zhipu')">测试连接</el-button>
|
||||
</div>
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.zhipu_api_key" type="password" show-password style="width: 400px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>🎯</span>
|
||||
<span>MiniMax</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'minimax'" @click="testConnection('minimax')">测试连接</el-button>
|
||||
</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.minimax_api_key" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Group ID" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.minimax_group_id" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div class="provider-card">
|
||||
<div class="provider-header">
|
||||
<div class="provider-name">
|
||||
<span>🌙</span>
|
||||
<span>Moonshot (Kimi)</span>
|
||||
</div>
|
||||
<el-button size="small" :loading="testing === 'moonshot'" @click="testConnection('moonshot')">测试连接</el-button>
|
||||
</div>
|
||||
<el-form-item label="API Key" label-width="100px" style="margin-bottom: 0">
|
||||
<el-input v-model="form.moonshot_api_key" type="password" show-password style="width: 400px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<el-button type="primary" @click="handleSave">保存配置</el-button>
|
||||
<!-- 保存按钮 -->
|
||||
<div style="margin-top: 20px; display: flex; gap: 12px;">
|
||||
<el-button type="primary" size="large" @click="handleSave" :loading="saving">
|
||||
保存所有配置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user