import { useEffect, useRef } from 'react' import { simulate, streamSimulation } from '../api/client' import { useSimulation } from '../hooks/useSimulation' import { DisasterMap } from './DisasterMap' import { ResourceBar } from './ResourceBar' import { EventFeed } from './EventFeed' import { AgentReasoningPanel } from './AgentReasoningPanel' import { ScorePanel } from './ScorePanel' import { ScoreGraph } from './ScoreGraph' import { ProbabilityMatrix } from './ProbabilityMatrix' import type { TaskInfo } from '../types' import { useLiveSimStore } from '../store/liveSimStore' import { CopilotQaPanel } from './CopilotQaPanel' interface Props { tasks: TaskInfo[] selectedTask: string setSelectedTask: (t: string) => void selectedAgent: 'greedy' | 'random' | 'ai_4stage' setSelectedAgent: (a: 'greedy' | 'random' | 'ai_4stage') => void } const AGENT_LABELS: Record = { greedy: 'Greedy Heuristic', random: 'Random Agent', ai_4stage: '4-Stage AI (Groq)', } const DIFFICULTY_COLOR: Record = { easy: 'text-green-400', medium: 'text-orange-400', hard: 'text-red-400', } export function SimulationTab({ tasks, selectedTask, setSelectedTask, selectedAgent, setSelectedAgent, }: Props) { const sim = useSimulation() const liveStatus = useLiveSimStore((s) => s.status) const liveMeta = useLiveSimStore((s) => s.meta) const liveSteps = useLiveSimStore((s) => s.steps) const liveStageTimeline = useLiveSimStore((s) => s.stageTimeline) const liveActiveStage = useLiveSimStore((s) => s.activeStage) const liveCurrentStepIndex = useLiveSimStore((s) => s.currentStepIndex) const liveDone = useLiveSimStore((s) => s.done) const liveError = useLiveSimStore((s) => s.error) const setLiveConnecting = useLiveSimStore((s) => s.setConnecting) const setLiveMeta = useLiveSimStore((s) => s.setMeta) const pushLiveStage = useLiveSimStore((s) => s.pushStage) const pushLiveStep = useLiveSimStore((s) => s.pushStep) const setLiveDone = useLiveSimStore((s) => s.setDone) const setLiveError = useLiveSimStore((s) => s.setError) const setLiveCurrentStepIndex = useLiveSimStore((s) => s.setCurrentStepIndex) const resetLive = useLiveSimStore((s) => s.reset) const streamCleanupRef = useRef<(() => void) | null>(null) const task = tasks.find(t => t.task_id === selectedTask) const run = () => { if (streamCleanupRef.current) { streamCleanupRef.current() streamCleanupRef.current = null } setLiveConnecting() let sawStep = false const fallbackToReplay = () => { if (streamCleanupRef.current) { streamCleanupRef.current() streamCleanupRef.current = null } resetLive() void sim.load(() => simulate(selectedTask, selectedAgent)) } try { streamCleanupRef.current = streamSimulation(selectedTask, selectedAgent, { onMeta: (meta) => setLiveMeta(meta), onStage: (event) => pushLiveStage(event), onStep: (step) => { sawStep = true pushLiveStep(step) }, onDone: (done) => { setLiveDone(done) streamCleanupRef.current = null }, onError: (message) => { streamCleanupRef.current = null if (!sawStep) { fallbackToReplay() return } setLiveError(message) }, }) } catch { fallbackToReplay() } } const liveResult = (liveMeta && liveSteps.length > 0) ? { task_id: liveMeta.task_id, agent: liveMeta.agent, final_score: liveDone?.final_score ?? null, cumulative_reward: liveDone?.cumulative_reward ?? 0, steps_taken: liveDone?.steps_taken ?? liveSteps.length, steps: liveSteps, } : null const usingLive = liveStatus !== 'idle' const liveCurrentStep = liveResult ? liveResult.steps[liveCurrentStepIndex] ?? null : null const obs = (liveCurrentStep?.observation ?? sim.currentStep?.observation) ?? null const action = (liveCurrentStep?.action ?? sim.currentStep?.action) ?? null const reasoning = (liveCurrentStep?.reasoning ?? sim.currentStep?.reasoning) ?? null const currentResult = (usingLive ? liveResult : sim.result) const currentStepIndex = usingLive ? liveCurrentStepIndex : sim.currentStepIndex const playbackLength = currentResult?.steps.length ?? 0 const isLoading = sim.isLoading || liveStatus === 'connecting' const mergedError = liveError ?? sim.error const falseSOSZones = task?.false_sos_zones ?? [] useEffect(() => { return () => { if (streamCleanupRef.current) { streamCleanupRef.current() streamCleanupRef.current = null } resetLive() } }, [resetLive]) const currentStageEvents = reasoning ? liveStageTimeline[liveCurrentStep?.step ?? -1] ?? [] : [] return (
{/* Controls */}
{/* Task selector */}
{/* Agent selector */}
{/* Speed */}
{(['slow', 'normal', 'fast'] as const).map(s => ( ))}
{/* Action buttons */}
{sim.result && !usingLive && ( <> )} {usingLive && playbackLength > 0 && ( <> )}
{/* Task info */} {task && (
{task.difficulty.toUpperCase()} {task.zones} zones {task.max_steps} steps {task.false_sos_zones.length > 0 && ( ⚠️ {task.false_sos_zones.length} false SOS zones )}
)}
{/* Fallback note (e.g. AI key missing → ran greedy instead) */} {liveMeta?.note && (
{liveMeta.note}
)} {/* Error */} {mergedError && (
{mergedError}
)} {/* Loading */} {isLoading && (

Running {AGENT_LABELS[selectedAgent]}…

{selectedAgent === 'ai_4stage' ? 'This takes ~30–60s (PyTorch → Triage → Planner → Action per step)' : 'Computing greedy heuristic…'}

)} {/* Empty State / Welcome Screen */} {!currentResult && !isLoading && !mergedError && (
🌐

Waiting for Initialization

Select an Agent and Task above, then click Run to begin simulating the crisis management network. Dynamic rendering and PyTorch analysis will load upon connection.

)} {/* Main content */} {currentResult && obs && (
{/* Left: Map + resources */}

Disaster Map — Step {(liveCurrentStep?.step ?? sim.currentStep?.step) ?? 0} / {currentResult.steps_taken}

{obs.weather === 'clear' ? '☀️ Clear' : obs.weather === 'storm' ? '⛈ Storm' : '🌊 Flood'}
{/* Right: Score + reasoning + feed */}
{reasoning && reasoning.pytorch_scores && ( )} {reasoning && ( )} {obs && reasoning && ( )}
)} {/* Event feed (full width below) */} {currentResult && currentResult.steps.length > 0 && ( )} {/* Step scrubber */} {currentResult && (
{ const value = parseInt(e.target.value) if (usingLive) { setLiveCurrentStepIndex(value) } else { sim.seekTo(value) } }} className="w-full accent-red-500" />
)}
) }