"use client"; import { defineRegistry } from "@json-render/react"; import { AlertTriangle, Check, CircleDashed, FileMinus, FilePen, FilePlus, Info, Loader2, TriangleAlert, X, } from "lucide-react"; import { agentReportCatalog } from "./catalog"; const toneStyles: Record = { neutral: "bg-muted text-muted-foreground", success: "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300", warning: "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300", error: "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-300", }; const calloutStyles: Record = { info: "border-blue-200 bg-blue-50 dark:border-blue-900 dark:bg-blue-950/40", success: "border-emerald-200 bg-emerald-50 dark:border-emerald-900 dark:bg-emerald-950/40", warning: "border-amber-200 bg-amber-50 dark:border-amber-900 dark:bg-amber-950/40", error: "border-red-200 bg-red-50 dark:border-red-900 dark:bg-red-950/40", }; const calloutIcons = { info: Info, success: Check, warning: TriangleAlert, error: AlertTriangle, } as const; const stepIcons = { done: , active: , pending: , error: , } as const; const fileChangeMeta = { created: { icon: FilePlus, label: "created", className: "text-emerald-600" }, modified: { icon: FilePen, label: "modified", className: "text-blue-600" }, deleted: { icon: FileMinus, label: "deleted", className: "text-red-600" }, } as const; type ChartPoint = { label: string; value: number }; // Charts are intentionally monochrome — they ink in the foreground color. const CHART_COLOR = "var(--foreground)"; /** Format a value compactly, appending an optional unit. */ function formatChartValue(value: number, unit: string | null): string { const rounded = Math.abs(value) >= 100 || Number.isInteger(value) ? Math.round(value).toString() : value.toFixed(1); return unit ? `${rounded}${unit}` : rounded; } function ChartFrame({ title, children, }: { title: string | null; children: React.ReactNode; }) { // No border/background of its own: a chart is content, not a card. This // keeps it from looking like a card nested inside a Card. return (
{title && (
{title}
)} {children}
); } export const { registry } = defineRegistry(agentReportCatalog, { actions: {}, components: { Stack: ({ props, children }) => (
{children}
), Grid: ({ props, children }) => (
{children}
), Card: ({ props, children }) => (
{props.title && (

{props.title}

)} {props.description && (

{props.description}

)}
{children}
), Heading: ({ props }) => { const sizes = { "1": "text-xl", "2": "text-lg", "3": "text-base" }; return (
{props.text}
); }, Text: ({ props }) => (

{props.content}

), Badge: ({ props }) => ( {props.label} ), Callout: ({ props }) => { const tone = props.tone ?? "info"; const Icon = calloutIcons[tone]; return (
{props.title && ( {props.title}: )} {props.content}
); }, Metric: ({ props }) => (
{props.label}
{props.value}
{props.detail && (
{props.detail}
)}
), Steps: ({ props }) => (
    {props.items.map((item, i) => (
  1. {stepIcons[item.status]} {item.title} {item.detail && ( {item.detail} )}
  2. ))}
), FileChange: ({ props }) => { const meta = fileChangeMeta[props.kind]; const Icon = meta.icon; return (
{props.path} {meta.label} {(props.additions != null || props.deletions != null) && ( {props.additions != null && ( +{props.additions}{" "} )} {props.deletions != null && ( -{props.deletions} )} )}
{props.summary && (
{props.summary}
)}
); }, CodeBlock: ({ props }) => (
{props.title && (
{props.title}
)}
          {props.code}
        
), Terminal: ({ props }) => (
$ {props.command} {props.exitCode != null && ( exit {props.exitCode} )}
{props.output && (
            {props.output}
          
)}
), TestResults: ({ props }) => (
{props.passed} passed 0 ? toneStyles.error : toneStyles.neutral }`} > {props.failed} failed {props.skipped != null && props.skipped > 0 && ( {props.skipped} skipped )}
{props.failures && props.failures.length > 0 && (
    {props.failures.map((f, i) => (
  • {f.name}
    {f.message}
  • ))}
)}
), BarChart: ({ props }) => { const data = props.data as ChartPoint[]; const color = CHART_COLOR; const values = data.map((d) => d.value); const max = Math.max(...values, 0); const min = Math.min(...values, 0); const span = max - min || 1; const zeroTop = ((max - 0) / span) * 100; return ( {data.length === 0 ? (
No data
) : (
{data.map((d, i) => { const rawHeight = (Math.abs(d.value) / span) * 100; const availableHeight = d.value >= 0 ? zeroTop : 100 - zeroTop; const height = d.value === 0 ? 0 : Math.min(Math.max(rawHeight, 1.5), availableHeight); const top = d.value >= 0 ? zeroTop - height : zeroTop; return (
{formatChartValue(d.value, props.unit)}
{d.value === 0 ? (
) : (
)}
{d.label}
); })}
)} ); }, LineChart: ({ props }) => { const data = props.data as ChartPoint[]; const color = CHART_COLOR; const W = 100; const H = 40; const values = data.map((d) => d.value); const max = Math.max(...values, 0); const min = Math.min(...values, 0); const span = max - min || 1; // Map each point into the viewBox; single point sits centered. const points = data.map((d, i) => { const x = data.length === 1 ? W / 2 : (i / (data.length - 1)) * W; const y = H - ((d.value - min) / span) * H; return { x, y }; }); const line = points.map((p) => `${p.x},${p.y}`).join(" "); const area = `0,${H} ${line} ${W},${H}`; const first = data[0]; const last = data[data.length - 1]; return ( {!first || !last ? (
No data
) : ( <>
{first.label} {" "} · {formatChartValue(first.value, props.unit)} {data.length > 1 && ( {last.label} {" "} · {formatChartValue(last.value, props.unit)} )}
)}
); }, }, }); export function Fallback({ type }: { type: string }) { return (
Unknown component: {type}
); }