Preserve provider changes before sending

This commit is contained in:
2026-07-26 17:44:45 -07:00
parent e2443162b0
commit cb5a973ac7
4 changed files with 102 additions and 16 deletions

View File

@@ -63,6 +63,10 @@ import {
type WorkspaceItem, type WorkspaceItem,
} from "@/lib/api"; } from "@/lib/api";
import { useSessionAuth } from "@/hooks/use-session-auth"; import { useSessionAuth } from "@/hooks/use-session-auth";
import {
getChatModelSelection,
getChatModelSelectionSyncKey,
} from "@/lib/chat-model-selection";
import { import {
resolveSidebarSelectionAfterRefresh, resolveSidebarSelectionAfterRefresh,
type SidebarSelection, type SidebarSelection,
@@ -545,14 +549,6 @@ function normalizeEnabledTools(value: unknown, availableTools: ChatToolInfo[]) {
); );
} }
function getChatModelSelection(chat: Pick<ChatSummary, "lastUsedProvider" | "lastUsedModel"> | Pick<ChatDetail, "lastUsedProvider" | "lastUsedModel"> | null) {
if (!chat?.lastUsedProvider || !chat.lastUsedModel?.trim()) return null;
return {
provider: chat.lastUsedProvider,
model: chat.lastUsedModel.trim(),
};
}
type ToolLogMetadata = { type ToolLogMetadata = {
kind: "tool_call"; kind: "tool_call";
toolCallId?: string; toolCallId?: string;
@@ -1562,15 +1558,32 @@ export default function App() {
return searches.find((search) => search.id === selectedItem.id) ?? null; return searches.find((search) => search.id === selectedItem.id) ?? null;
}, [searches, selectedItem]); }, [searches, selectedItem]);
useEffect(() => { const selectedChatModelSelection = useMemo(() => {
if (draftKind || selectedItem?.kind !== "chat") return; if (draftKind || selectedItem?.kind !== "chat") return null;
const detailSelection = selectedChat?.id === selectedItem.id ? getChatModelSelection(selectedChat) : null; const detailSelection = selectedChat?.id === selectedItem.id ? getChatModelSelection(selectedChat) : null;
const summarySelection = getChatModelSelection(selectedChatSummary); const summarySelection = getChatModelSelection(selectedChatSummary);
const nextSelection = detailSelection ?? summarySelection; return detailSelection ?? summarySelection;
if (!nextSelection) return; }, [
setProvider(nextSelection.provider); draftKind,
setModel(nextSelection.model); selectedChat?.id,
}, [draftKind, selectedChat, selectedChatSummary, selectedItem]); selectedChat?.lastUsedModel,
selectedChat?.lastUsedProvider,
selectedChatSummary?.id,
selectedChatSummary?.lastUsedModel,
selectedChatSummary?.lastUsedProvider,
selectedItem?.id,
selectedItem?.kind,
]);
const selectedChatModelSelectionSyncKey = getChatModelSelectionSyncKey(
selectedItem?.kind === "chat" ? selectedItem.id : null,
selectedChatModelSelection
);
useEffect(() => {
if (!selectedChatModelSelection) return;
setProvider(selectedChatModelSelection.provider);
setModel(selectedChatModelSelection.model);
}, [selectedChatModelSelectionSyncKey]);
useEffect(() => { useEffect(() => {
if (draftKind === "chat") return; if (draftKind === "chat") return;

View File

@@ -0,0 +1,24 @@
import type { Provider } from "./api";
type PersistedChatModel = {
lastUsedProvider: Provider | null;
lastUsedModel: string | null;
};
export type ChatModelSelection = {
provider: Provider;
model: string;
};
export function getChatModelSelection(chat: PersistedChatModel | null): ChatModelSelection | null {
if (!chat?.lastUsedProvider || !chat.lastUsedModel?.trim()) return null;
return {
provider: chat.lastUsedProvider,
model: chat.lastUsedModel.trim(),
};
}
export function getChatModelSelectionSyncKey(chatId: string | null, selection: ChatModelSelection | null) {
if (!chatId || !selection) return null;
return JSON.stringify([chatId, selection.provider, selection.model]);
}

View File

@@ -0,0 +1,49 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
getChatModelSelection,
getChatModelSelectionSyncKey,
} from "../src/lib/chat-model-selection.ts";
test("chat model selections are normalized from persisted metadata", () => {
assert.deepEqual(
getChatModelSelection({
lastUsedProvider: "anthropic",
lastUsedModel: " claude-sonnet-4-5 ",
}),
{
provider: "anthropic",
model: "claude-sonnet-4-5",
}
);
});
test("unrelated chat updates do not change the model synchronization key", () => {
const beforeSettingsSave = getChatModelSelection({
lastUsedProvider: "openai",
lastUsedModel: "gpt-4.1-mini",
});
const afterSettingsSave = getChatModelSelection({
lastUsedProvider: "openai",
lastUsedModel: "gpt-4.1-mini",
});
assert.equal(
getChatModelSelectionSyncKey("chat-1", beforeSettingsSave),
getChatModelSelectionSyncKey("chat-1", afterSettingsSave)
);
});
test("switching chats or persisted models changes the synchronization key", () => {
const original = { provider: "openai", model: "gpt-4.1-mini" };
const updated = { provider: "gemini", model: "gemini-3.5-flash" };
assert.notEqual(
getChatModelSelectionSyncKey("chat-1", original),
getChatModelSelectionSyncKey("chat-2", original)
);
assert.notEqual(
getChatModelSelectionSyncKey("chat-1", original),
getChatModelSelectionSyncKey("chat-1", updated)
);
});

View File

@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/pwa.ts","./src/root-router.tsx","./src/vite-env.d.ts","./src/components/sybil-character.tsx","./src/components/auth/auth-screen.tsx","./src/components/chat/chat-attachment-list.tsx","./src/components/chat/chat-messages-panel.tsx","./src/components/markdown/markdown-content.tsx","./src/components/search/search-results-panel.tsx","./src/components/ui/button.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/separator.tsx","./src/components/ui/textarea.tsx","./src/hooks/use-session-auth.ts","./src/lib/api.ts","./src/lib/sidebar-selection.ts","./src/lib/utils.ts","./src/pages/search-route-page.tsx"],"version":"5.9.3"} {"root":["./src/App.tsx","./src/main.tsx","./src/pwa.ts","./src/root-router.tsx","./src/vite-env.d.ts","./src/components/sybil-character.tsx","./src/components/auth/auth-screen.tsx","./src/components/chat/chat-attachment-list.tsx","./src/components/chat/chat-messages-panel.tsx","./src/components/markdown/markdown-content.tsx","./src/components/search/search-results-panel.tsx","./src/components/ui/button.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/separator.tsx","./src/components/ui/textarea.tsx","./src/hooks/use-session-auth.ts","./src/lib/api.ts","./src/lib/chat-model-selection.ts","./src/lib/sidebar-selection.ts","./src/lib/utils.ts","./src/pages/search-route-page.tsx"],"version":"5.9.3"}