[Feature] compare for 2 diff models (#4356)

* compare for 2 diff models

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* resolving gemini comments

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(studio): refine model-load toast stop action and compare selector sizing (#4369)

Co-authored-by: imagineer99 <samleejackson0@gmail.com>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: imagineer99 <samleejackson0@gmail.com>
This commit is contained in:
Manan Shah 2026-03-17 13:58:34 -05:00 committed by GitHub
commit 93ab09d195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 23 deletions

View file

@ -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 (
<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>
@ -36,18 +43,19 @@ export function ModelLoadDescription({
<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>

View file

@ -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;

View file

@ -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();