'use client'
import { useState, useMemo, useCallback } from 'react'
import type { ModelData } from '@/lib/types'
import { fmtPct, fmtNum, lucColor, ragColor, fairnessColor, creatorColor, CREATOR_COLORS } from '@/lib/utils'
import { LabLogo } from '@/components/LabLogo'
type SortKey = 'rank' | 'luc' | 'rag' | 'fairness'
interface Props {
models: ModelData[]
maxFairness: number
}
function ScoreCell({
value,
color,
max = 1,
format,
invert = false,
}: {
value: number | null
color: string
max?: number
format: (v: number | null) => string
invert?: boolean
}) {
const barWidth = value === null ? 0 : invert ? Math.max(0, 1 - value / max) : value / max
return (
{value === null ? (
—
) : (
<>
{format(value)}
>
)}
|
)
}
function ExpandedRow({ model, maxFairness }: { model: ModelData; maxFairness: number }) {
const lucMetrics = [
{ label: 'General', val: model.luc.general, color: lucColor(model.luc.general) },
{ label: 'Physics misuse', val: model.luc.physics, color: lucColor(model.luc.physics) },
{ label: 'Career scams', val: model.luc.career, color: lucColor(model.luc.career) },
{ label: 'Job discrimination', val: model.luc.jd, color: lucColor(model.luc.jd) },
]
const ragMetrics = [
{ label: 'Long In-Context Abstractive', val: model.rag.lcAbs, color: ragColor(model.rag.lcAbs) },
{ label: 'Long In-Context Factual', val: model.rag.lcFact, color: ragColor(model.rag.lcFact) },
{ label: 'HyDE RAG Abstractive', val: model.rag.hyAbs, color: ragColor(model.rag.hyAbs) },
{ label: 'HyDE RAG Factual', val: model.rag.hyFact, color: ragColor(model.rag.hyFact) },
]
const fairMetrics = [
{ label: 'Style disparity', val: model.fairness.style, invert: true },
{ label: 'Content disparity', val: model.fairness.con, invert: true },
]
return (
{/* LUC */}
Localised Undesired Content
{lucMetrics.map(m => (
))}
{/* RAG */}
RAG Robustness
{ragMetrics.map(m => (
))}
{/* Fairness */}
Fairness Disparity
{fairMetrics.map(m => {
const fc = fairnessColor(m.val)
const pct = m.val !== null ? Math.min(100, (m.val / maxFairness) * 100) : 0
return (
)
})}
|
)
}
export default function LeaderboardTable({ models, maxFairness }: Props) {
const [sortKey, setSortKey] = useState('luc')
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc')
const [query, setQuery] = useState('')
const [expandedRank, setExpandedRank] = useState(null)
const [activeCreator, setActiveCreator] = useState(null)
const creators = useMemo(() => [...new Set(models.map(m => m.creator))].sort(), [models])
const handleSort = useCallback((key: SortKey) => {
if (sortKey === key) {
setSortDir(d => (d === 'asc' ? 'desc' : 'asc'))
} else {
setSortKey(key)
setSortDir(key === 'fairness' ? 'asc' : 'desc')
}
}, [sortKey])
const sortedFiltered = useMemo(() => {
let list = models.filter(m => !m.archived)
if (activeCreator) list = list.filter(m => m.creator === activeCreator)
if (query.trim()) {
const q = query.toLowerCase()
list = list.filter(
m => m.model.toLowerCase().includes(q) || m.creator.toLowerCase().includes(q),
)
}
list.sort((a, b) => {
let va: number | null, vb: number | null
if (sortKey === 'rank') { va = a.rank; vb = b.rank }
else if (sortKey === 'luc') { va = a.luc.avg; vb = b.luc.avg }
else if (sortKey === 'rag') { va = a.rag.avg; vb = b.rag.avg }
else { va = a.fairness.avg; vb = b.fairness.avg }
if (va === null && vb === null) return 0
if (va === null) return 1
if (vb === null) return -1
return sortDir === 'asc' ? va - vb : vb - va
})
return list
}, [models, activeCreator, query, sortKey, sortDir])
function SortArrow({ k }: { k: SortKey }) {
if (sortKey !== k) return ↕
return {sortDir === 'asc' ? '↑' : '↓'}
}
return (
{/* Controls */}
{creators.map(c => (
))}
{/* Table */}
| handleSort('rank')} className={sortKey === 'rank' ? 'sort-active' : ''} style={{ width: 48 }}>
#
|
Creator |
Model |
handleSort('luc')} className={sortKey === 'luc' ? 'sort-active' : ''}>
Refusal Rate
|
handleSort('rag')} className={sortKey === 'rag' ? 'sort-active' : ''}>
RAG Score
|
handleSort('fairness')} className={sortKey === 'fairness' ? 'sort-active' : ''}>
Fairness ↓
|
{sortedFiltered.map((model, i) => {
const isExpanded = expandedRank === model.rank
const cc = creatorColor(model.creator)
return [
|
{i + 1}
|
|
|
fmtPct(v)}
/>
fmtPct(v)}
/>
fmtNum(v, 4)}
invert
/>
,
isExpanded && (
),
]
})}
{sortedFiltered.length} models · ↓ lower fairness score = more equitable
)
}