Commit
restyle debate form as editorial sections instead of stacked cards
commit
045c28a
1 changed file with +92 and −78
modified src/components/debate/new-debate.tsx +92 −78
| @@ -1,14 +1,13 @@ | ||
| 1 | 1 | 'use client'; |
| 2 | 2 | |
| 3 | -import { AlertTriangle, Gavel, KeyRound, Loader2, Play, RotateCcw, Save, Sparkles, Square } from 'lucide-react'; | |
| 3 | +import { AlertTriangle, Gavel, KeyRound, Loader2, Play, RotateCcw, Save, Square } from 'lucide-react'; | |
| 4 | 4 | import { useEffect, useMemo, useState } from 'react'; |
| 5 | 5 | import { ApiKeyDialog } from '@/components/api-key-dialog'; |
| 6 | 6 | import { DebateConsole } from '@/components/debate/debate-console'; |
| 7 | 7 | import { ModelCombobox } from '@/components/debate/model-combobox'; |
| 8 | 8 | import { ModelPicker } from '@/components/debate/model-picker'; |
| 9 | 9 | import { Badge } from '@/components/ui/badge'; |
| 10 | 10 | import { Button } from '@/components/ui/button'; |
| 11 | -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |
| 12 | 11 | import { Input } from '@/components/ui/input'; |
| 13 | 12 | import { Label } from '@/components/ui/label'; |
| 14 | 13 | import { Slider } from '@/components/ui/slider'; |
| @@ -216,61 +215,50 @@export function NewDebate({ fromDebateId }: { fromDebateId?: string }) { | ||
| 216 | 215 | |
| 217 | 216 | if (phase === 'idle') { |
| 218 | 217 | return ( |
| 219 | - <div className="mx-auto max-w-3xl space-y-5"> | |
| 220 | - <div className="space-y-1"> | |
| 221 | - <h1 className="flex items-center gap-2 text-2xl font-semibold"> | |
| 222 | - <Sparkles className="h-6 w-6 text-primary" /> New debate | |
| 218 | + <div className="mx-auto max-w-3xl"> | |
| 219 | + <div className="space-y-3 pb-10"> | |
| 220 | + <p className="font-mono text-xs uppercase tracking-[0.22em] text-muted-foreground">new debate</p> | |
| 221 | + <h1 className="font-display text-3xl font-medium tracking-tight sm:text-4xl"> | |
| 222 | + Put it to the <em className="text-primary">council</em>. | |
| 223 | 223 | </h1> |
| 224 | - <p className="text-sm text-muted-foreground"> | |
| 225 | - Convene a council of models to critique and revise each other, then a chairman synthesizes the answer. | |
| 224 | + <p className="max-w-xl text-sm leading-relaxed text-muted-foreground"> | |
| 225 | + A council of models answers your question, critiques each other blind, and revises. A chairman writes the | |
| 226 | + verdict. | |
| 226 | 227 | </p> |
| 227 | 228 | </div> |
| 228 | 229 | |
| 229 | - <Card> | |
| 230 | - <CardHeader className="pb-3"> | |
| 231 | - <CardTitle className="text-base">Question</CardTitle> | |
| 232 | - </CardHeader> | |
| 233 | - <CardContent className="space-y-2"> | |
| 234 | - <textarea | |
| 235 | - value={question} | |
| 236 | - onChange={(e) => setQuestion(e.target.value)} | |
| 237 | - onKeyDown={(e) => { | |
| 238 | - if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && canStart) { | |
| 239 | - e.preventDefault(); | |
| 240 | - launch(); | |
| 241 | - } | |
| 242 | - }} | |
| 243 | - placeholder={EXAMPLE} | |
| 244 | - rows={3} | |
| 245 | - aria-label="Debate question" | |
| 246 | - className="flex w-full resize-none rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | |
| 247 | - /> | |
| 248 | - <div className="flex items-center justify-between"> | |
| 249 | - <button className="text-xs text-primary hover:underline" onClick={() => setQuestion(EXAMPLE)}> | |
| 250 | - Use an example question | |
| 251 | - </button> | |
| 252 | - <span className="text-xs text-muted-foreground"> | |
| 253 | - <kbd className="rounded border bg-muted px-1 font-mono text-[10px]">Ctrl</kbd> +{' '} | |
| 254 | - <kbd className="rounded border bg-muted px-1 font-mono text-[10px]">Enter</kbd> to start | |
| 255 | - </span> | |
| 256 | - </div> | |
| 257 | - </CardContent> | |
| 258 | - </Card> | |
| 259 | - | |
| 260 | - <Card> | |
| 261 | - <CardHeader className="pb-3"> | |
| 262 | - <CardTitle className="text-base">Council</CardTitle> | |
| 263 | - </CardHeader> | |
| 264 | - <CardContent> | |
| 265 | - <ModelPicker council={council} onChange={setCouncil} /> | |
| 266 | - </CardContent> | |
| 267 | - </Card> | |
| 268 | - | |
| 269 | - <Card> | |
| 270 | - <CardHeader className="pb-3"> | |
| 271 | - <CardTitle className="text-base">Configuration</CardTitle> | |
| 272 | - </CardHeader> | |
| 273 | - <CardContent className="space-y-5"> | |
| 230 | + <FormSection number="01" label="Question"> | |
| 231 | + <textarea | |
| 232 | + value={question} | |
| 233 | + onChange={(e) => setQuestion(e.target.value)} | |
| 234 | + onKeyDown={(e) => { | |
| 235 | + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && canStart) { | |
| 236 | + e.preventDefault(); | |
| 237 | + launch(); | |
| 238 | + } | |
| 239 | + }} | |
| 240 | + placeholder={EXAMPLE} | |
| 241 | + rows={3} | |
| 242 | + aria-label="Debate question" | |
| 243 | + className="flex w-full resize-none rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | |
| 244 | + /> | |
| 245 | + <div className="mt-2 flex items-center justify-between"> | |
| 246 | + <button className="text-xs text-primary hover:underline" onClick={() => setQuestion(EXAMPLE)}> | |
| 247 | + Use an example question | |
| 248 | + </button> | |
| 249 | + <span className="text-xs text-muted-foreground"> | |
| 250 | + <kbd className="rounded border bg-muted px-1 font-mono text-[10px]">Ctrl</kbd> +{' '} | |
| 251 | + <kbd className="rounded border bg-muted px-1 font-mono text-[10px]">Enter</kbd> to start | |
| 252 | + </span> | |
| 253 | + </div> | |
| 254 | + </FormSection> | |
| 255 | + | |
| 256 | + <FormSection number="02" label="Council"> | |
| 257 | + <ModelPicker council={council} onChange={setCouncil} /> | |
| 258 | + </FormSection> | |
| 259 | + | |
| 260 | + <FormSection number="03" label="Rules"> | |
| 261 | + <div className="space-y-5"> | |
| 274 | 262 | <div className="grid gap-4 sm:grid-cols-2"> |
| 275 | 263 | <div className="space-y-1.5"> |
| 276 | 264 | <Label>Chairman</Label> |
| @@ -370,34 +358,36 @@export function NewDebate({ fromDebateId }: { fromDebateId?: string }) { | ||
| 370 | 358 | <Save className="h-3.5 w-3.5" /> Save |
| 371 | 359 | </Button> |
| 372 | 360 | </div> |
| 373 | - </CardContent> | |
| 374 | - </Card> | |
| 375 | - | |
| 376 | - {needsKey && ( | |
| 377 | - <div className="flex flex-wrap items-center gap-2 rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-sm"> | |
| 378 | - <KeyRound className="h-4 w-4 text-amber-500" /> | |
| 379 | - <span>Running a real debate needs an OpenRouter key. Add one, then press Start again.</span> | |
| 380 | - <Button variant="outline" size="sm" className="ml-auto" onClick={() => setKeyDialogOpen(true)}> | |
| 381 | - Add key | |
| 382 | - </Button> | |
| 383 | 361 | </div> |
| 384 | - )} | |
| 362 | + </FormSection> | |
| 363 | + | |
| 364 | + <div className="space-y-4 border-t pt-6"> | |
| 365 | + {needsKey && ( | |
| 366 | + <div className="flex flex-wrap items-center gap-2 rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-sm"> | |
| 367 | + <KeyRound className="h-4 w-4 text-amber-500" /> | |
| 368 | + <span>Running a real debate needs an OpenRouter key. Add one, then press Start again.</span> | |
| 369 | + <Button variant="outline" size="sm" className="ml-auto" onClick={() => setKeyDialogOpen(true)}> | |
| 370 | + Add key | |
| 371 | + </Button> | |
| 372 | + </div> | |
| 373 | + )} | |
| 374 | + | |
| 375 | + {errorMsg && ( | |
| 376 | + <div className="flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive"> | |
| 377 | + <AlertTriangle className="h-4 w-4" /> {errorMsg} | |
| 378 | + </div> | |
| 379 | + )} | |
| 385 | 380 | |
| 386 | - {errorMsg && ( | |
| 387 | - <div className="flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive"> | |
| 388 | - <AlertTriangle className="h-4 w-4" /> {errorMsg} | |
| 381 | + <div className="sticky bottom-4 flex items-center justify-end gap-3 pb-2"> | |
| 382 | + <span className="rounded-md bg-background/80 px-2.5 py-1.5 text-xs text-muted-foreground shadow-sm backdrop-blur"> | |
| 383 | + {canStart | |
| 384 | + ? `~${formatUsd(estimate)} est. · up to ${maxRounds} ${maxRounds === 1 ? 'round' : 'rounds'}` | |
| 385 | + : startBlocker} | |
| 386 | + </span> | |
| 387 | + <Button size="lg" disabled={!canStart} onClick={launch} className="shadow-lg"> | |
| 388 | + <Play className="h-4 w-4" /> Start debate | |
| 389 | + </Button> | |
| 389 | 390 | </div> |
| 390 | - )} | |
| 391 | - | |
| 392 | - <div className="sticky bottom-4 flex items-center justify-end gap-3"> | |
| 393 | - <span className="rounded-md bg-background/80 px-2.5 py-1.5 text-xs text-muted-foreground shadow-sm backdrop-blur"> | |
| 394 | - {canStart | |
| 395 | - ? `~${formatUsd(estimate)} est. · up to ${maxRounds} ${maxRounds === 1 ? 'round' : 'rounds'}` | |
| 396 | - : startBlocker} | |
| 397 | - </span> | |
| 398 | - <Button size="lg" disabled={!canStart} onClick={launch} className="shadow-lg"> | |
| 399 | - <Play className="h-4 w-4" /> Start debate | |
| 400 | - </Button> | |
| 401 | 391 | </div> |
| 402 | 392 | |
| 403 | 393 | <ApiKeyDialog |
| @@ -462,6 +452,30 @@export function NewDebate({ fromDebateId }: { fromDebateId?: string }) { | ||
| 462 | 452 | ); |
| 463 | 453 | } |
| 464 | 454 | |
| 455 | +/** | |
| 456 | + * Editorial form section: numbered mono label on a rail, content beside it, | |
| 457 | + * hairline rule above. Replaces the stacked-card look. | |
| 458 | + */ | |
| 459 | +function FormSection({ | |
| 460 | + number, | |
| 461 | + label, | |
| 462 | + children, | |
| 463 | +}: { | |
| 464 | + number: string; | |
| 465 | + label: string; | |
| 466 | + children: React.ReactNode; | |
| 467 | +}) { | |
| 468 | + return ( | |
| 469 | + <section className="grid gap-4 border-t py-8 sm:grid-cols-[9rem_1fr] sm:gap-8"> | |
| 470 | + <div className="flex items-baseline gap-2 sm:block"> | |
| 471 | + <span className="font-mono text-xs text-muted-foreground/60">{number}</span> | |
| 472 | + <h2 className="font-mono text-xs uppercase tracking-[0.22em] text-muted-foreground sm:mt-1.5">{label}</h2> | |
| 473 | + </div> | |
| 474 | + <div className="min-w-0">{children}</div> | |
| 475 | + </section> | |
| 476 | + ); | |
| 477 | +} | |
| 478 | + | |
| 465 | 479 | function SliderRow({ |
| 466 | 480 | label, |
| 467 | 481 | value, |