3030 </div >
3131 </el-card >
3232
33- <el-dialog v-model =" dialogVisible" :title =" form.id ? '编辑供应商' : '添加供应商'" width =" 560px " >
33+ <el-dialog v-model =" dialogVisible" :title =" form.id ? '编辑供应商' : '添加供应商'" width =" 620px " >
3434 <el-form :model =" form" label-width =" 90px" >
35- <el-form-item label =" 供应商名称" ><el-input v-model =" form.providerName" placeholder =" 如 DeepSeek / 通义千问" /></el-form-item >
35+ <el-form-item label =" 供应商名称" >
36+ <el-select v-model =" form.providerName" filterable allow-create default-first-option placeholder =" 选择或输入供应商名称"
37+ style =" width :100% " @change =" onProviderNameChange" >
38+ <el-option v-for =" p in presetProviders" :key =" p.name" :label =" p.name" :value =" p.name" />
39+ </el-select >
40+ </el-form-item >
3641 <el-form-item label =" 接口地址" ><el-input v-model =" form.baseUrl" placeholder =" https://api.deepseek.com/v1" /></el-form-item >
37- <el-form-item label =" API Key" ><el-input v-model =" form.apiKey" type =" password" show-password placeholder =" sk-..." /></el-form-item >
38- <el-form-item label =" 默认模型" ><el-input v-model =" form.model" placeholder =" deepseek-chat" /></el-form-item >
39- <el-form-item label =" 可用模型" ><el-input v-model =" form.models" placeholder =" 逗号分隔,如 deepseek-chat,deepseek-reasoner" /></el-form-item >
42+ <el-form-item label =" API Key" >
43+ <div style =" display :flex ;gap :6px ;width :100% " >
44+ <el-input v-model =" form.apiKey" type =" password" show-password placeholder =" sk-..." style =" flex :1 " />
45+ <el-button :loading =" testingConfig" @click =" doFetchModels" >获取模型/测试</el-button >
46+ </div >
47+ <div v-if =" testMsg" :style =" `font-size:12px;margin-top:4px;color:${testOk ? '#67c23a' : '#f56c6c'}`" >{{ testMsg }}</div >
48+ </el-form-item >
49+ <el-form-item label =" 默认模型" >
50+ <el-select v-model =" form.model" filterable allow-create default-first-option placeholder =" 选择或输入模型名" style =" width :100% " >
51+ <el-option v-for =" m in fetchedModels" :key =" m" :label =" m" :value =" m" />
52+ </el-select >
53+ </el-form-item >
54+ <el-form-item label =" 可用模型" >
55+ <el-select v-model =" selectedModels" multiple filterable allow-create default-first-option
56+ collapse-tags collapse-tags-tooltip placeholder =" 多选可用模型(对话中可切换)" style =" width :100% " >
57+ <el-option v-for =" m in fetchedModels" :key =" m" :label =" m" :value =" m" />
58+ </el-select >
59+ </el-form-item >
4060 <el-form-item label =" 启用" ><el-switch v-model =" form.enabled" /></el-form-item >
4161 <el-form-item label =" 备注" ><el-input v-model =" form.remark" /></el-form-item >
4262 </el-form >
@@ -57,6 +77,22 @@ const loading = ref(false)
5777const tableData = ref <any []>([])
5878const dialogVisible = ref (false )
5979const form = ref <any >({ id: 0 , providerName: ' ' , baseUrl: ' ' , apiKey: ' ' , model: ' ' , models: ' ' , enabled: true , remark: ' ' })
80+ // 模型拉取/多选状态
81+ const fetchedModels = ref <string []>([])
82+ const selectedModels = ref <string []>([])
83+ const testingConfig = ref (false )
84+ const testMsg = ref (' ' )
85+ const testOk = ref (false )
86+
87+ // 预置供应商(选择后自动填默认接口地址)
88+ const presetProviders = [
89+ { name: ' DeepSeek' , baseUrl: ' https://api.deepseek.com/v1' },
90+ { name: ' 通义千问' , baseUrl: ' https://dashscope.aliyuncs.com/compatible-mode/v1' },
91+ { name: ' Kimi (Moonshot)' , baseUrl: ' https://api.moonshot.cn/v1' },
92+ { name: ' OpenAI' , baseUrl: ' https://api.openai.com/v1' },
93+ { name: ' 智谱GLM' , baseUrl: ' https://open.bigmodel.cn/api/paas/v4' },
94+ { name: ' Ollama' , baseUrl: ' http://localhost:11434/v1' }
95+ ]
6096
6197onMounted (loadData )
6298
@@ -70,13 +106,50 @@ async function loadData() {
70106
71107function openAdd() {
72108 form .value = { id: 0 , providerName: ' ' , baseUrl: ' ' , apiKey: ' ' , model: ' ' , models: ' ' , enabled: true , remark: ' ' }
109+ fetchedModels .value = []
110+ selectedModels .value = []
111+ testMsg .value = ' '
73112 dialogVisible .value = true
74113}
75114function openEdit(row : any ) {
76115 form .value = { ... row , enabled: row .enabled === 1 }
116+ fetchedModels .value = String (row .models || ' ' ).split (' ,' ).map ((s : string ) => s .trim ()).filter (Boolean )
117+ selectedModels .value = [... fetchedModels .value ]
118+ testMsg .value = ' '
77119 dialogVisible .value = true
78120}
121+
122+ /** 选择预置供应商 → 自动填接口地址默认值 */
123+ function onProviderNameChange() {
124+ const preset = presetProviders .find ((p : any ) => p .name === form .value .providerName )
125+ if (preset && ! form .value .baseUrl ) form .value .baseUrl = preset .baseUrl
126+ }
127+
128+ /** 获取模型列表(兼作配置测试) */
129+ async function doFetchModels() {
130+ if (! form .value .baseUrl ) { ElMessage .warning (' 请先填写接口地址' ); return }
131+ testingConfig .value = true
132+ testMsg .value = ' '
133+ try {
134+ const res: any = await request .post (' /ai/fetch-models' , { baseUrl: form .value .baseUrl , apiKey: form .value .apiKey })
135+ const models: string [] = res .data ?.models || []
136+ fetchedModels .value = models
137+ // 默认模型:优先保留当前选择,否则取第一个
138+ if (models .length > 0 ) {
139+ if (! form .value .model || ! models .includes (form .value .model )) form .value .model = models [0 ]
140+ selectedModels .value = models .filter ((m : string ) => selectedModels .value .includes (m ))
141+ if (selectedModels .value .length === 0 ) selectedModels .value = [... models ]
142+ }
143+ testOk .value = true
144+ testMsg .value = ` ✅ 配置测试通过,共 ${models .length } 个模型 `
145+ } catch (e : any ) {
146+ testOk .value = false
147+ testMsg .value = e ?.message || ' 测试失败'
148+ } finally { testingConfig .value = false }
149+ }
150+
79151async function doSave() {
152+ form .value .models = selectedModels .value .join (' ,' )
80153 await request .post (' /ai/provider/save' , form .value )
81154 ElMessage .success (' 保存成功' )
82155 dialogVisible .value = false
0 commit comments