Spaces:
Sleeping
Sleeping
File size: 6,987 Bytes
80d8c84 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | import { useEffect, useState } from 'react';
import { Play, RotateCcw, Dices } from 'lucide-react';
import type { BackendRuntimeStatus, Difficulty, ScenarioTemplate, ResetParams } from '@/types';
import { cn } from '@/lib/utils';
import { sfx } from '@/lib/audio';
import { healthCheck } from '@/lib/api';
const TEMPLATES: { value: ScenarioTemplate; label: string }[] = [
{ value: 'math_reasoning', label: 'Math Reasoning' },
{ value: 'ml_benchmark', label: 'ML Benchmark' },
{ value: 'finance_trading', label: 'Finance Trading' },
];
const DIFFICULTIES: { value: Difficulty; label: string }[] = [
{ value: 'easy', label: 'Easy' },
{ value: 'medium', label: 'Medium' },
{ value: 'hard', label: 'Hard' },
];
interface ControlsProps {
onStart: (params: ResetParams) => void;
onStep?: () => void;
disabled?: boolean;
episodeActive?: boolean;
className?: string;
initialSeed?: number;
initialTemplate?: ScenarioTemplate;
initialDifficulty?: Difficulty;
runtimeStatus?: BackendRuntimeStatus | null;
}
export default function Controls({
onStart,
onStep,
disabled,
episodeActive,
className,
initialSeed,
initialTemplate,
initialDifficulty,
runtimeStatus,
}: ControlsProps) {
const [seed, setSeed] = useState<string>(initialSeed?.toString() ?? '42');
const [template, setTemplate] = useState<ScenarioTemplate>(initialTemplate ?? 'ml_benchmark');
const [difficulty, setDifficulty] = useState<Difficulty>(initialDifficulty ?? 'medium');
const [backendStatus, setBackendStatus] = useState<'checking' | 'ok' | 'error'>('checking');
const [backendMessage, setBackendMessage] = useState<string>('Checking backend connection...');
useEffect(() => {
if (initialSeed !== undefined) {
setSeed(initialSeed.toString());
}
}, [initialSeed]);
useEffect(() => {
if (initialTemplate) {
setTemplate(initialTemplate);
}
}, [initialTemplate]);
useEffect(() => {
if (initialDifficulty) {
setDifficulty(initialDifficulty);
}
}, [initialDifficulty]);
useEffect(() => {
let cancelled = false;
async function checkBackend() {
setBackendStatus('checking');
setBackendMessage('Checking backend connection...');
try {
await healthCheck();
if (!cancelled) {
setBackendStatus('ok');
setBackendMessage('Backend connected. The live environment is ready.');
}
} catch (error) {
if (!cancelled) {
const message = error instanceof Error ? error.message : 'Backend unavailable.';
setBackendStatus('error');
setBackendMessage(message);
}
}
}
void checkBackend();
return () => {
cancelled = true;
};
}, []);
function randomSeed() { sfx.click(); setSeed(Math.floor(Math.random() * 10000).toString()); }
function handleStart() { sfx.click(); onStart({ seed: seed ? parseInt(seed, 10) : undefined, template, difficulty }); }
return (
<div className={cn('rounded-lg border border-border bg-card p-4', className)}>
<div className="mb-3">
<h2 className="text-sm font-semibold">Replication Setup</h2>
<p className="mt-1 text-xs text-muted-foreground">
Choose the seeded paper-derived benchmark that becomes this negotiation environment.
</p>
<p
className={cn(
'mt-2 text-xs',
backendStatus === 'ok' && 'text-emerald-600',
backendStatus === 'checking' && 'text-muted-foreground',
backendStatus === 'error' && 'text-destructive',
)}
>
{backendMessage}
</p>
{runtimeStatus && (
<p className="mt-1 text-xs text-muted-foreground">
Scientist runtime: <span className="font-medium text-foreground">{runtimeStatus.scientist_runtime}</span>
{' '}({runtimeStatus.scientist_model})
</p>
)}
</div>
<div className="space-y-3">
<div>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Scenario Family</label>
<select value={template} onChange={(e) => setTemplate(e.target.value as ScenarioTemplate)} disabled={disabled || episodeActive}
className="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50">
{TEMPLATES.map((t) => <option key={t.value} value={t.value}>{t.label}</option>)}
</select>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Difficulty</label>
<div className="flex gap-1">
{DIFFICULTIES.map((d) => (
<button key={d.value} onClick={() => { sfx.click(); setDifficulty(d.value); }} disabled={disabled || episodeActive}
className={cn('flex-1 rounded-md border px-2 py-1.5 text-xs font-medium transition-colors',
difficulty === d.value ? 'border-primary bg-primary/10 text-primary' : 'border-border text-muted-foreground hover:bg-muted disabled:opacity-50')}>
{d.label}
</button>
))}
</div>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Seeded Benchmark</label>
<div className="flex gap-1.5">
<input type="number" value={seed} onChange={(e) => setSeed(e.target.value)} disabled={disabled || episodeActive} placeholder="Random"
className="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50" />
<button onClick={randomSeed} disabled={disabled || episodeActive}
className="rounded-md border border-border p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-50" title="Random seed">
<Dices className="h-4 w-4" />
</button>
</div>
</div>
<div className="flex gap-2 pt-1">
<button onClick={handleStart} disabled={disabled}
className={cn('flex flex-1 items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50',
episodeActive ? 'border border-border text-muted-foreground hover:bg-muted' : 'bg-primary text-primary-foreground hover:bg-primary/90')}>
{episodeActive ? (<><RotateCcw className="h-4 w-4" />Reset Episode</>) : (<><Play className="h-4 w-4" />Start Replication</>)}
</button>
{episodeActive && onStep && (
<button onClick={onStep} disabled={disabled}
className="flex items-center gap-1.5 rounded-md bg-scientist px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-scientist/90 disabled:opacity-50">
<Play className="h-4 w-4" />Advance Round
</button>
)}
</div>
</div>
</div>
);
}
|