From b00cb8d73375b96da800651c36ec22afd1582bbf Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 3 Oct 2026 17:47:02 -0700 Subject: [PATCH] feat(lens): simplify View run to traces and conclusions The left pane is the trace list. A soft highlighter carrying the provider and model slides to the trace being reviewed, and clicking a row shows just Lens's reasoning and verdicts. The right pane keeps one conclusion card per check. --- .../investigations/live/ConclusionsPanel.tsx | 66 +++------ .../lens/investigations/live/LiveDrawer.tsx | 76 ++++------ .../lens/investigations/live/LiveRun.tsx | 16 +-- .../lens/investigations/live/TraceList.tsx | 132 ++++++++++++++++++ .../investigations/live/useReviewPlayback.ts | 3 +- 5 files changed, 181 insertions(+), 112 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/ConclusionsPanel.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/ConclusionsPanel.tsx index 1b620f2eb6b..24864dc14ef 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/ConclusionsPanel.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/ConclusionsPanel.tsx @@ -39,47 +39,7 @@ export function ConclusionsPanel({ if (!groups.length) return

Nothing concluded yet.

; return (
- {SECTIONS.map(({ issue, title }) => ( - group.issue === issue)} - total={total} - selected={selected} - flashing={flashing} - onSelect={onSelect} - /> - ))} - {scope &&

{scope}

} -
- ); -} - -const SECTIONS = [ - { issue: true, title: "Issues" }, - { issue: false, title: "Patterns" }, -] as const; - -function GroupList({ - title, - groups, - total, - selected, - flashing, - onSelect, -}: { - title: string; - groups: readonly Conclusion[]; - total: number; - selected: string | null; - flashing: ReadonlySet; - onSelect: (key: string | null) => void; -}) { - if (!groups.length) return null; - return ( - <> -

{title}

-
    +
      {groups.map((group) => { const active = selected === group.key; return ( @@ -104,10 +64,25 @@ function GroupList({ group.issue ? "bg-[#e5484d]" : "bg-muted-foreground/40", )} /> - {group.label} + + + {group.label} + + {group.latest} + - - {group.count} + + + {group.count} + + {group.noted > 0 && ( + {group.noted} noted + )}
    - + {scope &&

    {scope}

    } + ); } 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 cff7d1ff934..8e562e3fb44 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveDrawer.tsx @@ -3,15 +3,13 @@ import { useState, type ReactNode } from "react"; import { StatusBadge, type StatusTone } from "@/components/shared/table_cells"; -import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; -import type { Conclusion, Phase, Playback } from "../../model/live"; -import type { Job, Review } from "../../model/types"; +import type { Conclusion, Playback } from "../../model/live"; +import type { Job } from "../../model/types"; import { ConclusionsPanel } from "./ConclusionsPanel"; import { ModelName } from "./LiveStrip"; -import { ReadingPanel } from "./ReadingPanel"; -import { ReviewQueue } from "./ReviewQueue"; +import { TraceList } from "./TraceList"; const STATUS: Record = { queued: { label: "Queued", tone: "neutral" }, @@ -21,7 +19,6 @@ const STATUS: Record = { cancelled: { label: "Cancelled", tone: "neutral" }, }; const PANE_TITLE = "text-[12px] font-semibold text-foreground"; -const SETTLED: Phase = { span: -1, typed: Number.MAX_SAFE_INTEGER, verdict: true }; export function LiveDrawer({ open, @@ -31,38 +28,27 @@ export function LiveDrawer({ status, reviewed, playback, - live, - focused, - following, - phase, groups, decided, scope, waiting, - onPick, - onFollow, }: { - waiting?: ReactNode; open: boolean; onClose: () => void; name: string; model: string; status: Job["status"]; reviewed: number; - playback: Pick; - live: boolean; - focused: Review | null; - following: boolean; - phase: Phase; + playback: Pick; groups: readonly Conclusion[]; decided: number; scope: string; - onPick: (review: Review) => void; - onFollow: () => void; + 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()}> @@ -71,43 +57,33 @@ export function LiveDrawer({ } className="flex flex-wrap items-center gap-2.5 pr-10"> - {!following && live && ( - - )} -
    -
    -

    {following && live ? "Now reviewing" : "Trace"}

    - {focused ? ( - - ) : ( -
    +
    +
    +
    +

    Traces

    + + {reviewed} reviewed{reviewed > shown && shown ? ` · showing latest ${shown}` : ""} + +
    + {empty ? ( +

    Waiting for the first trace…

    {waiting}
    + ) : ( + )}
    -
    -
    -

    Conclusions so far

    - -
    -
    -
    -

    Traces

    - - {reviewed} reviewed{reviewed > shown ? ` · showing latest ${shown}` : ""} - -
    - -
    +
    +

    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 d046fbd4069..067bf3be2fb 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/LiveRun.tsx @@ -7,9 +7,7 @@ import { analysisModel, conclusions, decidedReviews, - focusedReview, issueCount, - reviewKey, shownCount, stripState, } from "../../model/live"; @@ -37,19 +35,13 @@ export function LiveRun({ const reason = useQueueReason(job, queue); const [stripOpen, setStripOpen] = useStripOpen(); const [drawerOpen, setDrawerOpen] = useState(false); - const [pinned, setPinned] = useState(null); const playback = useReviewPlayback(reviews, live); const withReviews = { ...job, reviews: [...reviews] }; const model = analysisModel([...reviews.map((r) => r.model), ...modelsUsed(job.steps), job.settings.model]); - const { review: focused, following } = focusedReview(reviews, pinned, playback.current); const issues = issueCount(withReviews); const reviewed = live ? shownCount(job.reviewed, playback) : job.reviewed; const decided = decidedReviews(playback, playback.phase.verdict); - const pin = (review: Review) => setPinned(review === playback.current ? null : reviewKey(review)); - const open = (review?: Review) => { - if (review) pin(review); - setDrawerOpen(true); - }; + const open = () => setDrawerOpen(true); const waiting = reason && ( <> @@ -89,16 +81,10 @@ export function LiveRun({ status={job.status} reviewed={reviewed} playback={playback} - live={live} - focused={focused} - following={following} - phase={playback.phase} groups={conclusions(decided, job.settings.checks)} decided={decided.length} scope={job.reviewed > reviews.length ? `From the latest ${reviews.length} of ${job.reviewed} reviewed traces` : ""} waiting={waiting} - onPick={pin} - onFollow={() => setPinned(null)} /> ); diff --git a/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx b/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx new file mode 100644 index 00000000000..49ad1146699 --- /dev/null +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/TraceList.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { useLayoutEffect, useRef, 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 type { Review } from "../../model/types"; +import { ModelName } from "./LiveStrip"; + +const LIMIT = 80; +const RED = "text-[#e5484d]"; + +function Expanded({ review }: { review: Review }) { + const verdicts = review.verdicts.length + ? review.verdicts + : [{ check_id: "", kind: "pattern" as const, summary: review.cannot_assess ? "Not enough evidence to judge" : "No issue observed" }]; + return ( +
    + {review.reasoning &&

    {briefReasoning(review.reasoning)}

    } +
      + {verdicts.map((verdict, index) => ( +
    • +
    • + ))} +
    +
    + ); +} + +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 }; +} + +export function TraceList({ + playback, + model, + live, + group, +}: { + playback: Pick; + model: string; + live: boolean; + group: string | null; +}) { + const now = useNow(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.

    ; + return ( +
      + {box && ( +
    1. + + {open && } +
    2. + ); + })} +
    + ); +} 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 825872ba43d..2d6ab928f3c 100644 --- a/ui/litellm-dashboard/src/components/lens/investigations/live/useReviewPlayback.ts +++ b/ui/litellm-dashboard/src/components/lens/investigations/live/useReviewPlayback.ts @@ -3,7 +3,6 @@ import { useCallback, useEffect, useReducer, useState } from "react"; import { playbackPhase, playbackReducer, startPlayback, type Phase, type Playback } from "../../model/live"; -import { timeline } from "../../model/spanPreview"; import type { Review } from "../../model/types"; const FRAME_MS = 40; @@ -47,6 +46,6 @@ export function useReviewPlayback( const phase = still || !current ? { span: -1, typed: current?.reasoning.length ?? 0, verdict: true } - : playbackPhase(now - state.startedAt, state.duration, timeline(current.spans).length, current.reasoning.length); + : playbackPhase(now - state.startedAt, state.duration, current.spans.length, current.reasoning.length); return { ...state, phase, replay }; }