@alexkroman1/aai-cli 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_agent-D39qatZJ.mjs → _agent-D8zBb3M5.mjs} +2 -3
- package/dist/_agent.d.ts +39 -0
- package/dist/{_api-client-a6cPMebU.mjs → _api-client-MenP4-O7.mjs} +3 -1
- package/dist/_api-client.d.ts +37 -0
- package/dist/{_bundler-DdmuZzeR.mjs → _bundler-DFS4xxqE.mjs} +3 -13
- package/dist/_bundler.d.ts +46 -0
- package/dist/{_config-Czjhvaax.mjs → _config-D3F9km8X.mjs} +3 -3
- package/dist/_config.d.ts +57 -0
- package/dist/_default-html.d.ts +12 -0
- package/dist/_deploy.d.ts +28 -0
- package/dist/{_dev-server-BCbEUhmv.mjs → _dev-server-BBUWo0sb.mjs} +12 -14
- package/dist/_dev-server.d.ts +74 -0
- package/dist/{_init-H-lw7czL.mjs → _init-DU-sXH6S.mjs} +47 -65
- package/dist/_init.d.ts +7 -0
- package/dist/_mock-api.d.ts +22 -0
- package/dist/_mock-registry.d.ts +17 -0
- package/dist/_output.d.ts +50 -0
- package/dist/{_server-common-B75oco06.mjs → _server-common-CnaP_Urf.mjs} +2 -1
- package/dist/_server-common.d.ts +17 -0
- package/dist/_slug-api-19R5kZ0U.mjs +27 -0
- package/dist/_slug-api.d.ts +15 -0
- package/dist/_templates.d.ts +21 -0
- package/dist/{_typecheck-gate-B1GcKZX-.mjs → _typecheck-gate-9IHWDnl1.mjs} +2 -2
- package/dist/_typecheck-gate.d.ts +7 -0
- package/dist/{_ui-DXZ9prrM.mjs → _ui-8kOEB-JH.mjs} +6 -3
- package/dist/_ui.d.ts +22 -0
- package/dist/{_utils-C502jKo8.mjs → _utils-Ch0J4s6a.mjs} +17 -1
- package/dist/_utils.d.ts +48 -0
- package/dist/_vite-env.d.ts +8 -0
- package/dist/build-Xq1xxoSg.mjs +38 -0
- package/dist/build.d.ts +16 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +35 -52
- package/dist/{client-bundler-DO3GgO6p.mjs → client-bundler-BWDkUeEP.mjs} +5 -6
- package/dist/client-bundler.d.ts +21 -0
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-K24hW1ab.mjs → delete-kSXFLxek.mjs} +4 -4
- package/dist/delete.d.ts +18 -0
- package/dist/{deploy-D-dnt9L8.mjs → deploy-Ci0X1gp2.mjs} +8 -8
- package/dist/deploy.d.ts +15 -0
- package/dist/{dev-C8o-p13B.mjs → dev-g7sGt8qX.mjs} +3 -3
- package/dist/dev.d.ts +13 -0
- package/dist/{init-B53rR3yD.mjs → init-BjeK8crW.mjs} +10 -17
- package/dist/init.d.ts +24 -0
- package/dist/scaffold/.env.example +11 -0
- package/dist/scaffold/CLAUDE.md +919 -0
- package/dist/scaffold/global.d.ts +1 -0
- package/dist/scaffold/package.json +32 -0
- package/dist/scaffold/pnpm-workspace.yaml +13 -0
- package/dist/scaffold/tsconfig.json +19 -0
- package/dist/scaffold/vite.config.ts +14 -0
- package/dist/scaffold/vitest.config.ts +24 -0
- package/dist/{secret-C4unwCEw.mjs → secret-DOva9OGk.mjs} +21 -20
- package/dist/secret.d.ts +25 -0
- package/dist/{storage-tZM1J8qj.mjs → storage-vM6HjDZZ.mjs} +6 -13
- package/dist/storage.d.ts +22 -0
- package/dist/templates/code-interpreter/agent.ts +11 -0
- package/dist/templates/code-interpreter/system-prompt.md +18 -0
- package/dist/templates/dispatch-center/agent.test.ts +153 -0
- package/dist/templates/dispatch-center/agent.ts +50 -0
- package/dist/templates/dispatch-center/client.tsx +378 -0
- package/dist/templates/dispatch-center/shared.ts +631 -0
- package/dist/templates/dispatch-center/system-prompt.md +39 -0
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +27 -0
- package/dist/templates/dispatch-center/tools/incident_create.ts +79 -0
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +88 -0
- package/dist/templates/dispatch-center/tools/incident_get.ts +36 -0
- package/dist/templates/dispatch-center/tools/incident_triage.ts +78 -0
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +68 -0
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +52 -0
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +27 -0
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +149 -0
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +97 -0
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +36 -0
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +62 -0
- package/dist/templates/embedded-assets/agent.ts +55 -0
- package/dist/templates/embedded-assets/knowledge.json +20 -0
- package/dist/templates/health-assistant/agent.ts +174 -0
- package/dist/templates/health-assistant/system-prompt.md +17 -0
- package/dist/templates/infocom-adventure/agent.ts +119 -0
- package/dist/templates/infocom-adventure/client.tsx +279 -0
- package/dist/templates/infocom-adventure/shared.ts +38 -0
- package/dist/templates/infocom-adventure/system-prompt.md +43 -0
- package/dist/templates/math-buddy/agent.ts +16 -0
- package/dist/templates/math-buddy/system-prompt.md +12 -0
- package/dist/templates/night-owl/agent.ts +76 -0
- package/dist/templates/night-owl/client.tsx +130 -0
- package/dist/templates/night-owl/shared.ts +5 -0
- package/dist/templates/personal-finance/agent.ts +11 -0
- package/dist/templates/personal-finance/system-prompt.md +17 -0
- package/dist/templates/pipeline-simple/agent.test.ts +32 -0
- package/dist/templates/pipeline-simple/agent.ts +13 -0
- package/dist/templates/pizza-ordering/agent.test.ts +235 -0
- package/dist/templates/pizza-ordering/agent.ts +174 -0
- package/dist/templates/pizza-ordering/client.tsx +120 -0
- package/dist/templates/pizza-ordering/shared.ts +142 -0
- package/dist/templates/pizza-ordering/system-prompt.md +17 -0
- package/dist/templates/simple/agent.ts +5 -0
- package/dist/templates/solo-rpg/agent.test.ts +477 -0
- package/dist/templates/solo-rpg/agent.ts +37 -0
- package/dist/templates/solo-rpg/client.tsx +809 -0
- package/dist/templates/solo-rpg/shared.ts +747 -0
- package/dist/templates/solo-rpg/system-prompt.md +115 -0
- package/dist/templates/solo-rpg/tools/action_roll.ts +98 -0
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +69 -0
- package/dist/templates/solo-rpg/tools/check_state.ts +11 -0
- package/dist/templates/solo-rpg/tools/load_game.ts +25 -0
- package/dist/templates/solo-rpg/tools/oracle.ts +248 -0
- package/dist/templates/solo-rpg/tools/save_game.ts +20 -0
- package/dist/templates/solo-rpg/tools/setup_character.ts +208 -0
- package/dist/templates/solo-rpg/tools/update_state.ts +170 -0
- package/dist/templates/web-researcher/agent.ts +11 -0
- package/dist/{test-CgWhus_Z.mjs → test-3Gq4pqH_.mjs} +5 -7
- package/dist/test.d.ts +39 -0
- package/dist/tsdown.config.d.ts +2 -0
- package/dist/typecheck.d.ts +26 -0
- package/dist/typecheck.mjs +6 -10
- package/dist/worker-bundler.d.ts +29 -0
- package/package.json +9 -6
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
+
import type { ChatMessage } from "@alexkroman1/aai-ui";
|
|
3
|
+
import { client, useAgentState, useSession } from "@alexkroman1/aai-ui";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import type { DashboardView, DispatchState, IncidentSummary, Severity, Status } from "./shared.ts";
|
|
6
|
+
import { dashboardView } from "./shared.ts";
|
|
7
|
+
|
|
8
|
+
const CSS = `
|
|
9
|
+
@keyframes dc-pulse {
|
|
10
|
+
0%, 100% { opacity: 1; }
|
|
11
|
+
50% { opacity: 0.5; }
|
|
12
|
+
}
|
|
13
|
+
@keyframes dc-slide-in {
|
|
14
|
+
from { transform: translateY(10px); opacity: 0; }
|
|
15
|
+
to { transform: translateY(0); opacity: 1; }
|
|
16
|
+
}
|
|
17
|
+
.dc-messages::-webkit-scrollbar { width: 6px; }
|
|
18
|
+
.dc-messages::-webkit-scrollbar-track { background: transparent; }
|
|
19
|
+
.dc-messages::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
|
|
20
|
+
.dc-sidebar::-webkit-scrollbar { width: 6px; }
|
|
21
|
+
.dc-sidebar::-webkit-scrollbar-track { background: transparent; }
|
|
22
|
+
.dc-sidebar::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
|
|
23
|
+
@media (max-width: 900px) {
|
|
24
|
+
.dc-main { grid-template-columns: 1fr !important; grid-template-rows: auto 1fr !important; }
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
// Each color map is `satisfies`-pinned to the shared union it renders, so a
|
|
29
|
+
// renamed or added level/severity/status is a compile error here instead of
|
|
30
|
+
// a silently gray badge.
|
|
31
|
+
const alertColors: Record<string, string> = {
|
|
32
|
+
green: "#22c55e",
|
|
33
|
+
yellow: "#eab308",
|
|
34
|
+
orange: "#f97316",
|
|
35
|
+
red: "#ef4444",
|
|
36
|
+
} satisfies Record<DispatchState["alertLevel"], string>;
|
|
37
|
+
|
|
38
|
+
const severityColors: Record<string, string> = {
|
|
39
|
+
critical: "#ef4444",
|
|
40
|
+
urgent: "#f97316",
|
|
41
|
+
moderate: "#eab308",
|
|
42
|
+
minor: "#22c55e",
|
|
43
|
+
} satisfies Record<Severity, string>;
|
|
44
|
+
|
|
45
|
+
const statusColors: Record<string, string> = {
|
|
46
|
+
incoming: "#818cf8",
|
|
47
|
+
triaged: "#a78bfa",
|
|
48
|
+
dispatched: "#f59e0b",
|
|
49
|
+
en_route: "#3b82f6",
|
|
50
|
+
on_scene: "#22c55e",
|
|
51
|
+
resolved: "#6b7280",
|
|
52
|
+
escalated: "#ef4444",
|
|
53
|
+
} satisfies Record<Status, string>;
|
|
54
|
+
|
|
55
|
+
// The board before the first tool call — derived from the projection so a
|
|
56
|
+
// new DashboardView field can't silently miss the pre-first-tool-call render.
|
|
57
|
+
const EMPTY_DASH = dashboardView({});
|
|
58
|
+
|
|
59
|
+
function stateColor(state: string): string {
|
|
60
|
+
return state === "listening"
|
|
61
|
+
? "#22c55e"
|
|
62
|
+
: state === "thinking"
|
|
63
|
+
? "#eab308"
|
|
64
|
+
: state === "speaking"
|
|
65
|
+
? "#3b82f6"
|
|
66
|
+
: state === "ready"
|
|
67
|
+
? "#22c55e"
|
|
68
|
+
: "#6b7280";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Panel({ title, children }: { title: string; children: React.ReactNode }) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="rounded-lg p-3" style={{ background: "#1a1a2e", border: "1px solid #1e293b" }}>
|
|
74
|
+
<div
|
|
75
|
+
className="text-[10px] font-bold uppercase tracking-[1.5px] mb-2.5"
|
|
76
|
+
style={{ color: "#64748b" }}
|
|
77
|
+
>
|
|
78
|
+
{title}
|
|
79
|
+
</div>
|
|
80
|
+
{children}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function StatRow({ label, value, color }: { label: string; value: number; color?: string }) {
|
|
86
|
+
return (
|
|
87
|
+
<div className="flex justify-between items-center py-1 text-xs">
|
|
88
|
+
<span style={{ color: "#94a3b8" }}>{label}</span>
|
|
89
|
+
<span className="font-bold" style={{ color: color ?? "#e2e8f0" }}>
|
|
90
|
+
{value}
|
|
91
|
+
</span>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function IncidentCard({ inc }: { inc: IncidentSummary }) {
|
|
97
|
+
const sevColor = severityColors[inc.severity] || "#334155";
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
className="rounded-md p-2.5 mb-2"
|
|
101
|
+
style={{
|
|
102
|
+
background: "#0f172a",
|
|
103
|
+
animation: "dc-slide-in 0.3s ease-out",
|
|
104
|
+
border: `1px solid ${sevColor}40`,
|
|
105
|
+
borderLeft: `3px solid ${sevColor}`,
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<div className="flex justify-between items-center mb-1">
|
|
109
|
+
<span className="text-xs font-bold" style={{ color: "#f1f5f9" }}>
|
|
110
|
+
{inc.id}
|
|
111
|
+
</span>
|
|
112
|
+
<span
|
|
113
|
+
className="text-[9px] px-1.5 py-0.5 rounded font-bold uppercase"
|
|
114
|
+
style={{ background: `${sevColor}30`, color: sevColor }}
|
|
115
|
+
>
|
|
116
|
+
{inc.severity}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
{inc.location && (
|
|
120
|
+
<div className="text-[11px] mb-0.5" style={{ color: "#94a3b8" }}>
|
|
121
|
+
{inc.location}
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
<div
|
|
125
|
+
className="text-[10px] uppercase tracking-wider"
|
|
126
|
+
style={{ color: statusColors[inc.status] || "#6b7280" }}
|
|
127
|
+
>
|
|
128
|
+
{inc.status.replace("_", " ")}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function App() {
|
|
135
|
+
const session = useSession();
|
|
136
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
137
|
+
// The agent's own board, projected by `syncState`. This replaced a
|
|
138
|
+
// useState mirror that merged incident deltas out of tool events — the
|
|
139
|
+
// projection is already the complete list, so there is nothing to merge.
|
|
140
|
+
const dash = useAgentState<DashboardView>() ?? EMPTY_DASH;
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
144
|
+
}, [session.messages]);
|
|
145
|
+
|
|
146
|
+
const incidentList = [...dash.incidents].reverse();
|
|
147
|
+
const activeIncidents = incidentList.filter((i) => i.status !== "resolved");
|
|
148
|
+
const resolvedCount = incidentList.filter((i) => i.status === "resolved").length;
|
|
149
|
+
|
|
150
|
+
const alertLevel = dash.systemAlertLevel;
|
|
151
|
+
const alertBg = alertColors[alertLevel] || "#6b7280";
|
|
152
|
+
const alertTextColor = alertLevel === "yellow" ? "#000" : "#fff";
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<>
|
|
156
|
+
<style>{CSS}</style>
|
|
157
|
+
<div
|
|
158
|
+
className="flex flex-col min-h-screen m-0 p-0 font-mono"
|
|
159
|
+
style={{ background: "#0a0a0f", color: "#e2e8f0" }}
|
|
160
|
+
>
|
|
161
|
+
{/* Header */}
|
|
162
|
+
<div
|
|
163
|
+
className="flex items-center justify-between px-6 py-4 gap-4 flex-wrap shrink-0"
|
|
164
|
+
style={{
|
|
165
|
+
background: "linear-gradient(135deg, #1a1a2e, #16213e)",
|
|
166
|
+
borderBottom: "1px solid #1e293b",
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<div
|
|
170
|
+
className="flex items-center gap-2.5 text-lg font-bold uppercase tracking-wider"
|
|
171
|
+
style={{ color: "#f1f5f9" }}
|
|
172
|
+
>
|
|
173
|
+
<span style={{ color: "#3b82f6" }}>◆</span>
|
|
174
|
+
Dispatch Command Center
|
|
175
|
+
<span
|
|
176
|
+
className="w-2.5 h-2.5 rounded-full inline-block"
|
|
177
|
+
style={{
|
|
178
|
+
background: stateColor(session.state),
|
|
179
|
+
animation:
|
|
180
|
+
session.state === "listening"
|
|
181
|
+
? "dc-pulse 1.5s ease-in-out infinite"
|
|
182
|
+
: session.state === "thinking"
|
|
183
|
+
? "dc-pulse 0.8s ease-in-out infinite"
|
|
184
|
+
: "none",
|
|
185
|
+
}}
|
|
186
|
+
title={session.state}
|
|
187
|
+
/>
|
|
188
|
+
<span className="text-[11px] font-normal normal-case" style={{ color: "#64748b" }}>
|
|
189
|
+
{session.state === "listening"
|
|
190
|
+
? "LISTENING"
|
|
191
|
+
: session.state === "thinking"
|
|
192
|
+
? "PROCESSING"
|
|
193
|
+
: session.state === "speaking"
|
|
194
|
+
? "TRANSMITTING"
|
|
195
|
+
: session.state.toUpperCase()}
|
|
196
|
+
</span>
|
|
197
|
+
</div>
|
|
198
|
+
<div className="flex gap-2 items-center">
|
|
199
|
+
<span className="text-[10px] tracking-wider" style={{ color: "#64748b" }}>
|
|
200
|
+
SYSTEM ALERT:
|
|
201
|
+
</span>
|
|
202
|
+
<span
|
|
203
|
+
className="px-3 py-1 rounded text-[11px] font-bold uppercase tracking-wider"
|
|
204
|
+
style={{
|
|
205
|
+
background: alertBg,
|
|
206
|
+
color: alertTextColor,
|
|
207
|
+
animation: alertLevel === "red" ? "dc-pulse 1s ease-in-out infinite" : "none",
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{alertLevel.toUpperCase()}
|
|
211
|
+
</span>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Main content */}
|
|
216
|
+
<div
|
|
217
|
+
className="dc-main flex-1 grid overflow-hidden"
|
|
218
|
+
style={{ gridTemplateColumns: "1fr 320px" }}
|
|
219
|
+
>
|
|
220
|
+
{/* Left: conversation feed */}
|
|
221
|
+
<div
|
|
222
|
+
className="flex flex-col overflow-hidden"
|
|
223
|
+
style={{ borderRight: "1px solid #1e293b" }}
|
|
224
|
+
>
|
|
225
|
+
<div className="dc-messages flex-1 overflow-y-auto p-4 flex flex-col gap-2">
|
|
226
|
+
{session.messages.length === 0 && (
|
|
227
|
+
<div className="text-center p-10 text-[13px]" style={{ color: "#475569" }}>
|
|
228
|
+
Dispatch Command Center standing by. Click START to begin operations.
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
{session.messages.map((m: ChatMessage, i: number) => (
|
|
232
|
+
<div
|
|
233
|
+
key={i}
|
|
234
|
+
className="rounded-lg text-[13px] max-w-[85%] px-3.5 py-2.5"
|
|
235
|
+
style={{
|
|
236
|
+
lineHeight: 1.6,
|
|
237
|
+
alignSelf: m.role === "assistant" ? "flex-start" : "flex-end",
|
|
238
|
+
background: m.role === "assistant" ? "#1e293b" : "#172554",
|
|
239
|
+
animation: "dc-slide-in 0.2s ease-out",
|
|
240
|
+
borderLeft: m.role === "assistant" ? "3px solid #3b82f6" : "none",
|
|
241
|
+
borderRight: m.role !== "assistant" ? "3px solid #22d3ee" : "none",
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<div
|
|
245
|
+
className="text-[10px] uppercase tracking-wider mb-1"
|
|
246
|
+
style={{ color: "#64748b" }}
|
|
247
|
+
>
|
|
248
|
+
{m.role === "assistant" ? "DISPATCH" : "OPERATOR"}
|
|
249
|
+
</div>
|
|
250
|
+
{m.content}
|
|
251
|
+
</div>
|
|
252
|
+
))}
|
|
253
|
+
<div ref={messagesEndRef} />
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
{session.userTranscript !== null && (
|
|
257
|
+
<div
|
|
258
|
+
className="flex items-center px-4 py-2 text-xs italic min-h-8"
|
|
259
|
+
style={{ background: "#111827", borderTop: "1px solid #1e293b", color: "#64748b" }}
|
|
260
|
+
>
|
|
261
|
+
<span
|
|
262
|
+
className="w-2.5 h-2.5 rounded-full inline-block mr-2"
|
|
263
|
+
style={{ background: "#22c55e", animation: "dc-pulse 1.5s ease-in-out infinite" }}
|
|
264
|
+
/>
|
|
265
|
+
{session.userTranscript === "" ? "..." : session.userTranscript}
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
{session.error && (
|
|
269
|
+
<div
|
|
270
|
+
className="px-4 py-2 text-xs"
|
|
271
|
+
style={{ background: "#450a0a", color: "#fca5a5", borderTop: "1px solid #991b1b" }}
|
|
272
|
+
>
|
|
273
|
+
ERROR: {session.error.message} ({session.error.code})
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
<div
|
|
278
|
+
className="flex items-center gap-2.5 px-4 py-3"
|
|
279
|
+
style={{ background: "#111827", borderTop: "1px solid #1e293b" }}
|
|
280
|
+
>
|
|
281
|
+
{!session.started ? (
|
|
282
|
+
<button
|
|
283
|
+
type="button"
|
|
284
|
+
className="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer text-white"
|
|
285
|
+
style={{ background: "#2563eb" }}
|
|
286
|
+
onClick={() => session.start()}
|
|
287
|
+
>
|
|
288
|
+
Start Dispatch
|
|
289
|
+
</button>
|
|
290
|
+
) : (
|
|
291
|
+
<>
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
className="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer"
|
|
295
|
+
style={{
|
|
296
|
+
background: session.running ? "#334155" : "#2563eb",
|
|
297
|
+
color: session.running ? "#e2e8f0" : "white",
|
|
298
|
+
}}
|
|
299
|
+
onClick={() => session.toggle()}
|
|
300
|
+
>
|
|
301
|
+
{session.running ? "Pause" : "Resume"}
|
|
302
|
+
</button>
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
className="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer text-white"
|
|
306
|
+
style={{ background: "#dc2626" }}
|
|
307
|
+
onClick={() => session.reset()}
|
|
308
|
+
>
|
|
309
|
+
Reset
|
|
310
|
+
</button>
|
|
311
|
+
</>
|
|
312
|
+
)}
|
|
313
|
+
<div className="flex-1" />
|
|
314
|
+
<span className="text-[10px]" style={{ color: "#475569" }}>
|
|
315
|
+
{incidentList.length} incident{incidentList.length !== 1 ? "s" : ""} logged
|
|
316
|
+
</span>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Right: sidebar dashboard */}
|
|
321
|
+
<div
|
|
322
|
+
className="dc-sidebar overflow-y-auto p-4 flex flex-col gap-4"
|
|
323
|
+
style={{ background: "#111827" }}
|
|
324
|
+
>
|
|
325
|
+
<Panel title="Operations Summary">
|
|
326
|
+
<StatRow
|
|
327
|
+
label="Active Incidents"
|
|
328
|
+
value={activeIncidents.length}
|
|
329
|
+
color={activeIncidents.length > 3 ? "#ef4444" : "#e2e8f0"}
|
|
330
|
+
/>
|
|
331
|
+
<StatRow label="Resolved" value={resolvedCount} color="#22c55e" />
|
|
332
|
+
<StatRow label="Total Logged" value={incidentList.length} />
|
|
333
|
+
</Panel>
|
|
334
|
+
|
|
335
|
+
<Panel title="Active Incidents">
|
|
336
|
+
{activeIncidents.length === 0 ? (
|
|
337
|
+
<div className="text-xs text-center py-2" style={{ color: "#475569" }}>
|
|
338
|
+
No active incidents
|
|
339
|
+
</div>
|
|
340
|
+
) : (
|
|
341
|
+
activeIncidents.map((inc) => <IncidentCard key={inc.id} inc={inc} />)
|
|
342
|
+
)}
|
|
343
|
+
</Panel>
|
|
344
|
+
|
|
345
|
+
<Panel title="Severity Legend">
|
|
346
|
+
{Object.entries(severityColors).map(([sev, color]) => (
|
|
347
|
+
<div key={sev} className="flex items-center gap-2 py-0.5">
|
|
348
|
+
<span className="w-2.5 h-2.5 rounded-sm" style={{ background: color }} />
|
|
349
|
+
<span className="text-[11px] capitalize" style={{ color: "#94a3b8" }}>
|
|
350
|
+
{sev}
|
|
351
|
+
</span>
|
|
352
|
+
</div>
|
|
353
|
+
))}
|
|
354
|
+
</Panel>
|
|
355
|
+
|
|
356
|
+
<Panel title="Training Scenarios">
|
|
357
|
+
<div className="text-[11px] leading-relaxed" style={{ color: "#64748b" }}>
|
|
358
|
+
Say "run mass casualty scenario" or "simulate active shooter" to test dispatch
|
|
359
|
+
operations with complex multi-incident drills.
|
|
360
|
+
</div>
|
|
361
|
+
</Panel>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
</>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
client({
|
|
370
|
+
component: App,
|
|
371
|
+
theme: {
|
|
372
|
+
bg: "#0a0a0f",
|
|
373
|
+
primary: "#3b82f6",
|
|
374
|
+
text: "#e2e8f0",
|
|
375
|
+
surface: "#1a1a2e",
|
|
376
|
+
border: "#1e293b",
|
|
377
|
+
},
|
|
378
|
+
});
|