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
This commit is contained in:
1 parent
935c6e3c5b
commit
2fb11e13ba
4 files changed
+95
-16
No files matched your search
@@ -332,11 +332,11 @@ export class ModelManager implements IModelManager {
|
||||
...existingTextModelConfig.connectionConfig,
|
||||
...(config.connectionConfig || {})
|
||||
},
|
||||
// Deep merge paramOverrides
|
||||
paramOverrides: {
|
||||
...existingTextModelConfig.paramOverrides,
|
||||
...(config.paramOverrides || {})
|
||||
}
|
||||
// 处理 paramOverrides:如果明确传入了 paramOverrides,则直接替换而不是合并
|
||||
// 这样可以确保用户删除的参数不会被错误地保留
|
||||
paramOverrides: config.paramOverrides !== undefined
|
||||
? config.paramOverrides
|
||||
: existingTextModelConfig.paramOverrides || {}
|
||||
};
|
||||
|
||||
// 如果更新了关键字段,需要验证配置
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { computed, inject, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
NModal,
|
||||
@@ -403,11 +403,15 @@ const isSaving = manager.isSaving
|
||||
|
||||
const isEditing = computed(() => !!manager.editingModelId.value)
|
||||
|
||||
const handleUpdateShow = (value: boolean) => {
|
||||
const handleUpdateShow = async (value: boolean) => {
|
||||
emit('update:show', value)
|
||||
|
||||
// 只有在明确关闭时才重置表单状态
|
||||
if (!value) {
|
||||
// 等待父组件处理状态变化后再重置表单
|
||||
await nextTick()
|
||||
manager.resetFormState()
|
||||
}
|
||||
emit('update:show', value)
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
@@ -41,6 +41,11 @@ const handleModelUpdated = async (id?: string) => {
|
||||
if (targetId) {
|
||||
emit('modelsUpdated', targetId)
|
||||
}
|
||||
|
||||
// 保存成功后关闭模态框并重置表单状态
|
||||
showEditModal.value = false
|
||||
editingModelId.value = null
|
||||
manager.resetFormState()
|
||||
}
|
||||
|
||||
const handleTestConnection = async (id: string) => {
|
||||
@@ -48,16 +53,27 @@ const handleTestConnection = async (id: string) => {
|
||||
}
|
||||
|
||||
const handleEditModel = async (id: string) => {
|
||||
await manager.prepareForEdit(id)
|
||||
// 如果已经在编辑同一个模型且模态框已经打开,直接返回
|
||||
if (editingModelId.value === id && showEditModal.value === true) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果切换到不同的模型,重置表单状态
|
||||
if (editingModelId.value && editingModelId.value !== id) {
|
||||
manager.resetFormState()
|
||||
}
|
||||
|
||||
// 准备编辑模式(总是会执行,因为我们需要确保状态正确)
|
||||
await manager.prepareForEdit(id, true)
|
||||
editingModelId.value = id
|
||||
showEditModal.value = true
|
||||
}
|
||||
|
||||
const updateEditModalVisibility = (value: boolean) => {
|
||||
showEditModal.value = value
|
||||
// 当模态框关闭时,重置编辑状态但不重置表单数据
|
||||
if (!value) {
|
||||
editingModelId.value = null
|
||||
manager.resetFormState()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,18 @@ export function useTextModelManager() {
|
||||
)
|
||||
})
|
||||
|
||||
const canTestFormConnection = computed(() => !!editingModelId.value && !isTestingFormConnection.value)
|
||||
const canTestFormConnection = computed(() => {
|
||||
// 必须在编辑模式下
|
||||
if (!editingModelId.value) return false
|
||||
// 测试期间禁用
|
||||
if (isTestingFormConnection.value) return false
|
||||
// 必须有必需的连接配置
|
||||
if (!isConnectionConfigured.value) return false
|
||||
// 必须有模型名称
|
||||
if (!form.value.name?.trim()) return false
|
||||
|
||||
return true
|
||||
})
|
||||
const canRefreshModelOptions = computed(() => {
|
||||
return selectedProvider.value?.supportsDynamicModels && isConnectionConfigured.value && !isLoadingModelOptions.value
|
||||
})
|
||||
@@ -334,7 +345,12 @@ export function useTextModelManager() {
|
||||
formReady.value = true
|
||||
}
|
||||
|
||||
const prepareForEdit = async (id: string) => {
|
||||
const prepareForEdit = async (id: string, forceReload = true) => {
|
||||
// 如果已经在编辑同一个模型且不强制重新加载,则跳过
|
||||
if (!forceReload && editingModelId.value === id && formReady.value) {
|
||||
return
|
||||
}
|
||||
|
||||
resetFormState()
|
||||
editingModelId.value = id
|
||||
await ensureProvidersLoaded()
|
||||
@@ -546,13 +562,56 @@ export function useTextModelManager() {
|
||||
}
|
||||
|
||||
const testFormConnection = async () => {
|
||||
if (!editingModelId.value || isTestingFormConnection.value) return
|
||||
// 使用 canTestFormConnection 的完整校验逻辑
|
||||
if (!canTestFormConnection.value) return
|
||||
|
||||
isTestingFormConnection.value = true
|
||||
formConnectionStatus.value = { type: 'info', message: t('modelManager.testing') }
|
||||
|
||||
try {
|
||||
await llmService.testConnection(editingModelId.value)
|
||||
formConnectionStatus.value = { type: 'success', message: t('modelManager.testSuccess', { provider: form.value.name }) }
|
||||
toast.success(t('modelManager.testSuccess', { provider: form.value.name }))
|
||||
// 获取原始配置
|
||||
const existingConfig = await modelManager.getModel(editingModelId.value)
|
||||
if (!existingConfig) {
|
||||
throw new Error('模型配置不存在')
|
||||
}
|
||||
|
||||
// 创建临时测试配置,使用当前表单的参数覆盖
|
||||
// 注意:如果 API Key 被脱敏显示,需要使用原始 Key
|
||||
const connectionConfig = {
|
||||
...form.value.connectionConfig,
|
||||
apiKey: form.value.displayMaskedKey && form.value.originalApiKey
|
||||
? form.value.originalApiKey
|
||||
: form.value.connectionConfig.apiKey
|
||||
}
|
||||
|
||||
const tempConfig: TextModelConfig = {
|
||||
...existingConfig,
|
||||
id: `temp-test-${editingModelId.value}`,
|
||||
name: form.value.name,
|
||||
enabled: form.value.enabled,
|
||||
providerMeta: { ...existingConfig.providerMeta },
|
||||
modelMeta: { ...existingConfig.modelMeta },
|
||||
connectionConfig,
|
||||
paramOverrides: { ...(form.value.paramOverrides || {}) }
|
||||
}
|
||||
|
||||
// 创建临时模型用于测试
|
||||
await modelManager.addModel(tempConfig.id, tempConfig)
|
||||
|
||||
try {
|
||||
// 测试临时模型
|
||||
await llmService.testConnection(tempConfig.id)
|
||||
formConnectionStatus.value = { type: 'success', message: t('modelManager.testSuccess', { provider: form.value.name }) }
|
||||
toast.success(t('modelManager.testSuccess', { provider: form.value.name }))
|
||||
} finally {
|
||||
// 清理临时模型
|
||||
try {
|
||||
await modelManager.deleteModel(tempConfig.id)
|
||||
} catch (cleanupError) {
|
||||
console.warn('清理临时测试模型失败:', cleanupError)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('连接测试失败:', error)
|
||||
formConnectionStatus.value = {
|
||||
|
||||
Reference in new issue
Block a user