/** * RosterManager — admin panel for editing the agent roster. * * Add an agent from a natural-language description (backend generates the * persona), rename an existing agent, or retire/archive one. Only allowed * while the simulation is stopped; every edit writes a successor * checkpoint. * * Architecture: gated by auth; calls /api/roster/* endpoints. * * Design: edits are checkpointed so the timeline stays a faithful record * of which roster existed at each point. */ import { useEffect, useState } from "react"; import { useAuth } from "../hooks/useAuth"; import { apiUrl } from "../utils/api"; const panelStyle = { position: "fixed", right: 16, bottom: 64, width: 330, zIndex: 1100, border: "1px solid rgba(212,160,74,.32)", borderRadius: 6, background: "rgba(8,8,8,.96)", boxShadow: "0 14px 45px rgba(0,0,0,.62)", color: "#d0d0da", fontFamily: "'Outfit', sans-serif", padding: 13, }; const controlStyle = { width: "100%", marginTop: 7, padding: "7px 8px", borderRadius: 3, border: "1px solid rgba(212,160,74,.28)", background: "#101010", color: "#ddd", fontFamily: "inherit", fontSize: 12 }; export default function RosterManager({ open, onClose, simulationRunning, onError, isAuthenticated }) { const { token } = useAuth(); const [roster, setRoster] = useState([]); const [description, setDescription] = useState(""); const [selected, setSelected] = useState(""); const [newName, setNewName] = useState(""); const [busy, setBusy] = useState(false); const [notice, setNotice] = useState(""); const refresh = async () => { try { const response = await fetch(apiUrl("/api/roster"), { headers: token ? { Authorization: `Bearer ${token}` } : {}, }); const data = await response.json(); setRoster(data.agents || []); setSelected((previous) => previous || data.agents?.[0]?.id || ""); } catch (error) { onError(error.message); } }; useEffect(() => { if (open) refresh(); }, [open]); if (!open || !isAuthenticated) return null; const submit = async (path, payload, success) => { if (simulationRunning) { onError("Stop the simulation before editing the roster."); return; } setBusy(true); setNotice(""); try { const response = await fetch(apiUrl(path), { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify(payload) }); const data = await response.json(); if (!response.ok) throw new Error(data.error || "Roster change failed."); setNotice(`${success} Checkpoint ${data.checkpoint_tick} is now the latest timeline state.`); setDescription(""); setNewName(""); await refresh(); } catch (error) { onError(error.message); } finally { setBusy(false); } }; return