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 && ( -