feat(chat): eject current model and start fresh thread on model switch
This commit is contained in:
parent
1fa64ed2b9
commit
ef8cbdde93
2 changed files with 106 additions and 95 deletions
|
|
@ -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();
|
||||
|
|
@ -475,99 +484,99 @@ export function ChatPage(): ReactElement {
|
|||
|
||||
return (
|
||||
<div className="h-[calc(100dvh-4rem)] bg-background overflow-hidden">
|
||||
<GuidedTour {...tour.tourProps} />
|
||||
<SidebarProvider
|
||||
defaultOpen={true}
|
||||
open={sidebarOpen}
|
||||
onOpenChange={setSidebarOpen}
|
||||
className="!min-h-0 h-full w-full max-w-7xl mx-auto px-2 sm:px-4"
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "14rem",
|
||||
"--sidebar-width-icon": "3rem",
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<InlineSidebar>
|
||||
<ThreadSidebar
|
||||
view={view}
|
||||
onSelect={setView}
|
||||
onNewThread={handleNewThread}
|
||||
onNewCompare={handleNewCompare}
|
||||
showCompare={canCompare}
|
||||
/>
|
||||
</InlineSidebar>
|
||||
<GuidedTour {...tour.tourProps} />
|
||||
<SidebarProvider
|
||||
defaultOpen={true}
|
||||
open={sidebarOpen}
|
||||
onOpenChange={setSidebarOpen}
|
||||
className="!min-h-0 h-full w-full max-w-7xl mx-auto px-2 sm:px-4"
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "14rem",
|
||||
"--sidebar-width-icon": "3rem",
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<InlineSidebar>
|
||||
<ThreadSidebar
|
||||
view={view}
|
||||
onSelect={setView}
|
||||
onNewThread={handleNewThread}
|
||||
onNewCompare={handleNewCompare}
|
||||
showCompare={canCompare}
|
||||
/>
|
||||
</InlineSidebar>
|
||||
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<div className="flex h-11 shrink-0 items-center px-1.5 sm:px-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarTrigger />
|
||||
<TopBarActions
|
||||
onNewThread={handleNewThread}
|
||||
onNewCompare={handleNewCompare}
|
||||
showCompare={canCompare}
|
||||
/>
|
||||
<ModelSelector
|
||||
models={models}
|
||||
loraModels={loraModels}
|
||||
value={inferenceParams.checkpoint}
|
||||
onValueChange={handleCheckpointChange}
|
||||
onEject={handleEject}
|
||||
variant="ghost"
|
||||
open={modelSelectorOpen}
|
||||
onOpenChange={handleModelSelectorOpenChange}
|
||||
triggerDataTour="chat-model-selector"
|
||||
contentDataTour="chat-model-selector-popover"
|
||||
className="max-w-[62vw] sm:max-w-none"
|
||||
/>
|
||||
{loadingModel ? (
|
||||
<div
|
||||
className="flex items-center gap-1.5 text-muted-foreground"
|
||||
title={`Loading ${loadingModel.displayName}. This may include downloading.`}
|
||||
>
|
||||
<Spinner className="size-3.5 shrink-0" />
|
||||
<span className="text-xs">
|
||||
Downloading model…
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{modelsError && (
|
||||
<div className="ml-2 text-xs text-destructive truncate max-w-[28rem]">
|
||||
{modelsError}
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<div className="flex h-11 shrink-0 items-center px-1.5 sm:px-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<SidebarTrigger />
|
||||
<TopBarActions
|
||||
onNewThread={handleNewThread}
|
||||
onNewCompare={handleNewCompare}
|
||||
showCompare={canCompare}
|
||||
/>
|
||||
<ModelSelector
|
||||
models={models}
|
||||
loraModels={loraModels}
|
||||
value={inferenceParams.checkpoint}
|
||||
onValueChange={handleCheckpointChange}
|
||||
onEject={handleEject}
|
||||
variant="ghost"
|
||||
open={modelSelectorOpen}
|
||||
onOpenChange={handleModelSelectorOpenChange}
|
||||
triggerDataTour="chat-model-selector"
|
||||
contentDataTour="chat-model-selector-popover"
|
||||
className="max-w-[62vw] sm:max-w-none"
|
||||
/>
|
||||
{loadingModel ? (
|
||||
<div
|
||||
className="flex items-center gap-1.5 text-muted-foreground"
|
||||
title={`Loading ${loadingModel.displayName}. This may include downloading.`}
|
||||
>
|
||||
<Spinner className="size-3.5 shrink-0" />
|
||||
<span className="text-xs">
|
||||
Downloading model…
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{modelsError && (
|
||||
<div className="ml-2 text-xs text-destructive truncate max-w-[28rem]">
|
||||
{modelsError}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSettingsOpen((o) => !o)}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
title="Inference settings"
|
||||
data-tour="chat-settings"
|
||||
>
|
||||
<HugeiconsIcon icon={Settings04Icon} className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{view.mode === "single" ? (
|
||||
<SingleContent
|
||||
key={view.threadId ?? view.newThreadNonce ?? "new"}
|
||||
threadId={view.threadId}
|
||||
newThreadNonce={view.newThreadNonce}
|
||||
/>
|
||||
) : (
|
||||
<CompareContent key={view.pairId} pairId={view.pairId} />
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSettingsOpen((o) => !o)}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
title="Inference settings"
|
||||
data-tour="chat-settings"
|
||||
>
|
||||
<HugeiconsIcon icon={Settings04Icon} className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{view.mode === "single" ? (
|
||||
<SingleContent
|
||||
key={view.threadId ?? view.newThreadNonce ?? "new"}
|
||||
threadId={view.threadId}
|
||||
newThreadNonce={view.newThreadNonce}
|
||||
/>
|
||||
) : (
|
||||
<CompareContent key={view.pairId} pairId={view.pairId} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ChatSettingsPanel
|
||||
open={settingsOpen}
|
||||
params={inferenceParams}
|
||||
onParamsChange={setInferenceParams}
|
||||
autoTitle={autoTitle}
|
||||
onAutoTitleChange={setAutoTitle}
|
||||
/>
|
||||
</SidebarProvider>
|
||||
<ChatSettingsPanel
|
||||
open={settingsOpen}
|
||||
params={inferenceParams}
|
||||
onParamsChange={setInferenceParams}
|
||||
autoTitle={autoTitle}
|
||||
onAutoTitleChange={setAutoTitle}
|
||||
/>
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,8 +165,10 @@ export function useChatModelRuntime() {
|
|||
setLoadingModel({ id: modelId, displayName });
|
||||
try {
|
||||
async function performLoad(): Promise<void> {
|
||||
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({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue