From 1fb70ff6bed3ab32c2a2bcd3e76531d2ff897a93 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 3 Oct 2026 16:49:53 -0700 Subject: [PATCH] feat(lens): collapse the live run to an ambient line with show work --- .../lens/investigations/live/LiveRun.tsx | 157 +++++++++++------- .../investigations/live/useReviewPlayback.ts | 14 +- .../lens/investigations/live/useShowWork.ts | 32 ++++ 3 files changed, 136 insertions(+), 67 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/lens/investigations/live/useShowWork.ts diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx index 2dcd4900483..ba4867aa0ec 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx @@ -1,11 +1,12 @@ "use client"; +import { ChevronDown, Loader2 } from "lucide-react"; import { useMemo, useRef } from "react"; import { ProviderLogo } from "@/components/molecules/models/ProviderLogo"; -import { StatusBadge, type StatusTone } from "@/components/shared/table_cells"; import { Button } from "@/components/ui/button"; import { useNow } from "@/hooks/useNow"; +import { cn } from "@/lib/cva.config"; import { durationText, money } from "../../model/format"; import { modelsUsed } from "../../model/inbox"; @@ -18,7 +19,7 @@ import { providerOf, reviewKey, shownCount, - tokenLabel, + tickerLine, verdictLine, } from "../../model/live"; import type { Job, Review } from "../../model/types"; @@ -26,78 +27,106 @@ import { ConclusionsPanel, type Arrival } from "./ConclusionsPanel"; import { ReadingPanel } from "./ReadingPanel"; import { ReviewQueue } from "./ReviewQueue"; import { useReviewPlayback } from "./useReviewPlayback"; +import { useShowWork } from "./useShowWork"; -const STATUS: Record = { - queued: { label: "Queued", tone: "neutral" }, - running: { label: "Running", tone: "neutral" }, - completed: { label: "Done", tone: "neutral" }, - failed: { label: "Failed", tone: "error" }, - cancelled: { label: "Cancelled", tone: "neutral" }, -}; const COLUMN_HEAD = "sticky top-0 z-sticky flex h-8 shrink-0 items-center justify-between gap-2 border-b border-border bg-muted/40 px-3 text-[10px] tracking-[0.08em] text-muted-foreground uppercase backdrop-blur"; -const COLUMN = "flex min-h-0 flex-col overflow-y-auto"; +const COLUMN = "flex max-h-[320px] min-h-0 flex-col overflow-y-auto"; -function useArrival(review: Review | null, verdict: boolean, compact: boolean): Arrival | null { - const issue = review && verdict && !compact ? review.verdicts.find((v) => v.kind === "issue") : undefined; +function useArrival(review: Review | null, verdict: boolean, enabled: boolean): Arrival | null { + const issue = review && verdict && enabled ? review.verdicts.find((v) => v.kind === "issue") : undefined; const key = review && issue ? reviewKey(review) : ""; const checkId = issue?.check_id ?? ""; const text = review ? verdictLine(review) : ""; return useMemo(() => (key ? { key, checkId, text } : null), [key, checkId, text]); } -function ModelLabel({ model }: { model: string }) { +function Model({ model }: { model: string }) { if (!model) return null; const provider = providerOf(model); return ( - - {provider && } - {model} + + {provider && } + {model} + + ); +} + +function Ticker({ review }: { review: Review | null }) { + return ( + + {review && ( + + {tickerLine(review)} + + )} ); } export function LiveRun({ job, onCancel }: { job: Job; onCancel?: () => void }) { const live = job.status === "queued" || job.status === "running"; + const [open, toggle] = useShowWork(); const playback = useReviewPlayback(job.reviews, live); - const { current, played, phase, duration } = playback; + const { current, played, phase, duration, replay } = playback; const now = useNow(1000); const verdictRef = useRef(null); const model = analysisModel([...job.reviews.map((r) => r.model), ...modelsUsed(job.steps), job.settings.model]); const decided = phase.verdict && current ? [...played, current] : played; const groups = conclusions(decided, job.settings.checks); - const arrival = useArrival(current, phase.verdict, isCompact(duration)); + const arrival = useArrival(current, phase.verdict, open && !isCompact(duration)); const stats = liveStats(job, now); - const reviewed = shownCount(job.reviewed, playback); + const reviewed = live ? shownCount(job.reviewed, playback) : job.reviewed; const selected = job.coverage.selected; const percent = live ? Math.min(100, Math.round((reviewed / Math.max(1, selected)) * 100)) : 100; - const issues = decided.filter((r) => outcome(r) === "issue").length; - const status = STATUS[job.status]; + const issues = (live ? decided : job.reviews).filter((r) => outcome(r) === "issue").length; + const showWork = () => { + if (!open && !live) replay(); + toggle(); + }; + const issueText = `${issues} ${issues === 1 ? "issue" : "issues"}`; return (
-
-
- - - {live ? job.stage : "Complete"} +
+ {live ? ( + <> +
-
- {durationText(stats.elapsedSeconds)} - {tokenLabel(stats.tokens)} - {money(stats.cost)} - {live && onCancel && ( - - )} -
-
+ )} + {live && onCancel && ( + + )} + +
void }) aria-valuemax={100} aria-valuenow={percent} aria-valuetext={`${reviewed} of ${selected} traces reviewed`} - className="h-0.5 bg-foreground/10" + className="h-px bg-foreground/10" >
-
-
-
- Traces - {reviewed} + {open && ( +
+
+
+ Traces + {reviewed} +
+
- -
-
-
- {live ? "Reading trace" : "Last trace"} +
+
+ {live ? "Reading trace" : "Last trace"} +
+
- -
-
-
- Conclusions - - {issues} {issues === 1 ? "issue" : "issues"} - +
+
+ Conclusions + {issueText} +
+
-
-
+ )}
); } diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/useReviewPlayback.ts b/ui/litellm-dashboard/src/components/lens/investigations/live/useReviewPlayback.ts index 6d8979da777..2d6ab928f3c 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/useReviewPlayback.ts +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/useReviewPlayback.ts @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useReducer, useState } from "react"; +import { useCallback, useEffect, useReducer, useState } from "react"; import { playbackPhase, playbackReducer, startPlayback, type Phase, type Playback } from "../../model/live"; import type { Review } from "../../model/types"; @@ -11,7 +11,10 @@ function prefersReducedMotion(): boolean { return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches; } -export function useReviewPlayback(reviews: readonly Review[], live: boolean): Playback & { phase: Phase } { +export function useReviewPlayback( + reviews: readonly Review[], + live: boolean, +): Playback & { phase: Phase; replay: () => void } { const [still] = useState(prefersReducedMotion); const [state, dispatch] = useReducer(playbackReducer, undefined, () => playbackReducer(startPlayback(reviews, live), still ? { type: "settle" } : { type: "tick", now: Date.now() }), @@ -34,10 +37,15 @@ export function useReviewPlayback(reviews: readonly Review[], live: boolean): Pl return () => window.clearInterval(timer); }, [busy, still]); + const replay = useCallback(() => { + dispatch({ type: "replay" }); + if (still) dispatch({ type: "settle" }); + }, [still]); + const current = state.current; const phase = still || !current ? { span: -1, typed: current?.reasoning.length ?? 0, verdict: true } : playbackPhase(now - state.startedAt, state.duration, current.spans.length, current.reasoning.length); - return { ...state, phase }; + return { ...state, phase, replay }; } diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/useShowWork.ts b/ui/litellm-dashboard/src/components/lens/investigations/live/useShowWork.ts new file mode 100644 index 00000000000..65e23b46674 --- /dev/null +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/useShowWork.ts @@ -0,0 +1,32 @@ +"use client"; + +import { useCallback, useState } from "react"; + +const KEY = "litellm.lens.showWork"; + +function readStored(): boolean { + try { + return window.localStorage.getItem(KEY) === "1"; + } catch { + return false; + } +} + +function store(open: boolean): void { + try { + window.localStorage.setItem(KEY, open ? "1" : "0"); + } catch { + return; + } +} + +export function useShowWork(): [boolean, () => void] { + const [open, setOpen] = useState(readStored); + const toggle = useCallback(() => { + setOpen((current) => { + store(!current); + return !current; + }); + }, []); + return [open, toggle]; +}