"use client"; import { ChevronRightIcon, FlameIcon } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { type KeyboardEvent, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { SegmentedControl, SegmentedControlItem } from "@/components/ui/segmented-control"; import { cn } from "@/lib/utils"; export type FrameKind = "agent" | "llm" | "tool"; export type FlameLens = "time" | "tokens" | "cost"; export interface FlameFrame { id: string; name: string; kind: FrameKind; ms: number; tokens?: number; cost?: number; detail?: string; children?: FlameFrame[]; } export interface FlameProfileProps { root: FlameFrame; title?: string; defaultLens?: FlameLens; currency?: string; className?: string; } interface Placed { frame: FlameFrame; depth: number; x0: number; x1: number; path: FlameFrame[]; } const ROW = 28; const GAP = 2; const spring = { type: "spring", stiffness: 300, damping: 34, mass: 0.9 } as const; const kinds: Record = { agent: { label: "Agent", frame: "bg-emerald-500/14 text-emerald-900 hover:bg-emerald-500/24 dark:bg-emerald-400/14 dark:text-emerald-100 dark:hover:bg-emerald-400/24", dot: "bg-emerald-500 dark:bg-emerald-400", }, llm: { label: "Model call", frame: "bg-violet-500/16 text-violet-900 hover:bg-violet-500/26 dark:bg-violet-400/18 dark:text-violet-100 dark:hover:bg-violet-400/28", dot: "bg-violet-500 dark:bg-violet-400", }, tool: { label: "Tool", frame: "bg-sky-500/14 text-sky-900 hover:bg-sky-500/24 dark:bg-sky-400/14 dark:text-sky-100 dark:hover:bg-sky-400/24", dot: "bg-sky-500 dark:bg-sky-400", }, }; const lenses: { id: FlameLens; label: string }[] = [ { id: "time", label: "Time" }, { id: "tokens", label: "Tokens" }, { id: "cost", label: "Cost" }, ]; function totals(f: FlameFrame, memo: Map) { let tokens = f.tokens ?? 0; let cost = f.cost ?? 0; for (const c of f.children ?? []) { const t = totals(c, memo); tokens += t.tokens; cost += t.cost; } const out = { tokens, cost }; memo.set(f.id, out); return out; } const fmtMs = (ms: number) => (ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(ms < 10_000 ? 2 : 1)}s`); const fmtTokens = (n: number) => (n >= 1000 ? `${(n / 1000).toFixed(n >= 10_000 ? 0 : 1)}k` : `${Math.round(n)}`); const fmtCost = (n: number, cur: string) => `${cur}${n < 0.1 ? n.toFixed(3) : n.toFixed(2)}`; export function FlameProfile({ root, title = "Agent run", defaultLens = "time", currency = "$", className }: FlameProfileProps) { const reduce = useReducedMotion(); const [lens, setLens] = useState(defaultLens); const [zoomId, setZoomId] = useState(root.id); const [hover, setHover] = useState(null); const [tipOn, setTipOn] = useState(false); const [width, setWidth] = useState(520); const box = useRef(null); const tipTimer = useRef | undefined>(undefined); const warm = useRef(0); useLayoutEffect(() => { const el = box.current; if (!el) return; const ro = new ResizeObserver(() => setWidth(el.clientWidth)); ro.observe(el); setWidth(el.clientWidth); return () => ro.disconnect(); }, []); useEffect(() => () => clearTimeout(tipTimer.current), []); const sums = useMemo(() => { const memo = new Map(); totals(root, memo); return memo; }, [root]); const metric = (f: FlameFrame, l: FlameLens = lens) => l === "time" ? f.ms : l === "tokens" ? (sums.get(f.id)?.tokens ?? 0) : (sums.get(f.id)?.cost ?? 0); const placed = useMemo(() => { const out: Placed[] = []; const place = (f: FlameFrame, depth: number, x0: number, x1: number, path: FlameFrame[]) => { out.push({ frame: f, depth, x0, x1, path }); const kids = f.children ?? []; const sum = kids.reduce((s, c) => s + metric(c), 0); const scale = Math.max(metric(f), sum) || 1; let x = x0; for (const c of kids) { const w = ((x1 - x0) * metric(c)) / scale; place(c, depth + 1, x, x + w, [...path, f]); x += w; } }; place(root, 0, 0, 1, []); return out; }, [root, lens, sums]); const zoom = placed.find((p) => p.frame.id === zoomId) ?? placed[0]; const span = zoom.x1 - zoom.x0 || 1; const view = (p: Placed) => { const a = Math.min(Math.max((p.x0 - zoom.x0) / span, 0), 1); const b = Math.min(Math.max((p.x1 - zoom.x0) / span, 0), 1); const inside = p.depth >= zoom.depth && (p.frame.id === zoom.frame.id || p.path.some((f) => f.id === zoom.frame.id)); return { a, b, d: p.depth - zoom.depth, visible: inside && b - a > 0.0005 }; }; const rows = Math.max(...placed.filter((p) => view(p).visible).map((p) => p.depth - zoom.depth)) + 1; const maxRows = Math.max(...placed.map((p) => p.depth)) + 1; const leaves = placed.filter((p) => !p.frame.children?.length && view(p).visible); const hottest = leaves.reduce((best, p) => (!best || metric(p.frame) > metric(best.frame) ? p : best), null); const hotShare = hottest ? metric(hottest.frame) / (metric(zoom.frame) || 1) : 0; const fmt = (n: number, l: FlameLens = lens) => (l === "time" ? fmtMs(n) : l === "tokens" ? fmtTokens(n) : fmtCost(n, currency)); const zoomTotal = metric(zoom.frame); const ticks = [0, 0.25, 0.5, 0.75, 1]; const showTip = (id: string) => { clearTimeout(tipTimer.current); setHover(id); if (tipOn || performance.now() - warm.current < 500) setTipOn(true); else tipTimer.current = setTimeout(() => setTipOn(true), 260); }; const hideTip = () => { clearTimeout(tipTimer.current); if (tipOn) warm.current = performance.now(); setTipOn(false); setHover(null); }; const zoomTo = (id: string) => { setZoomId(id); hideTip(); }; const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && zoom.path.length) { e.preventDefault(); zoomTo(zoom.path[zoom.path.length - 1].id); } }; const hovered = hover ? placed.find((p) => p.frame.id === hover) : undefined; const hv = hovered ? view(hovered) : null; const tipX = hv ? Math.min(Math.max(((hv.a + hv.b) / 2) * width, 112), width - 112) : 0; return (
{title} {fmtMs(root.ms)} · {fmtTokens(sums.get(root.id)?.tokens ?? 0)} tokens · {fmtCost(sums.get(root.id)?.cost ?? 0, currency)}
{lenses.map((l) => ( setLens(l.id)} > {l.label} ))}
{placed.map((p) => { const v = view(p); const px = (v.b - v.a) * width; const hot = hottest?.frame.id === p.frame.id; const isZoom = p.frame.id === zoom.frame.id && zoom.path.length > 0; return ( zoomTo(isZoom ? (zoom.path[zoom.path.length - 1]?.id ?? root.id) : p.frame.id)} onFocus={() => showTip(p.frame.id)} onPointerEnter={() => showTip(p.frame.id)} style={{ height: ROW, pointerEvents: v.visible ? "auto" : "none" }} tabIndex={v.visible ? 0 : -1} transition={reduce ? { duration: 0 } : spring} type="button" > {hot && px > 28 && } {px > 52 && {p.frame.name}} {px > 150 && ( {fmt(metric(p.frame))} )} ); })}
{ticks.map((t) => ( 0 && t < 1 ? { left: `${t * 100}%` } : undefined} > {fmt(zoomTotal * t)} ))}
{tipOn && hovered && hv?.visible && (
{hovered.frame.name}
{kinds[hovered.frame.kind].label} {hovered.frame.detail ? ` · ${hovered.frame.detail}` : ""}
{lenses.map((l) => (
{l.label}
{fmt(metric(hovered.frame, l.id), l.id)}
))}
{Math.round((metric(hovered.frame) / (metric(root) || 1)) * 100)}% of the run’s {lens === "time" ? "time" : lens}
)}
    {(Object.keys(kinds) as FrameKind[]).map((k) => (
  • {kinds[k].label}
  • ))}
{hottest && ( )}
{`Sized by ${lens}. Showing ${zoom.frame.name}, ${fmt(zoomTotal)}. ${maxRows} levels deep.`}
); }