@alexkroman1/aai 0.7.11 → 0.7.12
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/README.md +1 -1
- package/dist/cli.js +70 -28
- package/dist/sdk/protocol.d.ts +6 -0
- package/dist/sdk/protocol.d.ts.map +1 -1
- package/dist/sdk/protocol.js +1 -0
- package/dist/sdk/protocol.js.map +1 -1
- package/dist/sdk/s2s.d.ts.map +1 -1
- package/dist/sdk/s2s.js +7 -3
- package/dist/sdk/s2s.js.map +1 -1
- package/dist/sdk/session.d.ts.map +1 -1
- package/dist/sdk/session.js +3 -0
- package/dist/sdk/session.js.map +1 -1
- package/dist/ui/_components/message_list.d.ts.map +1 -1
- package/dist/ui/_components/message_list.js +2 -1
- package/dist/ui/_components/message_list.js.map +1 -1
- package/dist/ui/components_mod.d.ts +1 -1
- package/dist/ui/components_mod.d.ts.map +1 -1
- package/dist/ui/components_mod.js +1 -1
- package/dist/ui/components_mod.js.map +1 -1
- package/dist/ui/mod.d.ts +3 -3
- package/dist/ui/mod.d.ts.map +1 -1
- package/dist/ui/mod.js +2 -2
- package/dist/ui/mod.js.map +1 -1
- package/dist/ui/session.d.ts +7 -0
- package/dist/ui/session.d.ts.map +1 -1
- package/dist/ui/session.js +21 -3
- package/dist/ui/session.js.map +1 -1
- package/dist/ui/signals.d.ts +14 -0
- package/dist/ui/signals.d.ts.map +1 -1
- package/dist/ui/signals.js +50 -1
- package/dist/ui/signals.js.map +1 -1
- package/package.json +3 -2
- package/templates/_shared/CLAUDE.md +74 -5
- package/templates/pizza-ordering/agent.ts +215 -0
- package/templates/pizza-ordering/client.tsx +324 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import "@alexkroman1/aai/ui/styles.css";
|
|
2
|
+
import { useState } from "preact/hooks";
|
|
3
|
+
import { ChatView, mount, useSession, useToolResult } from "@alexkroman1/aai/ui";
|
|
4
|
+
|
|
5
|
+
interface Pizza {
|
|
6
|
+
id: number;
|
|
7
|
+
size: string;
|
|
8
|
+
crust: string;
|
|
9
|
+
toppings: string[];
|
|
10
|
+
quantity: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface OrderInfo {
|
|
14
|
+
pizzas: Pizza[];
|
|
15
|
+
total: string;
|
|
16
|
+
orderPlaced: boolean;
|
|
17
|
+
orderNumber?: number;
|
|
18
|
+
estimatedMinutes?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SIZE_PRICES: Record<string, number> = {
|
|
22
|
+
small: 8.99,
|
|
23
|
+
medium: 11.99,
|
|
24
|
+
large: 14.99,
|
|
25
|
+
};
|
|
26
|
+
const CRUST_PRICES: Record<string, number> = {
|
|
27
|
+
thin: 0,
|
|
28
|
+
regular: 0,
|
|
29
|
+
thick: 1.0,
|
|
30
|
+
stuffed: 2.0,
|
|
31
|
+
};
|
|
32
|
+
const TOPPING_PRICES: Record<string, number> = {
|
|
33
|
+
pepperoni: 1.5,
|
|
34
|
+
sausage: 1.5,
|
|
35
|
+
mushrooms: 1.0,
|
|
36
|
+
onions: 1.0,
|
|
37
|
+
green_peppers: 1.0,
|
|
38
|
+
black_olives: 1.0,
|
|
39
|
+
bacon: 2.0,
|
|
40
|
+
ham: 1.5,
|
|
41
|
+
pineapple: 1.0,
|
|
42
|
+
jalapenos: 1.0,
|
|
43
|
+
extra_cheese: 1.5,
|
|
44
|
+
spinach: 1.0,
|
|
45
|
+
tomatoes: 1.0,
|
|
46
|
+
anchovies: 1.5,
|
|
47
|
+
chicken: 2.0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function pizzaPrice(p: Pizza): number {
|
|
51
|
+
const base = SIZE_PRICES[p.size] ?? 11.99;
|
|
52
|
+
const crust = CRUST_PRICES[p.crust] ?? 0;
|
|
53
|
+
const toppings = p.toppings.reduce(
|
|
54
|
+
(s, t) => s + (TOPPING_PRICES[t] ?? 1.0),
|
|
55
|
+
0,
|
|
56
|
+
);
|
|
57
|
+
return (base + crust + toppings) * p.quantity;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function PizzaIcon({ size }: { size: string }) {
|
|
61
|
+
const dim = size === "small" ? 36 : size === "large" ? 52 : 44;
|
|
62
|
+
return (
|
|
63
|
+
<svg
|
|
64
|
+
width={dim}
|
|
65
|
+
height={dim}
|
|
66
|
+
viewBox="0 0 100 100"
|
|
67
|
+
style={{ flexShrink: 0 }}
|
|
68
|
+
>
|
|
69
|
+
<circle cx="50" cy="50" r="48" fill="#F4C542" stroke="#D4A017" stroke-width="3" />
|
|
70
|
+
<circle cx="50" cy="50" r="42" fill="#E8A025" />
|
|
71
|
+
<circle cx="35" cy="35" r="7" fill="#C0392B" opacity="0.9" />
|
|
72
|
+
<circle cx="60" cy="30" r="6" fill="#C0392B" opacity="0.9" />
|
|
73
|
+
<circle cx="55" cy="55" r="7" fill="#C0392B" opacity="0.9" />
|
|
74
|
+
<circle cx="30" cy="58" r="6" fill="#C0392B" opacity="0.9" />
|
|
75
|
+
<circle cx="65" cy="65" r="5" fill="#C0392B" opacity="0.9" />
|
|
76
|
+
<circle cx="45" cy="68" r="4" fill="#27AE60" opacity="0.7" />
|
|
77
|
+
<circle cx="70" cy="42" r="4" fill="#27AE60" opacity="0.7" />
|
|
78
|
+
</svg>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function OrderPanel({ order }: { order: OrderInfo }) {
|
|
83
|
+
if (order.orderPlaced) {
|
|
84
|
+
return (
|
|
85
|
+
<div class="flex flex-col items-center gap-4 p-6 text-center">
|
|
86
|
+
<div style={{ fontSize: "48px" }}>✓</div>
|
|
87
|
+
<h2 class="text-lg font-bold text-aai-text">Order Placed</h2>
|
|
88
|
+
{order.orderNumber && (
|
|
89
|
+
<p class="text-aai-text opacity-70">
|
|
90
|
+
Order #{order.orderNumber}
|
|
91
|
+
</p>
|
|
92
|
+
)}
|
|
93
|
+
<p class="text-aai-primary font-bold text-xl">{order.total}</p>
|
|
94
|
+
{order.estimatedMinutes && (
|
|
95
|
+
<p class="text-aai-text opacity-60 text-sm">
|
|
96
|
+
Ready in ~{order.estimatedMinutes} minutes
|
|
97
|
+
</p>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (order.pizzas.length === 0) {
|
|
104
|
+
return (
|
|
105
|
+
<div class="flex flex-col items-center gap-3 p-6 text-center opacity-50">
|
|
106
|
+
<PizzaIcon size="large" />
|
|
107
|
+
<p class="text-aai-text text-sm">
|
|
108
|
+
Your order is empty. Tell me what you'd like.
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div class="flex flex-col gap-3 p-4">
|
|
116
|
+
<h3 class="text-sm font-bold text-aai-text opacity-60 uppercase tracking-wide">
|
|
117
|
+
Your Order
|
|
118
|
+
</h3>
|
|
119
|
+
{order.pizzas.map((p) => (
|
|
120
|
+
<div
|
|
121
|
+
key={p.id}
|
|
122
|
+
class="flex items-center gap-3 p-3 rounded-lg"
|
|
123
|
+
style={{ background: "var(--color-aai-surface)" }}
|
|
124
|
+
>
|
|
125
|
+
<PizzaIcon size={p.size} />
|
|
126
|
+
<div class="flex-1 min-w-0">
|
|
127
|
+
<p class="text-aai-text text-sm font-medium">
|
|
128
|
+
{p.quantity > 1 ? `${p.quantity}x ` : ""}
|
|
129
|
+
{p.size.charAt(0).toUpperCase() + p.size.slice(1)}{" "}
|
|
130
|
+
{p.crust} crust
|
|
131
|
+
</p>
|
|
132
|
+
<p class="text-aai-text opacity-60 text-xs truncate">
|
|
133
|
+
{p.toppings.length > 0
|
|
134
|
+
? p.toppings.map((t) => t.replace("_", " ")).join(", ")
|
|
135
|
+
: "cheese only"}
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
<p class="text-aai-primary text-sm font-bold whitespace-nowrap">
|
|
139
|
+
${pizzaPrice(p).toFixed(2)}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
))}
|
|
143
|
+
<div
|
|
144
|
+
class="flex justify-between items-center pt-3 mt-1"
|
|
145
|
+
style={{ borderTop: "1px solid var(--color-aai-border)" }}
|
|
146
|
+
>
|
|
147
|
+
<span class="text-aai-text font-bold">Total</span>
|
|
148
|
+
<span class="text-aai-primary font-bold text-lg">{order.total}</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function PizzaAgent() {
|
|
155
|
+
const { started, running, start, toggle, reset } = useSession();
|
|
156
|
+
const [order, setOrder] = useState<OrderInfo>({
|
|
157
|
+
pizzas: [],
|
|
158
|
+
total: "$0.00",
|
|
159
|
+
orderPlaced: false,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
useToolResult((toolName, result: any) => {
|
|
163
|
+
switch (toolName) {
|
|
164
|
+
case "add_pizza":
|
|
165
|
+
if (result.added) {
|
|
166
|
+
setOrder((prev) => ({
|
|
167
|
+
...prev,
|
|
168
|
+
pizzas: [...prev.pizzas, result.added],
|
|
169
|
+
total: result.orderTotal,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case "remove_pizza":
|
|
174
|
+
if (result.removed) {
|
|
175
|
+
setOrder((prev) => ({
|
|
176
|
+
...prev,
|
|
177
|
+
pizzas: prev.pizzas.filter(
|
|
178
|
+
(p: Pizza) => p.id !== result.removed.id,
|
|
179
|
+
),
|
|
180
|
+
total: result.orderTotal,
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case "update_pizza":
|
|
185
|
+
if (result.updated) {
|
|
186
|
+
setOrder((prev) => ({
|
|
187
|
+
...prev,
|
|
188
|
+
pizzas: prev.pizzas.map((p: Pizza) =>
|
|
189
|
+
p.id === result.updated.id ? result.updated : p,
|
|
190
|
+
),
|
|
191
|
+
total: result.orderTotal,
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
case "view_order":
|
|
196
|
+
if (result.pizzas) {
|
|
197
|
+
const total =
|
|
198
|
+
result.orderTotal ||
|
|
199
|
+
`$${result.pizzas.reduce((s: number, p: Pizza) => s + pizzaPrice(p), 0).toFixed(2)}`;
|
|
200
|
+
setOrder((prev) => ({ ...prev, total }));
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
case "place_order":
|
|
204
|
+
if (result.orderNumber) {
|
|
205
|
+
setOrder((prev) => ({
|
|
206
|
+
...prev,
|
|
207
|
+
orderPlaced: true,
|
|
208
|
+
orderNumber: result.orderNumber,
|
|
209
|
+
total: result.total,
|
|
210
|
+
estimatedMinutes: result.estimatedMinutes,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (!started.value) {
|
|
218
|
+
return (
|
|
219
|
+
<div
|
|
220
|
+
class="flex items-center justify-center h-screen"
|
|
221
|
+
style={{ background: "var(--color-aai-bg)" }}
|
|
222
|
+
>
|
|
223
|
+
<style>
|
|
224
|
+
{`
|
|
225
|
+
@keyframes float {
|
|
226
|
+
0%, 100% { transform: translateY(0px); }
|
|
227
|
+
50% { transform: translateY(-10px); }
|
|
228
|
+
}
|
|
229
|
+
.pizza-float { animation: float 3s ease-in-out infinite; }
|
|
230
|
+
`}
|
|
231
|
+
</style>
|
|
232
|
+
<div class="flex flex-col items-center gap-6">
|
|
233
|
+
<div class="pizza-float">
|
|
234
|
+
<svg width="120" height="120" viewBox="0 0 100 100">
|
|
235
|
+
<circle cx="50" cy="50" r="48" fill="#F4C542" stroke="#D4A017" stroke-width="3" />
|
|
236
|
+
<circle cx="50" cy="50" r="42" fill="#E8A025" />
|
|
237
|
+
<circle cx="35" cy="35" r="7" fill="#C0392B" opacity="0.9" />
|
|
238
|
+
<circle cx="60" cy="30" r="6" fill="#C0392B" opacity="0.9" />
|
|
239
|
+
<circle cx="55" cy="55" r="7" fill="#C0392B" opacity="0.9" />
|
|
240
|
+
<circle cx="30" cy="58" r="6" fill="#C0392B" opacity="0.9" />
|
|
241
|
+
<circle cx="65" cy="65" r="5" fill="#C0392B" opacity="0.9" />
|
|
242
|
+
<circle cx="45" cy="68" r="4" fill="#27AE60" opacity="0.7" />
|
|
243
|
+
<circle cx="70" cy="42" r="4" fill="#27AE60" opacity="0.7" />
|
|
244
|
+
</svg>
|
|
245
|
+
</div>
|
|
246
|
+
<h1 class="text-2xl font-bold text-aai-text">Pizza Palace</h1>
|
|
247
|
+
<p class="text-aai-text opacity-60 text-sm">
|
|
248
|
+
Voice-powered pizza ordering
|
|
249
|
+
</p>
|
|
250
|
+
<button
|
|
251
|
+
class="px-8 py-3 rounded-full text-white border-none cursor-pointer font-medium text-base"
|
|
252
|
+
style={{ background: "var(--color-aai-primary)" }}
|
|
253
|
+
onClick={start}
|
|
254
|
+
>
|
|
255
|
+
Start Ordering
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<div class="flex h-screen" style={{ background: "var(--color-aai-bg)" }}>
|
|
264
|
+
<div
|
|
265
|
+
class="w-80 flex-shrink-0 flex flex-col overflow-y-auto"
|
|
266
|
+
style={{
|
|
267
|
+
borderRight: "1px solid var(--color-aai-border)",
|
|
268
|
+
background: "var(--color-aai-bg)",
|
|
269
|
+
}}
|
|
270
|
+
>
|
|
271
|
+
<div class="p-4 flex items-center gap-3" style={{ borderBottom: "1px solid var(--color-aai-border)" }}>
|
|
272
|
+
<PizzaIcon size="small" />
|
|
273
|
+
<h2 class="text-base font-bold text-aai-text">Pizza Palace</h2>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="flex-1">
|
|
276
|
+
<OrderPanel order={order} />
|
|
277
|
+
</div>
|
|
278
|
+
<div
|
|
279
|
+
class="p-3 flex gap-2"
|
|
280
|
+
style={{ borderTop: "1px solid var(--color-aai-border)" }}
|
|
281
|
+
>
|
|
282
|
+
<button
|
|
283
|
+
class="flex-1 py-2 rounded-lg text-sm border-none cursor-pointer text-white"
|
|
284
|
+
style={{ background: running.value ? "#C0392B" : "var(--color-aai-primary)" }}
|
|
285
|
+
onClick={toggle}
|
|
286
|
+
>
|
|
287
|
+
{running.value ? "Pause" : "Resume"}
|
|
288
|
+
</button>
|
|
289
|
+
<button
|
|
290
|
+
class="py-2 px-4 rounded-lg text-sm cursor-pointer text-aai-text"
|
|
291
|
+
style={{
|
|
292
|
+
background: "var(--color-aai-surface)",
|
|
293
|
+
border: "1px solid var(--color-aai-border)",
|
|
294
|
+
}}
|
|
295
|
+
onClick={() => {
|
|
296
|
+
reset();
|
|
297
|
+
setOrder({
|
|
298
|
+
pizzas: [],
|
|
299
|
+
total: "$0.00",
|
|
300
|
+
orderPlaced: false,
|
|
301
|
+
});
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
New Order
|
|
305
|
+
</button>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="flex-1 min-w-0">
|
|
309
|
+
<ChatView />
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
mount(PizzaAgent, {
|
|
316
|
+
title: "Pizza Palace",
|
|
317
|
+
theme: {
|
|
318
|
+
bg: "#1a1008",
|
|
319
|
+
primary: "#E8A025",
|
|
320
|
+
text: "#f5f0e8",
|
|
321
|
+
surface: "#2a1f10",
|
|
322
|
+
border: "#3d2e18",
|
|
323
|
+
},
|
|
324
|
+
});
|