'use client' import { useState } from 'react' import type { ModelData } from '@/lib/types' import { LabLogo } from '@/components/LabLogo' import { creatorColor } from '@/lib/utils' const STRIP_ROWS = [ { key: 'luc' as const, label: 'Refusal Rate', invert: false, color: '#00C0F3', fmt: (v: number) => `${(v * 100).toFixed(0)}%` }, { key: 'rag' as const, label: 'RAG Score', invert: false, color: '#F4333D', fmt: (v: number) => `${(v * 100).toFixed(0)}%` }, { key: 'fairness' as const, label: 'Fairness', invert: true, color: '#BA2FA2', fmt: (v: number) => v.toFixed(3) }, ] 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 } export default function ScoreDistribution({ models, maxFairness }: { models: ModelData[]; maxFairness: number }) { const active = models.filter(m => !m.archived) const [tooltip, setTooltip] = useState<{ x: number; y: number; model: string; val: string; creator: string } | null>(null) const VW = 800 const ROW_H = 120 const LBL_W = 85 const PAD_R = 85 const AXIS_W = VW - LBL_W - PAD_R const DOT_R = 7 const VH = STRIP_ROWS.length * ROW_H const TT_W = 168 const TT_H = 36 const VIOLIN_H = 40 const VIOLIN_BW = AXIS_W * 0.07 return (
setTooltip(null)} > {STRIP_ROWS.map((row, ri) => { const y0 = ri * ROW_H const yMid = y0 + ROW_H / 2 const vals = active .map(m => ({ val: getAvg(m, row.key), creator: m.creator, model: m.model })) .filter(d => d.val !== null) as { val: number; creator: string; model: string }[] const rawMax = row.key === 'fairness' ? maxFairness : 1 const mean = vals.reduce((s, d) => s + d.val, 0) / vals.length const toX = (v: number) => { const pct = row.invert ? 1 - v / rawMax : v / rawMax return LBL_W + pct * AXIS_W } const meanX = toX(mean) return ( {/* Alternating row bg */} {ri % 2 === 1 && ( )} {/* Row divider */} {ri > 0 && ( )} {/* Axis line */} {/* Half violin */} {vals.length > 1 && (() => { const N = 90 const sampleXs = Array.from({ length: N }, (_, i) => LBL_W + (i / (N - 1)) * AXIS_W) const densities = sampleXs.map(px => vals.reduce((s, d) => s + Math.exp(-0.5 * ((px - toX(d.val)) / VIOLIN_BW) ** 2), 0) / (vals.length * VIOLIN_BW * Math.sqrt(2 * Math.PI)) ) const maxD = Math.max(...densities) if (maxD <= 0) return null const pts = sampleXs.map((px, i) => `L${px.toFixed(1)},${(yMid - (densities[i] / maxD) * VIOLIN_H).toFixed(1)}` ).join(' ') const d = `M${LBL_W},${yMid} ${pts} L${VW - PAD_R},${yMid} Z` return ( ) })()} {/* Tick marks + labels */} {[0, 0.25, 0.5, 0.75, 1].map(t => { const tx = LBL_W + t * AXIS_W const tickLabel = row.invert ? (t === 0 ? rawMax.toFixed(2) : t === 1 ? '0' : (rawMax * (1 - t)).toFixed(2)) : `${Math.round(t * 100)}%` return ( {tickLabel} ) })} {/* Mean line */} avg {row.fmt(mean)} {/* Dots — render hovered dot last so it stays on top */} {[...vals].sort((a, b) => { const aH = tooltip?.model === a.model ? 1 : 0 const bH = tooltip?.model === b.model ? 1 : 0 return aH - bH }).map((d, i) => { const isHovered = tooltip?.model === d.model const dimmed = tooltip !== null && !isHovered const cx = toX(d.val) return ( setTooltip({ x: cx, y: yMid, model: d.model, val: row.fmt(d.val), creator: d.creator, })} /> ) })} {/* Row label */} {row.label} ) })} {/* Tooltip — rendered last so it appears above all dots */} {tooltip && (() => { const ttX = Math.max(0, Math.min(tooltip.x - TT_W / 2, VW - TT_W)) const above = tooltip.y - DOT_R - TT_H - 8 const ttY = above < 2 ? tooltip.y + DOT_R + 6 : above const cc = creatorColor(tooltip.creator) return ( {tooltip.model} {tooltip.val} ) })()} {/* Logo overlays — one per row for the hovered model */} {tooltip && STRIP_ROWS.map((row, ri) => { const m = active.find(m => m.model === tooltip.model) if (!m) return null const val = getAvg(m, row.key) if (val === null) return null const rawMax = row.key === 'fairness' ? maxFairness : 1 const pct = row.invert ? 1 - val / rawMax : val / rawMax const cx = LBL_W + pct * AXIS_W const yMid = ri * ROW_H + ROW_H / 2 const pctX = cx / VW * 100 const pctY = yMid / VH * 100 return (
) })}
) }