"use client"; import { useMemo, useState } from "react"; import { cn } from "@/lib/utils"; import { hasBench, ms, pct, providerTone, usd, type ModelRow } from "./format"; /** Models × benchmark categories. Sequential single-hue fill (accent) by pass rate; judged score printed when present. */ export function Heatmap({ models, categories }: { models: ModelRow[]; categories: { id: string; label: string }[] }) { const rows = useMemo(() => models.filter(hasBench).sort((a, b) => (b.benchmark!.mean_score ?? 0) - (a.benchmark!.mean_score ?? 0) || (a.benchmark!.total_median_ms ?? 0) - (b.benchmark!.total_median_ms ?? 0)), [models]); const [hover, setHover] = useState<{ m: ModelRow; c: string; x: number; y: number } | null>(null); const fill = (p: number | null | undefined) => (p == null ? "var(--bg-sunk)" : `color-mix(in oklab, var(--accent) ${Math.round(6 + p * p * 52)}%, var(--bg-elev))`); const cell = hover ? hover.m.benchmark?.by_category?.[hover.c] : null; return (
{categories.map((c) => )}{rows.map((m) => {categories.map((c) => { const v = m.benchmark?.by_category?.[c.id]; return ; })} )}
Model
{c.label}
{m.id}
Fill = pass rate{[0, 0.5, 0.75, 0.9, 1].map((p) => )}0 → 100 %Number = LLM-judge score (1–5) where a judged rubric existsGrey = not run
{hover &&
{hover.m.id}
{categories.find((c) => c.id === hover.c)?.label}
{cell ?
Pass
{pct(cell.pass_rate)} ({cell.runs} runs)
Score
{cell.score.toFixed(2)}
{cell.judged != null && <>
Judged
{cell.judged.toFixed(2)} / 5
}
TTFT
{ms(cell.ttft_ms)}
Latency
{ms(cell.total_ms)}
tok/s
{Math.round(cell.tps)}
$/run
{usd(cell.cost_mean)}
:
Not run for this model.
}
}
); }