Files
AIHostingTycoon/apps/web/src/pages/LeaderboardPage.tsx
T
josh 4c1c0e9ff2
CI / build-and-push (push) Successful in 32s
Overhaul model system with multi-stage training, variants, benchmarks, and eval
Replace the single-stage training + flat capability score with a realistic AI
development pipeline: pre-training with Chinchilla scaling laws, SFT with
specializations, alignment with safety/capability tradeoffs (RLHF/DPO/Constitutional),
model families with distillation/fine-tuning/quantization variants, named benchmark
suite with compute-costing eval jobs, and segment-specific market quality.

Phases 1-6 of the model rework plan: new types, engine rewrite, save migration,
training events/risk system, concurrent training, variant creation, benchmark
evaluation with leaderboard, and market integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 07:36:34 -04:00

132 lines
4.9 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Trophy, Medal, Clock, TrendingUp } from 'lucide-react';
import { useGameStore } from '@/store';
import { formatMoney, formatNumber } from '@ai-tycoon/shared';
import { api, getAuthToken } from '@/lib/api';
interface LeaderboardEntry {
companyName: string;
score: number;
era: string;
tickCount: number;
}
const CATEGORIES = [
{ id: 'revenue', label: 'Highest Revenue', icon: TrendingUp },
{ id: 'fastest-agi', label: 'Fastest to AGI', icon: Clock },
{ id: 'capability', label: 'Best Model', icon: Trophy },
] as const;
export function LeaderboardPage() {
const [category, setCategory] = useState('revenue');
const [entries, setEntries] = useState<LeaderboardEntry[]>([]);
const [loading, setLoading] = useState(false);
const [submitted, setSubmitted] = useState(false);
const companyName = useGameStore((s) => s.meta.companyName);
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
const era = useGameStore((s) => s.meta.currentEra);
const tickCount = useGameStore((s) => s.meta.tickCount);
const bestModel = useGameStore((s) => s.models.bestDeployedModelScore);
useEffect(() => {
setLoading(true);
api.leaderboard.get(category)
.then(data => setEntries(data.entries))
.catch(() => setEntries([]))
.finally(() => setLoading(false));
}, [category]);
const handleSubmit = async () => {
if (!getAuthToken()) return;
const scoreMap: Record<string, number> = {
revenue: Math.floor(totalRevenue),
'fastest-agi': era === 'agi' ? tickCount : 0,
capability: Math.floor(bestModel * 10),
};
const score = scoreMap[category];
if (score <= 0) return;
try {
await api.leaderboard.submit({ companyName, category, score, era, tickCount });
setSubmitted(true);
const data = await api.leaderboard.get(category);
setEntries(data.entries);
} catch { /* ignore */ }
};
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold">Leaderboard</h2>
<div className="flex gap-2">
{CATEGORIES.map(cat => (
<button
key={cat.id}
onClick={() => { setCategory(cat.id); setSubmitted(false); }}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm border transition-colors ${
category === cat.id
? 'bg-accent/20 border-accent text-accent-light'
: 'bg-surface-900 border-surface-700 text-surface-300 hover:border-surface-500'
}`}
>
<cat.icon size={14} />
{cat.label}
</button>
))}
</div>
{getAuthToken() && !submitted && (
<button
onClick={handleSubmit}
className="bg-accent hover:bg-accent-dark text-white px-4 py-2 rounded-lg text-sm font-medium"
>
Submit My Score
</button>
)}
{submitted && <p className="text-sm text-success">Score submitted!</p>}
<div className="bg-surface-900 border border-surface-700 rounded-xl overflow-hidden">
{loading ? (
<div className="p-4 space-y-3">
{[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center gap-4 animate-pulse">
<div className="w-8 h-4 bg-surface-800 rounded" />
<div className="flex-1 h-4 bg-surface-800 rounded" />
<div className="w-16 h-4 bg-surface-800 rounded" />
<div className="w-12 h-4 bg-surface-800 rounded" />
</div>
))}
</div>
) : entries.length === 0 ? (
<div className="p-8 text-center text-surface-500">
<Trophy size={48} className="mx-auto mb-4 opacity-50" />
<p>No entries yet. Be the first!</p>
</div>
) : (
<table className="w-full text-sm">
<thead>
<tr className="border-b border-surface-700 text-surface-400 text-xs uppercase">
<th className="p-3 text-left w-12">#</th>
<th className="p-3 text-left">Company</th>
<th className="p-3 text-right">Score</th>
<th className="p-3 text-right">Era</th>
</tr>
</thead>
<tbody>
{entries.map((entry, i) => (
<tr key={i} className="border-b border-surface-800 hover:bg-surface-800/50">
<td className="p-3">
{i < 3 ? <Medal size={16} className={i === 0 ? 'text-yellow-400' : i === 1 ? 'text-gray-400' : 'text-orange-400'} /> : i + 1}
</td>
<td className="p-3 font-medium">{entry.companyName}</td>
<td className="p-3 text-right font-mono">{formatNumber(entry.score)}</td>
<td className="p-3 text-right text-surface-400 capitalize">{entry.era}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
);
}