Commit
comments
commit
82cf62a
2 changed files with +85 and −29
Jump to a changed file
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'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> |