From 620f6ca93827ae7d49f7fcf770a9cede73a37c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sun, 4 Oct 2026 10:30:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(AGC):=20=E7=9B=AE=E5=BD=95=E7=BC=BA=20unava?= =?UTF-8?q?ilableModels=20=E6=97=B6=E4=B8=8D=E5=86=8D=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - applyCatalog 归一化 catalog.unavailableModels,修掉按 reason 取降级文案时对 undefined 解引用(此前 26 个用例挂在这) - 用例跟进新降级文案;disabled 用例补上下线桶,断言「该模型已下线」专用文案 --- .../project-workspace/ConversationModelSelect.tsx | 7 +++++-- .../tests/conversationModelSelect.test.tsx | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx index 499e4b0fd..951f2274e 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx @@ -144,6 +144,9 @@ export function ConversationModelSelect({ showBusy: boolean, epochAtRequest: number, ) => { + // 目录契约的 `unavailableModels` 是加法字段:旧缓存 / 局部数据可能缺失。 + // 统一归一化一次,避免下面 setState 与按 reason 取文案时解引用 undefined。 + const catalogUnavailableModels = catalog.unavailableModels ?? []; if ( mountedRef.current && (appliedRevisionRef.current !== catalog.revision || @@ -151,7 +154,7 @@ export function ConversationModelSelect({ ) { appliedRevisionRef.current = catalog.revision; setModels(catalog.models); - setUnavailableModels(catalog.unavailableModels); + setUnavailableModels(catalogUnavailableModels); setDefaultModelId(catalog.defaultModelId); } const invoke = resolveTauriInvoke(); @@ -190,7 +193,7 @@ export function ConversationModelSelect({ if (saved) { // 命中不可用桶时按 reason 给具体原因;两桶都没有(后台直接删除)走泛化文案。 nextNotice = unavailableModelFallbackNotice( - catalog.unavailableModels.find((model) => model.id === saved) + catalogUnavailableModels.find((model) => model.id === saved) ?.reason, ); } diff --git a/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx b/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx index c2a5c252f..6a4f2a067 100644 --- a/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx +++ b/apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx @@ -315,7 +315,7 @@ test('falls back to the default model when the saved selection was removed', asy })); const onReady = vi.fn(); render(); - await screen.findByText('所选模型已停用,已切换为默认模型'); + await screen.findByText('所选模型已不可用,已切回默认模型'); await waitFor(() => expect(onReady).toHaveBeenLastCalledWith(true)); expect(screen.queryByText('private-old-model')).toBeNull(); expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { @@ -521,12 +521,22 @@ test('pre-send validation falls back when the selected model is disabled', async vi.mocked(loadClientLlmModels).mockResolvedValue({ defaultModelId: 'quality', models: [{ id: 'quality', displayName: '高质量', agentMode: 'codex' }], + // `fast` 仍在下线桶里:降级文案要按 `disabled` 取「该模型已下线」。 + unavailableModels: [ + { + id: 'fast', + displayName: '快速', + agentMode: 'cc', + protocol: 'anthropic', + reason: 'disabled', + }, + ], revision: 2, }); await act(async () => { await expect(ref.current?.ensureUsable()).resolves.toBe(true); }); - expect(screen.getByText('所选模型已停用,已切换为默认模型')).not.toBeNull(); + expect(screen.getByText('所选模型已下线,已切换为默认模型')).not.toBeNull(); expect(savedModelId).toBe('quality'); expect(invoke).toHaveBeenCalledWith('select_game_creator_model', { modelId: 'quality',