@alexkroman1/aai-cli 0.9.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_build-p1HHkdon.mjs +132 -0
  3. package/dist/_discover-BzlCDVZ6.mjs +161 -0
  4. package/dist/_init-l_uoyFCN.mjs +82 -0
  5. package/dist/_link-BGXGFYWa.mjs +47 -0
  6. package/dist/_server-common-qLA1QU2C.mjs +36 -0
  7. package/dist/_ui-kJIua5L9.mjs +44 -0
  8. package/dist/cli.mjs +318 -0
  9. package/dist/deploy-KyNJaoP5.mjs +86 -0
  10. package/dist/dev-DBFvKyzk.mjs +39 -0
  11. package/dist/init-BWG5OrQa.mjs +65 -0
  12. package/dist/rag-BnCMnccf.mjs +173 -0
  13. package/dist/secret-CzeHIGzE.mjs +50 -0
  14. package/dist/start-C1qkhU4O.mjs +23 -0
  15. package/package.json +39 -0
  16. package/templates/_shared/.env.example +5 -0
  17. package/templates/_shared/CLAUDE.md +1051 -0
  18. package/templates/_shared/biome.json +32 -0
  19. package/templates/_shared/global.d.ts +1 -0
  20. package/templates/_shared/index.html +16 -0
  21. package/templates/_shared/package.json +23 -0
  22. package/templates/_shared/tsconfig.json +15 -0
  23. package/templates/code-interpreter/agent.ts +27 -0
  24. package/templates/code-interpreter/client.tsx +3 -0
  25. package/templates/css.d.ts +1 -0
  26. package/templates/dispatch-center/agent.ts +1227 -0
  27. package/templates/dispatch-center/client.tsx +505 -0
  28. package/templates/embedded-assets/agent.ts +48 -0
  29. package/templates/embedded-assets/client.tsx +3 -0
  30. package/templates/embedded-assets/knowledge.json +20 -0
  31. package/templates/health-assistant/agent.ts +160 -0
  32. package/templates/health-assistant/client.tsx +3 -0
  33. package/templates/infocom-adventure/agent.ts +164 -0
  34. package/templates/infocom-adventure/client.tsx +300 -0
  35. package/templates/math-buddy/agent.ts +21 -0
  36. package/templates/math-buddy/client.tsx +3 -0
  37. package/templates/memory-agent/agent.ts +20 -0
  38. package/templates/memory-agent/client.tsx +3 -0
  39. package/templates/night-owl/agent.ts +98 -0
  40. package/templates/night-owl/client.tsx +12 -0
  41. package/templates/personal-finance/agent.ts +26 -0
  42. package/templates/personal-finance/client.tsx +3 -0
  43. package/templates/pizza-ordering/agent.ts +218 -0
  44. package/templates/pizza-ordering/client.tsx +264 -0
  45. package/templates/simple/agent.ts +6 -0
  46. package/templates/simple/client.tsx +3 -0
  47. package/templates/smart-research/agent.ts +164 -0
  48. package/templates/smart-research/client.tsx +3 -0
  49. package/templates/solo-rpg/agent.ts +1244 -0
  50. package/templates/solo-rpg/client.tsx +698 -0
  51. package/templates/support/README.md +62 -0
  52. package/templates/support/agent.ts +19 -0
  53. package/templates/support/client.tsx +3 -0
  54. package/templates/travel-concierge/agent.ts +29 -0
  55. package/templates/travel-concierge/client.tsx +3 -0
  56. package/templates/tsconfig.json +1 -0
  57. package/templates/web-researcher/agent.ts +17 -0
  58. package/templates/web-researcher/client.tsx +3 -0
