fix(AGC): 目录缺 unavailableModels 时不再崩溃
Project CI / Backend tests (pull_request) Failing after 1m26s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m5s
Project CI / Native shell tests (pull_request) Failing after 2m18s
Project CI / Frontend tests (pull_request) Failing after 1m7s
Project CI / Repository checks (pull_request) Failing after 1m15s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m3s
Project CI / AI game creator shell web tests (pull_request) Failing after 58s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled

- applyCatalog 归一化 catalog.unavailableModels,修掉按 reason 取降级文案时对 undefined 解引用(此前 26 个用例挂在这)
- 用例跟进新降级文案;disabled 用例补上下线桶,断言「该模型已下线」专用文案
This commit is contained in:
2026-10-04 10:30:41 +08:00
parent c391352ff4
commit 620f6ca938
2 changed files with 17 additions and 4 deletions
@@ -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,
);
}
@@ -315,7 +315,7 @@ test('falls back to the default model when the saved selection was removed', asy
}));
const onReady = vi.fn();
render(<ConversationModelSelect disabled={false} onReady={onReady} />);
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',