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(