diff --git a/studio/frontend/src/features/chat/components/model-load-status.tsx b/studio/frontend/src/features/chat/components/model-load-status.tsx index 19eff054f7..ad9946b0fe 100644 --- a/studio/frontend/src/features/chat/components/model-load-status.tsx +++ b/studio/frontend/src/features/chat/components/model-load-status.tsx @@ -4,8 +4,10 @@ import { Progress } from "@/components/ui/progress"; import { Spinner } from "@/components/ui/spinner"; import { Button } from "@/components/ui/button"; +import { XIcon } from "lucide-react"; type ModelLoadDescriptionProps = { + title?: string | null; message?: string | null; progressPercent?: number | null; progressLabel?: string | null; @@ -17,6 +19,7 @@ function clampProgress(value: number): number { } export function ModelLoadDescription({ + title, message, progressPercent, progressLabel, @@ -25,10 +28,14 @@ export function ModelLoadDescription({ const hasProgress = typeof progressPercent === "number"; return ( -
+
+
+ +
+ {title ?

{title}

: null} {hasProgress ? ( -
+
{progressLabel} {Math.round(clampProgress(progressPercent))}% @@ -36,18 +43,19 @@ export function ModelLoadDescription({
) : message ? ( -

{message}

+

{message}

) : null}
{onStop ? ( ) : null}
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 6927b1871f..cfdd4e774a 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 @@ -3,7 +3,6 @@ import { createElement, useCallback, useRef, useState } from "react"; import { toast } from "sonner"; -import { Spinner } from "@/components/ui/spinner"; import { ModelLoadDescription } from "../components/model-load-status"; import { getDownloadProgress, @@ -34,12 +33,10 @@ type SelectedModelInput = { }; const MODEL_LOAD_TOAST_CLASSNAMES = { - toast: "items-start gap-2.5 pr-8", - content: "gap-0.5", + toast: "items-start gap-2.5", + content: "gap-0.5 flex-1 min-w-0", title: "leading-5", - description: "mt-0", - closeButton: - "!left-auto !right-1.5 !top-1.5 !translate-x-0 !translate-y-0 !border-transparent !bg-transparent !shadow-none hover:!bg-transparent hover:opacity-70", + description: "mt-0 w-full", } as const; const LORA_SUFFIX_RE = /_(\d{9,})$/; @@ -200,12 +197,14 @@ export function useChatModelRuntime() { const renderLoadDescription = useCallback( ( + title: string, message: string, progressPercent?: number | null, progressLabel?: string | null, onStop?: () => void, ) => createElement(ModelLoadDescription, { + title, message, progressPercent, progressLabel, @@ -448,18 +447,19 @@ export function useChatModelRuntime() { } } + const toastTitle = isDownloaded ? "Starting model…" : "Downloading model…"; const toastId = toast( - isDownloaded ? "Starting model…" : "Downloading model…", + null, { - icon: createElement(Spinner, { className: "size-4" }), description: renderLoadDescription( + toastTitle, loadingDescription, isDownloaded ? null : 0, isDownloaded ? null : "Preparing download", cancelLoading, ), duration: Infinity, - closeButton: true, + closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) { @@ -505,18 +505,18 @@ export function useChatModelRuntime() { }); if (loadToastDismissedRef.current) return; toast( - "Downloading model…", + null, { id: toastId, - icon: createElement(Spinner, { className: "size-4" }), description: renderLoadDescription( + "Downloading model…", loadingDescription, pct, progressLabel, cancelLoading, ), duration: Infinity, - closeButton: true, + closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) return; @@ -542,17 +542,17 @@ export function useChatModelRuntime() { if (progressInterval) clearInterval(progressInterval); return; } - toast("Starting model…", { + toast(null, { id: toastId, - icon: createElement(Spinner, { className: "size-4" }), description: renderLoadDescription( + "Starting model…", "Download complete. Loading the model into memory.", 100, "Download complete", cancelLoading, ), duration: Infinity, - closeButton: true, + closeButton: false, classNames: MODEL_LOAD_TOAST_CLASSNAMES, onDismiss: (dismissedToast) => { if (loadToastIdRef.current !== dismissedToast.id) return; diff --git a/studio/frontend/src/features/chat/shared-composer.tsx b/studio/frontend/src/features/chat/shared-composer.tsx index c2ca25a658..018c3ba410 100644 --- a/studio/frontend/src/features/chat/shared-composer.tsx +++ b/studio/frontend/src/features/chat/shared-composer.tsx @@ -363,7 +363,7 @@ export function SharedComposer({ // Side 1: load → generate → wait if (handle1 && model1?.id) { toast("Loading Model 1…", { id: toastId, description: name1, duration: Infinity }); - await ensureModelLoaded(model1); + const status = await ensureModelLoaded(model1); toast("Generating with Model 1…", { id: toastId, description: name1, duration: Infinity }); const done = handle1.waitForRunEnd(); handle1.startRun();