/** Hill-climbing simulation: state gauges, hypothesis input, intervention picker, event log, debrief. */ import { useState } from 'react'; import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; import FormControlLabel from '@mui/material/FormControlLabel'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { ErrorNote, HBar, LoadingBox, SectionCard } from '../../components/labeleval/shared'; import { useAsync } from '../../components/hillclimb/shared'; import { getSimulationCatalog, startSimulation, stepSimulation } from '../../services/hillclimb'; import type { SimulationState } from '../../types/hillclimb'; function metricColor(name: string, value: number, inverted: string[]): string { const effective = inverted.includes(name) ? 100 - value : value; if (effective >= 60) return '#66bb6a'; if (effective >= 40) return '#f9a825'; return '#ef5350'; } export default function SimulationView() { const catalog = useAsync(getSimulationCatalog); const [sim, setSim] = useState(null); const [hypothesis, setHypothesis] = useState(''); const [interventionId, setInterventionId] = useState('add_monitoring'); const [revert, setRevert] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const start = () => { setBusy(true); setError(null); startSimulation(Math.floor(Math.random() * 100000), 8) .then(setSim) .catch((e: Error) => setError(e.message)) .finally(() => setBusy(false)); }; const step = () => { if (!sim || !hypothesis.trim()) return; setBusy(true); setError(null); stepSimulation(sim.sim_id, hypothesis, interventionId, revert) .then((s) => { setSim(s); setHypothesis(''); setRevert(false); }) .catch((e: Error) => setError(e.message)) .finally(() => setBusy(false)); }; if (catalog.loading && !catalog.data) return ; if (catalog.error && !catalog.data) return ; const inverted = catalog.data?.inverted_metrics ?? []; const interventions = catalog.data?.interventions ?? []; const scenario = catalog.data?.scenarios[0]; const lastTurn = sim?.history[sim.history.length - 1] ?? null; return ( {error ? : null} {!sim ? ( {scenario?.narrative} ) : null} {sim ? ( <> {lastTurn ? ( = 0 ? '+' : ''}${lastTurn.objective_delta.toFixed(1)} → ${ lastTurn.verdict === 'keep_recommended' ? 'KEEP' : 'REJECT' }`} sx={{ bgcolor: lastTurn.objective_delta >= 0 ? '#1b5e20' : '#5d1414', color: lastTurn.objective_delta >= 0 ? '#a5d6a7' : '#ffcdd2', fontWeight: 700, }} /> ) : null} {sim.status === 'complete' ? ( ) : null} {(catalog.data?.metrics ?? []).map((m) => ( ))} {sim.status === 'active' ? ( setHypothesis(e.target.value)} sx={{ mb: 1.5 }} /> setInterventionId(e.target.value)} sx={{ mb: 1 }} > {interventions.map((i) => ( {i.label} ))} {interventions.find((i) => i.id === interventionId)?.description}{' '} {interventions.find((i) => i.id === interventionId)?.second_order} {sim.history.length > 0 ? ( setRevert(e.target.checked)} />} label={ Reject previous intervention first (roll back its immediate effects) } /> ) : null} ) : null} {sim.history.length === 0 ? ( No turns yet. ) : null} {[...sim.history].reverse().map((t) => ( TURN {t.turn} · {t.intervention_id} · Δobj {t.objective_delta >= 0 ? '+' : ''} {t.objective_delta.toFixed(1)} {' '} “{t.hypothesis}” {t.delayed_landed.map((d, i) => ( ⏲ {d} ))} {t.events.map((e, i) => ( ⚠ {e} ))} ))} {sim.debrief ? ( = 0 ? '+' : ''}${sim.debrief.objective_delta})`} sx={{ bgcolor: '#232a31', fontFamily: 'monospace' }} /> {sim.debrief.competency_mappings.map((m) => ( {m.competency_id} — {m.reason} {m.quotes.map((q, i) => ( “{q}” ))} ))} ) : null} ) : null} ); }