diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx index 1184183088..c363704d61 100644 --- a/studio/frontend/src/features/chat/chat-page.tsx +++ b/studio/frontend/src/features/chat/chat-page.tsx @@ -218,9 +218,9 @@ function InlineSidebar({ className={cn( "bg-sidebar text-sidebar-foreground h-full overflow-hidden rounded-2xl corner-squircle transition-[width] duration-200 ease-linear", !collapsed && - (side === "left" - ? "border-r border-0 border-sidebar-border" - : "border-l border-0 border-sidebar-border"), + (side === "left" + ? "border-r border-0 border-sidebar-border" + : "border-l border-0 border-sidebar-border"), collapsed ? "w-0" : "w-(--sidebar-width)", )} > @@ -301,9 +301,18 @@ export function ChatPage(): ReactElement { const handleCheckpointChange = useCallback( (value: string, meta?: { isLora: boolean }) => { - void selectModel({ id: value, isLora: meta?.isLora }); + const currentCheckpoint = + useChatRuntimeStore.getState().params.checkpoint; + if (!value || value === currentCheckpoint) return; + setView({ mode: "single", newThreadNonce: crypto.randomUUID() }); + void (async () => { + if (currentCheckpoint) { + await ejectModel(); + } + await selectModel({ id: value, isLora: meta?.isLora }); + })(); }, - [selectModel], + [selectModel, ejectModel], ); const handleEject = useCallback(() => { void ejectModel(); @@ -349,6 +358,41 @@ export function ChatPage(): ReactElement { setViewBeforeCompare(null); }, [viewBeforeCompare]); + const handleThreadSelect = useCallback( + (nextView: ChatView) => { + setView(nextView); + + const threadId = + nextView.mode === "single" ? nextView.threadId : undefined; + const pairId = + nextView.mode === "compare" ? nextView.pairId : undefined; + + void (async () => { + let thread: import("./types").ThreadRecord | undefined; + if (threadId) { + thread = await db.threads.get(threadId); + } else if (pairId) { + thread = await db.threads + .where("pairId") + .equals(pairId) + .first(); + } + const threadModelId = thread?.modelId; + if (!threadModelId) return; + + const currentCheckpoint = + useChatRuntimeStore.getState().params.checkpoint; + if (threadModelId === currentCheckpoint) return; + + if (currentCheckpoint) { + await ejectModel(); + } + await selectModel({ id: threadModelId }); + })(); + }, + [ejectModel, selectModel], + ); + const models = useMemo( () => modelsFromStore.map((model) => ({ @@ -475,99 +519,99 @@ export function ChatPage(): ReactElement { return (
- - - - - + + + + + -
-
-
- - - - {loadingModel ? ( -
- - - Downloading model… - -
- ) : null} -
- {modelsError && ( -
- {modelsError} +
+
+
+ + + + {loadingModel ? ( +
+ + + Downloading model… + +
+ ) : null}
+ {modelsError && ( +
+ {modelsError} +
+ )} +
+ +
+ + {view.mode === "single" ? ( + + ) : ( + )} -
-
- {view.mode === "single" ? ( - - ) : ( - - )} -
- - - + +
); } diff --git a/studio/frontend/src/features/chat/db.ts b/studio/frontend/src/features/chat/db.ts index 007f2a4a05..cc000edae9 100644 --- a/studio/frontend/src/features/chat/db.ts +++ b/studio/frontend/src/features/chat/db.ts @@ -19,6 +19,20 @@ db.version(2) }) .upgrade((tx) => tx.table("messages").clear()); +db.version(3) + .stores({ + threads: "id, modelType, pairId, archived, createdAt", + messages: "id, threadId, createdAt", + }) + .upgrade((tx) => + tx + .table("threads") + .toCollection() + .modify((thread) => { + if (!thread.modelId) thread.modelId = ""; + }), + ); + export { db }; export function useLiveQuery( diff --git a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts index 961967cc9c..3dca94d7f5 100644 --- a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts +++ b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts @@ -165,8 +165,10 @@ export function useChatModelRuntime() { setLoadingModel({ id: modelId, displayName }); try { async function performLoad(): Promise { - if (params.checkpoint) { - await unloadModel({ model_path: params.checkpoint }); + const currentCheckpoint = + useChatRuntimeStore.getState().params.checkpoint; + if (currentCheckpoint) { + await unloadModel({ model_path: currentCheckpoint }); } const loadResponse = await loadModel({ diff --git a/studio/frontend/src/features/chat/runtime-provider.tsx b/studio/frontend/src/features/chat/runtime-provider.tsx index 9f99d309c7..ce553dcdbf 100644 --- a/studio/frontend/src/features/chat/runtime-provider.tsx +++ b/studio/frontend/src/features/chat/runtime-provider.tsx @@ -323,10 +323,13 @@ function createDexieAdapter( }, async initialize(threadId: string) { + const currentModelId = + useChatRuntimeStore.getState().params.checkpoint ?? ""; await db.threads.add({ id: threadId, title: "New Chat", modelType, + modelId: currentModelId, pairId, archived: false, createdAt: Date.now(), diff --git a/studio/frontend/src/features/chat/types.ts b/studio/frontend/src/features/chat/types.ts index 01fa4fe200..a60bcbe657 100644 --- a/studio/frontend/src/features/chat/types.ts +++ b/studio/frontend/src/features/chat/types.ts @@ -8,6 +8,7 @@ export interface ThreadRecord { id: string; title: string; modelType: ModelType; + modelId?: string; pairId?: string; archived: boolean; createdAt: number;