"use client" import { useState, useEffect, useMemo } from "react" import Link from "next/link" import { getLeaderboard, removeFromLeaderboard, type LeaderboardEntry } from "@/lib/api" import { FilterBar } from "@/components/filter-bar" import { DataTable, type Column } from "@/components/data-table" import { DropdownMenu } from "@/components/dropdown-menu" import { EmptyState, TrophyIcon } from "@/components/empty-state" export default function LeaderboardPage() { const [entries, setEntries] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Filters const [search, setSearch] = useState("") const [selectedProviders, setSelectedProviders] = useState([]) const [selectedBenchmarks, setSelectedBenchmarks] = useState([]) useEffect(() => { loadLeaderboard() }, []) async function loadLeaderboard() { try { setLoading(true) const data = await getLeaderboard() setEntries(data.entries) setError(null) } catch (e) { setError(e instanceof Error ? e.message : "Failed to load leaderboard") } finally { setLoading(false) } } async function handleRemove(id: number) { if (!confirm("Remove this entry from the leaderboard?")) return try { await removeFromLeaderboard(id) setEntries(entries.filter((e) => e.id !== id)) } catch (e) { alert(e instanceof Error ? e.message : "Failed to remove entry") } } // Get unique providers and benchmarks for filter options const providers = useMemo(() => { const counts: Record = {} entries.forEach((e) => { counts[e.provider] = (counts[e.provider] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: value, count, })) }, [entries]) const benchmarks = useMemo(() => { const counts: Record = {} entries.forEach((e) => { counts[e.benchmark] = (counts[e.benchmark] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: value, count, })) }, [entries]) // Filter entries const filteredEntries = useMemo(() => { return entries.filter((e) => { // Search filter if (search) { const searchLower = search.toLowerCase() const matchesSearch = e.version.toLowerCase().includes(searchLower) || e.runId.toLowerCase().includes(searchLower) || e.provider.toLowerCase().includes(searchLower) if (!matchesSearch) return false } // Provider filter if (selectedProviders.length > 0 && !selectedProviders.includes(e.provider)) { return false } // Benchmark filter if (selectedBenchmarks.length > 0 && !selectedBenchmarks.includes(e.benchmark)) { return false } return true }) }, [entries, search, selectedProviders, selectedBenchmarks]) // Get question types and registry - only when exactly one benchmark is selected const { visibleQuestionTypes, typeRegistry } = useMemo((): { visibleQuestionTypes: string[] typeRegistry: LeaderboardEntry["questionTypeRegistry"] } => { if (selectedBenchmarks.length !== 1) { return { visibleQuestionTypes: [], typeRegistry: null } } // Get all question types present in filtered entries for the selected benchmark const types = new Set() let registry: LeaderboardEntry["questionTypeRegistry"] = null filteredEntries.forEach((e) => { Object.keys(e.byQuestionType).forEach((t) => types.add(t)) if (!registry && e.questionTypeRegistry) { registry = e.questionTypeRegistry } }) return { visibleQuestionTypes: Array.from(types).sort(), typeRegistry: registry, } }, [selectedBenchmarks, filteredEntries]) // Build columns const columns: Column[] = useMemo(() => { const cols: Column[] = [ { key: "rank", header: "Rank", width: "60px", render: (_, idx) => {idx + 1}, }, { key: "provider", header: "Provider", render: (entry) => {entry.provider}, }, { key: "benchmark", header: "Benchmark", render: (entry) => {entry.benchmark}, }, { key: "version", header: "Version", render: (entry) => ( {entry.version} ), }, { key: "date", header: "Date", render: (entry) => { const date = new Date(entry.addedAt) return ( {date.getFullYear()}-{String(date.getMonth() + 1).padStart(2, "0")} ) }, }, ] // Add question type columns only when single benchmark is selected visibleQuestionTypes.forEach((type) => { const alias = typeRegistry?.[type]?.alias || type.replace(/[-_]/g, " ") cols.push({ key: type, header: alias, align: "center", render: (entry) => { const stats = entry.byQuestionType[type] if (!stats) { return } return {(stats.accuracy * 100).toFixed(0)}% }, }) }) // Accuracy column (always last) cols.push({ key: "accuracy", header: "Accuracy", align: "right", render: (entry) => ( {(entry.accuracy * 100).toFixed(1)}% ), }) // Actions column cols.push({ key: "actions", header: "", width: "40px", align: "right", render: (entry) => ( handleRemove(entry.id), danger: true, }, ]} /> ), }) return cols }, [visibleQuestionTypes, typeRegistry]) const clearFilters = () => { setSearch("") setSelectedProviders([]) setSelectedBenchmarks([]) } if (error) { return (

Leaderboard

{error}

) } return (
{/* Header */}

Leaderboard

{/* Filter Bar */} {!loading && entries.length > 0 && (
)} {/* Table */} {loading ? (

Loading leaderboard...

) : entries.length === 0 ? ( } title="No entries yet" description='Add runs to the leaderboard from the Runs page by clicking the three-dot menu on a completed run and selecting "Add to leaderboard".' /> ) : ( entry.id} /> )}
) }