Skip to content

Commit 2fb11e1

Browse files
committed
fix(ui): 修复模型连接测试的关键问题并增强代码质量
修复问题: 1. 修复 API 参数调用错误 - addModel 方法缺少 key 参数导致验证失败 2. 修复 API Key 脱敏问题 - 测试连接时使用脱敏后的 Key 导致认证失败 3. 修复状态管理时序问题 - 使用 nextTick 替代不可靠的 setTimeout 增强功能: 1. 强化连接测试的前置校验逻辑,增加配置完整性检查 2. 优化并发控制,测试期间自动禁用按钮,防止重复请求 3. 统一守卫逻辑到单一数据源,遵循 DRY 原则 4. 改进 paramOverrides 更新逻辑,支持参数删除操作 技术改进: - 使用 Vue nextTick API 替代 setTimeout,提升可靠性 - 实现临时模型测试模式,避免污染原始配置 - 增强 canTestFormConnection 校验,包含配置完整性检查 - 正确处理 API Key 脱敏/恢复逻辑 影响范围: - packages/core/src/services/model/manager.ts - packages/ui/src/composables/useTextModelManager.ts - packages/ui/src/components/TextModelEditModal.vue - packages/ui/src/components/TextModelManager.vue
1 parent 935c6e3 commit 2fb11e1

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

packages/core/src/services/model/manager.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,11 @@ export class ModelManager implements IModelManager {
332332
...existingTextModelConfig.connectionConfig,
333333
...(config.connectionConfig || {})
334334
},
335-
// Deep merge paramOverrides
336-
paramOverrides: {
337-
...existingTextModelConfig.paramOverrides,
338-
...(config.paramOverrides || {})
339-
}
335+
// 处理 paramOverrides:如果明确传入了 paramOverrides,则直接替换而不是合并
336+
// 这样可以确保用户删除的参数不会被错误地保留
337+
paramOverrides: config.paramOverrides !== undefined
338+
? config.paramOverrides
339+
: existingTextModelConfig.paramOverrides || {}
340340
};
341341

342342
// 如果更新了关键字段,需要验证配置

packages/ui/src/components/TextModelEditModal.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@
331331
</template>
332332

