@alexkroman1/aai 0.3.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/LICENSE +21 -0
- package/dist/cli.js +3436 -0
- package/package.json +78 -0
- package/sdk/_internal_types.ts +89 -0
- package/sdk/_mock_ws.ts +172 -0
- package/sdk/_timeout.ts +24 -0
- package/sdk/builtin_tools.ts +309 -0
- package/sdk/capnweb.ts +341 -0
- package/sdk/define_agent.ts +70 -0
- package/sdk/direct_executor.ts +195 -0
- package/sdk/kv.ts +183 -0
- package/sdk/mod.ts +35 -0
- package/sdk/protocol.ts +313 -0
- package/sdk/runtime.ts +65 -0
- package/sdk/s2s.ts +271 -0
- package/sdk/server.ts +198 -0
- package/sdk/session.ts +438 -0
- package/sdk/system_prompt.ts +47 -0
- package/sdk/types.ts +406 -0
- package/sdk/vector.ts +133 -0
- package/sdk/winterc_server.ts +141 -0
- package/sdk/worker_entry.ts +99 -0
- package/sdk/worker_shim.ts +170 -0
- package/sdk/ws_handler.ts +190 -0
- package/templates/_shared/.env.example +5 -0
- package/templates/_shared/package.json +17 -0
- package/templates/code-interpreter/agent.ts +27 -0
- package/templates/code-interpreter/client.tsx +2 -0
- package/templates/dispatch-center/agent.ts +1536 -0
- package/templates/dispatch-center/client.tsx +504 -0
- package/templates/embedded-assets/agent.ts +49 -0
- package/templates/embedded-assets/client.tsx +2 -0
- package/templates/embedded-assets/knowledge.json +20 -0
- package/templates/health-assistant/agent.ts +160 -0
- package/templates/health-assistant/client.tsx +2 -0
- package/templates/infocom-adventure/agent.ts +164 -0
- package/templates/infocom-adventure/client.tsx +299 -0
- package/templates/math-buddy/agent.ts +21 -0
- package/templates/math-buddy/client.tsx +2 -0
- package/templates/memory-agent/agent.ts +74 -0
- package/templates/memory-agent/client.tsx +2 -0
- package/templates/night-owl/agent.ts +98 -0
- package/templates/night-owl/client.tsx +28 -0
- package/templates/personal-finance/agent.ts +26 -0
- package/templates/personal-finance/client.tsx +2 -0
- package/templates/simple/agent.ts +6 -0
- package/templates/simple/client.tsx +2 -0
- package/templates/smart-research/agent.ts +164 -0
- package/templates/smart-research/client.tsx +2 -0
- package/templates/support/README.md +62 -0
- package/templates/support/agent.ts +19 -0
- package/templates/support/client.tsx +2 -0
- package/templates/travel-concierge/agent.ts +29 -0
- package/templates/travel-concierge/client.tsx +2 -0
- package/templates/web-researcher/agent.ts +17 -0
- package/templates/web-researcher/client.tsx +2 -0
- package/ui/_components/app.tsx +37 -0
- package/ui/_components/chat_view.tsx +36 -0
- package/ui/_components/controls.tsx +32 -0
- package/ui/_components/error_banner.tsx +18 -0
- package/ui/_components/message_bubble.tsx +21 -0
- package/ui/_components/message_list.tsx +61 -0
- package/ui/_components/state_indicator.tsx +17 -0
- package/ui/_components/thinking_indicator.tsx +19 -0
- package/ui/_components/tool_call_block.tsx +110 -0
- package/ui/_components/tool_icons.tsx +101 -0
- package/ui/_components/transcript.tsx +20 -0
- package/ui/audio.ts +170 -0
- package/ui/components.ts +49 -0
- package/ui/components_mod.ts +37 -0
- package/ui/mod.ts +48 -0
- package/ui/mount.tsx +112 -0
- package/ui/mount_context.ts +19 -0
- package/ui/session.ts +456 -0
- package/ui/session_mod.ts +27 -0
- package/ui/signals.ts +111 -0
- package/ui/types.ts +50 -0
- package/ui/worklets/capture-processor.js +62 -0
- package/ui/worklets/playback-processor.js +110 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { mount, useSession } from "@alexkroman1/aai/ui";
|
|
2
|
+
import type { Message } from "@alexkroman1/aai/ui";
|
|
3
|
+
import { useEffect, useRef } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
const CSS = `
|
|
6
|
+
@keyframes dc-pulse {
|
|
7
|
+
0%, 100% { opacity: 1; }
|
|
8
|
+
50% { opacity: 0.5; }
|
|
9
|
+
}
|
|
10
|
+
@keyframes dc-slide-in {
|
|
11
|
+
from { transform: translateY(10px); opacity: 0; }
|
|
12
|
+
to { transform: translateY(0); opacity: 1; }
|
|
13
|
+
}
|
|
14
|
+
.dc-messages::-webkit-scrollbar { width: 6px; }
|
|
15
|
+
.dc-messages::-webkit-scrollbar-track { background: transparent; }
|
|
16
|
+
.dc-messages::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
|
|
17
|
+
.dc-sidebar::-webkit-scrollbar { width: 6px; }
|
|
18
|
+
.dc-sidebar::-webkit-scrollbar-track { background: transparent; }
|
|
19
|
+
.dc-sidebar::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
|
|
20
|
+
@media (max-width: 900px) {
|
|
21
|
+
.dc-main { grid-template-columns: 1fr !important; grid-template-rows: auto 1fr !important; }
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const alertColors: Record<string, string> = {
|
|
26
|
+
green: "#22c55e",
|
|
27
|
+
yellow: "#eab308",
|
|
28
|
+
orange: "#f97316",
|
|
29
|
+
red: "#ef4444",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const severityColors: Record<string, string> = {
|
|
33
|
+
critical: "#ef4444",
|
|
34
|
+
urgent: "#f97316",
|
|
35
|
+
moderate: "#eab308",
|
|
36
|
+
minor: "#22c55e",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const statusColors: Record<string, string> = {
|
|
40
|
+
incoming: "#818cf8",
|
|
41
|
+
triaged: "#a78bfa",
|
|
42
|
+
dispatched: "#f59e0b",
|
|
43
|
+
"en_route": "#3b82f6",
|
|
44
|
+
"on_scene": "#22c55e",
|
|
45
|
+
resolved: "#6b7280",
|
|
46
|
+
escalated: "#ef4444",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
interface Incident {
|
|
50
|
+
id: string;
|
|
51
|
+
mentioned: number;
|
|
52
|
+
severity?: string;
|
|
53
|
+
status?: string;
|
|
54
|
+
location?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractIncidents(
|
|
58
|
+
messages: { role: string; text: string }[],
|
|
59
|
+
): Map<string, Incident> {
|
|
60
|
+
const incidents = new Map<string, Incident>();
|
|
61
|
+
for (const msg of messages) {
|
|
62
|
+
const incMatches = msg.text.matchAll(/INC-\d{4}/g);
|
|
63
|
+
for (const m of incMatches) {
|
|
64
|
+
const id = m[0];
|
|
65
|
+
if (!incidents.has(id)) {
|
|
66
|
+
incidents.set(id, { id, mentioned: 0 });
|
|
67
|
+
}
|
|
68
|
+
incidents.get(id)!.mentioned++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const lines = msg.text.split("\n");
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
const idMatch = line.match(/INC-\d{4}/);
|
|
74
|
+
if (!idMatch) continue;
|
|
75
|
+
const id = idMatch[0];
|
|
76
|
+
const inc = incidents.get(id) || { id, mentioned: 0 };
|
|
77
|
+
|
|
78
|
+
for (const sev of ["critical", "urgent", "moderate", "minor"]) {
|
|
79
|
+
if (line.toLowerCase().includes(sev)) inc.severity = sev;
|
|
80
|
+
}
|
|
81
|
+
for (
|
|
82
|
+
const st of [
|
|
83
|
+
"incoming",
|
|
84
|
+
"triaged",
|
|
85
|
+
"dispatched",
|
|
86
|
+
"en_route",
|
|
87
|
+
"on_scene",
|
|
88
|
+
"resolved",
|
|
89
|
+
"escalated",
|
|
90
|
+
]
|
|
91
|
+
) {
|
|
92
|
+
if (
|
|
93
|
+
line.toLowerCase().includes(st.replace("_", " ")) ||
|
|
94
|
+
line.toLowerCase().includes(st)
|
|
95
|
+
) inc.status = st;
|
|
96
|
+
}
|
|
97
|
+
const locMatch = line.match(/(?:at|to|location:?)\s+([^,.\n]{5,50})/i);
|
|
98
|
+
if (locMatch) inc.location = locMatch[1]!.trim();
|
|
99
|
+
|
|
100
|
+
incidents.set(id, inc);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return incidents;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractAlertLevel(
|
|
107
|
+
messages: { role: string; text: string }[],
|
|
108
|
+
): string {
|
|
109
|
+
let level = "green";
|
|
110
|
+
for (const msg of messages) {
|
|
111
|
+
const match = msg.text.match(/alert level[:\s]+(\w+)/i);
|
|
112
|
+
if (match) level = match[1]!.toLowerCase();
|
|
113
|
+
if (
|
|
114
|
+
msg.text.includes("alert level is red") ||
|
|
115
|
+
msg.text.includes("ALERT: RED")
|
|
116
|
+
) level = "red";
|
|
117
|
+
if (msg.text.includes("alert level is orange")) level = "orange";
|
|
118
|
+
if (msg.text.includes("alert level is yellow")) level = "yellow";
|
|
119
|
+
}
|
|
120
|
+
return level;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function stateColor(state: string): string {
|
|
124
|
+
return state === "listening"
|
|
125
|
+
? "#22c55e"
|
|
126
|
+
: state === "thinking"
|
|
127
|
+
? "#eab308"
|
|
128
|
+
: state === "speaking"
|
|
129
|
+
? "#3b82f6"
|
|
130
|
+
: state === "ready"
|
|
131
|
+
? "#22c55e"
|
|
132
|
+
: state === "error"
|
|
133
|
+
? "#ef4444"
|
|
134
|
+
: "#6b7280";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Panel(
|
|
138
|
+
{ title, children }: { title: string; children: preact.ComponentChildren },
|
|
139
|
+
) {
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
class="rounded-lg p-3"
|
|
143
|
+
style={{ background: "#1a1a2e", border: "1px solid #1e293b" }}
|
|
144
|
+
>
|
|
145
|
+
<div
|
|
146
|
+
class="text-[10px] font-bold uppercase tracking-[1.5px] mb-2.5"
|
|
147
|
+
style={{ color: "#64748b" }}
|
|
148
|
+
>
|
|
149
|
+
{title}
|
|
150
|
+
</div>
|
|
151
|
+
{children}
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function StatRow(
|
|
157
|
+
{ label, value, color }: { label: string; value: number; color?: string },
|
|
158
|
+
) {
|
|
159
|
+
return (
|
|
160
|
+
<div class="flex justify-between items-center py-1 text-xs">
|
|
161
|
+
<span style={{ color: "#94a3b8" }}>{label}</span>
|
|
162
|
+
<span class="font-bold" style={{ color: color || "#e2e8f0" }}>
|
|
163
|
+
{value}
|
|
164
|
+
</span>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function App() {
|
|
170
|
+
const ctrl = useSession();
|
|
171
|
+
const { session } = ctrl;
|
|
172
|
+
const msgs = session.messages.value;
|
|
173
|
+
const tx = session.userUtterance.value;
|
|
174
|
+
const state = session.state.value;
|
|
175
|
+
const error = session.error.value;
|
|
176
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
177
|
+
|
|
178
|
+
const incidents = extractIncidents(msgs);
|
|
179
|
+
const alertLevel = extractAlertLevel(msgs);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
183
|
+
}, [msgs.length]);
|
|
184
|
+
|
|
185
|
+
const incidentList = Array.from(incidents.values()).reverse();
|
|
186
|
+
const activeIncidents = incidentList.filter((i) => i.status !== "resolved");
|
|
187
|
+
const resolvedCount = incidentList.filter((i) => i.status === "resolved")
|
|
188
|
+
.length;
|
|
189
|
+
|
|
190
|
+
const alertBg = alertColors[alertLevel] || "#6b7280";
|
|
191
|
+
const alertTextColor = alertLevel === "yellow" ? "#000" : "#fff";
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<>
|
|
195
|
+
<style>{CSS}</style>
|
|
196
|
+
<div
|
|
197
|
+
class="flex flex-col min-h-screen m-0 p-0 font-mono"
|
|
198
|
+
style={{ background: "#0a0a0f", color: "#e2e8f0" }}
|
|
199
|
+
>
|
|
200
|
+
{/* Header */}
|
|
201
|
+
<div
|
|
202
|
+
class="flex items-center justify-between px-6 py-4 gap-4 flex-wrap shrink-0"
|
|
203
|
+
style={{
|
|
204
|
+
background: "linear-gradient(135deg, #1a1a2e, #16213e)",
|
|
205
|
+
borderBottom: "1px solid #1e293b",
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<div
|
|
209
|
+
class="flex items-center gap-2.5 text-lg font-bold uppercase tracking-wider"
|
|
210
|
+
style={{ color: "#f1f5f9" }}
|
|
211
|
+
>
|
|
212
|
+
<span style={{ color: "#3b82f6" }}>◆</span>
|
|
213
|
+
Dispatch Command Center
|
|
214
|
+
<span
|
|
215
|
+
class="w-2.5 h-2.5 rounded-full inline-block"
|
|
216
|
+
style={{
|
|
217
|
+
background: stateColor(state),
|
|
218
|
+
animation: state === "listening"
|
|
219
|
+
? "dc-pulse 1.5s ease-in-out infinite"
|
|
220
|
+
: state === "thinking"
|
|
221
|
+
? "dc-pulse 0.8s ease-in-out infinite"
|
|
222
|
+
: "none",
|
|
223
|
+
}}
|
|
224
|
+
title={state}
|
|
225
|
+
/>
|
|
226
|
+
<span
|
|
227
|
+
class="text-[11px] font-normal normal-case"
|
|
228
|
+
style={{ color: "#64748b" }}
|
|
229
|
+
>
|
|
230
|
+
{state === "listening"
|
|
231
|
+
? "LISTENING"
|
|
232
|
+
: state === "thinking"
|
|
233
|
+
? "PROCESSING"
|
|
234
|
+
: state === "speaking"
|
|
235
|
+
? "TRANSMITTING"
|
|
236
|
+
: state.toUpperCase()}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="flex gap-2 items-center">
|
|
240
|
+
<span
|
|
241
|
+
class="text-[10px] tracking-wider"
|
|
242
|
+
style={{ color: "#64748b" }}
|
|
243
|
+
>
|
|
244
|
+
SYSTEM ALERT:
|
|
245
|
+
</span>
|
|
246
|
+
<span
|
|
247
|
+
class="px-3 py-1 rounded text-[11px] font-bold uppercase tracking-wider"
|
|
248
|
+
style={{
|
|
249
|
+
background: alertBg,
|
|
250
|
+
color: alertTextColor,
|
|
251
|
+
animation: alertLevel === "red"
|
|
252
|
+
? "dc-pulse 1s ease-in-out infinite"
|
|
253
|
+
: "none",
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{alertLevel.toUpperCase()}
|
|
257
|
+
</span>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* Main content */}
|
|
262
|
+
<div
|
|
263
|
+
class="dc-main flex-1 grid overflow-hidden"
|
|
264
|
+
style={{ gridTemplateColumns: "1fr 320px" }}
|
|
265
|
+
>
|
|
266
|
+
{/* Left: conversation feed */}
|
|
267
|
+
<div
|
|
268
|
+
class="flex flex-col overflow-hidden"
|
|
269
|
+
style={{ borderRight: "1px solid #1e293b" }}
|
|
270
|
+
>
|
|
271
|
+
<div class="dc-messages flex-1 overflow-y-auto p-4 flex flex-col gap-2">
|
|
272
|
+
{msgs.length === 0 && (
|
|
273
|
+
<div
|
|
274
|
+
class="text-center p-10 text-[13px]"
|
|
275
|
+
style={{ color: "#475569" }}
|
|
276
|
+
>
|
|
277
|
+
Dispatch Command Center standing by. Click START to begin
|
|
278
|
+
operations.
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
{msgs.map((m: Message, i: number) => (
|
|
282
|
+
<div
|
|
283
|
+
key={i}
|
|
284
|
+
class="rounded-lg text-[13px] max-w-[85%] px-3.5 py-2.5"
|
|
285
|
+
style={{
|
|
286
|
+
lineHeight: 1.6,
|
|
287
|
+
alignSelf: m.role === "assistant"
|
|
288
|
+
? "flex-start"
|
|
289
|
+
: "flex-end",
|
|
290
|
+
background: m.role === "assistant" ? "#1e293b" : "#172554",
|
|
291
|
+
animation: "dc-slide-in 0.2s ease-out",
|
|
292
|
+
borderLeft: m.role === "assistant"
|
|
293
|
+
? "3px solid #3b82f6"
|
|
294
|
+
: "none",
|
|
295
|
+
borderRight: m.role !== "assistant"
|
|
296
|
+
? "3px solid #22d3ee"
|
|
297
|
+
: "none",
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
<div
|
|
301
|
+
class="text-[10px] uppercase tracking-wider mb-1"
|
|
302
|
+
style={{ color: "#64748b" }}
|
|
303
|
+
>
|
|
304
|
+
{m.role === "assistant" ? "DISPATCH" : "OPERATOR"}
|
|
305
|
+
</div>
|
|
306
|
+
{m.text}
|
|
307
|
+
</div>
|
|
308
|
+
))}
|
|
309
|
+
<div ref={messagesEndRef} />
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{tx !== null && (
|
|
313
|
+
<div
|
|
314
|
+
class="flex items-center px-4 py-2 text-xs italic min-h-8"
|
|
315
|
+
style={{
|
|
316
|
+
background: "#111827",
|
|
317
|
+
borderTop: "1px solid #1e293b",
|
|
318
|
+
color: "#64748b",
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
<span
|
|
322
|
+
class="w-2.5 h-2.5 rounded-full inline-block mr-2"
|
|
323
|
+
style={{
|
|
324
|
+
background: "#22c55e",
|
|
325
|
+
animation: "dc-pulse 1.5s ease-in-out infinite",
|
|
326
|
+
}}
|
|
327
|
+
/>
|
|
328
|
+
{tx || "..."}
|
|
329
|
+
</div>
|
|
330
|
+
)}
|
|
331
|
+
{error && (
|
|
332
|
+
<div
|
|
333
|
+
class="px-4 py-2 text-xs"
|
|
334
|
+
style={{
|
|
335
|
+
background: "#450a0a",
|
|
336
|
+
color: "#fca5a5",
|
|
337
|
+
borderTop: "1px solid #991b1b",
|
|
338
|
+
}}
|
|
339
|
+
>
|
|
340
|
+
ERROR: {error.message} ({error.code})
|
|
341
|
+
</div>
|
|
342
|
+
)}
|
|
343
|
+
|
|
344
|
+
<div
|
|
345
|
+
class="flex items-center gap-2.5 px-4 py-3"
|
|
346
|
+
style={{ background: "#111827", borderTop: "1px solid #1e293b" }}
|
|
347
|
+
>
|
|
348
|
+
{!ctrl.started.value
|
|
349
|
+
? (
|
|
350
|
+
<button
|
|
351
|
+
type="button"
|
|
352
|
+
class="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer text-white"
|
|
353
|
+
style={{ background: "#2563eb" }}
|
|
354
|
+
onClick={() => ctrl.start()}
|
|
355
|
+
>
|
|
356
|
+
Start Dispatch
|
|
357
|
+
</button>
|
|
358
|
+
)
|
|
359
|
+
: (
|
|
360
|
+
<>
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
class="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer"
|
|
364
|
+
style={{
|
|
365
|
+
background: ctrl.running.value ? "#334155" : "#2563eb",
|
|
366
|
+
color: ctrl.running.value ? "#e2e8f0" : "white",
|
|
367
|
+
}}
|
|
368
|
+
onClick={() => ctrl.toggle()}
|
|
369
|
+
>
|
|
370
|
+
{ctrl.running.value ? "Pause" : "Resume"}
|
|
371
|
+
</button>
|
|
372
|
+
<button
|
|
373
|
+
type="button"
|
|
374
|
+
class="px-4 py-2 border-none rounded-md font-mono text-xs font-semibold uppercase tracking-wider cursor-pointer text-white"
|
|
375
|
+
style={{ background: "#dc2626" }}
|
|
376
|
+
onClick={() => ctrl.reset()}
|
|
377
|
+
>
|
|
378
|
+
Reset
|
|
379
|
+
</button>
|
|
380
|
+
</>
|
|
381
|
+
)}
|
|
382
|
+
<div class="flex-1" />
|
|
383
|
+
<span class="text-[10px]" style={{ color: "#475569" }}>
|
|
384
|
+
{incidentList.length} incident
|
|
385
|
+
{incidentList.length !== 1 ? "s" : ""} logged
|
|
386
|
+
</span>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
{/* Right: sidebar dashboard */}
|
|
391
|
+
<div
|
|
392
|
+
class="dc-sidebar overflow-y-auto p-4 flex flex-col gap-4"
|
|
393
|
+
style={{ background: "#111827" }}
|
|
394
|
+
>
|
|
395
|
+
<Panel title="Operations Summary">
|
|
396
|
+
<StatRow
|
|
397
|
+
label="Active Incidents"
|
|
398
|
+
value={activeIncidents.length}
|
|
399
|
+
color={activeIncidents.length > 3 ? "#ef4444" : "#e2e8f0"}
|
|
400
|
+
/>
|
|
401
|
+
<StatRow label="Resolved" value={resolvedCount} color="#22c55e" />
|
|
402
|
+
<StatRow label="Total Logged" value={incidentList.length} />
|
|
403
|
+
</Panel>
|
|
404
|
+
|
|
405
|
+
<Panel title="Active Incidents">
|
|
406
|
+
{activeIncidents.length === 0
|
|
407
|
+
? (
|
|
408
|
+
<div
|
|
409
|
+
class="text-xs text-center py-2"
|
|
410
|
+
style={{ color: "#475569" }}
|
|
411
|
+
>
|
|
412
|
+
No active incidents
|
|
413
|
+
</div>
|
|
414
|
+
)
|
|
415
|
+
: activeIncidents.map((inc) => (
|
|
416
|
+
<div
|
|
417
|
+
key={inc.id}
|
|
418
|
+
class="rounded-md p-2.5 mb-2"
|
|
419
|
+
style={{
|
|
420
|
+
background: "#0f172a",
|
|
421
|
+
animation: "dc-slide-in 0.3s ease-out",
|
|
422
|
+
border: `1px solid ${
|
|
423
|
+
severityColors[inc.severity ?? ""] || "#334155"
|
|
424
|
+
}40`,
|
|
425
|
+
borderLeft: `3px solid ${
|
|
426
|
+
severityColors[inc.severity ?? ""] || "#334155"
|
|
427
|
+
}`,
|
|
428
|
+
}}
|
|
429
|
+
>
|
|
430
|
+
<div class="flex justify-between items-center mb-1">
|
|
431
|
+
<span
|
|
432
|
+
class="text-xs font-bold"
|
|
433
|
+
style={{ color: "#f1f5f9" }}
|
|
434
|
+
>
|
|
435
|
+
{inc.id}
|
|
436
|
+
</span>
|
|
437
|
+
{inc.severity && (
|
|
438
|
+
<span
|
|
439
|
+
class="text-[9px] px-1.5 py-0.5 rounded font-bold uppercase"
|
|
440
|
+
style={{
|
|
441
|
+
background: `${
|
|
442
|
+
severityColors[inc.severity ?? ""]
|
|
443
|
+
}30`,
|
|
444
|
+
color: severityColors[inc.severity ?? ""],
|
|
445
|
+
}}
|
|
446
|
+
>
|
|
447
|
+
{inc.severity}
|
|
448
|
+
</span>
|
|
449
|
+
)}
|
|
450
|
+
</div>
|
|
451
|
+
{inc.location && (
|
|
452
|
+
<div
|
|
453
|
+
class="text-[11px] mb-0.5"
|
|
454
|
+
style={{ color: "#94a3b8" }}
|
|
455
|
+
>
|
|
456
|
+
{inc.location}
|
|
457
|
+
</div>
|
|
458
|
+
)}
|
|
459
|
+
{inc.status && (
|
|
460
|
+
<div
|
|
461
|
+
class="text-[10px] uppercase tracking-wider"
|
|
462
|
+
style={{ color: statusColors[inc.status] || "#6b7280" }}
|
|
463
|
+
>
|
|
464
|
+
{inc.status.replace("_", " ")}
|
|
465
|
+
</div>
|
|
466
|
+
)}
|
|
467
|
+
</div>
|
|
468
|
+
))}
|
|
469
|
+
</Panel>
|
|
470
|
+
|
|
471
|
+
<Panel title="Severity Legend">
|
|
472
|
+
{Object.entries(severityColors).map(([sev, color]) => (
|
|
473
|
+
<div key={sev} class="flex items-center gap-2 py-0.5">
|
|
474
|
+
<span
|
|
475
|
+
class="w-2.5 h-2.5 rounded-sm"
|
|
476
|
+
style={{ background: color }}
|
|
477
|
+
/>
|
|
478
|
+
<span
|
|
479
|
+
class="text-[11px] capitalize"
|
|
480
|
+
style={{ color: "#94a3b8" }}
|
|
481
|
+
>
|
|
482
|
+
{sev}
|
|
483
|
+
</span>
|
|
484
|
+
</div>
|
|
485
|
+
))}
|
|
486
|
+
</Panel>
|
|
487
|
+
|
|
488
|
+
<Panel title="Training Scenarios">
|
|
489
|
+
<div
|
|
490
|
+
class="text-[11px] leading-relaxed"
|
|
491
|
+
style={{ color: "#64748b" }}
|
|
492
|
+
>
|
|
493
|
+
Say "run mass casualty scenario" or "simulate active shooter" to
|
|
494
|
+
test dispatch operations with complex multi-incident drills.
|
|
495
|
+
</div>
|
|
496
|
+
</Panel>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
</>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
mount(App);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineAgent } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import knowledge from "./knowledge.json" with { type: "json" };
|
|
4
|
+
|
|
5
|
+
type FaqEntry = { question: string; answer: string };
|
|
6
|
+
const faqs: FaqEntry[] = knowledge.faqs;
|
|
7
|
+
|
|
8
|
+
export default defineAgent({
|
|
9
|
+
name: "FAQ Bot",
|
|
10
|
+
instructions:
|
|
11
|
+
`You are a friendly FAQ assistant. Answer questions using ONLY the information \
|
|
12
|
+
from your embedded knowledge base. If the user asks something not covered by your \
|
|
13
|
+
knowledge base, say you don't have that information and suggest they check the official \
|
|
14
|
+
documentation.
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
- Keep answers concise and conversational — this is a voice agent
|
|
18
|
+
- Quote the knowledge base accurately, do not embellish
|
|
19
|
+
- If a question is ambiguous, ask the user to clarify
|
|
20
|
+
- Use 'search_knowledge' to find answers to specific questions
|
|
21
|
+
- Use 'list_topics' to see all available FAQ topics
|
|
22
|
+
- Always be helpful and polite`,
|
|
23
|
+
greeting:
|
|
24
|
+
"Hi! I'm your FAQ assistant. Ask me anything about the AAI agent framework and I'll look it up in my knowledge base.",
|
|
25
|
+
voice: "156fb8d2-335b-4950-9cb3-a2d33befec77", // Helpful Woman
|
|
26
|
+
tools: {
|
|
27
|
+
search_knowledge: {
|
|
28
|
+
description:
|
|
29
|
+
"Search the embedded FAQ knowledge base for an answer matching the user's question.",
|
|
30
|
+
parameters: z.object({
|
|
31
|
+
query: z.string().describe("The user's question to search for"),
|
|
32
|
+
}),
|
|
33
|
+
execute: ({ query }) => {
|
|
34
|
+
const q = query.toLowerCase();
|
|
35
|
+
const match = faqs.find((f) =>
|
|
36
|
+
f.question.toLowerCase().includes(q) ||
|
|
37
|
+
q.includes(f.question.toLowerCase()) ||
|
|
38
|
+
f.answer.toLowerCase().includes(q)
|
|
39
|
+
);
|
|
40
|
+
return match ?? { result: "No matching FAQ found." };
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
list_topics: {
|
|
44
|
+
description:
|
|
45
|
+
"List all available topics in the embedded FAQ knowledge base.",
|
|
46
|
+
execute: () => faqs.map((f) => f.question),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"faqs": [
|
|
3
|
+
{
|
|
4
|
+
"question": "What is AAI?",
|
|
5
|
+
"answer": "AAI is a framework for building voice-powered AI agents that run on Deno."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"question": "How do agents handle tools?",
|
|
9
|
+
"answer": "Agents define tools with a schema and handler function. The LLM decides when to call them based on the conversation."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"question": "What speech providers are supported?",
|
|
13
|
+
"answer": "AAI supports AssemblyAI for speech-to-text and text-to-speech."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"question": "Can agents access the internet?",
|
|
17
|
+
"answer": "Yes, agents run with network access and can make HTTP requests via the fetch API or built-in tools like web_search."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|