@assistant-ui/mcp-docs-server 0.1.26 → 0.1.28

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 (43) hide show
  1. package/.docs/organized/code-examples/waterfall.md +2 -2
  2. package/.docs/organized/code-examples/with-a2a.md +2 -2
  3. package/.docs/organized/code-examples/with-ag-ui.md +3 -3
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +4 -4
  5. package/.docs/organized/code-examples/with-artifacts.md +4 -4
  6. package/.docs/organized/code-examples/with-assistant-transport.md +2 -2
  7. package/.docs/organized/code-examples/with-chain-of-thought.md +4 -4
  8. package/.docs/organized/code-examples/with-cloud-standalone.md +4 -4
  9. package/.docs/organized/code-examples/with-cloud.md +4 -4
  10. package/.docs/organized/code-examples/with-custom-thread-list.md +4 -4
  11. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +511 -0
  12. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +6 -6
  13. package/.docs/organized/code-examples/with-expo.md +17 -17
  14. package/.docs/organized/code-examples/with-external-store.md +2 -2
  15. package/.docs/organized/code-examples/with-ffmpeg.md +217 -63
  16. package/.docs/organized/code-examples/with-generative-ui.md +841 -0
  17. package/.docs/organized/code-examples/with-google-adk.md +3 -3
  18. package/.docs/organized/code-examples/with-heat-graph.md +2 -2
  19. package/.docs/organized/code-examples/with-interactables.md +67 -9
  20. package/.docs/organized/code-examples/with-langgraph.md +3 -3
  21. package/.docs/organized/code-examples/with-livekit.md +591 -0
  22. package/.docs/organized/code-examples/with-parent-id-grouping.md +3 -3
  23. package/.docs/organized/code-examples/with-react-hook-form.md +5 -5
  24. package/.docs/organized/code-examples/with-react-ink.md +1 -1
  25. package/.docs/organized/code-examples/with-react-router.md +7 -7
  26. package/.docs/organized/code-examples/with-store.md +8 -3
  27. package/.docs/organized/code-examples/with-tanstack.md +4 -4
  28. package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
  29. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +9 -1
  30. package/.docs/raw/docs/(docs)/guides/interactables.mdx +99 -37
  31. package/.docs/raw/docs/(docs)/guides/mentions.mdx +406 -0
  32. package/.docs/raw/docs/(docs)/guides/slash-commands.mdx +275 -0
  33. package/.docs/raw/docs/(docs)/guides/tool-ui.mdx +29 -0
  34. package/.docs/raw/docs/(docs)/guides/voice.mdx +333 -0
  35. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +23 -0
  36. package/.docs/raw/docs/primitives/composer.mdx +27 -4
  37. package/.docs/raw/docs/runtimes/a2a/index.mdx +4 -0
  38. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +2 -2
  39. package/.docs/raw/docs/runtimes/assistant-transport.mdx +6 -2
  40. package/.docs/raw/docs/ui/context-display.mdx +2 -2
  41. package/.docs/raw/docs/ui/model-selector.mdx +1 -1
  42. package/.docs/raw/docs/ui/voice.mdx +172 -0
  43. package/package.json +5 -6
