Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions studio/frontend/src/features/chat/components/model-load-status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +19,7 @@ function clampProgress(value: number): number {
}

export function ModelLoadDescription({
title,
message,
progressPercent,
progressLabel,
Expand All @@ -25,29 +28,34 @@ export function ModelLoadDescription({
const hasProgress = typeof progressPercent === "number";

return (
<div className="flex items-center gap-1.5">
<div className="flex min-h-12 w-full items-stretch gap-2">
<div className="flex h-full shrink-0 items-center self-center">
<Spinner className="size-4 text-foreground" />
</div>
<div className="min-w-0 flex-1">
{title ? <p className="text-foreground leading-5 font-semibold">{title}</p> : null}
{hasProgress ? (
<div className="w-[12.5rem] max-w-full">
<div className="w-full pt-1">
<div className="flex items-center justify-between text-[10px] font-medium tracking-[0.08em] text-muted-foreground/80">
<span>{progressLabel}</span>
<span>{Math.round(clampProgress(progressPercent))}%</span>
</div>
<Progress value={clampProgress(progressPercent)} className="h-1 bg-foreground/[0.08]" />
</div>
) : message ? (
<p className="text-xs leading-relaxed text-muted-foreground">{message}</p>
<p className="pt-1 text-xs leading-relaxed text-muted-foreground">{message}</p>
) : null}
</div>
{onStop ? (
<Button
type="button"
size="xs"
variant="outline"
className="h-5 shrink-0 px-2 text-[10px]"
size="icon-sm"
variant="ghost"
aria-label="Stop model loading"
className="h-auto w-10 self-stretch shrink-0 rounded-xl text-muted-foreground hover:bg-destructive/10 hover:text-destructive focus-visible:text-destructive"
onClick={onStop}
>
Stop
<XIcon className="size-3.5" />
</Button>
) : null}
</div>
Expand Down
30 changes: 15 additions & 15 deletions studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,})$/;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion studio/frontend/src/features/chat/shared-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading