spb/llmindex Public
The discriminative, contamination-resistant, fully transparent LLM ranking — updated live.
TypeScript 77.9%
TeX 15.2%
Python 3.7%
SQL 1.4%
JavaScript 1.1%
Shell 0.5%
1/**2 * llmindex.io — interactive live Pareto frontier chart (full-page, SVG)3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * License: Proprietary — © Simon-Pierre Boucher, all rights reserved6 *7 * Form: scatter + Pareto step line (quality vs log-cost). Color encodes the8 * two-class identity frontier/dominated — palette #047857/#7c3aed validated9 * (lightness band, chroma, CVD ΔE 23.9 deutan, normal-vision ΔE 32, contrast).10 * Provider identity rides on labels/tooltips/logos, never on hue cycling.11 * CI whiskers on every point; frontier points carry direct labels; per-mark12 * hover tooltips with enlarged hit targets; polls fresh data every 15 s and13 * animates newly landed models in.14 */15'use client';1617import { useEffect, useMemo, useRef, useState } from 'react';18import { useRouter } from 'next/navigation';19import { ProviderLogo } from './ProviderLogo';2021export interface FrontierPoint {22 model: string;23 name: string;24 provider: string;25 score: number;26 score_low: number;27 score_high: number;28 latency_p50: number | null;29 cost_per_1k_items: number;30 on_frontier: boolean;31}3233const FRONTIER = '#047857';34const DOMINATED = '#7c3aed';35const INK = '#3f3f46';36const INK_MUTED = '#a1a1aa';37const GRID = '#e4e4e7';38const POLL_MS = 15000;3940const W = 1280;41const H = 720;42const M = { top: 28, right: 48, bottom: 64, left: 72 };43const IW = W - M.left - M.right;44const IH = H - M.top - M.bottom;4546function fmtCost(v: number): string {47 if (v >= 100) return `$${Math.round(v)}`;48 if (v >= 1) return `$${v.toFixed(v >= 10 ? 0 : 1)}`;49 if (v >= 0.01) return `$${v.toFixed(2)}`;50 return `$${v.toFixed(3)}`;51}5253export function FrontierChart({ initial }: { initial: FrontierPoint[] }) {54 const router = useRouter();55 const [points, setPoints] = useState<FrontierPoint[]>(initial);56 const [hover, setHover] = useState<FrontierPoint | null>(null);57 const [fresh, setFresh] = useState<Set<string>>(new Set());58 const [updatedAt, setUpdatedAt] = useState<Date | null>(null);59 const knownRef = useRef<Set<string>>(new Set(initial.map((p) => p.model)));6061 useEffect(() => {62 let cancelled = false;63 async function tick() {64 try {65 const res = await fetch('/api/v1/efficiency', { cache: 'no-store' });66 if (!res.ok || cancelled) return;67 const body = (await res.json()) as { points: FrontierPoint[] };68 if (!body.points) return;69 const newcomers = body.points.map((p) => p.model).filter((m) => !knownRef.current.has(m));70 if (newcomers.length > 0) {71 setFresh(new Set(newcomers));72 newcomers.forEach((m) => knownRef.current.add(m));73 setTimeout(() => setFresh(new Set()), 5000);74 }75 setPoints(body.points);76 setUpdatedAt(new Date());77 } catch {78 /* keep last state */79 }80 }81 const id = setInterval(tick, POLL_MS);82 tick();83 return () => {84 cancelled = true;85 clearInterval(id);86 };87 }, []);8889 const view = useMemo(() => {90 if (points.length === 0) return null;91 const costs = points.map((p) => p.cost_per_1k_items).filter((c) => c > 0);92 const minC = Math.min(...costs);93 const maxC = Math.max(...costs);94 const lo = Math.floor(Math.log10(minC) - 0.15);95 const hi = Math.ceil(Math.log10(maxC) + 0.15);96 const yMin = Math.max(0, Math.floor((Math.min(...points.map((p) => p.score_low)) - 50) / 100) * 100);97 const yMax = Math.min(1000, Math.ceil((Math.max(...points.map((p) => p.score_high)) + 50) / 100) * 100);9899 const x = (c: number): number => M.left + ((Math.log10(c) - lo) / (hi - lo)) * IW;100 const y = (s: number): number => M.top + (1 - (s - yMin) / (yMax - yMin)) * IH;101102 const xTicks: Array<{ v: number; major: boolean }> = [];103 for (let d = lo; d <= hi; d++) {104 xTicks.push({ v: 10 ** d, major: true });105 for (const m of [2, 5]) {106 const v = m * 10 ** d;107 if (Math.log10(v) < hi) xTicks.push({ v, major: false });108 }109 }110 const yTicks: number[] = [];111 for (let s = yMin; s <= yMax; s += 100) yTicks.push(s);112113 // Pareto step polyline (const-right steps in cost order).114 const front = points.filter((p) => p.on_frontier).sort((a, b) => a.cost_per_1k_items - b.cost_per_1k_items);115 let path = '';116 front.forEach((p, i) => {117 const px = x(p.cost_per_1k_items);118 const py = y(p.score);119 if (i === 0) path += `M ${px} ${py}`;120 else path += ` L ${px} ${y(front[i - 1]!.score)} L ${px} ${py}`;121 });122 if (front.length > 0) {123 path += ` L ${M.left + IW} ${y(front[front.length - 1]!.score)}`;124 }125126 // Direct labels on frontier points, nudged apart vertically when close.127 const labels = front.map((p) => ({ p, lx: x(p.cost_per_1k_items) + 12, ly: y(p.score) - 10 }));128 for (let i = 1; i < labels.length; i++) {129 const prev = labels[i - 1]!;130 const cur = labels[i]!;131 if (Math.abs(cur.ly - prev.ly) < 16 && cur.lx - prev.lx < 130) cur.ly = prev.ly - 16;132 }133134 return { x, y, xTicks, yTicks, path, labels, yMin, yMax };135 }, [points]);136137 if (!view || points.length === 0) {138 return (139 <div className="flex h-[50vh] items-center justify-center rounded-2xl border border-zinc-200 bg-white text-sm text-zinc-500">140 Waiting for the first scored models — the chart populates automatically as the live141 benchmark lands them.142 </div>143 );144 }145146 const shortName = (slug: string): string => slug.split('/')[1] ?? slug;147148 return (149 <div className="space-y-3">150 {/* legend + live status — one row above the chart */}151 <div className="flex flex-wrap items-center gap-x-5 gap-y-2 text-sm">152 <span className="flex items-center gap-2">153 <span className="inline-block h-3 w-3 rounded-full" style={{ background: FRONTIER }} />154 <span className="text-zinc-700">Pareto frontier ({points.filter((p) => p.on_frontier).length})</span>155 </span>156 <span className="flex items-center gap-2">157 <span className="inline-block h-3 w-3 rounded-full" style={{ background: DOMINATED, opacity: 0.75 }} />158 <span className="text-zinc-700">dominated ({points.filter((p) => !p.on_frontier).length})</span>159 </span>160 <span className="flex items-center gap-2 text-zinc-500">161 <span className="inline-block h-3 w-[2px] bg-zinc-400" /> 95% CI162 </span>163 <span className="ml-auto flex items-center gap-2 text-xs text-zinc-500">164 <span className="relative flex h-2 w-2">165 <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-60" />166 <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-400" />167 </span>168 live · {points.length} models{updatedAt ? ` · updated ${updatedAt.toLocaleTimeString()}` : ''}169 </span>170 </div>171172 <div className="relative overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm">173 <svg viewBox={`0 0 ${W} ${H}`} className="block w-full" role="img"174 aria-label="Pareto efficiency frontier: Global Index versus cost per thousand items">175 {/* grid */}176 {view.yTicks.map((s) => (177 <line key={`gy${s}`} x1={M.left} x2={M.left + IW} y1={view.y(s)} y2={view.y(s)}178 stroke={GRID} strokeWidth={1} />179 ))}180 {view.xTicks.filter((t) => t.major).map((t) => (181 <line key={`gx${t.v}`} x1={view.x(t.v)} x2={view.x(t.v)} y1={M.top} y2={M.top + IH}182 stroke={GRID} strokeWidth={1} />183 ))}184185 {/* axes labels */}186 {view.yTicks.map((s) => (187 <text key={`ty${s}`} x={M.left - 10} y={view.y(s) + 4} textAnchor="end"188 fontSize={13} fill={INK_MUTED}>{s}</text>189 ))}190 {view.xTicks.map((t) => (191 <text key={`tx${t.v}`} x={view.x(t.v)} y={M.top + IH + (t.major ? 24 : 20)} textAnchor="middle"192 fontSize={t.major ? 13 : 10.5} fill={t.major ? INK_MUTED : '#c4c4cc'}>193 {fmtCost(t.v)}194 </text>195 ))}196 <text x={M.left + IW / 2} y={H - 14} textAnchor="middle" fontSize={14} fill={INK}>197 cost per 1,000 items (USD, log scale) →198 </text>199 <text x={20} y={M.top + IH / 2} textAnchor="middle" fontSize={14} fill={INK}200 transform={`rotate(-90 20 ${M.top + IH / 2})`}>201 Global Index →202 </text>203204 {/* frontier step line */}205 <path d={view.path} fill="none" stroke={FRONTIER} strokeWidth={2} strokeLinejoin="round" opacity={0.85} />206207 {/* CI whiskers */}208 {points.map((p) => {209 const px = view.x(p.cost_per_1k_items);210 const color = p.on_frontier ? FRONTIER : DOMINATED;211 return (212 <g key={`w${p.model}`} opacity={0.45}>213 <line x1={px} x2={px} y1={view.y(p.score_low)} y2={view.y(p.score_high)}214 stroke={color} strokeWidth={1.5} />215 <line x1={px - 4} x2={px + 4} y1={view.y(p.score_low)} y2={view.y(p.score_low)} stroke={color} strokeWidth={1.5} />216 <line x1={px - 4} x2={px + 4} y1={view.y(p.score_high)} y2={view.y(p.score_high)} stroke={color} strokeWidth={1.5} />217 </g>218 );219 })}220221 {/* dots (2px surface ring separates overlapping marks) */}222 {points.map((p) => (223 <circle key={`d${p.model}`} cx={view.x(p.cost_per_1k_items)} cy={view.y(p.score)}224 r={p.on_frontier ? 7 : 5.5}225 fill={p.on_frontier ? FRONTIER : DOMINATED}226 fillOpacity={p.on_frontier ? 1 : 0.75}227 stroke="#ffffff" strokeWidth={2}228 className={fresh.has(p.model) ? 'animate-row-in' : undefined} />229 ))}230231 {/* selective direct labels: frontier only */}232 {view.labels.map(({ p, lx, ly }) => (233 <text key={`l${p.model}`} x={lx} y={ly} fontSize={12.5} fontWeight={600} fill={INK}>234 {shortName(p.model)}235 </text>236 ))}237238 {/* enlarged invisible hit targets */}239 {points.map((p) => (240 <circle key={`h${p.model}`} cx={view.x(p.cost_per_1k_items)} cy={view.y(p.score)} r={16}241 fill="transparent" style={{ cursor: 'pointer' }}242 onMouseEnter={() => setHover(p)} onMouseLeave={() => setHover(null)}243 onClick={() => router.push(`/models/${p.model}`)} />244 ))}245246 {/* hover ring */}247 {hover && (248 <circle cx={view.x(hover.cost_per_1k_items)} cy={view.y(hover.score)} r={11}249 fill="none" stroke={hover.on_frontier ? FRONTIER : DOMINATED} strokeWidth={2} opacity={0.6} />250 )}251 </svg>252253 {/* tooltip */}254 {hover && (255 <div256 className="pointer-events-none absolute z-10 w-64 rounded-xl border border-zinc-200 bg-white p-3 shadow-lg"257 style={{258 left: `min(max(${((view.x(hover.cost_per_1k_items) / W) * 100).toFixed(2)}% - 8rem, 0.5rem), calc(100% - 16.5rem))`,259 top: `calc(${((view.y(hover.score) / H) * 100).toFixed(2)}% - 0.5rem)`,260 transform: 'translateY(-100%)',261 }}262 >263 <div className="flex items-center gap-2">264 <ProviderLogo provider={hover.provider} size={18} />265 <span className="truncate text-sm font-semibold text-zinc-900">{hover.name}</span>266 </div>267 <div className="mt-2 grid grid-cols-2 gap-x-3 gap-y-1 text-xs text-zinc-600">268 <span>Global Index</span>269 <span className="text-right font-semibold tabular-nums text-zinc-900">270 {hover.score} <span className="font-normal text-zinc-500">[{hover.score_low}–{hover.score_high}]</span>271 </span>272 <span>Cost / 1k items</span>273 <span className="text-right tabular-nums">{fmtCost(hover.cost_per_1k_items)}</span>274 <span>Latency p50</span>275 <span className="text-right tabular-nums">276 {hover.latency_p50 != null ? `${(hover.latency_p50 / 1000).toFixed(1)}s` : '—'}277 </span>278 <span>Status</span>279 <span className="text-right">280 {hover.on_frontier ? (281 <span className="rounded-full bg-emerald-100 px-2 py-0.5 text-emerald-700">frontier</span>282 ) : (283 <span className="rounded-full bg-violet-100 px-2 py-0.5 text-violet-700">dominated</span>284 )}285 </span>286 </div>287 <p className="mt-2 text-[10px] text-zinc-400">click to open the model page</p>288 </div>289 )}290 </div>291 </div>292 );293}294