'use client' import { useEffect, useRef, useState, useMemo } from 'react' import type { ModelData, GuardrailData } from '@/lib/types' import { creatorColor, inferModelSize } from '@/lib/utils' import { LabLogo } from '@/components/LabLogo' import GuardrailBarChart from '@/components/GuardrailBarChart' const SIZE_COLORS = { Small: '#2BBFB3', Medium: '#E8892B', Large: '#9B72CF' } as const const SIZE_ORDER = { Small: 0, Medium: 1, Large: 2 } as const type ModelSize = keyof typeof SIZE_COLORS function getAvg(m: ModelData, key: 'luc' | 'rag' | 'fairness'): number | null { if (key === 'luc') return m.luc.avg if (key === 'rag') return m.rag.avg return m.fairness.avg } function LabSizeBreakdown({ models, maxFairness }: { models: ModelData[]; maxFairness: number }) { const active = useMemo(() => models.filter(m => !m.archived), [models]) const allCreators = useMemo(() => [...new Set(active.map(m => m.creator))].sort(), [active]) const [creator, setCreator] = useState(() => allCreators[0] ?? '') const [metric, setMetric] = useState<'luc' | 'rag' | 'fairness'>('luc') const [hoveredModel, setHoveredModel] = useState(null) const sel = allCreators.includes(creator) ? creator : (allCreators[0] ?? '') const rows = useMemo(() => { return active .filter(m => m.creator === sel && getAvg(m, metric) !== null) .map(m => ({ m, size: inferModelSize(m.model) as ModelSize })) .sort((a, b) => { const sd = SIZE_ORDER[a.size] - SIZE_ORDER[b.size] if (sd !== 0) return sd const va = getAvg(a.m, metric)!, vb = getAvg(b.m, metric)! return metric === 'fairness' ? va - vb : vb - va }) }, [active, sel, metric]) const groups = useMemo(() => (['Small', 'Medium', 'Large'] as ModelSize[]) .map(sz => ({ sz, items: rows.filter(r => r.size === sz) })) .filter(g => g.items.length > 0), [rows], ) const maxVal = metric === 'fairness' ? maxFairness : 1 const fieldAvg = (() => { const vals = active.filter(m => getAvg(m, metric) !== null).map(m => getAvg(m, metric)!) return vals.length > 0 ? vals.reduce((s, v) => s + v, 0) / vals.length : null })() const METRIC_LABELS: Record<'luc'|'rag'|'fairness', string> = { luc: 'Refusal Rate', rag: 'RAG Score', fairness: 'Fairness', } const fmtVal = (val: number) => metric === 'fairness' ? val.toFixed(3) : `${(val * 100).toFixed(0)}%` const fieldAvgPct = fieldAvg !== null ? Math.min(1, fieldAvg / maxVal) : null const n = rows.length const ROW_H = 32 const GROUP_HEADER_H = 28 const GROUP_GAP = 12 return (
{/* Controls */}
{allCreators.map(c => { const on = c === sel return ( ) })}
{(['luc', 'rag', 'fairness'] as const).map((m, i) => { const on = m === metric return ( ) })}
{n === 0 ? (
No data for {sel}
) : (
{/* Axis scale */}
{[0, 0.25, 0.5, 0.75, 1].map(t => ( {metric === 'fairness' ? (t * maxVal).toFixed(2) : `${Math.round(t * 100)}%`} ))}
{/* Groups */} {groups.map((g, gi) => (
0 ? GROUP_GAP : 0 }}> {/* Group header */}
{g.sz} {g.items.length} model{g.items.length !== 1 ? 's' : ''}
{/* Rows */} {g.items.map(r => { const val = getAvg(r.m, metric)! const pct = Math.min(1, val / maxVal) const isHovered = hoveredModel === r.m.model const cc = SIZE_COLORS[r.size] return (
setHoveredModel(r.m.model)} onMouseLeave={() => setHoveredModel(null)} style={{ display: 'flex', alignItems: 'center', height: ROW_H, gap: 0, borderRadius: 4, background: isHovered ? `${cc}0A` : 'transparent', transition: 'background 0.12s', cursor: 'default', }} > {/* Model name */}
{r.m.model}
{/* Bar track */}
{/* Field average marker */} {fieldAvgPct !== null && (
)} {/* Bar fill */}
{/* Value */}
{fmtVal(val)}
) })}
))} {/* Field average legend */} {fieldAvg !== null && (
field avg {fmtVal(fieldAvg)}
)}
)}
) } interface Props { models: ModelData[] guardrails: GuardrailData[] maxFairness: number mode: 'models' | 'guardrails' } export default function InsightsSection({ models, guardrails, maxFairness, mode }: Props) { const ref = useRef(null) useEffect(() => { const obs = new IntersectionObserver( entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); obs.unobserve(e.target) } }) }, { threshold: 0.05, rootMargin: '0px 0px -60px 0px' }, ) ref.current?.querySelectorAll('.reveal').forEach(el => obs.observe(el)) return () => obs.disconnect() }, [mode]) return (

Data in context

{mode === 'guardrails' && (
Metric Comparison
How guardrails rank across metrics
)} {mode === 'models' &&
Lab × Size Breakdown
How size affects performance within each lab

Models are grouped by approximate size: Small (≤ 10 B params),{' '} Medium (11–40 B), and{' '} Large (40 B+). Commercial closed-weight models are classified by model-family tier (e.g. Haiku → Small, Sonnet/Flash → Medium, Opus/Pro → Large).

Size classifications are approximate and inherently subjective — exact parameter counts are not published for most commercial models. Categorisations are based on publicly available information and model-family naming conventions at the time of evaluation; they may not reflect the true underlying model size.

}
) }