diff --git a/docs/api/rest.md b/docs/api/rest.md index 692ba06..7eb6876 100644 --- a/docs/api/rest.md +++ b/docs/api/rest.md @@ -111,6 +111,7 @@ Behavior notes: - If `chatId` is present, server validates chat existence. - For `chatId` calls, server stores only *new* non-assistant messages from provided history to avoid duplicates. - Server persists final assistant output and call metadata (`LlmCall`) in DB. +- Server updates chat-level model metadata on each call: `lastUsedProvider`/`lastUsedModel`; first successful/failed call also initializes `initiatedProvider`/`initiatedModel` if unset. ## Searches @@ -151,7 +152,16 @@ Search run notes: `ChatSummary` ```json -{ "id": "...", "title": null, "createdAt": "...", "updatedAt": "..." } +{ + "id": "...", + "title": null, + "createdAt": "...", + "updatedAt": "...", + "initiatedProvider": "openai|anthropic|xai|null", + "initiatedModel": "string|null", + "lastUsedProvider": "openai|anthropic|xai|null", + "lastUsedModel": "string|null" +} ``` `Message` @@ -172,6 +182,10 @@ Search run notes: "title": null, "createdAt": "...", "updatedAt": "...", + "initiatedProvider": "openai|anthropic|xai|null", + "initiatedModel": "string|null", + "lastUsedProvider": "openai|anthropic|xai|null", + "lastUsedModel": "string|null", "messages": [Message] } ``` diff --git a/server/prisma/migrations/20260214113000_add_chat_model_memory/migration.sql b/server/prisma/migrations/20260214113000_add_chat_model_memory/migration.sql new file mode 100644 index 0000000..01a49e9 --- /dev/null +++ b/server/prisma/migrations/20260214113000_add_chat_model_memory/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Chat" ADD COLUMN "initiatedProvider" TEXT; +ALTER TABLE "Chat" ADD COLUMN "initiatedModel" TEXT; +ALTER TABLE "Chat" ADD COLUMN "lastUsedProvider" TEXT; +ALTER TABLE "Chat" ADD COLUMN "lastUsedModel" TEXT; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 5bba200..39fbe5d 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -45,6 +45,11 @@ model Chat { title String? + initiatedProvider Provider? + initiatedModel String? + lastUsedProvider Provider? + lastUsedModel String? + user User? @relation(fields: [userId], references: [id]) userId String? diff --git a/server/src/llm/multiplexer.ts b/server/src/llm/multiplexer.ts index ac0884d..39be339 100644 --- a/server/src/llm/multiplexer.ts +++ b/server/src/llm/multiplexer.ts @@ -10,11 +10,12 @@ function asProviderEnum(p: Provider) { export async function runMultiplex(req: MultiplexRequest): Promise { const t0 = performance.now(); + const chatId = req.chatId ?? (await prisma.chat.create({ data: {}, select: { id: true } })).id; // Persist call record early so we can attach errors. const call = await prisma.llmCall.create({ data: { - chatId: req.chatId ?? (await prisma.chat.create({ data: {} })).id, + chatId, provider: asProviderEnum(req.provider) as any, model: req.model, request: req as any, @@ -22,6 +23,23 @@ export async function runMultiplex(req: MultiplexRequest): Promise | Pick | null) { + if (!chat?.lastUsedProvider || !chat.lastUsedModel?.trim()) return null; + return { + provider: chat.lastUsedProvider, + model: chat.lastUsedModel.trim(), + }; +} + type ModelComboboxProps = { options: string[]; value: string; @@ -212,6 +231,10 @@ function buildSidebarItems(chats: ChatSummary[], searches: SearchSummary[]): Sid title: getChatTitle(chat), updatedAt: chat.updatedAt, createdAt: chat.createdAt, + initiatedProvider: chat.initiatedProvider, + initiatedModel: chat.initiatedModel, + lastUsedProvider: chat.lastUsedProvider, + lastUsedModel: chat.lastUsedModel, })), ...searches.map((search) => ({ kind: "search" as const, @@ -219,6 +242,10 @@ function buildSidebarItems(chats: ChatSummary[], searches: SearchSummary[]): Sid title: getSearchTitle(search), updatedAt: search.updatedAt, createdAt: search.createdAt, + initiatedProvider: null, + initiatedModel: null, + lastUsedProvider: null, + lastUsedModel: null, })), ]; @@ -473,6 +500,16 @@ export default function App() { return searches.find((search) => search.id === selectedItem.id) ?? null; }, [searches, selectedItem]); + useEffect(() => { + if (draftKind || selectedItem?.kind !== "chat") return; + const detailSelection = selectedChat?.id === selectedItem.id ? getChatModelSelection(selectedChat) : null; + const summarySelection = getChatModelSelection(selectedChatSummary); + const nextSelection = detailSelection ?? summarySelection; + if (!nextSelection) return; + setProvider(nextSelection.provider); + setModel(nextSelection.model); + }, [draftKind, selectedChat, selectedChatSummary, selectedItem]); + const selectedTitle = useMemo(() => { if (draftKind === "chat") return "New chat"; if (draftKind === "search") return "New search"; @@ -611,6 +648,10 @@ export default function App() { title: chat.title, createdAt: chat.createdAt, updatedAt: chat.updatedAt, + initiatedProvider: chat.initiatedProvider, + initiatedModel: chat.initiatedModel, + lastUsedProvider: chat.lastUsedProvider, + lastUsedModel: chat.lastUsedModel, messages: [], }); setSelectedSearch(null); @@ -937,6 +978,9 @@ export default function App() { ) : null} {sidebarItems.map((item) => { const active = selectedItem?.kind === item.kind && selectedItem.id === item.id; + const initiatedLabel = item.kind === "chat" && item.initiatedModel + ? `${getProviderLabel(item.initiatedProvider)}${item.initiatedProvider ? " ยท " : ""}${item.initiatedModel}` + : null; return ( ); })} diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index d86abeb..38e67ec 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -3,6 +3,10 @@ export type ChatSummary = { title: string | null; createdAt: string; updatedAt: string; + initiatedProvider: Provider | null; + initiatedModel: string | null; + lastUsedProvider: Provider | null; + lastUsedModel: string | null; }; export type SearchSummary = { @@ -26,6 +30,10 @@ export type ChatDetail = { title: string | null; createdAt: string; updatedAt: string; + initiatedProvider: Provider | null; + initiatedModel: string | null; + lastUsedProvider: Provider | null; + lastUsedModel: string | null; messages: Message[]; };