@@ -0,0 +1,98 @@
1
+ import { defineAgent, tool } from "@alexkroman1/aai";
2
+ import { z } from "zod";
3
+
4
+ const PICKS: Record<string, Record<string, string[]>> = {
5
+ movie: {
6
+ chill: ["Lost in Translation", "The Grand Budapest Hotel", "Amelie"],
7
+ intense: ["Inception", "Interstellar", "The Dark Knight"],
8
+ cozy: ["When Harry Met Sally", "The Holiday", "Paddington 2"],
9
+ spooky: ["The Shining", "Get Out", "Hereditary"],
10
+ funny: ["The Big Lebowski", "Airplane!", "Superbad"],
11
+ },
12
+ music: {
13
+ chill: [
14
+ "Khruangbin — Con Todo El Mundo",
15
+ "Tycho — Dive",
16
+ "Bonobo — Migration",
17
+ ],
18
+ intense: [
19
+ "Radiohead — OK Computer",
20
+ "Tool — Lateralus",
21
+ "Deftones — White Pony",
22
+ ],
23
+ cozy: [
24
+ "Norah Jones — Come Away with Me",
25
+ "Iron & Wine — Our Endless Numbered Days",
26
+ "Bon Iver — For Emma, Forever Ago",
27
+ ],
28
+ spooky: [
29
+ "Portishead — Dummy",
30
+ "Massive Attack — Mezzanine",
31
+ "Boards of Canada — Music Has the Right to Children",
32
+ ],
33
+ funny: [
34
+ "Weird Al — Running with Scissors",
35
+ "Flight of the Conchords — S/T",
36
+ "Tenacious D — S/T",
37
+ ],
38
+ },
39
+ book: {
40
+ chill: [
41
+ "Norwegian Wood — Murakami",
42
+ "The Alchemist — Coelho",
43
+ "Siddhartha — Hesse",
44
+ ],
45
+ intense: [
46
+ "Blood Meridian — McCarthy",
47
+ "House of Leaves — Danielewski",
48
+ "Neuromancer — Gibson",
49
+ ],
50
+ cozy: [
51
+ "The House in the Cerulean Sea — Klune",
52
+ "A Man Called Ove — Backman",
53
+ "Anxious People — Backman",
54
+ ],
55
+ spooky: [
56
+ "The Haunting of Hill House — Jackson",
57
+ "Mexican Gothic — Moreno-Garcia",
58
+ "The Turn of the Screw — James",
59
+ ],
60
+ funny: [
61
+ "Good Omens — Pratchett & Gaiman",
62
+ "Hitchhiker's Guide — Adams",
63
+ "Catch-22 — Heller",
64
+ ],
65
+ },
66
+ };
67
+
68
+ export default defineAgent({
69
+ name: "Night Owl",
70
+ instructions:
71
+ `You are Night Owl, a cozy evening companion. You help people wind down, recommend entertainment, and share interesting facts about the night sky. Keep your tone warm and relaxed. Use short, conversational responses.
72
+
73
+ Use run_code for sleep calculations:
74
+ - Each sleep cycle is 90 minutes, plus 15 minutes to fall asleep
75
+ - Bedtime = wake_time - (cycles * 90 + 15) minutes
76
+ - If result is negative, add 1440 (24 hours in minutes)
77
+ - Format as HH:MM`,
78
+ greeting:
79
+ "Hey there, night owl. Try asking me for a cozy movie recommendation, or tell me what time you need to wake up and I'll calculate the best time to fall asleep.",
80
+ builtinTools: ["run_code"],
81
+ tools: {
82
+ recommend: tool({
83
+ description:
84
+ "Get recommendations for movies, music, or books based on mood.",
85
+ parameters: z.object({
86
+ category: z.enum(["movie", "music", "book"]),
87
+ mood: z.enum(["chill", "intense", "cozy", "spooky", "funny"]),
88
+ }),
89
+ execute: ({ category, mood }) => {
90
+ return {
91
+ category,
92
+ mood,
93
+ picks: PICKS[category]?.[mood] ?? [],
94
+ };
95
+ },
96
+ }),
97
+ },
98
+ });
@@ -0,0 +1,12 @@
1
+ import "@alexkroman1/aai-ui/styles.css";
2
+ import { ChatView, StartScreen, mount } from "@alexkroman1/aai-ui";
3
+
4
+ function NightOwl() {
5
+ return (
6
+ <StartScreen icon={<span class="text-5xl">&#x1F989;</span>} title="Night Owl" subtitle="your evening companion" buttonText="Start Conversation">
7
+ <ChatView />
8
+ </StartScreen>
9
+ );
10
+ }
11
+
12
+ mount(NightOwl);
@@ -0,0 +1,26 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "Penny",
5
+ greeting:
6
+ "Hey, I'm Penny, your personal finance helper. Try asking me something like, what's 100 dollars in euros, what's the price of bitcoin, or help me split a 120 dollar bill four ways with 20 percent tip.",
7
+ instructions:
8
+ `You are Penny, a friendly personal finance assistant. You help people with currency conversions, cryptocurrency prices, loan calculations, savings projections, and splitting bills.
9
+
10
+ Rules:
11
+ - Always show your math clearly when explaining calculations
12
+ - When discussing investments or crypto, remind users that prices fluctuate and this is not financial advice
13
+ - Be encouraging about savings goals
14
+ - Keep responses concise — this is a voice conversation
15
+ - Round dollar amounts to two decimal places for clarity
16
+
17
+ API endpoints (use fetch_json):
18
+ - Currency rates: https://open.er-api.com/v6/latest/{CODE} — returns { rates: { USD: 1.0, EUR: 0.85, ... } }
19
+ - Crypto prices: https://api.coingecko.com/api/v3/simple/price?ids={coin}&vs_currencies={cur}&include_24hr_change=true&include_market_cap=true
20
+
21
+ Math calculations (use run_code):
22
+ - Compound interest: FV = principal * (1 + rate/n)^(n*years) + monthly * ((1 + rate/n)^(n*years) - 1) / (rate/n)
23
+ - Loan payment: M = P * (r(1+r)^n) / ((1+r)^n - 1) where r = annual_rate/12, n = years*12
24
+ - Tip calculator: tip = bill * percent/100, per_person = (bill + tip) / people`,
25
+ builtinTools: ["run_code", "fetch_json"],
26
+ });
@@ -0,0 +1,3 @@
1
+ import "@alexkroman1/aai-ui/styles.css";
2
+ import { App, mount } from "@alexkroman1/aai-ui";
3
+ mount(App);
@@ -0,0 +1,218 @@
1
+ import { defineAgent, type ToolDef } from "@alexkroman1/aai";
2
+ import { z } from "zod";
3
+
4
+ function orderTool<P extends z.ZodObject<z.ZodRawShape>>(def: ToolDef<P, OrderState>): ToolDef<P, OrderState> {
5
+ return def;
6
+ }
7
+
8
+ interface Pizza {
9
+ id: number;
10
+ size: "small" | "medium" | "large";
11
+ crust: "thin" | "regular" | "thick" | "stuffed";
12
+ toppings: string[];
13
+ quantity: number;
14
+ }
15
+
16
+ interface OrderState {
17
+ pizzas: Pizza[];
18
+ nextId: number;
19
+ customerName: string;
20
+ orderPlaced: boolean;
21
+ }
22
+
23
+ const MENU = {
24
+ sizes: { small: 8.99, medium: 11.99, large: 14.99 },
25
+ crusts: { thin: 0, regular: 0, thick: 1.0, stuffed: 2.0 },
26
+ toppings: {
27
+ pepperoni: 1.5,
28
+ sausage: 1.5,
29
+ mushrooms: 1.0,
30
+ onions: 1.0,
31
+ green_peppers: 1.0,
32
+ black_olives: 1.0,
33
+ bacon: 2.0,
34
+ ham: 1.5,
35
+ pineapple: 1.0,
36
+ jalapenos: 1.0,
37
+ extra_cheese: 1.5,
38
+ spinach: 1.0,
39
+ tomatoes: 1.0,
40
+ anchovies: 1.5,
41
+ chicken: 2.0,
42
+ },
43
+ } as const;
44
+
45
+ function calculateTotal(pizzas: Pizza[]): number {
46
+ return pizzas.reduce((total, pizza) => {
47
+ const base = MENU.sizes[pizza.size];
48
+ const crust = MENU.crusts[pizza.crust];
49
+ const toppingsPrice = pizza.toppings.reduce(
50
+ (sum, t) =>
51
+ sum + (MENU.toppings[t as keyof typeof MENU.toppings] ?? 1.0),
52
+ 0,
53
+ );
54
+ return total + (base + crust + toppingsPrice) * pizza.quantity;
55
+ }, 0);
56
+ }
57
+
58
+ export default defineAgent({
59
+ name: "Pizza Palace",
60
+ greeting:
61
+ "Welcome to Pizza Palace. I can help you build your perfect pizza. What would you like to order?",
62
+ instructions: `You are a friendly pizza order-taker at Pizza Palace. Keep responses short and conversational, optimized for voice.
63
+
64
+ Your job is to help customers build their pizza order step by step. Guide them through size, crust, and toppings.
65
+
66
+ Menu info:
67
+ - Sizes: small ($8.99), medium ($11.99), large ($14.99)
68
+ - Crusts: thin (free), regular (free), thick (+$1), stuffed (+$2)
69
+ - Toppings: pepperoni ($1.50), sausage ($1.50), mushrooms ($1), onions ($1), green peppers ($1), black olives ($1), bacon ($2), ham ($1.50), pineapple ($1), jalapenos ($1), extra cheese ($1.50), spinach ($1), tomatoes ($1), anchovies ($1.50), chicken ($2)
70
+
71
+ Behavior:
72
+ - When a customer wants a pizza, collect size, crust, and toppings, then use add_pizza to add it.
73
+ - If they just say something like "pepperoni pizza", assume medium, regular crust, and confirm before adding.
74
+ - Always confirm what you added after using add_pizza.
75
+ - Use view_order when the customer asks to review their order.
76
+ - Use update_pizza if they want to change an existing pizza.
77
+ - Use remove_pizza if they want to remove one.
78
+ - When they say they are done ordering, use place_order.
79
+ - Suggest popular combos if they seem unsure. For example, "Our most popular is a large pepperoni with extra cheese."
80
+ - Always mention the running total after changes.
81
+ - Be warm but efficient. No long monologues.`,
82
+
83
+ state: (): OrderState => ({
84
+ pizzas: [],
85
+ nextId: 1,
86
+ customerName: "",
87
+ orderPlaced: false,
88
+ }),
89
+
90
+ tools: {
91
+ add_pizza: orderTool({
92
+ description:
93
+ "Add a pizza to the order. Use when the customer has decided on a pizza.",
94
+ parameters: z.object({
95
+ size: z.enum(["small", "medium", "large"]),
96
+ crust: z.enum(["thin", "regular", "thick", "stuffed"]),
97
+ toppings: z
98
+ .array(z.string())
99
+ .describe("List of topping names, e.g. ['pepperoni', 'mushrooms']"),
100
+ quantity: z.number().default(1),
101
+ }),
102
+ execute: (args, ctx) => {
103
+ const state = ctx.state;
104
+ const pizza: Pizza = {
105
+ id: state.nextId++,
106
+ size: args.size,
107
+ crust: args.crust,
108
+ toppings: args.toppings,
109
+ quantity: args.quantity,
110
+ };
111
+ state.pizzas.push(pizza);
112
+ const total = calculateTotal(state.pizzas);
113
+ return {
114
+ added: pizza,
115
+ orderTotal: `$${total.toFixed(2)}`,
116
+ itemCount: state.pizzas.length,
117
+ };
118
+ },
119
+ }),
120
+
121
+ remove_pizza: orderTool({
122
+ description: "Remove a pizza from the order by its ID.",
123
+ parameters: z.object({
124
+ pizza_id: z.number().describe("The pizza ID to remove"),
125
+ }),
126
+ execute: (args, ctx) => {
127
+ const state = ctx.state;
128
+ const idx = state.pizzas.findIndex((p) => p.id === args.pizza_id);
129
+ if (idx === -1) return { error: "Pizza not found in the order." };
130
+ const removed = state.pizzas.splice(idx, 1)[0];
131
+ const total = calculateTotal(state.pizzas);
132
+ return {
133
+ removed,
134
+ orderTotal: `$${total.toFixed(2)}`,
135
+ itemCount: state.pizzas.length,
136
+ };
137
+ },
138
+ }),
139
+
140
+ update_pizza: orderTool({
141
+ description:
142
+ "Update an existing pizza in the order. Only provided fields are changed.",
143
+ parameters: z.object({
144
+ pizza_id: z.number(),
145
+ size: z.enum(["small", "medium", "large"]).optional(),
146
+ crust: z.enum(["thin", "regular", "thick", "stuffed"]).optional(),
147
+ toppings: z.array(z.string()).optional(),
148
+ quantity: z.number().optional(),
149
+ }),
150
+ execute: (args, ctx) => {
151
+ const state = ctx.state;
152
+ const pizza = state.pizzas.find((p) => p.id === args.pizza_id);
153
+ if (!pizza) return { error: "Pizza not found in the order." };
154
+ if (args.size) pizza.size = args.size;
155
+ if (args.crust) pizza.crust = args.crust;
156
+ if (args.toppings) pizza.toppings = args.toppings;
157
+ if (args.quantity) pizza.quantity = args.quantity;
158
+ const total = calculateTotal(state.pizzas);
159
+ return {
160
+ updated: pizza,
161
+ orderTotal: `$${total.toFixed(2)}`,
162
+ };
163
+ },
164
+ }),
165
+
166
+ view_order: {
167
+ description:
168
+ "View the current order summary with all pizzas and total price.",
169
+ execute: (_args, ctx) => {
170
+ const state = ctx.state;
171
+ if (state.pizzas.length === 0)
172
+ return { message: "The order is empty." };
173
+ const total = calculateTotal(state.pizzas);
174
+ return {
175
+ pizzas: state.pizzas.map((p) => ({
176
+ id: p.id,
177
+ description: `${p.quantity}x ${p.size} ${p.crust} crust with ${p.toppings.length > 0 ? p.toppings.join(", ") : "cheese only"}`,
178
+ size: p.size,
179
+ crust: p.crust,
180
+ toppings: p.toppings,
181
+ quantity: p.quantity,
182
+ })),
183
+ orderTotal: `$${total.toFixed(2)}`,
184
+ };
185
+ },
186
+ },
187
+
188
+ set_customer_name: orderTool({
189
+ description: "Set the customer name for the order.",
190
+ parameters: z.object({ name: z.string() }),
191
+ execute: ({ name }, ctx) => {
192
+ const state = ctx.state;
193
+ state.customerName = name;
194
+ return { name };
195
+ },
196
+ }),
197
+
198
+ place_order: {
199
+ description:
200
+ "Place the final order. Use when the customer confirms they are done and ready to order.",
201
+ execute: (_args, ctx) => {
202
+ const state = ctx.state;
203
+ if (state.pizzas.length === 0)
204
+ return { error: "Cannot place an empty order." };
205
+ state.orderPlaced = true;
206
+ const total = calculateTotal(state.pizzas);
207
+ const orderNumber = Math.floor(1000 + Math.random() * 9000);
208
+ return {
209
+ orderNumber,
210
+ customerName: state.customerName || "Guest",
211
+ pizzas: state.pizzas.length,
212
+ total: `$${total.toFixed(2)}`,
213
+ estimatedMinutes: 15 + state.pizzas.length * 5,
214
+ };
215
+ },
216
+ },
217
+ },
218
+ });
@@ -0,0 +1,264 @@
1
+ import "@alexkroman1/aai-ui/styles.css";
2
+ import { useState } from "preact/hooks";
3
+ import { ChatView, SidebarLayout, StartScreen, 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
+ class="shrink-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 class="text-5xl">&#10003;</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 bg-aai-surface"
123
+ >
124
+ <PizzaIcon size={p.size} />
125
+ <div class="flex-1 min-w-0">
126
+ <p class="text-aai-text text-sm font-medium">
127
+ {p.quantity > 1 ? `${p.quantity}x ` : ""}
128
+ {p.size.charAt(0).toUpperCase() + p.size.slice(1)}{" "}
129
+ {p.crust} crust
130
+ </p>
131
+ <p class="text-aai-text opacity-60 text-xs truncate">
132
+ {p.toppings.length > 0
133
+ ? p.toppings.map((t) => t.replace("_", " ")).join(", ")
134
+ : "cheese only"}
135
+ </p>
136
+ </div>
137
+ <p class="text-aai-primary text-sm font-bold whitespace-nowrap">
138
+ ${pizzaPrice(p).toFixed(2)}
139
+ </p>
140
+ </div>
141
+ ))}
142
+ <div class="flex justify-between items-center pt-3 mt-1 border-t border-aai-border">
143
+ <span class="text-aai-text font-bold">Total</span>
144
+ <span class="text-aai-primary font-bold text-lg">{order.total}</span>
145
+ </div>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ function PizzaAgent() {
151
+ const { running, toggle, reset } = useSession();
152
+ const [order, setOrder] = useState<OrderInfo>({
153
+ pizzas: [],
154
+ total: "$0.00",
155
+ orderPlaced: false,
156
+ });
157
+
158
+ useToolResult((toolName, result: any) => {
159
+ switch (toolName) {
160
+ case "add_pizza":
161
+ if (result.added) {
162
+ setOrder((prev) => ({
163
+ ...prev,
164
+ pizzas: [...prev.pizzas, result.added],
165
+ total: result.orderTotal,
166
+ }));
167
+ }
168
+ break;
169
+ case "remove_pizza":
170
+ if (result.removed) {
171
+ setOrder((prev) => ({
172
+ ...prev,
173
+ pizzas: prev.pizzas.filter(
174
+ (p: Pizza) => p.id !== result.removed.id,
175
+ ),
176
+ total: result.orderTotal,
177
+ }));
178
+ }
179
+ break;
180
+ case "update_pizza":
181
+ if (result.updated) {
182
+ setOrder((prev) => ({
183
+ ...prev,
184
+ pizzas: prev.pizzas.map((p: Pizza) =>
185
+ p.id === result.updated.id ? result.updated : p,
186
+ ),
187
+ total: result.orderTotal,
188
+ }));
189
+ }
190
+ break;
191
+ case "view_order":
192
+ if (result.pizzas) {
193
+ const total =
194
+ result.orderTotal ||
195
+ `$${result.pizzas.reduce((s: number, p: Pizza) => s + pizzaPrice(p), 0).toFixed(2)}`;
196
+ setOrder((prev) => ({ ...prev, total }));
197
+ }
198
+ break;
199
+ case "place_order":
200
+ if (result.orderNumber) {
201
+ setOrder((prev) => ({
202
+ ...prev,
203
+ orderPlaced: true,
204
+ orderNumber: result.orderNumber,
205
+ total: result.total,
206
+ estimatedMinutes: result.estimatedMinutes,
207
+ }));
208
+ }
209
+ break;
210
+ }
211
+ });
212
+
213
+ const sidebar = (
214
+ <>
215
+ <div class="p-4 flex items-center gap-3 border-b border-aai-border">
216
+ <PizzaIcon size="small" />
217
+ <h2 class="text-base font-bold text-aai-text">Pizza Palace</h2>
218
+ </div>
219
+ <div class="flex-1">
220
+ <OrderPanel order={order} />
221
+ </div>
222
+ <div class="p-3 flex gap-2 border-t border-aai-border">
223
+ <button
224
+ class={`flex-1 py-2 rounded-lg text-sm border-none cursor-pointer text-white ${running.value ? "bg-aai-error" : "bg-aai-primary"}`}
225
+ onClick={toggle}
226
+ >
227
+ {running.value ? "Pause" : "Resume"}
228
+ </button>
229
+ <button
230
+ class="py-2 px-4 rounded-lg text-sm cursor-pointer text-aai-text bg-aai-surface border border-aai-border"
231
+ onClick={() => {
232
+ reset();
233
+ setOrder({
234
+ pizzas: [],
235
+ total: "$0.00",
236
+ orderPlaced: false,
237
+ });
238
+ }}
239
+ >
240
+ New Order
241
+ </button>
242
+ </div>
243
+ </>
244
+ );
245
+
246
+ return (
247
+ <StartScreen icon={<PizzaIcon size="large" />} title="Pizza Palace" subtitle="Voice-powered pizza ordering" buttonText="Start Ordering">
248
+ <SidebarLayout sidebar={sidebar}>
249
+ <ChatView />
250
+ </SidebarLayout>
251
+ </StartScreen>
252
+ );
253
+ }
254
+
255
+ mount(PizzaAgent, {
256
+ title: "Pizza Palace",
257
+ theme: {
258
+ bg: "#1a1008",
259
+ primary: "#E8A025",
260
+ text: "#f5f0e8",
261
+ surface: "#2a1f10",
262
+ border: "#3d2e18",
263
+ },
264
+ });
@@ -0,0 +1,6 @@
1
+ import { defineAgent } from "@alexkroman1/aai";
2
+
3
+ export default defineAgent({
4
+ name: "Simple Assistant",
5
+ // builtinTools: ["web_search", "visit_webpage", "fetch_json", "run_code"],
6
+ });
@@ -0,0 +1,3 @@
1
+ import "@alexkroman1/aai-ui/styles.css";
2
+ import { App, mount } from "@alexkroman1/aai-ui";
3
+ mount(App);