From f0abe1bea1feb5978293b21b68fa4849cb13a6a6 Mon Sep 17 00:00:00 2001 From: ishaan-berri <155045088+ishaan-berri@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:56:48 -0700 Subject: [PATCH] feat(lens): live dot field timeline and full-screen traces view (#44390) * feat(lens): add dot field layout and live status helpers for the traces timeline * test(lens): cover dot field layout, agent colors and live status * feat(lens): draw the traces timeline as a live dot field * feat(lens): add the sweep animation for the live traces timeline * feat(lens): put the lens tabs in a compact header and fill the screen with traces --- ui/litellm-dashboard/src/app/globals.css | 9 + .../src/components/lens/LensWorkspace.tsx | 38 +-- .../view_logs/TraceView/TracesTimeline.tsx | 261 ++++++++++++++---- .../view_logs/TraceView/lensField.test.ts | 114 ++++++++ .../view_logs/TraceView/lensField.ts | 67 +++++ 5 files changed, 423 insertions(+), 66 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/lensField.test.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/lensField.ts diff --git a/ui/litellm-dashboard/src/app/globals.css b/ui/litellm-dashboard/src/app/globals.css index e1ff921648f..72d304c0dc7 100644 --- a/ui/litellm-dashboard/src/app/globals.css +++ b/ui/litellm-dashboard/src/app/globals.css @@ -473,3 +473,12 @@ [data-slot="dialog-content"][data-nested-dialog-open] { visibility: hidden; } + +@keyframes lens-sweep { + from { + left: -6rem; + } + to { + left: 100%; + } +} diff --git a/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx b/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx index 0a3d3ae6a0d..a0ac3e3e59c 100644 --- a/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx +++ b/ui/litellm-dashboard/src/components/lens/LensWorkspace.tsx @@ -69,29 +69,31 @@ function LensContent({ const openDemo = onDemo ? () => onDemo(activeTab) : undefined; return ( -
-
-

-

-
-
+
{demo && } (demo ? setDemoTab(value as Tab) : void setTab(value as Tab))} - className="min-h-0 flex-1 gap-4" + className="min-h-0 flex-1 gap-2" > - - - Traces - - - Investigations - - - +
+
+

+

+ + + Traces + + + Investigations + + +
+
+
+ ({ index: Math.floor((moment(run.start_time).valueOf() - range.startMs) / width), failed: run.error_count > 0, + agent: traceAgentNames(run)[0] ?? "", })); return Array.from({ length: buckets }, (_, i) => { const hits = placed.filter((p) => p.index === i); @@ -40,6 +44,7 @@ export function bucketRuns(runs: readonly TraceSummary[], range: TimeWindow, buc endMs: range.startMs + (i + 1) * width, runs: hits.length, failed: hits.filter((p) => p.failed).length, + agents: hits.filter((p) => !p.failed).map((p) => p.agent), }; }); } @@ -73,44 +78,202 @@ interface TracesTimelineProps { onSelect: (selection: TimeWindow | null) => void; } -function BucketBar({ - bucket, - max, - dimmed, - hovered, -}: { - bucket: Bucket; +const FAILED_RED = "#e5484d"; +const DOT_PITCH = 6; +const FIELD_HEIGHT = FIELD_ROWS * DOT_PITCH; + +function fieldDotColor(lit: boolean, dot: ReturnType[number], grid: string): string { + if (!lit) return grid; + if (dot.kind === "failed") return FAILED_RED; + return dot.kind === "run" ? dot.color : grid; +} + +interface FieldFrame { + buckets: readonly Bucket[]; max: number; - dimmed: boolean; - hovered: boolean; + band: Band | null; + hover: number | null; + progress: number; +} + +function dotRadius(lit: boolean, hovered: boolean): number { + if (!lit) return 0.9; + return hovered ? 2.4 : 1.9; +} + +function drawField(canvas: HTMLCanvasElement, { buckets, max, band, hover, progress }: FieldFrame) { + const context = canvas.getContext("2d"); + const width = canvas.clientWidth; + if (!context || width === 0) return; + const ratio = window.devicePixelRatio || 1; + canvas.width = Math.round(width * ratio); + canvas.height = Math.round(FIELD_HEIGHT * ratio); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + context.clearRect(0, 0, width, FIELD_HEIGHT); + const grid = document.documentElement.classList.contains("dark") ? "rgba(255,255,255,0.08)" : "rgba(15,23,42,0.08)"; + const columnWidth = width / buckets.length; + const pitch = columnWidth / FIELD_COLS; + buckets.forEach((bucket, i) => { + const dots = columnDots(bucket, max, i * 7919 + bucket.runs); + const visible = Math.ceil(dots.length * progress); + const dimmed = band !== null && (i < band.lo || i > band.hi); + dots.forEach((dot, index) => { + const lit = dot.kind !== "grid" && index < visible; + context.globalAlpha = lit && dimmed ? 0.15 : 1; + context.fillStyle = fieldDotColor(lit, dot, grid); + context.beginPath(); + context.arc( + i * columnWidth + pitch * ((index % FIELD_COLS) + 0.5), + FIELD_HEIGHT - DOT_PITCH * (Math.floor(index / FIELD_COLS) + 0.5), + dotRadius(lit, hover === i), + 0, + Math.PI * 2, + ); + context.fill(); + }); + }); + context.globalAlpha = 1; +} + +function useRiseIn(): number { + const [progress, setProgress] = useState(0); + useEffect(() => { + if (typeof window.matchMedia !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + setProgress(1); + return; + } + const start = performance.now(); + let frame = requestAnimationFrame(function rise(now) { + const t = Math.min(1, (now - start) / 700); + setProgress(1 - (1 - t) ** 3); + if (t < 1) frame = requestAnimationFrame(rise); + }); + return () => cancelAnimationFrame(frame); + }, []); + return progress; +} + +function DotField({ + buckets, + max, + band, + hover, +}: { + buckets: readonly Bucket[]; + max: number; + band: Band | null; + hover: number | null; }) { + const canvas = useRef(null); + const progress = useRiseIn(); + useEffect(() => { + const node = canvas.current; + if (!node) return; + const frame: FieldFrame = { buckets, max, band, hover, progress }; + const paint = () => drawField(node, frame); + paint(); + if (typeof ResizeObserver === "undefined") return; + const observer = new ResizeObserver(paint); + observer.observe(node); + return () => observer.disconnect(); + }, [buckets, max, band, hover, progress]); return ( -
- {bucket.runs > 0 && ( -
- {bucket.failed > 0 && ( -
- )} -
+