profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

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

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,