From b1742323087434b0645b5525f02f68cdf1cf52c7 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 3 Oct 2026 17:55:12 -0700 Subject: [PATCH] feat(lens): show what the worker is reading and make View run obvious Each trace in flight gets a highlighted row with the model and a live timer, and becomes its completed row in place. Completed rows show the real review time. View run is an outline button next to the progress line, and clicking anywhere on the strip opens it too. --- .../lens/investigations/live/LiveDrawer.tsx | 38 +++--- .../lens/investigations/live/LiveRun.tsx | 22 ++-- .../lens/investigations/live/LiveStrip.tsx | 71 +++++++---- .../lens/investigations/live/TraceList.tsx | 110 ++++++++---------- 4 files changed, 123 insertions(+), 118 deletions(-) diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx index 8e562e3fb44..6b55c0778e0 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx @@ -5,8 +5,8 @@ import { useState, type ReactNode } from "react"; import { StatusBadge, type StatusTone } from "@/components/shared/table_cells"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; -import type { Conclusion, Playback } from "../../model/live"; -import type { Job } from "../../model/types"; +import type { Conclusion, InFlight } from "../../model/live"; +import type { Job, Review } from "../../model/types"; import { ConclusionsPanel } from "./ConclusionsPanel"; import { ModelName } from "./LiveStrip"; import { TraceList } from "./TraceList"; @@ -27,9 +27,10 @@ export function LiveDrawer({ model, status, reviewed, - playback, + reviews, + now, + reading, groups, - decided, scope, waiting, }: { @@ -39,16 +40,15 @@ export function LiveDrawer({ model: string; status: Job["status"]; reviewed: number; - playback: Pick; + reviews: readonly Review[]; + now: string | null; + reading: readonly InFlight[]; groups: readonly Conclusion[]; - decided: number; scope: string; waiting?: ReactNode; }) { const [group, setGroup] = useState(null); const badge = STATUS[status]; - const shown = playback.played.length + (playback.current ? 1 : 0); - const empty = !shown && !playback.pending.length; return ( !value && onClose()}> @@ -64,26 +64,28 @@ export function LiveDrawer({

Traces

- {reviewed} reviewed{reviewed > shown && shown ? ` · showing latest ${shown}` : ""} + {now ?? `${reviewed} reviewed`} + {reviewed > reviews.length && reviews.length ? ` · showing latest ${reviews.length}` : ""}
- {empty ? ( + {reviews.length || reading.length ? ( + + ) : (

Waiting for the first trace…

{waiting}
- ) : ( - )}

Conclusions so far

- +
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 067bf3be2fb..0b9bffc0455 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx @@ -6,9 +6,9 @@ import { modelsUsed } from "../../model/inbox"; import { analysisModel, conclusions, - decidedReviews, + inFlight, issueCount, - shownCount, + nowLine, stripState, } from "../../model/live"; import { queueReasonText } from "../../model/status"; @@ -18,7 +18,6 @@ import { useQueueReason, type QueueContext } from "../useQueueReason"; import { LiveDrawer } from "./LiveDrawer"; import { LiveStrip } from "./LiveStrip"; import { useStripOpen } from "./useLivePanels"; -import { useReviewPlayback } from "./useReviewPlayback"; export function LiveRun({ job, @@ -35,12 +34,10 @@ export function LiveRun({ const reason = useQueueReason(job, queue); const [stripOpen, setStripOpen] = useStripOpen(); const [drawerOpen, setDrawerOpen] = useState(false); - const playback = useReviewPlayback(reviews, live); + const reading = inFlight(job); const withReviews = { ...job, reviews: [...reviews] }; const model = analysisModel([...reviews.map((r) => r.model), ...modelsUsed(job.steps), job.settings.model]); const issues = issueCount(withReviews); - const reviewed = live ? shownCount(job.reviewed, playback) : job.reviewed; - const decided = decidedReviews(playback, playback.phase.verdict); const open = () => setDrawerOpen(true); const waiting = reason && ( <> @@ -56,8 +53,8 @@ export function LiveRun({ model={model} state={stripState(withReviews, model, reason ? queueReasonText(reason) : undefined)} waiting={waiting} - playback={playback} - reviewed={reviewed} + reviews={reviews} + reviewed={job.reviewed} selected={job.coverage.selected} issues={issues} cost={job.cost} @@ -79,10 +76,11 @@ export function LiveRun({ name={name} model={model} status={job.status} - reviewed={reviewed} - playback={playback} - groups={conclusions(decided, job.settings.checks)} - decided={decided.length} + reviewed={job.reviewed} + reviews={reviews} + now={job.status === "running" ? nowLine(job, reading.length) : null} + reading={reading} + groups={conclusions(reviews, job.settings.checks)} scope={job.reviewed > reviews.length ? `From the latest ${reviews.length} of ${job.reviewed} reviewed traces` : ""} waiting={waiting} /> diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveStrip.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveStrip.tsx index b493e286084..5ef6d694864 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveStrip.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveStrip.tsx @@ -1,6 +1,6 @@ "use client"; -import { PanelRight, X } from "lucide-react"; +import { ArrowRight, PanelRight, X } from "lucide-react"; import type { ReactNode } from "react"; import { ProviderLogo } from "@/components/molecules/models/ProviderLogo"; @@ -10,8 +10,7 @@ import { useNow } from "@/hooks/useNow"; import { cn } from "@/lib/cva.config"; import { money } from "../../model/format"; -import { outcome, providerOf, queueRows, reviewKey, shortVerdict, type IssueCount, type StripState } from "../../model/live"; -import type { Playback } from "../../model/live"; +import { newestFirst, outcome, providerOf, reviewKey, shortVerdict, type IssueCount, type StripState } from "../../model/live"; import type { Review } from "../../model/types"; const RECENT = 3; @@ -29,14 +28,17 @@ export function ModelName({ model, size = "sm" }: { model: string; size?: keyof ); } -function RecentLine({ review, now, onOpen }: { review: Review; now: number; onOpen: (review: Review) => void }) { +function RecentLine({ review, now, onOpen }: { review: Review; now: number; onOpen: () => void }) { const result = outcome(review); const issue = result === "issue"; return (
  • {state.kind === "failed" && ( @@ -99,7 +117,7 @@ export function LiveStrip({

    )} {state.kind === "waiting" && ( -
    +
    event.stopPropagation()}> {waiting ?? state.message}
    )} @@ -111,15 +129,18 @@ export function LiveStrip({ )}
    -
    - - -
    + ); } diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx index 49ad1146699..876158fa935 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx @@ -1,16 +1,16 @@ "use client"; -import { useLayoutEffect, useRef, useState } from "react"; +import { useState } from "react"; import { agoLabel } from "@/components/view_logs/TraceView/lensField"; import { useNow } from "@/hooks/useNow"; import { cn } from "@/lib/cva.config"; -import { briefReasoning, inGroup, outcome, reviewKey, shortVerdict, traceRows, type Playback } from "../../model/live"; +import { briefReasoning, durationLabel, inGroup, liveRows, outcome, shortVerdict, type InFlight } from "../../model/live"; import type { Review } from "../../model/types"; import { ModelName } from "./LiveStrip"; -const LIMIT = 80; +const LIMIT = 200; const RED = "text-[#e5484d]"; function Expanded({ review }: { review: Review }) { @@ -38,89 +38,73 @@ function Expanded({ review }: { review: Review }) { ); } -function useHighlighter(active: string | null, layout: string) { - const list = useRef(null); - const [box, setBox] = useState<{ top: number; height: number } | null>(null); - useLayoutEffect(() => { - const row = active ? list.current?.querySelector(`[data-row="${CSS.escape(active)}"]`) : null; - setBox(row ? { top: row.offsetTop, height: row.offsetHeight } : null); - }, [active, layout]); - return { list, box }; +function ReadingRow({ item, model, now }: { item: InFlight; model: string; now: number }) { + return ( +
    + + {item.agent || "trace"} + + + + + {item.trace_id.slice(0, 8)} + + + reading · {durationLabel(Math.max(0, now - Date.parse(item.started_at)))} + +
    + ); } export function TraceList({ - playback, + reading, + reviews, model, - live, group, }: { - playback: Pick; + reading: readonly InFlight[]; + reviews: readonly Review[]; model: string; - live: boolean; group: string | null; }) { - const now = useNow(5000); + const now = useNow(reading.length ? 200 : 5000); const [expanded, setExpanded] = useState(null); - const rows = traceRows(playback, LIMIT) - .map((row) => (live || row.state !== "reviewing" ? row : { ...row, state: "done" as const })) - .filter(({ review, state }) => state !== "done" || inGroup(review, group)); - const active = live && playback.current ? reviewKey(playback.current) : null; - const { list, box } = useHighlighter(active, `${expanded}|${rows.map((row) => reviewKey(row.review)).join(",")}`); - if (!rows.length) return

    No traces yet.

    ; + const rows = liveRows(reading, reviews, LIMIT).filter((row) => row.kind === "reading" || inGroup(row.review, group)); + if (!rows.length) return

    No traces in this group yet.

    ; return ( -
      - {box && ( -