"use client" import { useCallback, useEffect, useMemo, useState } from "react" import { useParams, usePathname, useRouter, useSearchParams } from "next/navigation" import Link from "next/link" import { ArrowLeft, ArrowUpRight, BarChart3, Grid3X3, Search } from "lucide-react" import { Navigation } from "@/components/navigation" import { EvalDetail } from "@/components/eval-detail" import { ParamRangePicker } from "@/components/param-range-picker" import { useAudienceMode } from "@/components/audience-mode-provider" import type { BenchmarkEvalSummary } from "@/lib/eval-processing" import { fetchEvalSummary } from "@/lib/dashboard-data-client" import { PARAM_RANGE_MAX_INDEX, parseParamsBillionsFromModelName, paramStepToNumeric } from "@/lib/param-range" export default function EvalDetailPage() { const params = useParams() const pathname = usePathname() const router = useRouter() const searchParams = useSearchParams() const [summary, setSummary] = useState(null) const [subSummaries, setSubSummaries] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [matrixSearch, setMatrixSearch] = useState("") const returnTo = searchParams.get("from") const currentDetailHref = useMemo(() => { const params = new URLSearchParams(searchParams.toString()) params.delete("from") const query = params.toString() return query ? `${pathname}?${query}` : pathname }, [pathname, searchParams]) const handleBack = useCallback(() => { if (returnTo?.startsWith("/")) { router.push(returnTo) return } if (typeof window !== "undefined" && window.history.length > 1) { router.back() return } router.push("/evals") }, [returnTo, router]) useEffect(() => { const load = async () => { try { const evalId = decodeURIComponent(params.id as string) const found = await fetchEvalSummary(evalId) setSummary(found) document.title = `${found.evaluation_name} | Benchmark` if (found.is_aggregated && found.aggregate_sources?.length) { const subs = await Promise.all( found.aggregate_sources.map(async (source) => { try { return await fetchEvalSummary(source.evaluation_id) } catch { return null } }) ) setSubSummaries(subs.filter((s): s is BenchmarkEvalSummary => s !== null)) } } catch (err) { console.error(err) setError("Evaluation not found") } finally { setLoading(false) } } load() }, [params.id]) if (loading) { return (
Loading evaluation record…
) } if (error || !summary) { return (
{error ?? "Evaluation not found"}
) } const isComposite = summary.is_aggregated && (summary.aggregate_sources?.length ?? 0) > 1 return (
{isComposite ? ( ) : ( )}
) } // --------------------------------------------------------------------------- // Composite (suite) view — paper §3.2 "composite reporting unit" // Surfaces sub-benchmarks as a hairline grid and a per-model × per-metric // matrix table. Both modes (research / policy) share the same chrome; the // policy-note panel changes per benchmark, surfaced from the sub-summary card. // --------------------------------------------------------------------------- type Tab = "metrics" | "matrix" function CompositeEvalView({ summary, subSummaries, matrixSearch, onMatrixSearchChange, currentDetailHref, }: { summary: BenchmarkEvalSummary subSummaries: BenchmarkEvalSummary[] matrixSearch: string onMatrixSearchChange: (v: string) => void currentDetailHref: string }) { const { mode } = useAudienceMode() const isPolicy = mode === "policy" const [tab, setTab] = useState("metrics") const sources = summary.aggregate_sources ?? [] const subBenchmarkCount = sources.length const card = summary.benchmark_card const goal = card?.purpose_and_intended_users?.goal?.trim() const overview = card?.benchmark_details?.overview?.trim() const limitations = card?.purpose_and_intended_users?.limitations?.trim() const audience = card?.purpose_and_intended_users?.audience const audienceText = Array.isArray(audience) ? audience.join("; ") : audience const lede = isPolicy ? overview || goal || `Suite aggregating ${subBenchmarkCount} component benchmarks across ${summary.models_count.toLocaleString()} models.` : goal || overview || `Suite aggregating ${subBenchmarkCount} component benchmarks across ${summary.models_count.toLocaleString()} models.` return (
{/* HERO ------------------------------------------------------------- */}

{summary.evaluation_name}

{summary.composite_benchmark_name && summary.composite_benchmark_name !== summary.evaluation_name && ( <> {summary.composite_benchmark_name} · )} {summary.category} · {summary.metric_config.lower_is_better ? "Lower is better ↓" : "Higher is better ↑"}

{lede}

