Quellcode durchsuchen

修复冒号 Provider 的模型切换

LSamien vor 2 Monaten
Ursprung
Commit
3db275496b

+ 13 - 0
src/client/utils/sessionModel.ts

@@ -6,6 +6,19 @@ export interface SessionModelSelection {
   provider?: string
 }
 
+/**
+ * The model chooser uses this exact value for its option IDs. Providers may
+ * themselves contain colons, so callers must compare the whole value instead
+ * of splitting it back into provider and model parts.
+ */
+export function modelChoiceId(option: Pick<ModelOption, 'id' | 'provider'>): string {
+  return `${option.provider}:${option.id}`
+}
+
+export function modelForChoiceId(options: ModelOption[], choiceId: string): ModelOption | undefined {
+  return options.find(option => modelChoiceId(option) === choiceId)
+}
+
 export function modelForSession(options: ModelOption[], model?: string, provider?: string): ModelOption | undefined {
   const id = model?.trim()
   if (!id) return undefined

+ 3 - 3
src/client/views/ChatView.vue

@@ -22,6 +22,7 @@ import WorkspaceView from '@/components/workspace/WorkspaceView.vue'
 import { chatInteraction, chatMessagesToUi, sessionSidebarItem } from '@/components/workspace/viewModels'
 import { consumeLibraryItemForComposer, loadComposerFile } from '@/components/workspace/pendingComposer'
 import { readAgentShowThinking, writeAgentShowThinking } from '@/utils/sessionPreferences'
+import { modelChoiceId, modelForChoiceId } from '@/utils/sessionModel'
 import { estimateConversationTokens } from '@/utils/contextUsage'
 import { useAuthStore } from '@/stores/auth'
 import { useChatStore } from '@/stores/chat'
@@ -83,7 +84,7 @@ const forkSourceTitle = computed(() => {
   if (!parentId) return ''
   return chat.sessions.find(session => session.id === parentId && session.profile === activeSession.value?.profile)?.title || parentId
 })
-const selectedModelId = computed(() => chat.selectedModel ? `${chat.selectedModel.provider}:${chat.selectedModel.id}` : '')
+const selectedModelId = computed(() => chat.selectedModel ? modelChoiceId(chat.selectedModel) : '')
 const reasoningOptions: ChoiceOption[] = [
   { id: '', label: '默认', description: '使用模型或 Profile 的默认强度' },
   { id: 'none', label: '关闭', description: '不使用额外推理' },
@@ -208,8 +209,7 @@ async function selectModel(id: string) {
     modelDialog.value = false
     return
   }
-  const [provider, ...rest] = id.split(':')
-  const model = chat.models.find(item => item.provider === provider && item.id === rest.join(':'))
+  const model = modelForChoiceId(chat.models, id)
   if (!model || modelSwitching.value) return
   modelSwitching.value = true
   try {

+ 1 - 1
src/server/websocket.ts

@@ -84,7 +84,7 @@ function normalizedConfigParams(params: Record<string, unknown>): Record<string,
   const key = safeString(params.key, 'config key', true)
   if (key === 'model') {
     const value = typeof params.value === 'string' ? params.value.trim() : ''
-    if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,511} --provider [A-Za-z0-9][A-Za-z0-9._-]{0,127}( --session)?$/.test(value)) {
+    if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,511} --provider [A-Za-z0-9][A-Za-z0-9._:-]{0,127}( --session)?$/.test(value)) {
       throw new HttpError(400, 'Model selection is invalid')
     }
     return {

+ 6 - 1
tests/client/sessionModel.test.ts

@@ -1,5 +1,5 @@
 import { describe, expect, it } from 'vitest'
-import { modelForSession, modelSelectionFromSessionInfo } from '@/utils/sessionModel'
+import { modelForChoiceId, modelForSession, modelSelectionFromSessionInfo } from '@/utils/sessionModel'
 import { normalizeSession } from '@/utils/normalize'
 
 const models = [
@@ -32,4 +32,9 @@ describe('session model selection', () => {
       model_config: JSON.stringify({ model: 'omni', provider: 'custom:tingly' }),
     })).toMatchObject({ model: 'omni', provider: 'custom:tingly' })
   })
+
+  it('finds a chosen model when its provider contains a colon', () => {
+    const tinglyModel = { id: 'omni', name: 'Omni', provider: 'Custom:Tingly' }
+    expect(modelForChoiceId([tinglyModel], 'Custom:Tingly:omni')).toEqual(tinglyModel)
+  })
 })

+ 12 - 0
tests/server/websocket.test.ts

@@ -348,6 +348,18 @@ describe('WebSocket relay', () => {
       params: { session_id: 'stored-session', text: '下一条', queued: true },
     })
 
+    socket.send(JSON.stringify({
+      id: 'model-with-scoped-provider',
+      method: 'config.set',
+      params: { session_id: 'stored-session', key: 'model', value: 'omni --provider Custom:Tingly --session' },
+    }))
+    await wsMessage(socket)
+    expect(received[3]).toEqual({
+      id: 'model-with-scoped-provider',
+      method: 'config.set',
+      params: { session_id: 'stored-session', key: 'model', value: 'omni --provider Custom:Tingly --session' },
+    })
+
     socket.send(JSON.stringify({
       id: 'forbidden',
       method: 'config.set',