profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM
model-panel.tsx 3,523 bytes
1 'use client';
2
3 import { AlertTriangle, ChevronDown, Clock, Coins } from 'lucide-react';
4 import { Badge } from '@/components/ui/badge';
5 import {
6 Collapsible,
7 CollapsibleContent,
8 CollapsibleTrigger,
9 } from '@/components/ui/collapsible';
10 import { RichText } from '@/components/debate/rich-text';
11 import type { AnswerRecord, StageType } from '@/core/types';
12 import { participantColor, participantTag } from '@/lib/model-visuals';
13 import { cn, formatLatency, formatUsd } from '@/lib/utils';
14
15 interface ModelPanelProps {
16 index: number;
17 displayName: string;
18 model: string;
19 answer?: AnswerRecord;
20 streamingText?: string;
21 workingStage?: StageType;
22 dropped?: boolean;
23 }
24
25 const STAGE_LABEL: Record<StageType, string> = {
26 answer: 'Answering',
27 critique: 'Critiquing peers',
28 revision: 'Revising',
29 convergence: 'Assessing',
30 synthesis: 'Synthesizing',
31 provenance: 'Auditing claims',
32 };
33
34 export function ModelPanel({
35 index,
36 displayName,
37 model,
38 answer,
39 streamingText,
40 workingStage,
41 dropped,
42 }: ModelPanelProps) {
43 const color = participantColor(index);
44 const text = answer?.content ?? streamingText ?? '';
45 const isStreaming = workingStage === 'answer' && !answer;
46
47 return (
48 <Collapsible defaultOpen className="flex flex-col rounded-xl border bg-card">
49 <CollapsibleTrigger className="group flex items-center gap-2 p-3 text-left">
50 <span
51 className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-xs font-bold text-white"
52 style={{ backgroundColor: color }}
53 >
54 {participantTag(index)}
55 </span>
56 <div className="min-w-0 flex-1">
57 <div className="truncate text-sm font-semibold">{displayName}</div>
58 <div className="truncate font-mono text-[11px] text-muted-foreground">{model}</div>
59 </div>
60 {dropped ? (
61 <Badge variant="destructive" className="gap-1">
62 <AlertTriangle className="h-3 w-3" /> dropped
63 </Badge>
64 ) : workingStage ? (
65 <Badge variant="warning" className="animate-pulse-subtle">
66 {STAGE_LABEL[workingStage]}
67 </Badge>
68 ) : answer ? (
69 <Badge variant="secondary">round {answer.round}</Badge>
70 ) : null}
71 <ChevronDown className="h-4 w-4 text-muted-foreground transition-transform group-data-[state=closed]:-rotate-90" />
72 </CollapsibleTrigger>
73
74 <CollapsibleContent>
75 <div className="scrollbar-thin max-h-[420px] overflow-y-auto border-t px-4 py-3">
76 {text ? (
77 <RichText text={text} className={cn(isStreaming && 'streaming-caret')} />
78 ) : dropped ? (
79 <p className="text-sm text-muted-foreground">This model dropped out of the debate.</p>
80 ) : (
81 <p className="text-sm text-muted-foreground">
82 {workingStage ? `${STAGE_LABEL[workingStage]}...` : 'Waiting...'}
83 </p>
84 )}
85 </div>
86 {answer && (
87 <div className="flex items-center gap-4 border-t px-4 py-2 text-[11px] text-muted-foreground">
88 <span className="flex items-center gap-1">
89 <Clock className="h-3 w-3" /> {formatLatency(answer.latencyMs)}
90 </span>
91 <span className="flex items-center gap-1">
92 <Coins className="h-3 w-3" /> {formatUsd(answer.usage.costUsd)}
93 </span>
94 <span>{answer.usage.totalTokens} tok</span>
95 </div>
96 )}
97 </CollapsibleContent>
98 </Collapsible>
99 );
100 }
101