Components {subBenchmarkCount}
Models {summary.models_count.toLocaleString()}
Metrics {summary.metrics_count ?? subBenchmarkCount}
{summary.tags?.languages && summary.tags.languages.length > 0 && (
Languages {summary.tags.languages.slice(0, 3).join(", ")}
)}
{/* POLICY NOTE (policy mode only) ----------------------------------- */} {isPolicy && (overview || limitations || audienceText) && (
Policy note
{overview && ( <>
Measures
{overview}
)} {limitations && ( <>
Caveat
{limitations}
)} {audienceText && ( <>
Intended for
{audienceText}
)}
)} {/* TAB SWITCH ------------------------------------------------------- */}

{tab === "metrics" ? "Sub-benchmarks" : "Score breakdown"}

{tab === "metrics" ? "Each card is one component benchmark inside this suite. Click a card to inspect its leaderboard, sub-tasks and benchmark card." : "Per-model scores across every component metric. Each column is a separately reported measure — distinct measures stay separate instead of collapsing into one number."}

{tab === "metrics" ? ( ) : ( )}
) } // --------------------------------------------------------------------------- // Sub-benchmark cards (paper-aligned fam-grid) // --------------------------------------------------------------------------- function SubBenchmarkGrid({ sources, subSummaries, currentDetailHref, }: { sources: NonNullable subSummaries: BenchmarkEvalSummary[] currentDetailHref: string }) { const subMap = useMemo( () => new Map(subSummaries.map((s) => [s.evaluation_id, s])), [subSummaries] ) if (sources.length === 0) { return (
No component benchmarks reported
) } return (
{sources.map((source) => { const sub = subMap.get(source.evaluation_id) const card = sub?.benchmark_card const overview = card?.benchmark_details?.overview ?? sub?.metric_config?.evaluation_description const goal = card?.purpose_and_intended_users?.goal const summaryLine = goal || overview return (
Component benchmark
{source.models_count} model{source.models_count === 1 ? "" : "s"}

{card?.benchmark_details?.name ?? source.composite_benchmark_name}

{source.evaluation_id}
{summaryLine && (

{summaryLine}

)} {sub?.best_model && (
Top {sub.best_model.name} {(sub.best_model.score * 100).toFixed(1)}%
)}
Open
) })}
) } // --------------------------------------------------------------------------- // Matrix leaderboard (models × metrics) — paper-aligned ec-htable // --------------------------------------------------------------------------- function MatrixLeaderboard({ summary: _summary, subSummaries, search, onSearchChange, }: { summary: BenchmarkEvalSummary subSummaries: BenchmarkEvalSummary[] search: string onSearchChange: (v: string) => void }) { const [sortCol, setSortCol] = useState("avg") const [sortAsc, setSortAsc] = useState(false) const [page, setPage] = useState(1) const [hiddenCols, setHiddenCols] = useState>(new Set()) const [minParamStep, setMinParamStep] = useState(0) const [maxParamStep, setMaxParamStep] = useState(PARAM_RANGE_MAX_INDEX) const PAGE_SIZE = 50 const metricDirection = useMemo(() => { const map = new Map() for (const sub of subSummaries) { map.set(sub.evaluation_name, sub.metric_config.lower_is_better) } return map }, [subSummaries]) const { models, metrics } = useMemo(() => { const metricNames = subSummaries.map((s) => s.evaluation_name) const modelScores = new Map }>() for (const sub of subSummaries) { for (const result of sub.model_results) { const id = result.model_info.id const existing = modelScores.get(id) ?? { name: result.model_info.name, developer: result.model_info.developer ?? "", scores: new Map(), } existing.scores.set(sub.evaluation_name, result.score) modelScores.set(id, existing) } } const modelList = Array.from(modelScores.entries()) .map(([id, data]) => { const validScores = Array.from(data.scores.values()).filter( (s): s is number => s != null && Number.isFinite(s) && s > -99 ) const avg = validScores.length > 0 ? validScores.reduce((a, b) => a + b, 0) / validScores.length : 0 const sizeB = parseParamsBillionsFromModelName(data.name) ?? parseParamsBillionsFromModelName(id) return { id, name: data.name, developer: data.developer, avg, scores: data.scores, sizeB } }) return { models: modelList, metrics: metricNames } }, [subSummaries]) const visibleMetrics = useMemo( () => metrics.filter((m) => !hiddenCols.has(m)), [metrics, hiddenCols] ) const sortedModels = useMemo(() => { return [...models].sort((a, b) => { if (sortCol === "name") { return sortAsc ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) } const aVal = sortCol === "avg" ? a.avg : (a.scores.get(sortCol) ?? -Infinity) const bVal = sortCol === "avg" ? b.avg : (b.scores.get(sortCol) ?? -Infinity) return sortAsc ? aVal - bVal : bVal - aVal }) }, [models, sortCol, sortAsc]) const numericMinParams = paramStepToNumeric(minParamStep, "min") const numericMaxParams = paramStepToNumeric(maxParamStep, "max") const [showUnknownSize, setShowUnknownSize] = useState(true) const hasParameterData = useMemo(() => models.some((m) => m.sizeB != null), [models]) const query = search.trim().toLowerCase() const filteredModels = sortedModels.filter((m) => { if (query && !( m.name.toLowerCase().includes(query) || m.developer.toLowerCase().includes(query) || m.id.toLowerCase().includes(query) )) return false if (m.sizeB == null) return showUnknownSize if (numericMinParams != null && m.sizeB < numericMinParams) return false if (numericMaxParams != null && m.sizeB > numericMaxParams) return false return true }) const pagedModels = filteredModels.slice(0, page * PAGE_SIZE) const hasMore = pagedModels.length < filteredModels.length const metricRanges = useMemo(() => { const ranges = new Map() for (const metric of visibleMetrics) { const scores = models.map((m) => m.scores.get(metric)).filter( (s): s is number => s != null && Number.isFinite(s) && s > -99 ) if (scores.length > 0) { ranges.set(metric, { min: Math.min(...scores), max: Math.max(...scores) }) } } return ranges }, [models, visibleMetrics]) function isValidScore(score: number | null | undefined): score is number { return score != null && Number.isFinite(score) && score > -99 } function scoreColor(metric: string, score: number): string { const range = metricRanges.get(metric) if (!range || range.max === range.min) return "" const lower = metricDirection.get(metric) ?? false const pct = lower ? (range.max - score) / (range.max - range.min) : (score - range.min) / (range.max - range.min) if (pct >= 0.8) return "bg-emerald-100 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-200" if (pct >= 0.6) return "bg-sky-50 dark:bg-sky-950/30 text-sky-800 dark:text-sky-200" if (pct <= 0.2) return "bg-red-50 dark:bg-red-950/30 text-red-800 dark:text-red-200" return "" } function formatScore(score: number): string { if (Math.abs(score) >= 100) return score.toFixed(1) if (Math.abs(score) >= 10) return score.toFixed(2) return score.toFixed(3).replace(/0+$/g, "").replace(/\.$/, "") } function handleSort(col: string) { if (sortCol === col) setSortAsc(!sortAsc) else { setSortCol(col); setSortAsc(false) } } const sortIndicator = (col: string) => sortCol === col ? (sortAsc ? " ▲" : " ▼") : "" function toggleCol(metric: string) { setHiddenCols((prev) => { const next = new Set(prev) if (next.has(metric)) next.delete(metric) else next.add(metric) return next }) } if (subSummaries.length === 0) { return (
Loading component benchmark data…
) } return (
{/* Filter row */}
onSearchChange(e.target.value)} placeholder="Search models…" className="ec-input" style={{ paddingLeft: 36 }} />
{hasParameterData && ( { setMinParamStep(0) setMaxParamStep(PARAM_RANGE_MAX_INDEX) }} showUnknownSize={showUnknownSize} onShowUnknownSizeChange={setShowUnknownSize} className="min-w-[260px] flex-1 sm:max-w-[420px]" /> )}
{filteredModels.length} models × {visibleMetrics.length} metrics
{/* Column toggles */}
{metrics.map((metric) => { const off = hiddenCols.has(metric) return ( ) })}
{/* Matrix table */}
{visibleMetrics.map((m) => ( ))} {visibleMetrics.map((metric) => ( ))} {pagedModels.map((model, idx) => ( {visibleMetrics.map((metric) => { const score = model.scores.get(metric) const valid = isValidScore(score) return ( ) })} ))}
# handleSort("name")} style={{ cursor: "pointer" }} > Model{sortIndicator("name")} handleSort("avg")} style={{ cursor: "pointer" }} > Avg{sortIndicator("avg")} handleSort(metric)} style={{ cursor: "pointer" }} title={metric} > {metric}{sortIndicator(metric)}
{idx + 1}
{model.name}
{model.developer}
{formatScore(model.avg)} {valid ? formatScore(score) : "—"}
{hasMore && (
)}
) }