233 lines
11 KiB
JavaScript
233 lines
11 KiB
JavaScript
|
|
/**
|
||
|
|
* LLM 配置组件
|
||
|
|
*/
|
||
|
|
app.component('llm-config-page', {
|
||
|
|
props: ['config'],
|
||
|
|
emits: ['save'],
|
||
|
|
setup(props, { emit }) {
|
||
|
|
const { ref, reactive, watch } = 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: ''
|
||
|
|
});
|
||
|
|
|
||
|
|
// 监听 props 变化
|
||
|
|
watch(() => props.config, (newVal) => {
|
||
|
|
if (newVal) {
|
||
|
|
Object.assign(form, newVal);
|
||
|
|
}
|
||
|
|
}, { 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'] }
|
||
|
|
];
|
||
|
|
|
||
|
|
// 当前选中提供商的模型列表
|
||
|
|
const currentModels = Vue.computed(() => {
|
||
|
|
const provider = providers.find(p => p.value === form.default_provider);
|
||
|
|
return provider ? provider.models : [];
|
||
|
|
});
|
||
|
|
|
||
|
|
// 测试连接
|
||
|
|
const testing = ref(null);
|
||
|
|
async function testConnection(provider) {
|
||
|
|
testing.value = provider;
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/llm/test/${provider}`, { method: 'POST' });
|
||
|
|
const data = await res.json();
|
||
|
|
if (data.success) {
|
||
|
|
ElementPlus.ElMessage.success(`${provider} 连接成功`);
|
||
|
|
} else {
|
||
|
|
ElementPlus.ElMessage.error(`${provider} 连接失败: ${data.error}`);
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
ElementPlus.ElMessage.error(`测试失败: ${e.message}`);
|
||
|
|
}
|
||
|
|
testing.value = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 保存配置
|
||
|
|
function handleSave() {
|
||
|
|
emit('save', { ...form });
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
form,
|
||
|
|
providers,
|
||
|
|
currentModels,
|
||
|
|
testing,
|
||
|
|
testConnection,
|
||
|
|
handleSave
|
||
|
|
};
|
||
|
|
},
|
||
|
|
template: `
|
||
|
|
<div class="llm-config">
|
||
|
|
<el-card class="config-card">
|
||
|
|
<template #header>
|
||
|
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||
|
|
<span>默认提供商</span>
|
||
|
|
</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-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>
|
||
|
|
</el-form>
|
||
|
|
</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>
|
||
|
|
</div>
|
||
|
|
`
|
||
|
|
});
|