333333
<script setup lang="ts">
334-
import { computed, inject } from 'vue'
334+
import { computed, inject, nextTick } from 'vue'
335335
import { useI18n } from 'vue-i18n'
336336
import {
337337
NModal,
@@ -403,11 +403,15 @@ const isSaving = manager.isSaving
403403
404404
const isEditing = computed(() => !!manager.editingModelId.value)
405405
406-
const handleUpdateShow = (value: boolean) => {
406+
const handleUpdateShow = async (value: boolean) => {
407+
emit('update:show', value)
408+
409+
// 只有在明确关闭时才重置表单状态
407410
if (!value) {
411+
// 等待父组件处理状态变化后再重置表单
412+
await nextTick()
408413
manager.resetFormState()
409414
}
410-
emit('update:show', value)
411415
}
412416
413417
const handleSubmit = async () => {

packages/ui/src/components/TextModelManager.vue

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,39 @@ const handleModelUpdated = async (id?: string) => {
4141
if (targetId) {
4242
emit('modelsUpdated', targetId)
4343
}
44+
45+
// 保存成功后关闭模态框并重置表单状态
46+
showEditModal.value = false
47+
editingModelId.value = null
48+
manager.resetFormState()
4449
}
4550
4651
const handleTestConnection = async (id: string) => {
4752
await manager.testConfigConnection(id)
4853
}
4954
5055
const handleEditModel = async (id: string) => {
51-
await manager.prepareForEdit(id)
56+
// 如果已经在编辑同一个模型且模态框已经打开,直接返回
57+
if (editingModelId.value === id && showEditModal.value === true) {
58+
return
59+
}
60+
61+
// 如果切换到不同的模型,重置表单状态
62+
if (editingModelId.value && editingModelId.value !== id) {
63+
manager.resetFormState()
64+
}
65+
66+
// 准备编辑模式(总是会执行,因为我们需要确保状态正确)
67+
await manager.prepareForEdit(id, true)
5268
editingModelId.value = id
5369
showEditModal.value = true
5470
}
5571
5672
const updateEditModalVisibility = (value: boolean) => {
5773
showEditModal.value = value
74+
// 当模态框关闭时,重置编辑状态但不重置表单数据
5875
if (!value) {
5976
editingModelId.value = null
60-
manager.resetFormState()
6177
}
6278
}
6379

packages/ui/src/composables/useTextModelManager.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,18 @@ export function useTextModelManager() {
139139
)
140140
})
141141

142-
const canTestFormConnection = computed(() => !!editingModelId.value && !isTestingFormConnection.value)
142+
const canTestFormConnection = computed(() => {
143+
// 必须在编辑模式下
144+
if (!editingModelId.value) return false
145+
// 测试期间禁用
146+
if (isTestingFormConnection.value) return false
147+
// 必须有必需的连接配置
148+
if (!isConnectionConfigured.value) return false
149+
// 必须有模型名称
150+
if (!form.value.name?.trim()) return false
151+
152+
return true
153+
})
143154
const canRefreshModelOptions = computed(() => {
144155
return selectedProvider.value?.supportsDynamicModels && isConnectionConfigured.value && !isLoadingModelOptions.value
145156
})
@@ -334,7 +345,12 @@ export function useTextModelManager() {
334345
formReady.value = true
335346
}
336347

337-
const prepareForEdit = async (id: string) => {
348+
const prepareForEdit = async (id: string, forceReload = true) => {
349+
// 如果已经在编辑同一个模型且不强制重新加载,则跳过
350+
if (!forceReload && editingModelId.value === id && formReady.value) {
351+
return
352+
}
353+
338354
resetFormState()
339355
editingModelId.value = id
340356
await ensureProvidersLoaded()
@@ -546,13 +562,56 @@ export function useTextModelManager() {
546562
}
547563

548564
const testFormConnection = async () => {
549-
if (!editingModelId.value || isTestingFormConnection.value) return
565+
// 使用 canTestFormConnection 的完整校验逻辑
566+
if (!canTestFormConnection.value) return
567+
550568
isTestingFormConnection.value = true
551569
formConnectionStatus.value = { type: 'info', message: t('modelManager.testing') }
570+
552571
try {
553-
await llmService.testConnection(editingModelId.value)
554-
formConnectionStatus.value = { type: 'success', message: t('modelManager.testSuccess', { provider: form.value.name }) }
555-
toast.success(t('modelManager.testSuccess', { provider: form.value.name }))
572+
// 获取原始配置
573+
const existingConfig = await modelManager.getModel(editingModelId.value)
574+
if (!existingConfig) {
575+
throw new Error('模型配置不存在')
576+
}
577+
578+
// 创建临时测试配置,使用当前表单的参数覆盖
579+
// 注意:如果 API Key 被脱敏显示,需要使用原始 Key
580+
const connectionConfig = {
581+
...form.value.connectionConfig,
582+
apiKey: form.value.displayMaskedKey && form.value.originalApiKey
583+
? form.value.originalApiKey
584+
: form.value.connectionConfig.apiKey
585+
}
586+
587+
const tempConfig: TextModelConfig = {
588+
...existingConfig,
589+
id: `temp-test-${editingModelId.value}`,
590+
name: form.value.name,
591+
enabled: form.value.enabled,
592+
providerMeta: { ...existingConfig.providerMeta },
593+
modelMeta: { ...existingConfig.modelMeta },
594+
connectionConfig,
595+
paramOverrides: { ...(form.value.paramOverrides || {}) }
596+
}
597+
598+
// 创建临时模型用于测试
599+
await modelManager.addModel(tempConfig.id, tempConfig)
600+
601+
try {
602+
// 测试临时模型
603+
await llmService.testConnection(tempConfig.id)
604+
formConnectionStatus.value = { type: 'success', message: t('modelManager.testSuccess', { provider: form.value.name }) }
605+
toast.success(t('modelManager.testSuccess', { provider: form.value.name }))
606+
} finally {
607+
// 清理临时模型
608+
try {
609+
await modelManager.deleteModel(tempConfig.id)
610+
} catch (cleanupError) {
611+
console.warn('清理临时测试模型失败:', cleanupError)
612+
}
613+
}
614+
556615
} catch (error: any) {
557616
console.error('连接测试失败:', error)
558617
formConnectionStatus.value = {

0 commit comments

Comments
 (0)