fix(chat): 保留页面切换后的提供商与模型选择
This commit is contained in:
@@ -227,3 +227,7 @@ Agent 工具列表按 `mcp.<server_id>.<remote_name>` 的远程工具名匹配
|
||||
卡片默认显示三行摘要,完整服务原文可展开查看,展开操作不会改变工具选择。服务已提供中文说明时优先保留;未收录的 MCP 工具明确提示暂无中文说明,不将本地摘要当作服务协议或自动翻译结果。原始说明及其中的参数规则完整保留。
|
||||
|
||||
验证:前端 84 项测试、类型检查与生产构建通过。新增回归覆盖不同服务器命名空间、未知工具、服务中文说明、原文完整性,以及选择工具时保留原始 ID。
|
||||
|
||||
### 聊天模型选择审阅修复
|
||||
|
||||
返回聊天页时保留仍启用的提供商与手动模型 ID,仅刷新其模型列表;未选择、已删除或已禁用的提供商才回退到默认值。提供商加载失败时保留当前选择并展示错误。新增页面重新挂载与异常分支回归,前端共 89 项测试通过。
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { beforeEach, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
import { useProviderStore } from '@/stores/provider'
|
||||
import { useSkillStore } from '@/stores/skill'
|
||||
import ChatView from './ChatView.vue'
|
||||
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
|
||||
vi.mock('@/stores/editor', () => ({ useEditorStore: () => ({}) }))
|
||||
vi.mock('@/stores/workspace', () => ({ useWorkspaceStore: () => ({}) }))
|
||||
vi.mock('@/components/common/MarkdownContent.vue', () => ({ default: { template: '<div />' } }))
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
const providers = useProviderStore()
|
||||
providers.providers = ['a', 'b'].map(id => ({
|
||||
provider_id: id, provider_type: 'openai_compatible', name: id,
|
||||
default_model: `${id}-default`, enabled: true, capabilities: { chat: true }, has_credential: false,
|
||||
}))
|
||||
providers.defaultProviderId = 'a'
|
||||
vi.spyOn(providers, 'loadProviders').mockResolvedValue(undefined)
|
||||
vi.spyOn(providers, 'loadModels').mockResolvedValue([])
|
||||
vi.spyOn(useSkillStore(), 'loadSkills').mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('preserves the selected provider and manual model after leaving and returning to chat', async () => {
|
||||
const chat = useChatStore()
|
||||
const first = mount(ChatView)
|
||||
await flushPromises()
|
||||
await first.get('select').setValue('b')
|
||||
await first.get('input[list="chat-models"]').setValue('b-manual')
|
||||
first.unmount()
|
||||
const returned = mount(ChatView)
|
||||
await flushPromises()
|
||||
expect(chat.selectedProviderId).toBe('b')
|
||||
expect(chat.selectedModel).toBe('b-manual')
|
||||
expect(useProviderStore().loadModels).toHaveBeenLastCalledWith('b')
|
||||
returned.unmount()
|
||||
})
|
||||
|
||||
it.each(['missing', 'disabled', 'unselected'])('uses the default when the selected provider is %s', async state => {
|
||||
const chat = useChatStore()
|
||||
chat.selectedProviderId = state === 'unselected' ? '' : state === 'missing' ? 'deleted' : 'b'
|
||||
chat.selectedModel = 'old-model'
|
||||
if (state === 'disabled') useProviderStore().providers[1]!.enabled = false
|
||||
const wrapper = mount(ChatView)
|
||||
await flushPromises()
|
||||
expect(chat.selectedProviderId).toBe('a')
|
||||
expect(chat.selectedModel).toBe('a-default')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('preserves the selection when provider discovery fails', async () => {
|
||||
const chat = useChatStore()
|
||||
chat.selectedProviderId = 'b'
|
||||
chat.selectedModel = 'b-manual'
|
||||
useProviderStore().error = 'offline'
|
||||
const wrapper = mount(ChatView)
|
||||
await flushPromises()
|
||||
expect(chat.selectedProviderId).toBe('b')
|
||||
expect(chat.selectedModel).toBe('b-manual')
|
||||
expect(wrapper.get('.error-banner').text()).toBe('offline')
|
||||
wrapper.unmount()
|
||||
})
|
||||
@@ -22,18 +22,28 @@ const availableModels = computed(() => providerStore.modelsByProvider[chatStore.
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await Promise.all([providerStore.loadProviders(), skillStore.loadSkills()])
|
||||
chatStore.selectedProviderId = providerStore.defaultProviderId
|
||||
if (providerStore.error) return
|
||||
const selected = providerStore.enabledProviders.find(p => p.provider_id === chatStore.selectedProviderId)
|
||||
if (!selected) {
|
||||
chatStore.selectedProviderId = providerStore.defaultProviderId
|
||||
} else {
|
||||
await refreshModels(selected.provider_id)
|
||||
}
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : '无法加载 AI 配置,请检查后端连接。'
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => chatStore.selectedProviderId, async (providerId) => {
|
||||
chatStore.selectedModel = providerStore.providers.find(p => p.provider_id === providerId)?.default_model ?? ''
|
||||
async function refreshModels(providerId: string) {
|
||||
loadError.value = ''
|
||||
if (!providerId) return
|
||||
try { await providerStore.loadModels(providerId) }
|
||||
catch (error) { if (chatStore.selectedProviderId === providerId) loadError.value = error instanceof Error ? error.message : '模型列表加载失败,请手动填写模型 ID。' }
|
||||
}
|
||||
|
||||
watch(() => chatStore.selectedProviderId, async (providerId) => {
|
||||
chatStore.selectedModel = providerStore.providers.find(p => p.provider_id === providerId)?.default_model ?? ''
|
||||
await refreshModels(providerId)
|
||||
})
|
||||
|
||||
function send() { void chatStore.sendMessage(chatStore.inputText) }
|
||||
|
||||
Reference in New Issue
Block a user