import React, { useState, useEffect, useCallback } from 'react'; import { RotateCcw } from 'lucide-react'; import NetworkDiagram from './NetworkDiagram'; import SensorInputForm from './SensorInputForm'; import CommandBuilder from './CommandBuilder'; import ResultsPanel from './ResultsPanel'; import StatsTable from './StatsTable'; import HistoryTimeline from './HistoryTimeline'; import { getNetwork, getCommands, getStats, getHistory, submitSensorInput, runSimulation, createCommand, deleteCommand, resetSimulation, } from '../../utils/api'; import '../../styles/decidron.css'; const DEFAULT_VALUE = '97'; export default function DecidronSimulator({ settings }) { const [network, setNetwork] = useState(null); const [commands, setCommands] = useState([]); const [stats, setStats] = useState(null); const [history, setHistory] = useState([]); const [results, setResults] = useState([]); const [pending, setPending] = useState([]); const [selected, setSelected] = useState(null); // null = live network const [running, setRunning] = useState(false); const [error, setError] = useState(null); // Live sensor-input form values, pre-populated so the user can press // "Run Simulation" immediately and see the network react. const [channel, setChannel] = useState(''); const [value, setValue] = useState(DEFAULT_VALUE); const refreshAll = useCallback(async () => { try { const [net, cmds, st, hist] = await Promise.all([ getNetwork(), getCommands(), getStats(), getHistory(), ]); setNetwork(net); setCommands(cmds); setStats(st); setHistory(hist.snapshots); } catch (err) { setError(err.message); } }, []); useEffect(() => { refreshAll(); }, [refreshAll]); // Default the channel to the first sensor once the network loads. useEffect(() => { if (network && !channel) { const firstSensor = network.nodes.find((n) => n.type === 'sensor'); if (firstSensor) setChannel(firstSensor.id); } }, [network, channel]); const handleQueue = async () => { if (!channel || value === '') return; setError(null); try { await submitSensorInput(channel, value); setPending((prev) => [...prev, { channel, value }]); } catch (err) { setError(err.message); } }; const handleRun = async () => { // Run queued inputs if any; otherwise run the current form value // inline so a single click works on first load. const inputs = pending.length > 0 ? null : [{ channel, value }]; if (pending.length === 0 && (!channel || value === '')) return; setRunning(true); setError(null); try { const resp = await runSimulation(inputs); setResults(resp.results); setNetwork(resp.network); setSelected(null); setPending([]); const [st, hist] = await Promise.all([getStats(), getHistory()]); setStats(st); setHistory(hist.snapshots); } catch (err) { setError(err.message); } finally { setRunning(false); } }; const handleCreate = async (cmd, replaceName = null) => { await createCommand(cmd); if (replaceName && replaceName !== cmd.name) { await deleteCommand(replaceName); } setCommands(await getCommands()); }; const handleDelete = async (name) => { setError(null); try { await deleteCommand(name); setCommands(await getCommands()); } catch (err) { setError(err.message); } }; const handleReset = async () => { setError(null); try { await resetSimulation(); setResults([]); setPending([]); setSelected(null); await refreshAll(); } catch (err) { setError(err.message); } }; if (!network) { return (
{error ?
{error}
:

Loading…

}
); } const sensors = network.nodes.filter((n) => n.type === 'sensor'); const shownDiagram = selected ? selected.diagram : network.diagram; const shownVersion = selected ? selected.version : network.version; const { panels, layoutMode } = settings; return (
{error &&
{error}
}

Decidron Network (v{shownVersion}{selected ? ' — historical' : ' — live'})

Sensors (blue) feed coupled Decidrons (purple) that drive actuators (green). Commands can modify this topology.

setSelected( s.version === network.version ? null : s )} />
{panels.sensorInput && ( )} {panels.commands && ( )} {panels.results && } {panels.stats && }
); }