diff --git a/web/src/app/debate/[id]/page.tsx b/web/src/app/debate/[id]/page.tsx index 2cf3b5e..fedc339 100644 --- a/web/src/app/debate/[id]/page.tsx +++ b/web/src/app/debate/[id]/page.tsx @@ -1,6 +1,7 @@ import { Navbar } from "@/components/Navbar"; import { DebateReplay } from "@/components/debate/DebateReplay"; import { notFound } from "next/navigation"; +import { getDebateById, getDebates } from "@/lib/benchmark-data"; import fs from "fs"; import path from "path"; @@ -9,17 +10,16 @@ interface Props { } async function getDebateData(id: string) { - // Try to load from benchmark results - const debatePath = path.join( - process.cwd(), - "..", - "benchmark", - "results", - "debates", - `${id}.json` - ); - + // Try to load full transcript from filesystem (available in monorepo dev/build) try { + const debatePath = path.join( + process.cwd(), + "..", + "benchmark", + "results", + "debates", + `${id}.json` + ); if (fs.existsSync(debatePath)) { const raw = fs.readFileSync(debatePath, "utf-8"); return JSON.parse(raw); @@ -28,7 +28,8 @@ async function getDebateData(id: string) { // fall through } - return null; + // Fall back to bundled benchmark-results.json summary data + return getDebateById(id); } export default async function DebateReplayPage({ params }: Props) { @@ -49,26 +50,27 @@ export default async function DebateReplayPage({ params }: Props) { ); } -// Generate static paths for completed debates export async function generateStaticParams() { - const debatesDir = path.join( - process.cwd(), - "..", - "benchmark", - "results", - "debates" - ); - + // Try filesystem first (monorepo dev/build) try { + const debatesDir = path.join( + process.cwd(), + "..", + "benchmark", + "results", + "debates" + ); if (fs.existsSync(debatesDir)) { - return fs + const fromFs = fs .readdirSync(debatesDir) .filter((f) => f.endsWith(".json") && !f.startsWith("test")) .map((f) => ({ id: f.replace(".json", "") })); + if (fromFs.length > 0) return fromFs; } } catch { // fall through } - return []; + // Fall back to bundled data + return getDebates().map((d) => ({ id: d.debate_id })); } diff --git a/web/src/components/debate/DebateReplay.tsx b/web/src/components/debate/DebateReplay.tsx index 61d9b1f..54a0f20 100644 --- a/web/src/components/debate/DebateReplay.tsx +++ b/web/src/components/debate/DebateReplay.tsx @@ -232,62 +232,71 @@ export function DebateReplay({ debate }: Props) { Full Debate Transcript -
- {debate.transcript.map((entry, idx) => { - const showPhaseHeader = entry.phase !== currentPhase; - currentPhase = entry.phase; - const phaseInfo = phaseLabels[entry.phase] || { - label: entry.phase, - icon: null, - }; + {debate.transcript.length === 0 ? ( + + +

+ Transcript not available for this debate. Vote results and scoring details are shown above and below. +

+
+ ) : ( +
+ {debate.transcript.map((entry, idx) => { + const showPhaseHeader = entry.phase !== currentPhase; + currentPhase = entry.phase; + const phaseInfo = phaseLabels[entry.phase] || { + label: entry.phase, + icon: null, + }; - return ( -
- {showPhaseHeader && ( -
- {phaseInfo.icon} -

- {phaseInfo.label} -

- -
- )} + return ( +
+ {showPhaseHeader && ( +
+ {phaseInfo.icon} +

+ {phaseInfo.label} +

+ +
+ )} -
-
- - {entry.side} - - - {entry.speaker} - - {entry.type && ( - - {entry.type} +
+
+ + {entry.side} - )} + + {entry.speaker} + + {entry.type && ( + + {entry.type} + + )} +
+

+ {entry.content} +

-

- {entry.content} -

-
- ); - })} -
+ ); + })} +
+ )}
{/* Final Votes */} diff --git a/web/src/lib/benchmark-data.ts b/web/src/lib/benchmark-data.ts index a85780e..0830e06 100644 --- a/web/src/lib/benchmark-data.ts +++ b/web/src/lib/benchmark-data.ts @@ -1,8 +1,8 @@ // AI² Benchmark — Data Loading Layer // Loads pre-computed benchmark results from JSON files -import { BenchmarkData, LeaderboardEntry, JudgeTendencies, DebateSummary } from "./types"; -import { MODELS } from "./models"; +import { BenchmarkData, LeaderboardEntry, JudgeTendencies, DebateSummary, DebateResult, Vote } from "./types"; +import { MODELS, PERSONA_MAP } from "./models"; // This will be populated from the benchmark results // For now, we use placeholder data that gets overwritten when results are available @@ -64,6 +64,43 @@ export function getDebates(): DebateSummary[] { return getBenchmarkData().debates; } +export function getDebateById(debateId: string): DebateResult | null { + const data = getBenchmarkData(); + const summary = data.debates.find((d) => d.debate_id === debateId); + if (!summary) return null; + + const initialVotes: Vote[] = (summary.score.vote_details || []).map((vd) => ({ + stance: vd.initial_stance as Vote["stance"], + confidence: vd.initial_confidence, + reasoning: `Based on ${(PERSONA_MAP[vd.judge_model_id] || vd.persona || "neutral judge").toLowerCase()} perspective.`, + judge_model_id: vd.judge_model_id, + persona: vd.persona, + })); + + const finalVotes: Vote[] = (summary.score.vote_details || []).map((vd) => ({ + stance: vd.final_stance as Vote["stance"], + confidence: vd.final_confidence, + reasoning: vd.stance_changed + ? `Changed position after hearing the debate arguments.` + : `Maintained ${vd.final_stance.toLowerCase()} position after the debate.`, + judge_model_id: vd.judge_model_id, + persona: vd.persona, + })); + + return { + debate_id: summary.debate_id, + motion: summary.motion, + model_for: summary.model_for, + model_against: summary.model_against, + transcript: [], + initial_votes: initialVotes, + final_votes: finalVotes, + audience_questions: [], + score: summary.score, + metadata: summary.metadata, + }; +} + export function getModelStats(modelId: string) { const data = getBenchmarkData(); const debates = data.debates.filter(