profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM

Commit

comments

commit 82cf62a

2 changed files with +85 and −29

Jump to a changed file
  1. src/components/debate/new-debate.tsx +25 −5
  2. src/components/landing-hero.tsx +60 −24
modified src/components/debate/new-debate.tsx +25 −5
@@ -209,7 +209,10 @@export function NewDebate() {
209 209 <CardContent className="space-y-5">
210 210 <div className="grid gap-4 sm:grid-cols-2">
211 211 <div className="space-y-1.5">
212 - <Label>Chairman (synthesis)</Label>
212 + <Label>Chairman</Label>
213 + <p className="text-xs text-muted-foreground">
214 + Reads every model&apos;s answer and writes the final verdict. Best chosen from outside the council.
215 + </p>
213 216 <ModelCombobox value={chairman} onChange={setChairman} placeholder="Independent model preferred" />
214 217 {providerConflict && (
215 218 <p className="flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400">
@@ -218,29 +221,43 @@export function NewDebate() {
218 221 )}
219 222 </div>
220 223 <div className="space-y-1.5">
221 - <Label>Convergence assessor</Label>
224 + <Label>Agreement checker</Label>
225 + <p className="text-xs text-muted-foreground">
226 + A fast, cheap model that scores how much the council still disagrees after each round.
227 + </p>
222 228 <ModelCombobox value={convergenceModel} onChange={setConvergenceModel} placeholder="A fast, cheap model" />
223 229 </div>
224 230 </div>
225 231
226 - <SliderRow label="Max rounds" value={maxRounds} min={1} max={5} step={1} onChange={setMaxRounds} display={String(maxRounds)} />
227 232 <SliderRow
228 - label="Convergence threshold"
233 + label="Debate rounds"
234 + value={maxRounds}
235 + min={1}
236 + max={5}
237 + step={1}
238 + onChange={setMaxRounds}
239 + display={String(maxRounds)}
240 + hint="How many times the models critique and revise each other. More rounds refine the answer but cost more."
241 + />
242 + <SliderRow
243 + label="Stop-early threshold"
229 244 value={threshold}
230 245 min={50}
231 246 max={100}
232 247 step={1}
233 248 onChange={setThreshold}
234 249 display={`${threshold}/100`}
250 + hint="End the debate early once the council's agreement reaches this score, instead of using every round."
235 251 />
236 252 <SliderRow
237 - label="Temperature"
253 + label="Creativity"
238 254 value={temperature}
239 255 min={0}
240 256 max={1.5}
241 257 step={0.1}
242 258 onChange={setTemperature}
243 259 display={temperature.toFixed(1)}
260 + hint="Low keeps answers focused and consistent. High makes them more varied and creative."
244 261 />
245 262
246 263 {presets.length > 0 && (
@@ -339,6 +356,7 @@function SliderRow({
339 356 step,
340 357 onChange,
341 358 display,
359 + hint,
342 360 }: {
343 361 label: string;
344 362 value: number;
@@ -347,6 +365,7 @@function SliderRow({
347 365 step: number;
348 366 onChange: (v: number) => void;
349 367 display: string;
368 + hint?: string;
350 369 }) {
351 370 return (
352 371 <div className="space-y-2">
@@ -355,6 +374,7 @@function SliderRow({
355 374 <span className="font-mono text-xs text-muted-foreground">{display}</span>
356 375 </div>
357 376 <Slider value={[value]} min={min} max={max} step={step} onValueChange={(v) => onChange(v[0]!)} />
377 + {hint && <p className="text-xs leading-relaxed text-muted-foreground">{hint}</p>}
358 378 </div>
359 379 );
360 380 }
modified src/components/landing-hero.tsx +60 −24
@@ -1,8 +1,8 @@
1 1 'use client';
2 2
3 -import { ArrowRight } from 'lucide-react';
3 +import { ArrowRight, Pause, Play } from 'lucide-react';
4 4 import Link from 'next/link';
5 -import { useEffect } from 'react';
5 +import { useEffect, useState } from 'react';
6 6 import { CouncilNodes } from '@/components/debate/council-nodes';
7 7 import { DebateConsole } from '@/components/debate/debate-console';
8 8 import { Button } from '@/components/ui/button';
@@ -11,27 +11,27 @@import { useDebatePlayback } from '@/hooks/use-debate-playback';
11 11 import { truncate } from '@/lib/utils';
12 12
13 13 /**
14 - * The landing hero: a real recorded debate auto-plays and loops, front and
15 - * center. Almost no marketing copy - the product sells itself in motion. The
16 - * live console is clipped to a viewport-height frame with a soft fade, so the
17 - * page stays tight while the debate animates.
14 + * The landing hero: a real recorded debate, front and center. It loads showing
15 + * the finished verdict - nothing moving - so a visitor gets the payoff at once.
16 + * Pressing play replays the whole deliberation live (streaming tokens, critique,
17 + * revision, synthesis); it settles back on the answer and never loops on its own.
18 18 */
19 19 export function LandingHero({ result }: { result: DebateResult }) {
20 - const { view, state, progress, play, restart, setSpeed } = useDebatePlayback(result);
20 + const { view, state, progress, play, pause, restart, skipToEnd, setSpeed } = useDebatePlayback(result);
21 + const [hasPlayed, setHasPlayed] = useState(false);
21 22
22 - // Autoplay a little faster than a real debate so the loop stays lively.
23 + // Load on the final answer, ready to read. We only animate on demand.
23 24 useEffect(() => {
24 25 setSpeed(2);
25 - play();
26 + skipToEnd();
26 27 // eslint-disable-next-line react-hooks/exhaustive-deps
27 28 }, []);
28 29
29 - // Loop: once it finishes, hold on the final answer briefly, then replay.
30 - useEffect(() => {
31 - if (state !== 'finished') return;
32 - const t = setTimeout(() => restart(), 4000);
33 - return () => clearTimeout(t);
34 - }, [state, restart]);
30 + const isLive = state === 'playing';
31 + const watch = () => {
32 + setHasPlayed(true);
33 + restart();
34 + };
35 35
36 36 return (
37 37 <section className="container py-10 md:py-14">
@@ -40,8 +40,17 @@export function LandingHero({ result }: { result: DebateResult }) {
40 40 className="reveal mb-5 inline-flex items-center gap-2 font-mono text-[11px] uppercase tracking-[0.22em] text-muted-foreground"
41 41 style={{ animationDelay: '0ms' }}
42 42 >
43 - <span className="h-1.5 w-1.5 animate-pulse-subtle rounded-full bg-emerald-500" />
44 - the council is in session
43 + {isLive ? (
44 + <>
45 + <span className="h-1.5 w-1.5 animate-pulse-subtle rounded-full bg-emerald-500" />
46 + the council is in session
47 + </>
48 + ) : (
49 + <>
50 + <span className="h-1.5 w-1.5 rounded-full bg-primary/60" />
51 + one real debate, start to finish
52 + </>
53 + )}
45 54 </div>
46 55 <h1
47 56 className="reveal font-display text-4xl font-medium leading-[1.04] tracking-[-0.01em] sm:text-[4.25rem]"
@@ -53,8 +62,8 @@export function LandingHero({ result }: { result: DebateResult }) {
53 62 className="reveal mx-auto mt-5 max-w-xl text-pretty text-lg leading-relaxed text-muted-foreground"
54 63 style={{ animationDelay: '160ms' }}
55 64 >
56 - A real debate, replaying live below. A council answers, critiques each other blind, and revises. A chairman
57 - calls it.
65 + Below is the verdict from a real debate. Press play to watch how the council got there: they answer, critique
66 + each other blind, and revise, before a chairman calls it.
58 67 </p>
59 68 <div
60 69 className="reveal mt-7 flex flex-wrap items-center justify-center gap-3"
@@ -96,18 +105,45 @@export function LandingHero({ result }: { result: DebateResult }) {
96 105 {truncate(view.question || result.config.question, 64)}
97 106 </span>
98 107 </div>
99 - <span className="ml-auto flex shrink-0 items-center gap-1.5 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-500 sm:ml-0">
100 - <span className="h-1.5 w-1.5 animate-pulse-subtle rounded-full bg-emerald-500" />
101 - live
102 - </span>
108 + {isLive ? (
109 + <span className="ml-auto flex shrink-0 items-center gap-1.5 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-500 sm:ml-0">
110 + <span className="h-1.5 w-1.5 animate-pulse-subtle rounded-full bg-emerald-500" />
111 + live
112 + </span>
113 + ) : (
114 + <span className="ml-auto flex shrink-0 items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground sm:ml-0">
115 + verdict
116 + </span>
117 + )}
103 118 </div>
104 119
105 120 <div className="p-4 md:p-6">
106 121 <DebateConsole view={view} showActions={false} />
107 122 </div>
108 123 </div>
109 124
110 - <div className="mt-5 flex justify-center">
125 + <div className="mt-5 flex flex-wrap items-center justify-center gap-3">
126 + {state === 'playing' ? (
127 + <Button variant="secondary" size="sm" onClick={pause}>
128 + <Pause className="h-4 w-4" /> Pause
129 + </Button>
130 + ) : state === 'paused' ? (
131 + <Button size="sm" onClick={play}>
132 + <Play className="h-4 w-4" /> Resume
133 + </Button>
134 + ) : (
135 + <Button size="sm" onClick={watch} className="group">
136 + <Play className="h-4 w-4 transition-transform group-hover:scale-110" />
137 + {hasPlayed ? 'Replay the debate' : 'Watch it unfold'}
138 + </Button>
139 + )}
140 +
141 + {(state === 'playing' || state === 'paused') && (
142 + <Button variant="ghost" size="sm" onClick={skipToEnd}>
143 + Skip to the answer
144 + </Button>
145 + )}
146 +
111 147 <Button asChild variant="outline" size="sm">
112 148 <Link href={`/demo/${result.debateId}`}>Open the full replay</Link>
113 149 </Button>