import React, { useState, useEffect } from "react"; import { createRoot } from "react-dom/client"; import "./history.css"; // ── Types ─────────────────────────────────────────────────────────────────── type Model = { id: string; name: string }; type TaskInfo = { model: Model; startedAt: number; finishedAt?: number; result?: string; error?: string }; type VoteInfo = { voter: Model; startedAt: number; finishedAt?: number; votedFor?: Model; error?: boolean }; type RoundState = { num: number; phase: "prompting" | "answering" | "voting" | "done"; prompter: Model; promptTask: TaskInfo; prompt?: string; contestants: [Model, Model]; answerTasks: [TaskInfo, TaskInfo]; votes: VoteInfo[]; scoreA?: number; scoreB?: number; }; // ── Shared UI Utils ───────────────────────────────────────────────────────── const MODEL_COLORS: Record = { "Gemini 3.1 Pro": "#4285F4", "Kimi K2": "#00E599", "DeepSeek 3.2": "#4D6BFE", "GLM-5": "#1F63EC", "GPT-5.2": "#10A37F", "Opus 4.6": "#D97757", "Sonnet 4.6": "#D97757", "Grok 4.1": "#FFFFFF", }; function getColor(name: string): string { return MODEL_COLORS[name] ?? "#A1A1A1"; } function getLogo(name: string): string | null { if (name.includes("Gemini")) return "/assets/logos/gemini.svg"; if (name.includes("Kimi")) return "/assets/logos/kimi.svg"; if (name.includes("DeepSeek")) return "/assets/logos/deepseek.svg"; if (name.includes("GLM")) return "/assets/logos/glm.svg"; if (name.includes("GPT")) return "/assets/logos/openai.svg"; if (name.includes("Opus") || name.includes("Sonnet")) return "/assets/logos/claude.svg"; if (name.includes("Grok")) return "/assets/logos/grok.svg"; return null; } function ModelName({ model, className = "" }: { model: Model; className?: string }) { const logo = getLogo(model.name); const color = getColor(model.name); return ( {logo && } {model.name} ); } // ── Components ────────────────────────────────────────────────────────────── function HistoryContestant({ task, votes, voters }: { task: TaskInfo; votes: number; voters: Model[]; }) { const color = getColor(task.model.name); return (
“{task.result}”
{votes} {votes === 1 ? 'vote' : 'votes'}
{voters.map(v => { const logo = getLogo(v.name); if (!logo) return null; return {v.name}; })}
); } function HistoryCard({ round }: { round: RoundState }) { const [contA, contB] = round.contestants; let votesA = 0, votesB = 0; const votersA: Model[] = []; const votersB: Model[] = []; for (const v of round.votes) { if (v.votedFor?.name === contA.name) { votesA++; votersA.push(v.voter); } else if (v.votedFor?.name === contB.name) { votesB++; votersB.push(v.voter); } } const isAWinner = votesA > votesB; const isBWinner = votesB > votesA; return (
Prompted by
{round.prompt}
R{round.num}
{isAWinner &&
WINNER
}
“{round.answerTasks[0].result}”
{votesA} {votesA === 1 ? 'vote' : 'votes'}
{votersA.map(v => getLogo(v.name) && )}
{isBWinner &&
WINNER
}
“{round.answerTasks[1].result}”
{votesB} {votesB === 1 ? 'vote' : 'votes'}
{votersB.map(v => getLogo(v.name) && )}
); } // ── App ───────────────────────────────────────────────────────────────────── function App() { const [rounds, setRounds] = useState([]); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); fetch(`/api/history?page=${page}`) .then(res => res.json()) .then(data => { setRounds(data.rounds); setTotalPages(data.totalPages || 1); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, [page]); return (
QUIPSLOP History
Past Rounds
{loading ? (
Loading...
) : error ? (
{error}
) : rounds.length === 0 ? (
No past rounds found.
) : ( <>
{rounds.map(r => ( ))}
{totalPages > 1 && (
Page {page} of {totalPages}
)} )}
); } // ── Mount ─────────────────────────────────────────────────────────────────── const root = createRoot(document.getElementById("root")!); root.render();