From 6fb7e8124f88619aa1e297c7fdcfc194167c69f2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 3 Oct 2026 18:09:50 -0700 Subject: [PATCH] feat(lens): put the now reading stage above the trace list in View run --- .../lens/investigations/live/LiveDrawer.tsx | 131 ++++++++++++------ .../lens/investigations/live/LiveRun.tsx | 8 +- .../lens/investigations/live/TraceList.tsx | 119 +++------------- .../lens/investigations/live/useLensMode.ts | 23 --- 4 files changed, 118 insertions(+), 163 deletions(-) delete mode 100644 ui/litellm-dashboard/src/components/lens/investigations/live/useLensMode.ts 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 b3947522732..8fef86d47cb 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx @@ -5,12 +5,14 @@ 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, InFlight } from "../../model/live"; -import type { Job, Review } from "../../model/types"; +import { conclusions, type InFlight } from "../../model/live"; +import { releasedReviews } from "../../model/stage"; +import type { Job, Review, Settings } from "../../model/types"; import { ConclusionsPanel } from "./ConclusionsPanel"; import { ModelName } from "./LiveStrip"; +import { NowReading } from "./NowReading"; import { TraceList } from "./TraceList"; -import { useLensMode } from "./useLensMode"; +import { useStage } from "./useStage"; const STATUS: Record = { queued: { label: "Queued", tone: "neutral" }, @@ -21,6 +23,33 @@ const STATUS: Record = { }; const PANE_TITLE = "text-[12px] font-semibold text-foreground"; +function Stage({ + reviews, + reading, + running, + slots, + model, + counter, + checks, + children, +}: { + reviews: readonly Review[]; + reading: readonly InFlight[]; + running: boolean; + slots: number; + model: string; + counter: string; + checks: Settings["checks"]; + children: (listed: readonly Review[], groups: ReturnType, nowReading: ReactNode) => ReactNode; +}) { + const { stage, now, charMs } = useStage(reviews, reading, running, slots); + const listed = releasedReviews(reviews, stage); + const nowReading = running && ( + + ); + return <>{children(listed, conclusions(listed, checks), nowReading)}; +} + export function LiveDrawer({ open, onClose, @@ -29,10 +58,11 @@ export function LiveDrawer({ status, reviewed, reviews, - now, - done, reading, - groups, + counter, + done, + slots, + checks, scope, waiting, }: { @@ -43,17 +73,17 @@ export function LiveDrawer({ status: Job["status"]; reviewed: number; reviews: readonly Review[]; - now: string | null; - done: string | null; reading: readonly InFlight[]; - groups: readonly Conclusion[]; + counter: string; + done: string | null; + slots: number; + checks: Settings["checks"]; scope: string; waiting?: ReactNode; }) { const [group, setGroup] = useState(null); const badge = STATUS[status]; - const live = status === "queued" || status === "running"; - const mode = useLensMode(live); + const running = status === "running"; return ( !value && onClose()}> @@ -64,36 +94,59 @@ export function LiveDrawer({ -
-
-
-

Traces

- - {now ?? done ?? `${reviewed} reviewed`} - {done && !now && } - {reviewed > reviews.length && reviews.length ? ` · showing latest ${reviews.length}` : ""} - -
- {reviews.length || reading.length || status === "running" ? ( - - ) : ( -
-

Waiting for the first trace…

- {waiting} + {open && ( + + {(listed, groups, nowReading) => ( +
+
+
+

Traces

+ + {done ?? `${reviewed} reviewed`} + {done && } + {reviewed > reviews.length && reviews.length ? ` · showing latest ${reviews.length}` : ""} + +
+ {nowReading} + {listed.length || reviews.length ? ( + + ) : ( + !running && ( +
+

Waiting for the first trace…

+ {waiting} +
+ ) + )} +
+
+

Conclusions so far

+ +
)} -
-
-

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 7c4a16365dd..771d29f9a5e 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx @@ -6,7 +6,6 @@ import { modelsUsed } from "../../model/inbox"; import { analysisModel, doneLine, - conclusions, inFlight, issueCount, nowLine, @@ -20,6 +19,8 @@ import { LiveDrawer } from "./LiveDrawer"; import { LiveStrip } from "./LiveStrip"; import { useStripOpen } from "./useLivePanels"; +const MAX_LANES = 4; + export function LiveRun({ job, reviews, @@ -79,10 +80,11 @@ export function LiveRun({ status={job.status} reviewed={job.reviewed} reviews={reviews} - now={job.status === "running" ? nowLine(job, reading.length) : null} reading={reading} + counter={nowLine(job, reading.length)} done={job.status === "completed" ? doneLine(job) : null} - groups={conclusions(reviews, job.settings.checks)} + slots={Math.max(1, Math.min(job.settings.concurrency, MAX_LANES))} + checks={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/TraceList.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx index d502ab4794d..7959dfed0a6 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx @@ -1,21 +1,18 @@ "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, durationLabel, inGroup, liveRows, outcome, shortVerdict, type InFlight } from "../../model/live"; +import { briefReasoning, durationLabel, inGroup, newestFirst, outcome, shortVerdict } from "../../model/live"; import type { Review } from "../../model/types"; -import { ModelName } from "./LiveStrip"; const LIMIT = 200; const RED = "text-[#e5484d]"; const ROW = "grid h-9 w-full grid-cols-[0.75rem_minmax(0,8rem)_minmax(0,1fr)_auto] items-center gap-2 px-2 text-left text-[12px]"; -export type LensMode = "live" | "settling" | "off"; - function Expanded({ review }: { review: Review }) { const verdicts = review.verdicts.length ? review.verdicts @@ -41,19 +38,6 @@ function Expanded({ review }: { review: Review }) { ); } -function ReadingRow({ item, now }: { item: InFlight; now: number }) { - return ( -
- - {item.agent || "trace"} - {item.trace_id.slice(0, 8)} - - reading · {durationLabel(Math.max(0, now - Date.parse(item.started_at)))} - -
- ); -} - function DoneRow({ review, now, open, onToggle }: { review: Review; now: number; open: boolean; onToggle: () => void }) { const result = outcome(review); return ( @@ -81,87 +65,26 @@ function DoneRow({ review, now, open, onToggle }: { review: Review; now: number; ); } -function useLensBox(target: string, layout: string) { - const list = useRef(null); - const [box, setBox] = useState<{ top: number; height: number } | null>(null); - useLayoutEffect(() => { - const nodes = [...(list.current?.querySelectorAll(`[data-lens="${target}"]`) ?? [])]; - const first = nodes[0]; - const last = nodes.at(-1); - setBox(first && last ? { top: first.offsetTop, height: last.offsetTop + last.offsetHeight - first.offsetTop } : null); - }, [target, layout]); - return { list, box }; -} - -function Lens({ box, model, mode }: { box: { top: number; height: number }; model: string; mode: LensMode }) { - return ( - - ); -} - -export function TraceList({ - reading, - reviews, - model, - group, - mode, - nowLine, -}: { - reading: readonly InFlight[]; - reviews: readonly Review[]; - model: string; - group: string | null; - mode: LensMode; - nowLine: string | null; -}) { - const now = useNow(reading.length ? 200 : 5000); +export function TraceList({ reviews, group }: { reviews: readonly Review[]; group: string | null }) { + const now = useNow(5000); const [expanded, setExpanded] = useState(null); - const rows = liveRows(reading, reviews, LIMIT).filter((row) => row.kind === "reading" || inGroup(row.review, group)); - const inFlight = rows.some((row) => row.kind === "reading"); - const target = inFlight ? "reading" : "slot"; - const layout = `${expanded}|${rows.map((row) => `${row.kind}:${row.key}`).join(",")}`; - const { list, box } = useLensBox(target, layout); - const showSlot = mode === "live" && !inFlight; + const rows = newestFirst(reviews, LIMIT).filter((review) => inGroup(review, group)); + if (!rows.length) return

No traces here yet.

; return ( -
- {box && mode !== "off" && } - {showSlot && nowLine && ( -
-
- )} -
    - {rows.map((row) => ( -
  1. - {row.kind === "reading" ? ( - - ) : ( - setExpanded(expanded === row.key ? null : row.key)} - /> - )} -
  2. - ))} -
- {!rows.length && !showSlot && ( -

No traces in this group yet.

- )} -
+
    + {rows.map((review) => ( +
  1. + setExpanded(expanded === review.execution_id ? null : review.execution_id)} + /> +
  2. + ))} +
); } diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/useLensMode.ts b/ui/litellm-dashboard/src/components/lens/investigations/live/useLensMode.ts deleted file mode 100644 index ff350072f9b..00000000000 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/useLensMode.ts +++ /dev/null @@ -1,23 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -import type { LensMode } from "./TraceList"; - -const SETTLE_MS = 400; - -export function useLensMode(live: boolean): LensMode { - const [wasLive, setWasLive] = useState(live); - const [settled, setSettled] = useState(!live); - if (live && !wasLive) { - setWasLive(true); - setSettled(false); - } - useEffect(() => { - if (live || settled) return; - const timer = window.setTimeout(() => setSettled(true), SETTLE_MS); - return () => window.clearTimeout(timer); - }, [live, settled]); - if (live) return "live"; - return settled ? "off" : "settling"; -}