@alexkroman1/aai 0.7.11 → 0.8.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/README.md +1 -1
- package/dist/aai.js +1 -1
- package/dist/cli.js +693 -349
- package/dist/sdk/_internal_types.d.ts +0 -1
- package/dist/sdk/_internal_types.d.ts.map +1 -1
- package/dist/sdk/_internal_types.js.map +1 -1
- package/dist/sdk/_render_check.d.ts +7 -0
- package/dist/sdk/_render_check.d.ts.map +1 -0
- package/dist/sdk/_render_check.js +38 -0
- package/dist/sdk/_render_check.js.map +1 -0
- package/dist/sdk/builtin_tools.d.ts.map +1 -1
- package/dist/sdk/builtin_tools.js +21 -0
- package/dist/sdk/builtin_tools.js.map +1 -1
- package/dist/sdk/define_agent.d.ts.map +1 -1
- package/dist/sdk/define_agent.js +0 -1
- package/dist/sdk/define_agent.js.map +1 -1
- package/dist/sdk/direct_executor.d.ts.map +1 -1
- package/dist/sdk/direct_executor.js +0 -1
- package/dist/sdk/direct_executor.js.map +1 -1
- package/dist/sdk/memory_tools.d.ts +38 -0
- package/dist/sdk/memory_tools.d.ts.map +1 -0
- package/dist/sdk/memory_tools.js +77 -0
- package/dist/sdk/memory_tools.js.map +1 -0
- package/dist/sdk/mod.d.ts +3 -1
- package/dist/sdk/mod.d.ts.map +1 -1
- package/dist/sdk/mod.js +2 -0
- package/dist/sdk/mod.js.map +1 -1
- package/dist/sdk/protocol.d.ts +9 -3
- package/dist/sdk/protocol.d.ts.map +1 -1
- package/dist/sdk/protocol.js +2 -1
- package/dist/sdk/protocol.js.map +1 -1
- package/dist/sdk/s2s.d.ts.map +1 -1
- package/dist/sdk/s2s.js +9 -3
- package/dist/sdk/s2s.js.map +1 -1
- package/dist/sdk/session.d.ts.map +1 -1
- package/dist/sdk/session.js +5 -0
- package/dist/sdk/session.js.map +1 -1
- package/dist/sdk/types.d.ts +23 -14
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/types.js +29 -0
- package/dist/sdk/types.js.map +1 -1
- package/dist/ui/_components/app.d.ts.map +1 -1
- package/dist/ui/_components/app.js +6 -7
- package/dist/ui/_components/app.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/sidebar_layout.d.ts +19 -0
- package/dist/ui/_components/sidebar_layout.d.ts.map +1 -0
- package/dist/ui/_components/sidebar_layout.js +21 -0
- package/dist/ui/_components/sidebar_layout.js.map +1 -0
- package/dist/ui/_components/start_screen.d.ts +24 -0
- package/dist/ui/_components/start_screen.d.ts.map +1 -0
- package/dist/ui/_components/start_screen.js +25 -0
- package/dist/ui/_components/start_screen.js.map +1 -0
- package/dist/ui/_hooks.d.ts +21 -0
- package/dist/ui/_hooks.d.ts.map +1 -0
- package/dist/ui/_hooks.js +35 -0
- package/dist/ui/_hooks.js.map +1 -0
- package/dist/ui/components.d.ts +20 -0
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +12 -0
- package/dist/ui/components.js.map +1 -1
- package/dist/ui/components_mod.d.ts +3 -2
- package/dist/ui/components_mod.d.ts.map +1 -1
- package/dist/ui/components_mod.js +3 -2
- package/dist/ui/components_mod.js.map +1 -1
- package/dist/ui/mod.d.ts +4 -3
- package/dist/ui/mod.d.ts.map +1 -1
- package/dist/ui/mod.js +3 -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 +55 -3
- package/dist/ui/signals.js.map +1 -1
- package/package.json +19 -2
- package/templates/_shared/CLAUDE.md +305 -116
- package/templates/_shared/package.json +4 -1
- package/templates/dispatch-center/agent.ts +43 -72
- package/templates/embedded-assets/agent.ts +4 -5
- package/templates/health-assistant/agent.ts +7 -7
- package/templates/infocom-adventure/agent.ts +20 -20
- package/templates/memory-agent/agent.ts +1 -55
- package/templates/night-owl/agent.ts +4 -4
- package/templates/night-owl/client.tsx +6 -23
- package/templates/pizza-ordering/agent.ts +214 -0
- package/templates/pizza-ordering/client.tsx +264 -0
- package/templates/smart-research/agent.ts +10 -10
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { defineAgent, tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
interface Pizza {
|
|
5
|
+
id: number;
|
|
6
|
+
size: "small" | "medium" | "large";
|
|
7
|
+
crust: "thin" | "regular" | "thick" | "stuffed";
|
|
8
|
+
toppings: string[];
|
|
9
|
+
quantity: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface OrderState {
|
|
13
|
+
pizzas: Pizza[];
|
|
14
|
+
nextId: number;
|
|
15
|
+
customerName: string;
|
|
16
|
+
orderPlaced: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const MENU = {
|
|
20
|
+
sizes: { small: 8.99, medium: 11.99, large: 14.99 },
|
|
21
|
+
crusts: { thin: 0, regular: 0, thick: 1.0, stuffed: 2.0 },
|
|
22
|
+
toppings: {
|
|
23
|
+
pepperoni: 1.5,
|
|
24
|
+
sausage: 1.5,
|
|
25
|
+
mushrooms: 1.0,
|
|
26
|
+
onions: 1.0,
|
|
27
|
+
green_peppers: 1.0,
|
|
28
|
+
black_olives: 1.0,
|
|
29
|
+
bacon: 2.0,
|
|
30
|
+
ham: 1.5,
|
|
31
|
+
pineapple: 1.0,
|
|
32
|
+
jalapenos: 1.0,
|
|
33
|
+
extra_cheese: 1.5,
|
|
34
|
+
spinach: 1.0,
|
|
35
|
+
tomatoes: 1.0,
|
|
36
|
+
anchovies: 1.5,
|
|
37
|
+
chicken: 2.0,
|
|
38
|
+
},
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
function calculateTotal(pizzas: Pizza[]): number {
|
|
42
|
+
return pizzas.reduce((total, pizza) => {
|
|
43
|
+
const base = MENU.sizes[pizza.size];
|
|
44
|
+
const crust = MENU.crusts[pizza.crust];
|
|
45
|
+
const toppingsPrice = pizza.toppings.reduce(
|
|
46
|
+
(sum, t) =>
|
|
47
|
+
sum + (MENU.toppings[t as keyof typeof MENU.toppings] ?? 1.0),
|
|
48
|
+
0,
|
|
49
|
+
);
|
|
50
|
+
return total + (base + crust + toppingsPrice) * pizza.quantity;
|
|
51
|
+
}, 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default defineAgent({
|
|
55
|
+
name: "Pizza Palace",
|
|
56
|
+
greeting:
|
|
57
|
+
"Welcome to Pizza Palace. I can help you build your perfect pizza. What would you like to order?",
|
|
58
|
+
instructions: `You are a friendly pizza order-taker at Pizza Palace. Keep responses short and conversational, optimized for voice.
|
|
59
|
+
|
|
60
|
+
Your job is to help customers build their pizza order step by step. Guide them through size, crust, and toppings.
|
|
61
|
+
|
|
62
|
+
Menu info:
|
|
63
|
+
- Sizes: small ($8.99), medium ($11.99), large ($14.99)
|
|
64
|
+
- Crusts: thin (free), regular (free), thick (+$1), stuffed (+$2)
|
|
65
|
+
- 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)
|
|
66
|
+
|
|
67
|
+
Behavior:
|
|
68
|
+
- When a customer wants a pizza, collect size, crust, and toppings, then use add_pizza to add it.
|
|
69
|
+
- If they just say something like "pepperoni pizza", assume medium, regular crust, and confirm before adding.
|
|
70
|
+
- Always confirm what you added after using add_pizza.
|
|
71
|
+
- Use view_order when the customer asks to review their order.
|
|
72
|
+
- Use update_pizza if they want to change an existing pizza.
|
|
73
|
+
- Use remove_pizza if they want to remove one.
|
|
74
|
+
- When they say they are done ordering, use place_order.
|
|
75
|
+
- Suggest popular combos if they seem unsure. For example, "Our most popular is a large pepperoni with extra cheese."
|
|
76
|
+
- Always mention the running total after changes.
|
|
77
|
+
- Be warm but efficient. No long monologues.`,
|
|
78
|
+
|
|
79
|
+
state: (): OrderState => ({
|
|
80
|
+
pizzas: [],
|
|
81
|
+
nextId: 1,
|
|
82
|
+
customerName: "",
|
|
83
|
+
orderPlaced: false,
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
tools: {
|
|
87
|
+
add_pizza: tool({
|
|
88
|
+
description:
|
|
89
|
+
"Add a pizza to the order. Use when the customer has decided on a pizza.",
|
|
90
|
+
parameters: z.object({
|
|
91
|
+
size: z.enum(["small", "medium", "large"]),
|
|
92
|
+
crust: z.enum(["thin", "regular", "thick", "stuffed"]),
|
|
93
|
+
toppings: z
|
|
94
|
+
.array(z.string())
|
|
95
|
+
.describe("List of topping names, e.g. ['pepperoni', 'mushrooms']"),
|
|
96
|
+
quantity: z.number().default(1),
|
|
97
|
+
}),
|
|
98
|
+
execute: (args, ctx) => {
|
|
99
|
+
const state = ctx.state as OrderState;
|
|
100
|
+
const pizza: Pizza = {
|
|
101
|
+
id: state.nextId++,
|
|
102
|
+
size: args.size,
|
|
103
|
+
crust: args.crust,
|
|
104
|
+
toppings: args.toppings,
|
|
105
|
+
quantity: args.quantity,
|
|
106
|
+
};
|
|
107
|
+
state.pizzas.push(pizza);
|
|
108
|
+
const total = calculateTotal(state.pizzas);
|
|
109
|
+
return {
|
|
110
|
+
added: pizza,
|
|
111
|
+
orderTotal: `$${total.toFixed(2)}`,
|
|
112
|
+
itemCount: state.pizzas.length,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
|
|
117
|
+
remove_pizza: tool({
|
|
118
|
+
description: "Remove a pizza from the order by its ID.",
|
|
119
|
+
parameters: z.object({
|
|
120
|
+
pizza_id: z.number().describe("The pizza ID to remove"),
|
|
121
|
+
}),
|
|
122
|
+
execute: (args, ctx) => {
|
|
123
|
+
const state = ctx.state as OrderState;
|
|
124
|
+
const idx = state.pizzas.findIndex((p) => p.id === args.pizza_id);
|
|
125
|
+
if (idx === -1) return { error: "Pizza not found in the order." };
|
|
126
|
+
const removed = state.pizzas.splice(idx, 1)[0];
|
|
127
|
+
const total = calculateTotal(state.pizzas);
|
|
128
|
+
return {
|
|
129
|
+
removed,
|
|
130
|
+
orderTotal: `$${total.toFixed(2)}`,
|
|
131
|
+
itemCount: state.pizzas.length,
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
|
|
136
|
+
update_pizza: tool({
|
|
137
|
+
description:
|
|
138
|
+
"Update an existing pizza in the order. Only provided fields are changed.",
|
|
139
|
+
parameters: z.object({
|
|
140
|
+
pizza_id: z.number(),
|
|
141
|
+
size: z.enum(["small", "medium", "large"]).optional(),
|
|
142
|
+
crust: z.enum(["thin", "regular", "thick", "stuffed"]).optional(),
|
|
143
|
+
toppings: z.array(z.string()).optional(),
|
|
144
|
+
quantity: z.number().optional(),
|
|
145
|
+
}),
|
|
146
|
+
execute: (args, ctx) => {
|
|
147
|
+
const state = ctx.state as OrderState;
|
|
148
|
+
const pizza = state.pizzas.find((p) => p.id === args.pizza_id);
|
|
149
|
+
if (!pizza) return { error: "Pizza not found in the order." };
|
|
150
|
+
if (args.size) pizza.size = args.size;
|
|
151
|
+
if (args.crust) pizza.crust = args.crust;
|
|
152
|
+
if (args.toppings) pizza.toppings = args.toppings;
|
|
153
|
+
if (args.quantity) pizza.quantity = args.quantity;
|
|
154
|
+
const total = calculateTotal(state.pizzas);
|
|
155
|
+
return {
|
|
156
|
+
updated: pizza,
|
|
157
|
+
orderTotal: `$${total.toFixed(2)}`,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
|
|
162
|
+
view_order: {
|
|
163
|
+
description:
|
|
164
|
+
"View the current order summary with all pizzas and total price.",
|
|
165
|
+
execute: (_args, ctx) => {
|
|
166
|
+
const state = ctx.state as OrderState;
|
|
167
|
+
if (state.pizzas.length === 0)
|
|
168
|
+
return { message: "The order is empty." };
|
|
169
|
+
const total = calculateTotal(state.pizzas);
|
|
170
|
+
return {
|
|
171
|
+
pizzas: state.pizzas.map((p) => ({
|
|
172
|
+
id: p.id,
|
|
173
|
+
description: `${p.quantity}x ${p.size} ${p.crust} crust with ${p.toppings.length > 0 ? p.toppings.join(", ") : "cheese only"}`,
|
|
174
|
+
size: p.size,
|
|
175
|
+
crust: p.crust,
|
|
176
|
+
toppings: p.toppings,
|
|
177
|
+
quantity: p.quantity,
|
|
178
|
+
})),
|
|
179
|
+
orderTotal: `$${total.toFixed(2)}`,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
set_customer_name: tool({
|
|
185
|
+
description: "Set the customer name for the order.",
|
|
186
|
+
parameters: z.object({ name: z.string() }),
|
|
187
|
+
execute: ({ name }, ctx) => {
|
|
188
|
+
const state = ctx.state as OrderState;
|
|
189
|
+
state.customerName = name;
|
|
190
|
+
return { name };
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
|
|
194
|
+
place_order: {
|
|
195
|
+
description:
|
|
196
|
+
"Place the final order. Use when the customer confirms they are done and ready to order.",
|
|
197
|
+
execute: (_args, ctx) => {
|
|
198
|
+
const state = ctx.state as OrderState;
|
|
199
|
+
if (state.pizzas.length === 0)
|
|
200
|
+
return { error: "Cannot place an empty order." };
|
|
201
|
+
state.orderPlaced = true;
|
|
202
|
+
const total = calculateTotal(state.pizzas);
|
|
203
|
+
const orderNumber = Math.floor(1000 + Math.random() * 9000);
|
|
204
|
+
return {
|
|
205
|
+
orderNumber,
|
|
206
|
+
customerName: state.customerName || "Guest",
|
|
207
|
+
pizzas: state.pizzas.length,
|
|
208
|
+
total: `$${total.toFixed(2)}`,
|
|
209
|
+
estimatedMinutes: 15 + state.pizzas.length * 5,
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
@@ -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">✓</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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defineAgent } from "@alexkroman1/aai";
|
|
1
|
+
import { defineAgent, tool } from "@alexkroman1/aai";
|
|
2
|
+
import type { HookContext, StepInfo } from "@alexkroman1/aai";
|
|
2
3
|
import { z } from "zod";
|
|
3
|
-
import type { HookContext, StepInfo } from "@alexkroman1/aai/types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Smart Research Agent — demonstrates all 5 advanced features:
|
|
@@ -88,18 +88,18 @@ Always search first, then analyze, then answer. Be thorough but concise.`,
|
|
|
88
88
|
},
|
|
89
89
|
|
|
90
90
|
tools: {
|
|
91
|
-
save_source: {
|
|
91
|
+
save_source: tool({
|
|
92
92
|
description: "Save a source URL found during research for later analysis",
|
|
93
93
|
parameters: z.object({
|
|
94
94
|
url: z.string().describe("The source URL"),
|
|
95
95
|
title: z.string().describe("Brief title or description"),
|
|
96
96
|
}),
|
|
97
|
-
execute: (
|
|
97
|
+
execute: ({ url, title }, ctx) => {
|
|
98
98
|
const state = ctx.state;
|
|
99
|
-
state.sources.push(`${
|
|
99
|
+
state.sources.push(`${title}: ${url}`);
|
|
100
100
|
return { saved: true, totalSources: state.sources.length };
|
|
101
101
|
},
|
|
102
|
-
},
|
|
102
|
+
}),
|
|
103
103
|
|
|
104
104
|
mark_complex: {
|
|
105
105
|
description:
|
|
@@ -126,25 +126,25 @@ Always search first, then analyze, then answer. Be thorough but concise.`,
|
|
|
126
126
|
},
|
|
127
127
|
|
|
128
128
|
// Feature 2: ctx.messages — access conversation history in tools
|
|
129
|
-
analyze: {
|
|
129
|
+
analyze: tool({
|
|
130
130
|
description:
|
|
131
131
|
"Analyze all gathered sources and conversation context to form a conclusion",
|
|
132
132
|
parameters: z.object({
|
|
133
133
|
focus: z.string().describe("What aspect to focus the analysis on"),
|
|
134
134
|
}),
|
|
135
|
-
execute: (
|
|
135
|
+
execute: ({ focus }, ctx) => {
|
|
136
136
|
const state = ctx.state;
|
|
137
137
|
// Use ctx.messages to see what's been discussed
|
|
138
138
|
const userMessages = ctx.messages.filter((m) => m.role === "user");
|
|
139
139
|
return {
|
|
140
|
-
focus
|
|
140
|
+
focus,
|
|
141
141
|
sources: state.sources,
|
|
142
142
|
conversationTurns: userMessages.length,
|
|
143
143
|
totalMessages: ctx.messages.length,
|
|
144
144
|
phase: state.phase,
|
|
145
145
|
};
|
|
146
146
|
},
|
|
147
|
-
},
|
|
147
|
+
}),
|
|
148
148
|
|
|
149
149
|
conversation_summary: {
|
|
150
150
|
description: "Get a summary of the conversation so far",
|