import { Suspense, use, useActionState, useState } from "react"; import { compareModels, getModels } from "../api"; import type { CompareItem, ModelSummary } from "../api"; import ConfidenceBars from "./ConfidenceBars"; /** * Mirrors the backend's DEFAULT_COMPARE_MODELS: only these two are loaded at * startup. The other registry entries (finbert, xlm-twitter) are loaded * lazily by the backend on first use, so the UI keeps them off by default * and warns about the one-time load cost. */ const DEFAULT_COMPARE = ["twitter-roberta", "distilbert-sst2"]; const LABEL_BADGE: Record = { negative: "bg-red-100 text-red-700", neutral: "bg-slate-100 text-slate-700", positive: "bg-emerald-100 text-emerald-700", }; interface CompareState { rows: CompareItem[]; error: string | null; } export default function CompareModels() { // React 19 data fetching: create the registry promise once (useState // initializer runs a single time, so its identity is stable) and let a // child read it with use() under Suspense - no useEffect/setState dance. const [registryPromise] = useState(() => getModels("sentiment") .then((r) => r.models) .catch(() => [] as ModelSummary[]), ); return ( Loading model registry…

}>
); } function CompareForm({ registryPromise }: { registryPromise: Promise }) { const registry = use(registryPromise); const [text, setText] = useState("Our quarterly revenue outlook improved"); const [selected, setSelected] = useState(DEFAULT_COMPARE); // The comparison is a classic form action: submit -> pending -> result or // error. useActionState owns that lifecycle, so there is no hand-rolled // loading/error useState pair here. const [result, formAction, isPending] = useActionState( async () => { try { return { rows: (await compareModels(text, selected)).results, error: null }; } catch (e) { return { rows: [], error: e instanceof Error ? e.message : "Compare failed" }; } }, { rows: [], error: null }, ); const toggle = (id: string) => { setSelected((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id], ); }; if (registry.length === 0) { return

Model registry unavailable. Is the backend running?

; } return (