@@ -0,0 +1,841 @@
1
+ # Example: with-generative-ui
2
+
3
+ ## app/api/chat/route.ts
4
+
5
+ ```typescript
6
+ import { openai } from "@ai-sdk/openai";
7
+ import {
8
+ streamText,
9
+ convertToModelMessages,
10
+ stepCountIs,
11
+ jsonSchema,
12
+ tool,
13
+ zodSchema,
14
+ } from "ai";
15
+ import type { UIMessage } from "ai";
16
+ import { z } from "zod";
17
+
18
+ export const maxDuration = 30;
19
+
20
+ type ToolDef = { description?: string; parameters: Record<string, unknown> };
21
+
22
+ export async function POST(req: Request) {
23
+ const {
24
+ messages,
25
+ system,
26
+ tools: clientTools,
27
+ }: {
28
+ messages: UIMessage[];
29
+ system?: string;
30
+ tools?: Record<string, ToolDef>;
31
+ } = await req.json();
32
+
33
+ // Convert client-defined frontend tools to AI SDK format
34
+ const frontendToolDefs = clientTools
35
+ ? Object.fromEntries(
36
+ Object.entries(clientTools).map(([name, def]) => [
37
+ name,
38
+ {
39
+ description: def.description ?? "",
40
+ inputSchema: jsonSchema(def.parameters),
41
+ },
42
+ ]),
43
+ )
44
+ : {};
45
+
46
+ const result = streamText({
47
+ model: openai("gpt-4o"),
48
+ messages: await convertToModelMessages(messages),
49
+ stopWhen: stepCountIs(10),
50
+ ...(system ? { system } : {}),
51
+ tools: {
52
+ ...frontendToolDefs,
53
+
54
+ // Backend tool: generate chart data
55
+ generate_chart: tool({
56
+ description:
57
+ "Generate a chart. Return structured data for rendering a bar, line, or pie chart. Use this when the user asks for data visualization, charts, graphs, or comparisons.",
58
+ inputSchema: zodSchema(
59
+ z.object({
60
+ title: z.string().describe("Chart title"),
61
+ type: z
62
+ .enum(["bar", "line", "pie"])
63
+ .describe("Chart type to render"),
64
+ data: z
65
+ .array(z.record(z.string(), z.union([z.string(), z.number()])))
66
+ .describe(
67
+ "Array of data objects, e.g. [{month: 'Jan', revenue: 100}]",
68
+ ),
69
+ xKey: z
70
+ .string()
71
+ .describe("Key in each data object to use for the x-axis/labels"),
72
+ dataKeys: z
73
+ .array(z.string())
74
+ .describe("Keys in each data object to chart as series/values"),
75
+ }),
76
+ ),
77
+ execute: async () => {
78
+ return { success: true };
79
+ },
80
+ }),
81
+
82
+ // Backend tool: show location on map
83
+ show_location: tool({
84
+ description:
85
+ "Show a location on a map. Use this when the user asks about a place, wants to see directions, or needs to see a location.",
86
+ inputSchema: zodSchema(
87
+ z.object({
88
+ name: z.string().describe("Name of the place"),
89
+ address: z.string().optional().describe("Street address"),
90
+ lat: z.number().describe("Latitude"),
91
+ lng: z.number().describe("Longitude"),
92
+ }),
93
+ ),
94
+ execute: async () => {
95
+ return { success: true };
96
+ },
97
+ }),
98
+ },
99
+ } as Parameters<typeof streamText>[0]);
100
+
101
+ return result.toUIMessageStreamResponse();
102
+ }
103
+
104
+ ```
105
+
106
+ ## app/globals.css
107
+
108
+ ```css
109
+ @import "tailwindcss";
110
+ @import "tw-animate-css";
111
+
112
+ @source "../../../packages/ui/src";
113
+
114
+ @custom-variant dark (&:is(.dark *));
115
+
116
+ @theme inline {
117
+ --radius-sm: calc(var(--radius) - 4px);
118
+ --radius-md: calc(var(--radius) - 2px);
119
+ --radius-lg: var(--radius);
120
+ --radius-xl: calc(var(--radius) + 4px);
121
+ --color-background: var(--background);
122
+ --color-foreground: var(--foreground);
123
+ --color-card: var(--card);
124
+ --color-card-foreground: var(--card-foreground);
125
+ --color-popover: var(--popover);
126
+ --color-popover-foreground: var(--popover-foreground);
127
+ --color-primary: var(--primary);
128
+ --color-primary-foreground: var(--primary-foreground);
129
+ --color-secondary: var(--secondary);
130
+ --color-secondary-foreground: var(--secondary-foreground);
131
+ --color-muted: var(--muted);
132
+ --color-muted-foreground: var(--muted-foreground);
133
+ --color-accent: var(--accent);
134
+ --color-accent-foreground: var(--accent-foreground);
135
+ --color-destructive: var(--destructive);
136
+ --color-border: var(--border);
137
+ --color-input: var(--input);
138
+ --color-ring: var(--ring);
139
+ --color-chart-1: var(--chart-1);
140
+ --color-chart-2: var(--chart-2);
141
+ --color-chart-3: var(--chart-3);
142
+ --color-chart-4: var(--chart-4);
143
+ --color-chart-5: var(--chart-5);
144
+ --color-sidebar: var(--sidebar);
145
+ --color-sidebar-foreground: var(--sidebar-foreground);
146
+ --color-sidebar-primary: var(--sidebar-primary);
147
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
148
+ --color-sidebar-accent: var(--sidebar-accent);
149
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
150
+ --color-sidebar-border: var(--sidebar-border);
151
+ --color-sidebar-ring: var(--sidebar-ring);
152
+ }
153
+
154
+ :root {
155
+ --radius: 0.625rem;
156
+ --background: oklch(1 0 0);
157
+ --foreground: oklch(0.141 0.005 285.823);
158
+ --card: oklch(1 0 0);
159
+ --card-foreground: oklch(0.141 0.005 285.823);
160
+ --popover: oklch(1 0 0);
161
+ --popover-foreground: oklch(0.141 0.005 285.823);
162
+ --primary: oklch(0.21 0.006 285.885);
163
+ --primary-foreground: oklch(0.985 0 0);
164
+ --secondary: oklch(0.967 0.001 286.375);
165
+ --secondary-foreground: oklch(0.21 0.006 285.885);
166
+ --muted: oklch(0.967 0.001 286.375);
167
+ --muted-foreground: oklch(0.552 0.016 285.938);
168
+ --accent: oklch(0.967 0.001 286.375);
169
+ --accent-foreground: oklch(0.21 0.006 285.885);
170
+ --destructive: oklch(0.577 0.245 27.325);
171
+ --border: oklch(0.92 0.004 286.32);
172
+ --input: oklch(0.92 0.004 286.32);
173
+ --ring: oklch(0.705 0.015 286.067);
174
+ --chart-1: oklch(0.646 0.222 41.116);
175
+ --chart-2: oklch(0.6 0.118 184.704);
176
+ --chart-3: oklch(0.398 0.07 227.392);
177
+ --chart-4: oklch(0.828 0.189 84.429);
178
+ --chart-5: oklch(0.769 0.188 70.08);
179
+ --sidebar: oklch(0.985 0 0);
180
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
181
+ --sidebar-primary: oklch(0.21 0.006 285.885);
182
+ --sidebar-primary-foreground: oklch(0.985 0 0);
183
+ --sidebar-accent: oklch(0.967 0.001 286.375);
184
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
185
+ --sidebar-border: oklch(0.92 0.004 286.32);
186
+ --sidebar-ring: oklch(0.705 0.015 286.067);
187
+ }
188
+
189
+ .dark {
190
+ --background: oklch(0.141 0.005 285.823);
191
+ --foreground: oklch(0.985 0 0);
192
+ --card: oklch(0.21 0.006 285.885);
193
+ --card-foreground: oklch(0.985 0 0);
194
+ --popover: oklch(0.21 0.006 285.885);
195
+ --popover-foreground: oklch(0.985 0 0);
196
+ --primary: oklch(0.92 0.004 286.32);
197
+ --primary-foreground: oklch(0.21 0.006 285.885);
198
+ --secondary: oklch(0.274 0.006 286.033);
199
+ --secondary-foreground: oklch(0.985 0 0);
200
+ --muted: oklch(0.274 0.006 286.033);
201
+ --muted-foreground: oklch(0.705 0.015 286.067);
202
+ --accent: oklch(0.274 0.006 286.033);
203
+ --accent-foreground: oklch(0.985 0 0);
204
+ --destructive: oklch(0.704 0.191 22.216);
205
+ --border: oklch(1 0 0 / 10%);
206
+ --input: oklch(1 0 0 / 15%);
207
+ --ring: oklch(0.552 0.016 285.938);
208
+ --chart-1: oklch(0.488 0.243 264.376);
209
+ --chart-2: oklch(0.696 0.17 162.48);
210
+ --chart-3: oklch(0.769 0.188 70.08);
211
+ --chart-4: oklch(0.627 0.265 303.9);
212
+ --chart-5: oklch(0.645 0.246 16.439);
213
+ --sidebar: oklch(0.21 0.006 285.885);
214
+ --sidebar-foreground: oklch(0.985 0 0);
215
+ --sidebar-primary: oklch(0.488 0.243 264.376);
216
+ --sidebar-primary-foreground: oklch(0.985 0 0);
217
+ --sidebar-accent: oklch(0.274 0.006 286.033);
218
+ --sidebar-accent-foreground: oklch(0.985 0 0);
219
+ --sidebar-border: oklch(1 0 0 / 10%);
220
+ --sidebar-ring: oklch(0.552 0.016 285.938);
221
+ }
222
+
223
+ @layer base {
224
+ * {
225
+ @apply border-border outline-ring/50;
226
+ }
227
+ body {
228
+ @apply bg-background text-foreground;
229
+ }
230
+ }
231
+
232
+ ```
233
+
234
+ ## app/layout.tsx
235
+
236
+ ```tsx
237
+ import type { Metadata } from "next";
238
+ import "./globals.css";
239
+
240
+ export const metadata: Metadata = {
241
+ title: "assistant-ui Generative UI Example",
242
+ description:
243
+ "Example showcasing generative UI tool components: charts, date pickers, forms, and maps",
244
+ };
245
+
246
+ export default function RootLayout({
247
+ children,
248
+ }: Readonly<{
249
+ children: React.ReactNode;
250
+ }>) {
251
+ return (
252
+ <html lang="en">
253
+ <body className="h-dvh">{children}</body>
254
+ </html>
255
+ );
256
+ }
257
+
258
+ ```
259
+
260
+ ## app/page.tsx
261
+
262
+ ```tsx
263
+ "use client";
264
+
265
+ import { Thread } from "@/components/assistant-ui/thread";
266
+ import {
267
+ AssistantRuntimeProvider,
268
+ Suggestions,
269
+ useAui,
270
+ useAssistantTool,
271
+ } from "@assistant-ui/react";
272
+ import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
273
+ import { lastAssistantMessageIsCompleteWithToolCalls } from "ai";
274
+ import { z } from "zod";
275
+ import { ChartToolUI } from "@/components/chart-tool-ui";
276
+ import { DatePickerToolUI } from "@/components/date-picker-tool-ui";
277
+ import { ContactFormToolUI } from "@/components/contact-form-tool-ui";
278
+ import { LocationToolUI } from "@/components/location-tool-ui";
279
+
280
+ // Register frontend tool schemas (no execute — resolved via addResult in the UI)
281
+ function FrontendTools() {
282
+ useAssistantTool({
283
+ toolName: "select_date",
284
+ description:
285
+ "Ask the user to select a date. Use this when you need to collect a date (e.g. for scheduling, booking, deadlines).",
286
+ parameters: z.object({
287
+ prompt: z.string().describe("Message to display to the user"),
288
+ minDate: z.string().optional().describe("Minimum date (ISO string)"),
289
+ maxDate: z.string().optional().describe("Maximum date (ISO string)"),
290
+ }),
291
+ });
292
+
293
+ useAssistantTool({
294
+ toolName: "collect_contact",
295
+ description:
296
+ "Collect contact information from the user. Use this when you need the user's name, email, or phone number.",
297
+ parameters: z.object({
298
+ prompt: z.string().describe("Message to display to the user"),
299
+ fields: z
300
+ .array(z.enum(["name", "email", "phone"]))
301
+ .describe("Which fields to collect"),
302
+ }),
303
+ });
304
+
305
+ return null;
306
+ }
307
+
308
+ export default function Home() {
309
+ const runtime = useChatRuntime({
310
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
311
+ });
312
+
313
+ const aui = useAui({
314
+ suggestions: Suggestions([
315
+ {
316
+ title: "Show a bar chart",
317
+ label: "of quarterly revenue",
318
+ prompt:
319
+ "Create a bar chart showing quarterly revenue: Q1 $45k, Q2 $52k, Q3 $61k, Q4 $58k",
320
+ },
321
+ {
322
+ title: "Pick a date",
323
+ label: "for a meeting",
324
+ prompt: "I need to schedule a meeting. Ask me to pick a date.",
325
+ },
326
+ {
327
+ title: "Collect my contact info",
328
+ label: "name, email, phone",
329
+ prompt:
330
+ "I want to sign up for the newsletter. Ask for my name, email, and phone number.",
331
+ },
332
+ {
333
+ title: "Show me on the map",
334
+ label: "the Eiffel Tower",
335
+ prompt: "Show me the Eiffel Tower on a map",
336
+ },
337
+ ]),
338
+ });
339
+
340
+ return (
341
+ <AssistantRuntimeProvider aui={aui} runtime={runtime}>
342
+ {/* Frontend tools: register schemas, resolved via addResult in UI */}
343
+ <FrontendTools />
344
+ {/* Tool UIs: render components for each tool call */}
345
+ <ChartToolUI />
346
+ <LocationToolUI />
347
+ <DatePickerToolUI />
348
+ <ContactFormToolUI />
349
+ <main className="h-full">
350
+ <Thread />
351
+ </main>
352
+ </AssistantRuntimeProvider>
353
+ );
354
+ }
355
+
356
+ ```
357
+
358
+ ## components.json
359
+
360
+ ```json
361
+ {
362
+ "$schema": "https://ui.shadcn.com/schema.json",
363
+ "style": "new-york",
364
+ "rsc": true,
365
+ "tsx": true,
366
+ "tailwind": {
367
+ "config": "",
368
+ "css": "app/globals.css",
369
+ "baseColor": "zinc",
370
+ "cssVariables": true,
371
+ "prefix": ""
372
+ },
373
+ "aliases": {
374
+ "components": "@/components",
375
+ "utils": "@/lib/utils",
376
+ "ui": "@/components/ui",
377
+ "lib": "@/lib",
378
+ "hooks": "@/hooks"
379
+ },
380
+ "iconLibrary": "lucide",
381
+ "registries": {
382
+ "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
383
+ }
384
+ }
385
+
386
+ ```
387
+
388
+ ## components/chart-tool-ui.tsx
389
+
390
+ ```tsx
391
+ "use client";
392
+
393
+ import { makeAssistantToolUI } from "@assistant-ui/react";
394
+ import {
395
+ BarChart,
396
+ Bar,
397
+ LineChart,
398
+ Line,
399
+ PieChart,
400
+ Pie,
401
+ Cell,
402
+ XAxis,
403
+ YAxis,
404
+ CartesianGrid,
405
+ Tooltip,
406
+ Legend,
407
+ ResponsiveContainer,
408
+ } from "recharts";
409
+ import { Loader2Icon } from "lucide-react";
410
+
411
+ type ChartArgs = {
412
+ title: string;
413
+ type: "bar" | "line" | "pie";
414
+ data: Array<Record<string, string | number>>;
415
+ xKey: string;
416
+ dataKeys: string[];
417
+ };
418
+
419
+ type ChartResult = {
420
+ success: boolean;
421
+ };
422
+
423
+ const COLORS = [
424
+ "oklch(0.646 0.222 41.116)",
425
+ "oklch(0.6 0.118 184.704)",
426
+ "oklch(0.398 0.07 227.392)",
427
+ "oklch(0.828 0.189 84.429)",
428
+ "oklch(0.769 0.188 70.08)",
429
+ ];
430
+
431
+ export const ChartToolUI = makeAssistantToolUI<ChartArgs, ChartResult>({
432
+ toolName: "generate_chart",
433
+ render: function ChartUI({ args, status }) {
434
+ if (status.type === "running" && !args.data?.length) {
435
+ return (
436
+ <div className="flex items-center gap-2 rounded-lg border p-4">
437
+ <Loader2Icon className="size-4 animate-spin text-muted-foreground" />
438
+ <span className="text-muted-foreground text-sm">
439
+ Generating chart...
440
+ </span>
441
+ </div>
442
+ );
443
+ }
444
+
445
+ const { title, type, data, xKey, dataKeys } = args;
446
+ if (!data?.length || !dataKeys?.length) return null;
447
+
448
+ return (
449
+ <div className="my-2 rounded-lg border p-4">
450
+ <h3 className="mb-3 text-center font-semibold text-sm">{title}</h3>
451
+ <div className="h-[280px] w-full">
452
+ <ResponsiveContainer width="100%" height="100%">
453
+ {type === "bar" ? (
454
+ <BarChart data={data}>
455
+ <CartesianGrid strokeDasharray="3 3" vertical={false} />
456
+ <XAxis
457
+ dataKey={xKey}
458
+ tickLine={false}
459
+ axisLine={false}
460
+ fontSize={12}
461
+ />
462
+ <YAxis tickLine={false} axisLine={false} fontSize={12} />
463
+ <Tooltip />
464
+ <Legend />
465
+ {dataKeys.map((key, i) => (
466
+ <Bar
467
+ key={key}
468
+ dataKey={key}
469
+ fill={COLORS[i % COLORS.length]}
470
+ radius={[4, 4, 0, 0]}
471
+ />
472
+ ))}
473
+ </BarChart>
474
+ ) : type === "line" ? (
475
+ <LineChart data={data}>
476
+ <CartesianGrid strokeDasharray="3 3" vertical={false} />
477
+ <XAxis
478
+ dataKey={xKey}
479
+ tickLine={false}
480
+ axisLine={false}
481
+ fontSize={12}
482
+ />
483
+ <YAxis tickLine={false} axisLine={false} fontSize={12} />
484
+ <Tooltip />
485
+ <Legend />
486
+ {dataKeys.map((key, i) => (
487
+ <Line
488
+ key={key}
489
+ type="monotone"
490
+ dataKey={key}
491
+ stroke={COLORS[i % COLORS.length] ?? "#8884d8"}
492
+ strokeWidth={2}
493
+ dot={false}
494
+ />
495
+ ))}
496
+ </LineChart>
497
+ ) : (
498
+ <PieChart>
499
+ <Tooltip />
500
+ <Legend />
501
+ <Pie
502
+ data={data}
503
+ dataKey={dataKeys[0]!}
504
+ nameKey={xKey}
505
+ cx="50%"
506
+ cy="50%"
507
+ outerRadius={80}
508
+ label
509
+ >
510
+ {data.map((_, i) => (
511
+ <Cell
512
+ key={i}
513
+ fill={COLORS[i % COLORS.length] ?? "#8884d8"}
514
+ />
515
+ ))}
516
+ </Pie>
517
+ </PieChart>
518
+ )}
519
+ </ResponsiveContainer>
520
+ </div>
521
+ </div>
522
+ );
523
+ },
524
+ });
525
+
526
+ ```
527
+
528
+ ## components/contact-form-tool-ui.tsx
529
+
530
+ ```tsx
531
+ "use client";
532
+
533
+ import { useState } from "react";
534
+ import { makeAssistantToolUI } from "@assistant-ui/react";
535
+ import { UserIcon, CheckCircle2Icon } from "lucide-react";
536
+
537
+ type ContactFormArgs = {
538
+ prompt: string;
539
+ fields: Array<"name" | "email" | "phone">;
540
+ };
541
+
542
+ type ContactFormResult = {
543
+ name?: string;
544
+ email?: string;
545
+ phone?: string;
546
+ };
547
+
548
+ export const ContactFormToolUI = makeAssistantToolUI<
549
+ ContactFormArgs,
550
+ ContactFormResult
551
+ >({
552
+ toolName: "collect_contact",
553
+ render: function ContactFormUI({ args, result, addResult }) {
554
+ const [form, setForm] = useState<ContactFormResult>({});
555
+
556
+ if (result) {
557
+ return (
558
+ <div className="my-2 rounded-lg border border-green-200 bg-green-50 p-3">
559
+ <div className="mb-1 flex items-center gap-2">
560
+ <CheckCircle2Icon className="size-4 text-green-600" />
561
+ <span className="font-medium text-green-800 text-sm">
562
+ Contact info collected
563
+ </span>
564
+ </div>
565
+ <div className="ml-6 space-y-0.5 text-green-700 text-xs">
566
+ {result.name && <p>Name: {result.name}</p>}
567
+ {result.email && <p>Email: {result.email}</p>}
568
+ {result.phone && <p>Phone: {result.phone}</p>}
569
+ </div>
570
+ </div>
571
+ );
572
+ }
573
+
574
+ const fields = args.fields ?? ["name", "email", "phone"];
575
+ const isValid = fields.every((f) => form[f]?.trim());
576
+
577
+ return (
578
+ <div className="my-2 rounded-lg border p-4">
579
+ <div className="mb-3 flex items-center gap-2">
580
+ <UserIcon className="size-4 text-muted-foreground" />
581
+ <span className="font-medium text-sm">{args.prompt}</span>
582
+ </div>
583
+ <div className="space-y-2">
584
+ {fields.includes("name") && (
585
+ <input
586
+ type="text"
587
+ placeholder="Name"
588
+ value={form.name ?? ""}
589
+ onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))}
590
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm"
591
+ />
592
+ )}
593
+ {fields.includes("email") && (
594
+ <input
595
+ type="email"
596
+ placeholder="Email"
597
+ value={form.email ?? ""}
598
+ onChange={(e) =>
599
+ setForm((p) => ({ ...p, email: e.target.value }))
600
+ }
601
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm"
602
+ />
603
+ )}
604
+ {fields.includes("phone") && (
605
+ <input
606
+ type="tel"
607
+ placeholder="Phone"
608
+ value={form.phone ?? ""}
609
+ onChange={(e) =>
610
+ setForm((p) => ({ ...p, phone: e.target.value }))
611
+ }
612
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm"
613
+ />
614
+ )}
615
+ <button
616
+ type="button"
617
+ disabled={!isValid}
618
+ onClick={() => addResult(form)}
619
+ className="w-full rounded-md bg-primary px-4 py-2 font-medium text-primary-foreground text-sm disabled:opacity-50"
620
+ >
621
+ Submit
622
+ </button>
623
+ </div>
624
+ </div>
625
+ );
626
+ },
627
+ });
628
+
629
+ ```
630
+
631
+ ## components/date-picker-tool-ui.tsx
632
+
633
+ ```tsx
634
+ "use client";
635
+
636
+ import { useState } from "react";
637
+ import { makeAssistantToolUI } from "@assistant-ui/react";
638
+ import { CalendarIcon, CheckCircle2Icon } from "lucide-react";
639
+
640
+ type DatePickerArgs = {
641
+ prompt: string;
642
+ minDate?: string;
643
+ maxDate?: string;
644
+ };
645
+
646
+ type DatePickerResult = {
647
+ date: string;
648
+ };
649
+
650
+ export const DatePickerToolUI = makeAssistantToolUI<
651
+ DatePickerArgs,
652
+ DatePickerResult
653
+ >({
654
+ toolName: "select_date",
655
+ render: function DatePickerUI({ args, result, addResult }) {
656
+ const [value, setValue] = useState("");
657
+
658
+ if (result) {
659
+ return (
660
+ <div className="my-2 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 p-3">
661
+ <CheckCircle2Icon className="size-4 text-green-600" />
662
+ <span className="text-green-800 text-sm">
663
+ Selected: {new Date(result.date).toLocaleDateString()}
664
+ </span>
665
+ </div>
666
+ );
667
+ }
668
+
669
+ return (
670
+ <div className="my-2 rounded-lg border p-4">
671
+ <div className="mb-3 flex items-center gap-2">
672
+ <CalendarIcon className="size-4 text-muted-foreground" />
673
+ <span className="font-medium text-sm">{args.prompt}</span>
674
+ </div>
675
+ <div className="flex items-center gap-2">
676
+ <input
677
+ type="date"
678
+ value={value}
679
+ min={args.minDate}
680
+ max={args.maxDate}
681
+ onChange={(e) => setValue(e.target.value)}
682
+ className="rounded-md border bg-background px-3 py-2 text-sm"
683
+ />
684
+ <button
685
+ type="button"
686
+ disabled={!value}
687
+ onClick={() => addResult({ date: new Date(value).toISOString() })}
688
+ className="rounded-md bg-primary px-4 py-2 font-medium text-primary-foreground text-sm disabled:opacity-50"
689
+ >
690
+ Confirm
691
+ </button>
692
+ </div>
693
+ </div>
694
+ );
695
+ },
696
+ });
697
+
698
+ ```
699
+
700
+ ## components/location-tool-ui.tsx
701
+
702
+ ```tsx
703
+ "use client";
704
+
705
+ import { makeAssistantToolUI } from "@assistant-ui/react";
706
+ import { MapPinIcon, Loader2Icon } from "lucide-react";
707
+
708
+ type LocationArgs = {
709
+ name: string;
710
+ address?: string;
711
+ lat: number;
712
+ lng: number;
713
+ };
714
+
715
+ type LocationResult = {
716
+ success: boolean;
717
+ };
718
+
719
+ export const LocationToolUI = makeAssistantToolUI<LocationArgs, LocationResult>(
720
+ {
721
+ toolName: "show_location",
722
+ render: function LocationUI({ args, status }) {
723
+ if (status.type === "running" && !args.lat) {
724
+ return (
725
+ <div className="flex items-center gap-2 rounded-lg border p-4">
726
+ <Loader2Icon className="size-4 animate-spin text-muted-foreground" />
727
+ <span className="text-muted-foreground text-sm">
728
+ Loading location...
729
+ </span>
730
+ </div>
731
+ );
732
+ }
733
+
734
+ const { name, address, lat, lng } = args;
735
+ if (!lat || !lng) return null;
736
+
737
+ const mapSrc = `https://www.openstreetmap.org/export/embed.html?bbox=${lng - 0.01},${lat - 0.01},${lng + 0.01},${lat + 0.01}&layer=mapnik&marker=${lat},${lng}`;
738
+
739
+ return (
740
+ <div className="my-2 overflow-hidden rounded-lg border">
741
+ <div className="flex items-center gap-2 border-b px-3 py-2">
742
+ <MapPinIcon className="size-4 text-muted-foreground" />
743
+ <div>
744
+ <p className="font-medium text-sm">{name}</p>
745
+ {address && (
746
+ <p className="text-muted-foreground text-xs">{address}</p>
747
+ )}
748
+ </div>
749
+ </div>
750
+ <iframe
751
+ title={`Map of ${name}`}
752
+ src={mapSrc}
753
+ className="h-[200px] w-full border-0"
754
+ />
755
+ </div>
756
+ );
757
+ },
758
+ },
759
+ );
760
+
761
+ ```
762
+
763
+ ## next.config.ts
764
+
765
+ ```typescript
766
+ import type { NextConfig } from "next";
767
+
768
+ const nextConfig: NextConfig = {
769
+ /* config options here */
770
+ };
771
+
772
+ export default nextConfig;
773
+
774
+ ```
775
+
776
+ ## package.json
777
+
778
+ ```json
779
+ {
780
+ "name": "with-generative-ui",
781
+ "version": "0.0.0",
782
+ "private": true,
783
+ "type": "module",
784
+ "scripts": {
785
+ "dev": "next dev",
786
+ "build": "next build",
787
+ "start": "next start"
788
+ },
789
+ "dependencies": {
790
+ "@ai-sdk/openai": "^3.0.51",
791
+ "@assistant-ui/react": "workspace:*",
792
+ "@assistant-ui/react-ai-sdk": "workspace:*",
793
+ "@assistant-ui/ui": "workspace:*",
794
+ "ai": "^6.0.148",
795
+ "class-variance-authority": "^0.7.1",
796
+ "clsx": "^2.1.1",
797
+ "lucide-react": "^1.7.0",
798
+ "next": "^16.2.2",
799
+ "react": "^19.2.4",
800
+ "react-dom": "^19.2.4",
801
+ "recharts": "^3.8.1",
802
+ "tailwind-merge": "^3.5.0",
803
+ "zod": "^4.3.6"
804
+ },
805
+ "devDependencies": {
806
+ "@assistant-ui/x-buildutils": "workspace:*",
807
+ "@tailwindcss/postcss": "^4.2.2",
808
+ "@types/node": "^25.5.2",
809
+ "@types/react": "^19.2.14",
810
+ "@types/react-dom": "^19.2.3",
811
+ "postcss": "^8.5.8",
812
+ "tailwindcss": "^4.2.2",
813
+ "tw-animate-css": "^1.4.0",
814
+ "typescript": "5.9.3"
815
+ }
816
+ }
817
+
818
+ ```
819
+
820
+ ## tsconfig.json
821
+
822
+ ```json
823
+ {
824
+ "extends": "@assistant-ui/x-buildutils/ts/next",
825
+ "compilerOptions": {
826
+ "paths": {
827
+ "@/*": ["./*"],
828
+ "@/components/assistant-ui/*": [
829
+ "../../packages/ui/src/components/assistant-ui/*"
830
+ ],
831
+ "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
832
+ "@/lib/utils": ["../../packages/ui/src/lib/utils"],
833
+ "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
834
+ }
835
+ },
836
+ "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
837
+ "exclude": ["node_modules"]
838
+ }
839
+
840
+ ```
841
+