import React, { useState, useEffect } from "react"; import { createRoot } from "react-dom/client"; import "./frontend.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; }; type GameState = { completed: RoundState[]; active: RoundState | null; scores: Record; done: boolean }; type ServerMessage = { type: "state"; data: GameState; totalRounds: number }; // ── Model colors & logos ───────────────────────────────────────────────────── 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", "MiniMax 2.5": "#FF3B30", }; 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"; if (name.includes("MiniMax")) return "/assets/logos/minimax.svg"; return null; } // ── Helpers ────────────────────────────────────────────────────────────────── function Dots() { return ...; } function ModelTag({ model, small }: { model: Model; small?: boolean }) { const logo = getLogo(model.name); const color = getColor(model.name); return ( {logo && } {model.name} ); } // ── Prompt ─────────────────────────────────────────────────────────────────── function PromptCard({ round }: { round: RoundState }) { if (round.phase === "prompting" && !round.prompt) { return (
is writing a prompt
); } if (round.promptTask.error) { return (
Prompt generation failed
); } return (
Prompted by
{round.prompt}
); } // ── Contestant ─────────────────────────────────────────────────────────────── function ContestantCard({ task, voteCount, totalVotes, isWinner, showVotes, voters, }: { task: TaskInfo; voteCount: number; totalVotes: number; isWinner: boolean; showVotes: boolean; voters: VoteInfo[]; }) { const color = getColor(task.model.name); const pct = totalVotes > 0 ? Math.round((voteCount / totalVotes) * 100) : 0; return (
{isWinner && WIN}
{!task.finishedAt ? (

) : task.error ? (

{task.error}

) : (

“{task.result}”

)}
{showVotes && (
{voteCount} vote{voteCount !== 1 ? "s" : ""} {voters.map((v, i) => { const logo = getLogo(v.voter.name); return logo ? ( {v.voter.name} ) : ( {v.voter.name[0]} ); })}
)}
); } // ── Arena ───────────────────────────────────────────────────────────────────── function Arena({ round, total }: { round: RoundState; total: number | null }) { const [contA, contB] = round.contestants; const showVotes = round.phase === "voting" || round.phase === "done"; const isDone = round.phase === "done"; let votesA = 0, votesB = 0; for (const v of round.votes) { if (v.votedFor?.name === contA.name) votesA++; else if (v.votedFor?.name === contB.name) votesB++; } const totalVotes = votesA + votesB; const votersA = round.votes.filter(v => v.votedFor?.name === contA.name); const votersB = round.votes.filter(v => v.votedFor?.name === contB.name); const phaseText = round.phase === "prompting" ? "Writing prompt" : round.phase === "answering" ? "Answering" : round.phase === "voting" ? "Judges voting" : "Complete"; return (
Round {round.num}{total ? /{total} : null} {phaseText}
{round.phase !== "prompting" && (
votesB} showVotes={showVotes} voters={votersA} /> votesA} showVotes={showVotes} voters={votersB} />
)} {isDone && votesA === votesB && totalVotes > 0 && (
Tie
)}
); } // ── Game Over ──────────────────────────────────────────────────────────────── function GameOver({ scores }: { scores: Record }) { const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]); const champion = sorted[0]; return (
Game Over
{champion && champion[1] > 0 && (
👑 {getLogo(champion[0]) && } {champion[0]} is the funniest AI
)}
); } // ── Standings ──────────────────────────────────────────────────────────────── function Standings({ scores, activeRound }: { scores: Record; activeRound: RoundState | null }) { const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]); const maxScore = sorted[0]?.[1] || 1; const competing = activeRound ? new Set([activeRound.contestants[0].name, activeRound.contestants[1].name]) : new Set(); return ( ); } // ── Connecting ─────────────────────────────────────────────────────────────── function ConnectingScreen() { return (
Qwipslop
Connecting
); } // ── App ────────────────────────────────────────────────────────────────────── function App() { const [state, setState] = useState(null); const [totalRounds, setTotalRounds] = useState(null); const [connected, setConnected] = useState(false); useEffect(() => { const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${wsProtocol}//${window.location.host}/ws`; let ws: WebSocket; let reconnectTimer: ReturnType; function connect() { ws = new WebSocket(wsUrl); ws.onopen = () => setConnected(true); ws.onclose = () => { setConnected(false); reconnectTimer = setTimeout(connect, 2000); }; ws.onmessage = (e) => { const msg: ServerMessage = JSON.parse(e.data); if (msg.type === "state") { setState(msg.data); setTotalRounds(msg.totalRounds); } }; } connect(); return () => { clearTimeout(reconnectTimer); ws?.close(); }; }, []); if (!connected || !state) return ; const lastCompleted = state.completed[state.completed.length - 1]; const isNextPrompting = state.active?.phase === "prompting" && !state.active.prompt; const displayRound = isNextPrompting && lastCompleted ? lastCompleted : state.active; return (
Qwipslop
{state.done ? ( ) : displayRound ? ( ) : (
Starting
)} {isNextPrompting && lastCompleted && (
is writing the next prompt
)}
); } // ── Mount ──────────────────────────────────────────────────────────────────── const root = createRoot(document.getElementById("root")!); root.render();