@assistant-ui/mcp-docs-server 0.1.7 → 0.1.9

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 (40) hide show
  1. package/.docs/organized/code-examples/with-ai-sdk-v5.md +24 -15
  2. package/.docs/organized/code-examples/with-assistant-transport.md +1599 -0
  3. package/.docs/organized/code-examples/with-cloud.md +12 -10
  4. package/.docs/organized/code-examples/with-external-store.md +10 -8
  5. package/.docs/organized/code-examples/with-ffmpeg.md +17 -14
  6. package/.docs/organized/code-examples/with-langgraph.md +83 -47
  7. package/.docs/organized/code-examples/with-parent-id-grouping.md +10 -8
  8. package/.docs/organized/code-examples/with-react-hook-form.md +17 -14
  9. package/.docs/raw/docs/api-reference/integrations/react-data-stream.mdx +194 -0
  10. package/.docs/raw/docs/api-reference/overview.mdx +6 -0
  11. package/.docs/raw/docs/api-reference/primitives/Composer.mdx +31 -0
  12. package/.docs/raw/docs/api-reference/primitives/Message.mdx +108 -3
  13. package/.docs/raw/docs/api-reference/primitives/Thread.mdx +59 -0
  14. package/.docs/raw/docs/api-reference/primitives/ThreadList.mdx +128 -0
  15. package/.docs/raw/docs/api-reference/primitives/ThreadListItem.mdx +160 -0
  16. package/.docs/raw/docs/api-reference/runtimes/AssistantRuntime.mdx +0 -11
  17. package/.docs/raw/docs/api-reference/runtimes/ComposerRuntime.mdx +3 -3
  18. package/.docs/raw/docs/copilots/assistant-frame.mdx +399 -0
  19. package/.docs/raw/docs/devtools.mdx +51 -0
  20. package/.docs/raw/docs/getting-started.mdx +20 -19
  21. package/.docs/raw/docs/guides/Attachments.mdx +6 -13
  22. package/.docs/raw/docs/guides/Tools.mdx +56 -13
  23. package/.docs/raw/docs/guides/context-api.mdx +574 -0
  24. package/.docs/raw/docs/migrations/v0-12.mdx +125 -0
  25. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +2 -2
  26. package/.docs/raw/docs/runtimes/custom/local.mdx +17 -4
  27. package/.docs/raw/docs/runtimes/data-stream.mdx +287 -0
  28. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +6 -5
  29. package/.docs/raw/docs/runtimes/mastra/overview.mdx +3 -3
  30. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +13 -13
  31. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +5 -0
  32. package/.docs/raw/docs/ui/ThreadList.mdx +54 -16
  33. package/dist/{chunk-L4K23SWI.js → chunk-NVNFQ5ZO.js} +4 -1
  34. package/dist/index.js +1 -1
  35. package/dist/prepare-docs/prepare.js +1 -1
  36. package/dist/stdio.js +1 -1
  37. package/package.json +7 -7
  38. package/.docs/raw/docs/concepts/architecture.mdx +0 -19
  39. package/.docs/raw/docs/concepts/runtime-layer.mdx +0 -163
  40. package/.docs/raw/docs/concepts/why.mdx +0 -9
@@ -0,0 +1,1599 @@
1
+ # Example: with-assistant-transport
2
+
3
+ ## app/globals.css
4
+
5
+ ```css
6
+ @import "tailwindcss";
7
+
8
+ @plugin "tailwindcss-animate";
9
+ @import "tw-animate-css";
10
+
11
+ @custom-variant dark (&:is(.dark *));
12
+
13
+ @theme inline {
14
+ --color-background: var(--background);
15
+ --color-foreground: var(--foreground);
16
+ --font-sans: var(--font-geist-sans);
17
+ --font-mono: var(--font-geist-mono);
18
+ --color-sidebar-ring: var(--sidebar-ring);
19
+ --color-sidebar-border: var(--sidebar-border);
20
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
21
+ --color-sidebar-accent: var(--sidebar-accent);
22
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
23
+ --color-sidebar-primary: var(--sidebar-primary);
24
+ --color-sidebar-foreground: var(--sidebar-foreground);
25
+ --color-sidebar: var(--sidebar);
26
+ --color-chart-5: var(--chart-5);
27
+ --color-chart-4: var(--chart-4);
28
+ --color-chart-3: var(--chart-3);
29
+ --color-chart-2: var(--chart-2);
30
+ --color-chart-1: var(--chart-1);
31
+ --color-ring: var(--ring);
32
+ --color-input: var(--input);
33
+ --color-border: var(--border);
34
+ --color-destructive: var(--destructive);
35
+ --color-accent-foreground: var(--accent-foreground);
36
+ --color-accent: var(--accent);
37
+ --color-muted-foreground: var(--muted-foreground);
38
+ --color-muted: var(--muted);
39
+ --color-secondary-foreground: var(--secondary-foreground);
40
+ --color-secondary: var(--secondary);
41
+ --color-primary-foreground: var(--primary-foreground);
42
+ --color-primary: var(--primary);
43
+ --color-popover-foreground: var(--popover-foreground);
44
+ --color-popover: var(--popover);
45
+ --color-card-foreground: var(--card-foreground);
46
+ --color-card: var(--card);
47
+ --radius-sm: calc(var(--radius) - 4px);
48
+ --radius-md: calc(var(--radius) - 2px);
49
+ --radius-lg: var(--radius);
50
+ --radius-xl: calc(var(--radius) + 4px);
51
+ }
52
+
53
+ :root {
54
+ --radius: 0.625rem;
55
+ --card: oklch(1 0 0);
56
+ --card-foreground: oklch(0.141 0.005 285.823);
57
+ --popover: oklch(1 0 0);
58
+ --popover-foreground: oklch(0.141 0.005 285.823);
59
+ --primary: oklch(0.21 0.006 285.885);
60
+ --primary-foreground: oklch(0.985 0 0);
61
+ --secondary: oklch(0.967 0.001 286.375);
62
+ --secondary-foreground: oklch(0.21 0.006 285.885);
63
+ --muted: oklch(0.967 0.001 286.375);
64
+ --muted-foreground: oklch(0.552 0.016 285.938);
65
+ --accent: oklch(0.967 0.001 286.375);
66
+ --accent-foreground: oklch(0.21 0.006 285.885);
67
+ --destructive: oklch(0.577 0.245 27.325);
68
+ --border: oklch(0.92 0.004 286.32);
69
+ --input: oklch(0.92 0.004 286.32);
70
+ --ring: oklch(0.705 0.015 286.067);
71
+ --chart-1: oklch(0.646 0.222 41.116);
72
+ --chart-2: oklch(0.6 0.118 184.704);
73
+ --chart-3: oklch(0.398 0.07 227.392);
74
+ --chart-4: oklch(0.828 0.189 84.429);
75
+ --chart-5: oklch(0.769 0.188 70.08);
76
+ --sidebar: oklch(0.985 0 0);
77
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
78
+ --sidebar-primary: oklch(0.21 0.006 285.885);
79
+ --sidebar-primary-foreground: oklch(0.985 0 0);
80
+ --sidebar-accent: oklch(0.967 0.001 286.375);
81
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
82
+ --sidebar-border: oklch(0.92 0.004 286.32);
83
+ --sidebar-ring: oklch(0.705 0.015 286.067);
84
+ --background: oklch(1 0 0);
85
+ --foreground: oklch(0.141 0.005 285.823);
86
+ }
87
+
88
+ .dark {
89
+ --background: oklch(0.141 0.005 285.823);
90
+ --foreground: oklch(0.985 0 0);
91
+ --card: oklch(0.21 0.006 285.885);
92
+ --card-foreground: oklch(0.985 0 0);
93
+ --popover: oklch(0.21 0.006 285.885);
94
+ --popover-foreground: oklch(0.985 0 0);
95
+ --primary: oklch(0.92 0.004 286.32);
96
+ --primary-foreground: oklch(0.21 0.006 285.885);
97
+ --secondary: oklch(0.274 0.006 286.033);
98
+ --secondary-foreground: oklch(0.985 0 0);
99
+ --muted: oklch(0.274 0.006 286.033);
100
+ --muted-foreground: oklch(0.705 0.015 286.067);
101
+ --accent: oklch(0.274 0.006 286.033);
102
+ --accent-foreground: oklch(0.985 0 0);
103
+ --destructive: oklch(0.704 0.191 22.216);
104
+ --border: oklch(1 0 0 / 10%);
105
+ --input: oklch(1 0 0 / 15%);
106
+ --ring: oklch(0.552 0.016 285.938);
107
+ --chart-1: oklch(0.488 0.243 264.376);
108
+ --chart-2: oklch(0.696 0.17 162.48);
109
+ --chart-3: oklch(0.769 0.188 70.08);
110
+ --chart-4: oklch(0.627 0.265 303.9);
111
+ --chart-5: oklch(0.645 0.246 16.439);
112
+ --sidebar: oklch(0.21 0.006 285.885);
113
+ --sidebar-foreground: oklch(0.985 0 0);
114
+ --sidebar-primary: oklch(0.488 0.243 264.376);
115
+ --sidebar-primary-foreground: oklch(0.985 0 0);
116
+ --sidebar-accent: oklch(0.274 0.006 286.033);
117
+ --sidebar-accent-foreground: oklch(0.985 0 0);
118
+ --sidebar-border: oklch(1 0 0 / 10%);
119
+ --sidebar-ring: oklch(0.552 0.016 285.938);
120
+ }
121
+
122
+ @layer base {
123
+ * {
124
+ @apply border-border outline-ring/50;
125
+ }
126
+ body {
127
+ @apply bg-background text-foreground;
128
+ }
129
+ }
130
+
131
+ ```
132
+
133
+ ## app/layout.tsx
134
+
135
+ ```tsx
136
+ import type { Metadata } from "next";
137
+ import "./globals.css";
138
+
139
+ export const metadata: Metadata = {
140
+ title: "assistant-ui with Assistant Transport",
141
+ description:
142
+ "An example of using assistant-ui with the assistant-transport runtime",
143
+ };
144
+
145
+ export default function RootLayout({
146
+ children,
147
+ }: {
148
+ children: React.ReactNode;
149
+ }) {
150
+ return (
151
+ <html lang="en">
152
+ <body className="font-sans antialiased">
153
+ <div className="flex h-screen flex-col">
154
+ <header className="bg-background border-b px-4 py-2">
155
+ <h1 className="text-lg font-semibold">
156
+ Assistant Transport Example
157
+ </h1>
158
+ </header>
159
+ <main className="flex-1 overflow-hidden">{children}</main>
160
+ </div>
161
+ </body>
162
+ </html>
163
+ );
164
+ }
165
+
166
+ ```
167
+
168
+ ## app/MyRuntimeProvider.tsx
169
+
170
+ ```tsx
171
+ "use client";
172
+
173
+ import {
174
+ AssistantRuntimeProvider,
175
+ AssistantTransportConnectionMetadata,
176
+ makeAssistantTool,
177
+ } from "@assistant-ui/react";
178
+ import {
179
+ convertLangChainMessages,
180
+ LangChainMessage,
181
+ } from "@assistant-ui/react-langgraph";
182
+ import { useAssistantTransportRuntime } from "@assistant-ui/react";
183
+ import React, { ReactNode } from "react";
184
+ import { z } from "zod";
185
+ import { createMessageConverter } from "../../../packages/react/dist/legacy-runtime/runtime-cores/external-store/createMessageConverter";
186
+
187
+ // Frontend tool with execute function
188
+ const WeatherTool = makeAssistantTool({
189
+ type: "frontend",
190
+ toolName: "get_weather",
191
+ description: "Get the current weather for a city",
192
+ parameters: z.object({
193
+ location: z.string().describe("The city to get weather for"),
194
+ unit: z
195
+ .enum(["celsius", "fahrenheit"])
196
+ .optional()
197
+ .describe("Temperature unit"),
198
+ }),
199
+ execute: async ({ location, unit = "celsius" }) => {
200
+ console.log(`Getting weather for ${location} in ${unit}`);
201
+ // Simulate API call
202
+ await new Promise((resolve) => setTimeout(resolve, 1000));
203
+
204
+ const temp = Math.floor(Math.random() * 30) + 10;
205
+ const conditions = ["sunny", "cloudy", "rainy", "partly cloudy"];
206
+ const condition = conditions[Math.floor(Math.random() * conditions.length)];
207
+
208
+ return {
209
+ location,
210
+ temperature: temp,
211
+ unit,
212
+ condition,
213
+ humidity: Math.floor(Math.random() * 40) + 40,
214
+ windSpeed: Math.floor(Math.random() * 20) + 5,
215
+ };
216
+ },
217
+ streamCall: async (reader) => {
218
+ console.log("streamCall", reader);
219
+ const city = await reader.args.get("location");
220
+ console.log("location", city);
221
+
222
+ const args = await reader.args.get();
223
+ console.log("args", args);
224
+
225
+ const result = await reader.response.get();
226
+ console.log("result", result);
227
+ },
228
+ });
229
+
230
+ type MyRuntimeProviderProps = {
231
+ children: ReactNode;
232
+ };
233
+
234
+ type State = {
235
+ messages: LangChainMessage[];
236
+ };
237
+
238
+ const LangChainMessageConverter = createMessageConverter(
239
+ convertLangChainMessages,
240
+ );
241
+
242
+ const converter = (
243
+ state: State,
244
+ connectionMetadata: AssistantTransportConnectionMetadata,
245
+ ) => {
246
+ const optimisticStateMessages = connectionMetadata.pendingCommands.map(
247
+ (c): LangChainMessage[] => {
248
+ if (c.type === "add-message") {
249
+ return [
250
+ {
251
+ type: "human" as const,
252
+ content: [
253
+ {
254
+ type: "text" as const,
255
+ text: c.message.parts
256
+ .map((p) => (p.type === "text" ? p.text : ""))
257
+ .join("\n"),
258
+ },
259
+ ],
260
+ },
261
+ ];
262
+ }
263
+ return [];
264
+ },
265
+ );
266
+
267
+ const messages = [...state.messages, ...optimisticStateMessages.flat()];
268
+ console.log({ state, messages });
269
+ return {
270
+ messages: LangChainMessageConverter.toThreadMessages(messages),
271
+ isRunning: connectionMetadata.isSending || false,
272
+ };
273
+ };
274
+
275
+ export function MyRuntimeProvider({ children }: MyRuntimeProviderProps) {
276
+ const runtime = useAssistantTransportRuntime({
277
+ initialState: {
278
+ messages: [],
279
+ },
280
+ api:
281
+ process.env["NEXT_PUBLIC_API_URL"] || "http://localhost:8010/assistant",
282
+ converter,
283
+ headers: async () => ({
284
+ "Test-Header": "test-value",
285
+ }),
286
+ body: {
287
+ "Test-Body": "test-value",
288
+ },
289
+ onResponse: () => {
290
+ console.log("Response received from server");
291
+ },
292
+ onFinish: () => {
293
+ console.log("Conversation completed");
294
+ },
295
+ onError: (error: Error) => {
296
+ console.error("Assistant transport error:", error);
297
+ },
298
+ onCancel: () => {
299
+ console.log("Request cancelled");
300
+ },
301
+ });
302
+
303
+ return (
304
+ <AssistantRuntimeProvider runtime={runtime}>
305
+ <WeatherTool />
306
+
307
+ {children}
308
+ </AssistantRuntimeProvider>
309
+ );
310
+ }
311
+
312
+ ```
313
+
314
+ ## app/page.tsx
315
+
316
+ ```tsx
317
+ "use client";
318
+
319
+ import { Thread } from "@/components/assistant-ui/thread";
320
+ import { MyRuntimeProvider } from "./MyRuntimeProvider";
321
+
322
+ export default function Home() {
323
+ return (
324
+ <MyRuntimeProvider>
325
+ <div className="h-full">
326
+ <Thread />
327
+ </div>
328
+ </MyRuntimeProvider>
329
+ );
330
+ }
331
+
332
+ ```
333
+
334
+ ## components.json
335
+
336
+ ```json
337
+ {
338
+ "$schema": "https://ui.shadcn.com/schema.json",
339
+ "style": "new-york",
340
+ "rsc": true,
341
+ "tsx": true,
342
+ "tailwind": {
343
+ "config": "tailwind.config.js",
344
+ "css": "app/globals.css",
345
+ "baseColor": "zinc",
346
+ "cssVariables": true,
347
+ "prefix": ""
348
+ },
349
+ "iconLibrary": "lucide",
350
+ "aliases": {
351
+ "components": "@/components",
352
+ "utils": "@/lib/utils",
353
+ "ui": "@/components/ui",
354
+ "lib": "@/lib",
355
+ "hooks": "@/hooks"
356
+ },
357
+ "registries": {}
358
+ }
359
+
360
+ ```
361
+
362
+ ## components/assistant-ui/markdown-text.tsx
363
+
364
+ ```tsx
365
+ "use client";
366
+
367
+ import "@assistant-ui/react-markdown/styles/dot.css";
368
+
369
+ import {
370
+ type CodeHeaderProps,
371
+ MarkdownTextPrimitive,
372
+ unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
373
+ useIsMarkdownCodeBlock,
374
+ } from "@assistant-ui/react-markdown";
375
+ import remarkGfm from "remark-gfm";
376
+ import { type FC, memo, useState } from "react";
377
+ import { CheckIcon, CopyIcon } from "lucide-react";
378
+
379
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
380
+ import { cn } from "@/lib/utils";
381
+
382
+ const MarkdownTextImpl = () => {
383
+ return (
384
+ <MarkdownTextPrimitive
385
+ remarkPlugins={[remarkGfm]}
386
+ className="aui-md"
387
+ components={defaultComponents}
388
+ />
389
+ );
390
+ };
391
+
392
+ export const MarkdownText = memo(MarkdownTextImpl);
393
+
394
+ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
395
+ const { isCopied, copyToClipboard } = useCopyToClipboard();
396
+ const onCopy = () => {
397
+ if (!code || isCopied) return;
398
+ copyToClipboard(code);
399
+ };
400
+
401
+ return (
402
+ <div className="mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
403
+ <span className="lowercase [&>span]:text-xs">{language}</span>
404
+ <TooltipIconButton tooltip="Copy" onClick={onCopy}>
405
+ {!isCopied && <CopyIcon />}
406
+ {isCopied && <CheckIcon />}
407
+ </TooltipIconButton>
408
+ </div>
409
+ );
410
+ };
411
+
412
+ const useCopyToClipboard = ({
413
+ copiedDuration = 3000,
414
+ }: {
415
+ copiedDuration?: number;
416
+ } = {}) => {
417
+ const [isCopied, setIsCopied] = useState<boolean>(false);
418
+
419
+ const copyToClipboard = (value: string) => {
420
+ if (!value) return;
421
+
422
+ navigator.clipboard.writeText(value).then(() => {
423
+ setIsCopied(true);
424
+ setTimeout(() => setIsCopied(false), copiedDuration);
425
+ });
426
+ };
427
+
428
+ return { isCopied, copyToClipboard };
429
+ };
430
+
431
+ const defaultComponents = memoizeMarkdownComponents({
432
+ h1: ({ className, ...props }) => (
433
+ <h1
434
+ className={cn(
435
+ "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
436
+ className,
437
+ )}
438
+ {...props}
439
+ />
440
+ ),
441
+ h2: ({ className, ...props }) => (
442
+ <h2
443
+ className={cn(
444
+ "mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
445
+ className,
446
+ )}
447
+ {...props}
448
+ />
449
+ ),
450
+ h3: ({ className, ...props }) => (
451
+ <h3
452
+ className={cn(
453
+ "mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
454
+ className,
455
+ )}
456
+ {...props}
457
+ />
458
+ ),
459
+ h4: ({ className, ...props }) => (
460
+ <h4
461
+ className={cn(
462
+ "mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
463
+ className,
464
+ )}
465
+ {...props}
466
+ />
467
+ ),
468
+ h5: ({ className, ...props }) => (
469
+ <h5
470
+ className={cn(
471
+ "my-4 text-lg font-semibold first:mt-0 last:mb-0",
472
+ className,
473
+ )}
474
+ {...props}
475
+ />
476
+ ),
477
+ h6: ({ className, ...props }) => (
478
+ <h6
479
+ className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
480
+ {...props}
481
+ />
482
+ ),
483
+ p: ({ className, ...props }) => (
484
+ <p
485
+ className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)}
486
+ {...props}
487
+ />
488
+ ),
489
+ a: ({ className, ...props }) => (
490
+ <a
491
+ className={cn(
492
+ "text-primary font-medium underline underline-offset-4",
493
+ className,
494
+ )}
495
+ {...props}
496
+ />
497
+ ),
498
+ blockquote: ({ className, ...props }) => (
499
+ <blockquote
500
+ className={cn("border-l-2 pl-6 italic", className)}
501
+ {...props}
502
+ />
503
+ ),
504
+ ul: ({ className, ...props }) => (
505
+ <ul
506
+ className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
507
+ {...props}
508
+ />
509
+ ),
510
+ ol: ({ className, ...props }) => (
511
+ <ol
512
+ className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
513
+ {...props}
514
+ />
515
+ ),
516
+ hr: ({ className, ...props }) => (
517
+ <hr className={cn("my-5 border-b", className)} {...props} />
518
+ ),
519
+ table: ({ className, ...props }) => (
520
+ <table
521
+ className={cn(
522
+ "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
523
+ className,
524
+ )}
525
+ {...props}
526
+ />
527
+ ),
528
+ th: ({ className, ...props }) => (
529
+ <th
530
+ className={cn(
531
+ "bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right",
532
+ className,
533
+ )}
534
+ {...props}
535
+ />
536
+ ),
537
+ td: ({ className, ...props }) => (
538
+ <td
539
+ className={cn(
540
+ "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
541
+ className,
542
+ )}
543
+ {...props}
544
+ />
545
+ ),
546
+ tr: ({ className, ...props }) => (
547
+ <tr
548
+ className={cn(
549
+ "m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
550
+ className,
551
+ )}
552
+ {...props}
553
+ />
554
+ ),
555
+ sup: ({ className, ...props }) => (
556
+ <sup
557
+ className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
558
+ {...props}
559
+ />
560
+ ),
561
+ pre: ({ className, ...props }) => (
562
+ <pre
563
+ className={cn(
564
+ "overflow-x-auto !rounded-t-none rounded-b-lg bg-black p-4 text-white",
565
+ className,
566
+ )}
567
+ {...props}
568
+ />
569
+ ),
570
+ code: function Code({ className, ...props }) {
571
+ const isCodeBlock = useIsMarkdownCodeBlock();
572
+ return (
573
+ <code
574
+ className={cn(
575
+ !isCodeBlock && "bg-muted rounded border font-semibold",
576
+ className,
577
+ )}
578
+ {...props}
579
+ />
580
+ );
581
+ },
582
+ CodeHeader,
583
+ });
584
+
585
+ ```
586
+
587
+ ## components/assistant-ui/thread.tsx
588
+
589
+ ```tsx
590
+ import {
591
+ ThreadPrimitive,
592
+ ComposerPrimitive,
593
+ MessagePrimitive,
594
+ ActionBarPrimitive,
595
+ BranchPickerPrimitive,
596
+ ErrorPrimitive,
597
+ } from "@assistant-ui/react";
598
+ import type { FC } from "react";
599
+ import {
600
+ ArrowDownIcon,
601
+ ArrowUpIcon,
602
+ PlusIcon,
603
+ CopyIcon,
604
+ CheckIcon,
605
+ PencilIcon,
606
+ RefreshCwIcon,
607
+ ChevronLeftIcon,
608
+ ChevronRightIcon,
609
+ Square,
610
+ } from "lucide-react";
611
+
612
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
613
+ import { motion } from "framer-motion";
614
+ import { Button } from "@/components/ui/button";
615
+ import { cn } from "@/lib/utils";
616
+ import { MarkdownText } from "./markdown-text";
617
+ import { ToolFallback } from "./tool-fallback";
618
+
619
+ export const Thread: FC = () => {
620
+ return (
621
+ <ThreadPrimitive.Root
622
+ className="bg-background flex h-full flex-col"
623
+ style={{
624
+ ["--thread-max-width" as string]: "48rem",
625
+ ["--thread-padding-x" as string]: "1rem",
626
+ }}
627
+ >
628
+ <ThreadPrimitive.Viewport className="relative flex min-w-0 flex-1 flex-col gap-6 overflow-y-scroll">
629
+ <ThreadWelcome />
630
+
631
+ <ThreadPrimitive.Messages
632
+ components={{
633
+ UserMessage,
634
+ EditComposer,
635
+ AssistantMessage,
636
+ }}
637
+ />
638
+
639
+ <ThreadPrimitive.If empty={false}>
640
+ <motion.div className="min-h-6 min-w-6 shrink-0" />
641
+ </ThreadPrimitive.If>
642
+ </ThreadPrimitive.Viewport>
643
+
644
+ <Composer />
645
+ </ThreadPrimitive.Root>
646
+ );
647
+ };
648
+
649
+ const ThreadScrollToBottom: FC = () => {
650
+ return (
651
+ <ThreadPrimitive.ScrollToBottom asChild>
652
+ <TooltipIconButton
653
+ tooltip="Scroll to bottom"
654
+ variant="outline"
655
+ className="dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
656
+ >
657
+ <ArrowDownIcon />
658
+ </TooltipIconButton>
659
+ </ThreadPrimitive.ScrollToBottom>
660
+ );
661
+ };
662
+
663
+ const ThreadWelcome: FC = () => {
664
+ return (
665
+ <ThreadPrimitive.Empty>
666
+ <div className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col px-[var(--thread-padding-x)]">
667
+ <div className="flex w-full flex-grow flex-col items-center justify-center">
668
+ <div className="flex size-full flex-col justify-center px-8 md:mt-20">
669
+ <motion.div
670
+ initial={{ opacity: 0, y: 10 }}
671
+ animate={{ opacity: 1, y: 0 }}
672
+ exit={{ opacity: 0, y: 10 }}
673
+ transition={{ delay: 0.5 }}
674
+ className="text-2xl font-semibold"
675
+ >
676
+ Hello there!
677
+ </motion.div>
678
+ <motion.div
679
+ initial={{ opacity: 0, y: 10 }}
680
+ animate={{ opacity: 1, y: 0 }}
681
+ exit={{ opacity: 0, y: 10 }}
682
+ transition={{ delay: 0.6 }}
683
+ className="text-muted-foreground/65 text-2xl"
684
+ >
685
+ How can I help you today?
686
+ </motion.div>
687
+ </div>
688
+ </div>
689
+ </div>
690
+ </ThreadPrimitive.Empty>
691
+ );
692
+ };
693
+
694
+ const ThreadWelcomeSuggestions: FC = () => {
695
+ return (
696
+ <div className="grid w-full gap-2 sm:grid-cols-2">
697
+ {[
698
+ {
699
+ title: "What are the advantages",
700
+ label: "of using Assistant Cloud?",
701
+ action: "What are the advantages of using Assistant Cloud?",
702
+ },
703
+ {
704
+ title: "Write code to",
705
+ label: `demonstrate topological sorting`,
706
+ action: `Write code to demonstrate topological sorting`,
707
+ },
708
+ {
709
+ title: "Help me write an essay",
710
+ label: `about AI chat applications`,
711
+ action: `Help me write an essay about AI chat applications`,
712
+ },
713
+ {
714
+ title: "What is the weather",
715
+ label: "in San Francisco?",
716
+ action: "What is the weather in San Francisco?",
717
+ },
718
+ ].map((suggestedAction, index) => (
719
+ <motion.div
720
+ initial={{ opacity: 0, y: 20 }}
721
+ animate={{ opacity: 1, y: 0 }}
722
+ exit={{ opacity: 0, y: 20 }}
723
+ transition={{ delay: 0.05 * index }}
724
+ key={`suggested-action-${suggestedAction.title}-${index}`}
725
+ className="[&:nth-child(n+3)]:hidden sm:[&:nth-child(n+3)]:block"
726
+ >
727
+ <ThreadPrimitive.Suggestion
728
+ prompt={suggestedAction.action}
729
+ method="replace"
730
+ autoSend
731
+ asChild
732
+ >
733
+ <Button
734
+ variant="ghost"
735
+ className="dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-xl border px-4 py-3.5 text-left text-sm sm:flex-col"
736
+ aria-label={suggestedAction.action}
737
+ >
738
+ <span className="font-medium">{suggestedAction.title}</span>
739
+ <p className="text-muted-foreground">{suggestedAction.label}</p>
740
+ </Button>
741
+ </ThreadPrimitive.Suggestion>
742
+ </motion.div>
743
+ ))}
744
+ </div>
745
+ );
746
+ };
747
+
748
+ const Composer: FC = () => {
749
+ return (
750
+ <div className="bg-background relative mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-[var(--thread-padding-x)] pb-4 md:pb-6">
751
+ <ThreadScrollToBottom />
752
+ <ThreadPrimitive.Empty>
753
+ <ThreadWelcomeSuggestions />
754
+ </ThreadPrimitive.Empty>
755
+ <ComposerPrimitive.Root className="relative flex w-full flex-col rounded-2xl focus-within:ring-2 focus-within:ring-black focus-within:ring-offset-2 dark:focus-within:ring-white">
756
+ <ComposerPrimitive.Input
757
+ placeholder="Send a message..."
758
+ className="bg-muted border-border dark:border-muted-foreground/15 focus:outline-primary placeholder:text-muted-foreground max-h-[calc(50dvh)] min-h-16 w-full resize-none rounded-t-2xl border-x border-t px-4 pb-3 pt-2 text-base outline-none"
759
+ rows={1}
760
+ autoFocus
761
+ aria-label="Message input"
762
+ />
763
+ <ComposerAction />
764
+ </ComposerPrimitive.Root>
765
+ </div>
766
+ );
767
+ };
768
+
769
+ const ComposerAction: FC = () => {
770
+ return (
771
+ <div className="bg-muted border-border dark:border-muted-foreground/15 relative flex items-center justify-between rounded-b-2xl border-x border-b p-2">
772
+ <TooltipIconButton
773
+ tooltip="Attach file"
774
+ variant="ghost"
775
+ className="hover:bg-foreground/15 dark:hover:bg-background/50 scale-115 p-3.5"
776
+ onClick={() => {
777
+ console.log("Attachment clicked - not implemented");
778
+ }}
779
+ >
780
+ <PlusIcon />
781
+ </TooltipIconButton>
782
+
783
+ <ThreadPrimitive.If running={false}>
784
+ <ComposerPrimitive.Send asChild>
785
+ <Button
786
+ type="submit"
787
+ variant="default"
788
+ className="dark:border-muted-foreground/90 border-muted-foreground/60 hover:bg-primary/75 size-8 rounded-full border"
789
+ aria-label="Send message"
790
+ >
791
+ <ArrowUpIcon className="size-5" />
792
+ </Button>
793
+ </ComposerPrimitive.Send>
794
+ </ThreadPrimitive.If>
795
+
796
+ <ThreadPrimitive.If running>
797
+ <ComposerPrimitive.Cancel asChild>
798
+ <Button
799
+ type="button"
800
+ variant="default"
801
+ className="dark:border-muted-foreground/90 border-muted-foreground/60 hover:bg-primary/75 size-8 rounded-full border"
802
+ aria-label="Stop generating"
803
+ >
804
+ <Square className="size-3.5 fill-white dark:size-4 dark:fill-black" />
805
+ </Button>
806
+ </ComposerPrimitive.Cancel>
807
+ </ThreadPrimitive.If>
808
+ </div>
809
+ );
810
+ };
811
+
812
+ const MessageError: FC = () => {
813
+ return (
814
+ <MessagePrimitive.Error>
815
+ <ErrorPrimitive.Root className="border-destructive bg-destructive/10 dark:bg-destructive/5 text-destructive mt-2 rounded-md border p-3 text-sm dark:text-red-200">
816
+ <ErrorPrimitive.Message className="line-clamp-2" />
817
+ </ErrorPrimitive.Root>
818
+ </MessagePrimitive.Error>
819
+ );
820
+ };
821
+
822
+ const AssistantMessage: FC = () => {
823
+ return (
824
+ <MessagePrimitive.Root asChild>
825
+ <motion.div
826
+ className="relative mx-auto grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] px-[var(--thread-padding-x)] py-4"
827
+ initial={{ y: 5, opacity: 0 }}
828
+ animate={{ y: 0, opacity: 1 }}
829
+ data-role="assistant"
830
+ >
831
+ <div className="ring-border bg-background col-start-1 row-start-1 flex size-8 shrink-0 items-center justify-center rounded-full ring-1">
832
+ <StarIcon size={14} />
833
+ </div>
834
+
835
+ <div className="text-foreground col-span-2 col-start-2 row-start-1 ml-4 break-words leading-7">
836
+ <MessagePrimitive.Content
837
+ components={{
838
+ Text: MarkdownText,
839
+ tools: { Fallback: ToolFallback },
840
+ }}
841
+ />
842
+ <MessageError />
843
+ </div>
844
+
845
+ <AssistantActionBar />
846
+
847
+ <BranchPicker className="col-start-2 row-start-2 -ml-2 mr-2" />
848
+ </motion.div>
849
+ </MessagePrimitive.Root>
850
+ );
851
+ };
852
+
853
+ const AssistantActionBar: FC = () => {
854
+ return (
855
+ <ActionBarPrimitive.Root
856
+ hideWhenRunning
857
+ autohide="not-last"
858
+ autohideFloat="single-branch"
859
+ className="text-muted-foreground data-floating:bg-background data-floating:absolute data-floating:mt-2 data-floating:rounded-md data-floating:border data-floating:p-1 data-floating:shadow-sm col-start-3 row-start-2 ml-3 mt-3 flex gap-1"
860
+ >
861
+ <ActionBarPrimitive.Copy asChild>
862
+ <TooltipIconButton tooltip="Copy">
863
+ <MessagePrimitive.If copied>
864
+ <CheckIcon />
865
+ </MessagePrimitive.If>
866
+ <MessagePrimitive.If copied={false}>
867
+ <CopyIcon />
868
+ </MessagePrimitive.If>
869
+ </TooltipIconButton>
870
+ </ActionBarPrimitive.Copy>
871
+ <ActionBarPrimitive.Reload asChild>
872
+ <TooltipIconButton tooltip="Refresh">
873
+ <RefreshCwIcon />
874
+ </TooltipIconButton>
875
+ </ActionBarPrimitive.Reload>
876
+ </ActionBarPrimitive.Root>
877
+ );
878
+ };
879
+
880
+ const UserMessage: FC = () => {
881
+ return (
882
+ <MessagePrimitive.Root asChild>
883
+ <motion.div
884
+ className="mx-auto grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-1 px-[var(--thread-padding-x)] py-4 [&:where(>*)]:col-start-2"
885
+ initial={{ y: 5, opacity: 0 }}
886
+ animate={{ y: 0, opacity: 1 }}
887
+ data-role="user"
888
+ >
889
+ <UserActionBar />
890
+
891
+ <div className="bg-muted text-foreground col-start-2 break-words rounded-3xl px-5 py-2.5">
892
+ <MessagePrimitive.Content components={{ Text: MarkdownText }} />
893
+ </div>
894
+
895
+ <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
896
+ </motion.div>
897
+ </MessagePrimitive.Root>
898
+ );
899
+ };
900
+
901
+ const UserActionBar: FC = () => {
902
+ return (
903
+ <ActionBarPrimitive.Root
904
+ hideWhenRunning
905
+ autohide="not-last"
906
+ className="col-start-1 mr-3 mt-2.5 flex flex-col items-end"
907
+ >
908
+ <ActionBarPrimitive.Edit asChild>
909
+ <TooltipIconButton tooltip="Edit">
910
+ <PencilIcon />
911
+ </TooltipIconButton>
912
+ </ActionBarPrimitive.Edit>
913
+ </ActionBarPrimitive.Root>
914
+ );
915
+ };
916
+
917
+ const EditComposer: FC = () => {
918
+ return (
919
+ <div className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-[var(--thread-padding-x)]">
920
+ <ComposerPrimitive.Root className="bg-muted max-w-7/8 ml-auto flex w-full flex-col rounded-xl">
921
+ <ComposerPrimitive.Input
922
+ className="text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
923
+ autoFocus
924
+ />
925
+
926
+ <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
927
+ <ComposerPrimitive.Cancel asChild>
928
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
929
+ Cancel
930
+ </Button>
931
+ </ComposerPrimitive.Cancel>
932
+ <ComposerPrimitive.Send asChild>
933
+ <Button size="sm" aria-label="Update message">
934
+ Update
935
+ </Button>
936
+ </ComposerPrimitive.Send>
937
+ </div>
938
+ </ComposerPrimitive.Root>
939
+ </div>
940
+ );
941
+ };
942
+
943
+ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
944
+ className,
945
+ ...rest
946
+ }) => {
947
+ return (
948
+ <BranchPickerPrimitive.Root
949
+ hideWhenSingleBranch
950
+ className={cn(
951
+ "text-muted-foreground inline-flex items-center text-xs",
952
+ className,
953
+ )}
954
+ {...rest}
955
+ >
956
+ <BranchPickerPrimitive.Previous asChild>
957
+ <TooltipIconButton tooltip="Previous">
958
+ <ChevronLeftIcon />
959
+ </TooltipIconButton>
960
+ </BranchPickerPrimitive.Previous>
961
+ <span className="font-medium">
962
+ <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
963
+ </span>
964
+ <BranchPickerPrimitive.Next asChild>
965
+ <TooltipIconButton tooltip="Next">
966
+ <ChevronRightIcon />
967
+ </TooltipIconButton>
968
+ </BranchPickerPrimitive.Next>
969
+ </BranchPickerPrimitive.Root>
970
+ );
971
+ };
972
+
973
+ const StarIcon = ({ size = 14 }: { size?: number }) => (
974
+ <svg
975
+ width={size}
976
+ height={size}
977
+ viewBox="0 0 16 16"
978
+ fill="none"
979
+ xmlns="http://www.w3.org/2000/svg"
980
+ >
981
+ <path
982
+ d="M8 0L9.79611 6.20389L16 8L9.79611 9.79611L8 16L6.20389 9.79611L0 8L6.20389 6.20389L8 0Z"
983
+ fill="currentColor"
984
+ />
985
+ </svg>
986
+ );
987
+
988
+ ```
989
+
990
+ ## components/assistant-ui/tool-fallback.tsx
991
+
992
+ ```tsx
993
+ import { ToolCallMessagePartComponent } from "@assistant-ui/react";
994
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
995
+ import { useState } from "react";
996
+ import { Button } from "../ui/button";
997
+
998
+ export const ToolFallback: ToolCallMessagePartComponent = ({
999
+ toolName,
1000
+ argsText,
1001
+ result,
1002
+ }) => {
1003
+ const [isCollapsed, setIsCollapsed] = useState(true);
1004
+ return (
1005
+ <div className="mb-4 flex w-full flex-col gap-3 rounded-lg border py-3">
1006
+ <div className="flex items-center gap-2 px-4">
1007
+ <CheckIcon className="size-4" />
1008
+ <p className="flex-grow">
1009
+ Used tool: <b>{toolName}</b>
1010
+ </p>
1011
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1012
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1013
+ </Button>
1014
+ </div>
1015
+ {!isCollapsed && (
1016
+ <div className="flex flex-col gap-2 border-t pt-2">
1017
+ <div className="px-4">
1018
+ <pre className="whitespace-pre-wrap">{argsText}</pre>
1019
+ </div>
1020
+ {result !== undefined && (
1021
+ <div className="border-t border-dashed px-4 pt-2">
1022
+ <p className="font-semibold">Result:</p>
1023
+ <pre className="whitespace-pre-wrap">
1024
+ {typeof result === "string"
1025
+ ? result
1026
+ : JSON.stringify(result, null, 2)}
1027
+ </pre>
1028
+ </div>
1029
+ )}
1030
+ </div>
1031
+ )}
1032
+ </div>
1033
+ );
1034
+ };
1035
+
1036
+ ```
1037
+
1038
+ ## components/assistant-ui/tooltip-icon-button.tsx
1039
+
1040
+ ```tsx
1041
+ "use client";
1042
+
1043
+ import { ComponentPropsWithoutRef, forwardRef } from "react";
1044
+ import { Slottable } from "@radix-ui/react-slot";
1045
+
1046
+ import {
1047
+ Tooltip,
1048
+ TooltipContent,
1049
+ TooltipTrigger,
1050
+ } from "@/components/ui/tooltip";
1051
+ import { Button } from "@/components/ui/button";
1052
+ import { cn } from "@/lib/utils";
1053
+
1054
+ export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1055
+ tooltip: string;
1056
+ side?: "top" | "bottom" | "left" | "right";
1057
+ };
1058
+
1059
+ export const TooltipIconButton = forwardRef<
1060
+ HTMLButtonElement,
1061
+ TooltipIconButtonProps
1062
+ >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
1063
+ return (
1064
+ <Tooltip>
1065
+ <TooltipTrigger asChild>
1066
+ <Button
1067
+ variant="ghost"
1068
+ size="icon"
1069
+ {...rest}
1070
+ className={cn("size-6 p-1", className)}
1071
+ ref={ref}
1072
+ >
1073
+ <Slottable>{children}</Slottable>
1074
+ <span className="sr-only">{tooltip}</span>
1075
+ </Button>
1076
+ </TooltipTrigger>
1077
+ <TooltipContent side={side}>{tooltip}</TooltipContent>
1078
+ </Tooltip>
1079
+ );
1080
+ });
1081
+
1082
+ TooltipIconButton.displayName = "TooltipIconButton";
1083
+
1084
+ ```
1085
+
1086
+ ## components/ui/button.tsx
1087
+
1088
+ ```tsx
1089
+ import * as React from "react";
1090
+ import { Slot } from "@radix-ui/react-slot";
1091
+ import { cva, type VariantProps } from "class-variance-authority";
1092
+
1093
+ import { cn } from "@/lib/utils";
1094
+
1095
+ const buttonVariants = cva(
1096
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
1097
+ {
1098
+ variants: {
1099
+ variant: {
1100
+ default:
1101
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
1102
+ destructive:
1103
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1104
+ outline:
1105
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
1106
+ secondary:
1107
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
1108
+ ghost:
1109
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1110
+ link: "text-primary underline-offset-4 hover:underline",
1111
+ },
1112
+ size: {
1113
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
1114
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
1115
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1116
+ icon: "size-9",
1117
+ },
1118
+ },
1119
+ defaultVariants: {
1120
+ variant: "default",
1121
+ size: "default",
1122
+ },
1123
+ },
1124
+ );
1125
+
1126
+ function Button({
1127
+ className,
1128
+ variant,
1129
+ size,
1130
+ asChild = false,
1131
+ ...props
1132
+ }: React.ComponentProps<"button"> &
1133
+ VariantProps<typeof buttonVariants> & {
1134
+ asChild?: boolean;
1135
+ }) {
1136
+ const Comp = asChild ? Slot : "button";
1137
+
1138
+ return (
1139
+ <Comp
1140
+ data-slot="button"
1141
+ className={cn(buttonVariants({ variant, size, className }))}
1142
+ {...props}
1143
+ />
1144
+ );
1145
+ }
1146
+
1147
+ export { Button, buttonVariants };
1148
+
1149
+ ```
1150
+
1151
+ ## components/ui/tooltip.tsx
1152
+
1153
+ ```tsx
1154
+ "use client";
1155
+
1156
+ import * as React from "react";
1157
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1158
+
1159
+ import { cn } from "@/lib/utils";
1160
+
1161
+ function TooltipProvider({
1162
+ delayDuration = 0,
1163
+ ...props
1164
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1165
+ return (
1166
+ <TooltipPrimitive.Provider
1167
+ data-slot="tooltip-provider"
1168
+ delayDuration={delayDuration}
1169
+ {...props}
1170
+ />
1171
+ );
1172
+ }
1173
+
1174
+ function Tooltip({
1175
+ ...props
1176
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1177
+ return (
1178
+ <TooltipProvider>
1179
+ <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1180
+ </TooltipProvider>
1181
+ );
1182
+ }
1183
+
1184
+ function TooltipTrigger({
1185
+ ...props
1186
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
1187
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
1188
+ }
1189
+
1190
+ function TooltipContent({
1191
+ className,
1192
+ sideOffset = 0,
1193
+ children,
1194
+ ...props
1195
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
1196
+ return (
1197
+ <TooltipPrimitive.Portal>
1198
+ <TooltipPrimitive.Content
1199
+ data-slot="tooltip-content"
1200
+ sideOffset={sideOffset}
1201
+ className={cn(
1202
+ "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin) z-50 w-fit text-balance rounded-md px-3 py-1.5 text-xs",
1203
+ className,
1204
+ )}
1205
+ {...props}
1206
+ >
1207
+ {children}
1208
+ <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1209
+ </TooltipPrimitive.Content>
1210
+ </TooltipPrimitive.Portal>
1211
+ );
1212
+ }
1213
+
1214
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1215
+
1216
+ ```
1217
+
1218
+ ## eslint.config.ts
1219
+
1220
+ ```typescript
1221
+ export { default } from "@assistant-ui/x-buildutils/eslint";
1222
+
1223
+ ```
1224
+
1225
+ ## lib/utils.ts
1226
+
1227
+ ```typescript
1228
+ import { clsx, type ClassValue } from "clsx";
1229
+ import { twMerge } from "tailwind-merge";
1230
+
1231
+ export function cn(...inputs: ClassValue[]) {
1232
+ return twMerge(clsx(inputs));
1233
+ }
1234
+
1235
+ ```
1236
+
1237
+ ## next.config.js
1238
+
1239
+ ```javascript
1240
+ /** @type {import('next').NextConfig} */
1241
+ const nextConfig = {
1242
+ experimental: {
1243
+ optimizePackageImports: ["@assistant-ui/react"],
1244
+ },
1245
+ async rewrites() {
1246
+ return [
1247
+ {
1248
+ source: "/assistant/:path*",
1249
+ destination: "http://localhost:8000/assistant/:path*",
1250
+ },
1251
+ ];
1252
+ },
1253
+ };
1254
+
1255
+ export default nextConfig;
1256
+
1257
+ ```
1258
+
1259
+ ## package.json
1260
+
1261
+ ```json
1262
+ {
1263
+ "name": "example-with-assistant-transport",
1264
+ "private": true,
1265
+ "version": "0.0.0",
1266
+ "type": "module",
1267
+ "dependencies": {
1268
+ "@assistant-ui/react": "workspace:^",
1269
+ "@assistant-ui/react-langgraph": "workspace:^",
1270
+ "@assistant-ui/react-markdown": "workspace:^",
1271
+ "@radix-ui/react-slot": "^1.2.3",
1272
+ "@radix-ui/react-tooltip": "^1.2.8",
1273
+ "@tailwindcss/postcss": "^4.1.13",
1274
+ "assistant-stream": "^0.2.30",
1275
+ "class-variance-authority": "^0.7.1",
1276
+ "clsx": "^2.1.1",
1277
+ "framer-motion": "^12.23.22",
1278
+ "lucide-react": "^0.544.0",
1279
+ "next": "15.5.4",
1280
+ "postcss": "^8.5.6",
1281
+ "react": "19.1.1",
1282
+ "react-dom": "19.1.1",
1283
+ "remark-gfm": "^4.0.1",
1284
+ "tailwind-merge": "^3.3.1",
1285
+ "tailwindcss": "^4.1.13",
1286
+ "tailwindcss-animate": "^1.0.7",
1287
+ "zod": "^4.1.11"
1288
+ },
1289
+ "devDependencies": {
1290
+ "@assistant-ui/x-buildutils": "workspace:*",
1291
+ "@types/node": "^24.5.2",
1292
+ "@types/react": "^19.1.15",
1293
+ "@types/react-dom": "^19.1.9",
1294
+ "eslint": "^9",
1295
+ "eslint-config-next": "15.5.4",
1296
+ "tw-animate-css": "^1.4.0",
1297
+ "typescript": "^5.9.2"
1298
+ },
1299
+ "scripts": {
1300
+ "dev": "next dev",
1301
+ "build": "next build",
1302
+ "start": "next start",
1303
+ "lint": "eslint ."
1304
+ }
1305
+ }
1306
+
1307
+ ```
1308
+
1309
+ ## postcss.config.js
1310
+
1311
+ ```javascript
1312
+ const config = {
1313
+ plugins: {
1314
+ "@tailwindcss/postcss": {},
1315
+ },
1316
+ };
1317
+
1318
+ export default config;
1319
+
1320
+ ```
1321
+
1322
+ ## README.md
1323
+
1324
+ ```markdown
1325
+ # Assistant Transport Example
1326
+
1327
+ This example demonstrates how to use assistant-ui with the `useAssistantTransportRuntime` hook to connect to a custom backend server that implements the assistant-transport protocol.
1328
+
1329
+ ## Overview
1330
+
1331
+ The Assistant Transport runtime allows you to connect assistant-ui to any backend server that can handle:
1332
+
1333
+ - `AddMessageCommand` - for sending user messages
1334
+ - `AddToolResultCommand` - for sending tool execution results
1335
+ - Streaming responses using the `assistant-stream` format
1336
+
1337
+ ## Prerequisites
1338
+
1339
+ Before running this example, you'll need:
1340
+
1341
+ 1. A backend server that implements the assistant-transport protocol
1342
+ 2. Node.js 18+ installed
1343
+ 3. pnpm package manager
1344
+
1345
+ ## Getting Started
1346
+
1347
+ ### 1. Install Dependencies
1348
+
1349
+ ```bash
1350
+ pnpm install
1351
+ ```
1352
+
1353
+ ### 2. Set Up Environment Variables
1354
+
1355
+ Copy the example environment file:
1356
+
1357
+ ```bash
1358
+ cp .env.local.example .env.local
1359
+ ```
1360
+
1361
+ Update the `NEXT_PUBLIC_API_URL` in `.env.local` to point to your backend server:
1362
+
1363
+ ```env
1364
+ NEXT_PUBLIC_API_URL=http://localhost:8000/assistant
1365
+ ```
1366
+
1367
+ ### 3. Start the Development Server
1368
+
1369
+ ```bash
1370
+ pnpm dev
1371
+ ```
1372
+
1373
+ The application will be available at [http://localhost:3000](http://localhost:3000).
1374
+
1375
+ ## Backend Server Requirements
1376
+
1377
+ Your backend server should:
1378
+
1379
+ 1. Accept POST requests at the configured endpoint (e.g., `/assistant`)
1380
+ 2. Handle the following command types in the request body:
1381
+ - `AddMessageCommand`: `{ type: "add-message", message: { role: "user", parts: [...] } }`
1382
+ - `AddToolResultCommand`: `{ type: "add-tool-result", toolCallId: string, result: object }`
1383
+ 3. Return streaming responses using the `assistant-stream` format
1384
+ 4. Include CORS headers to allow requests from the frontend
1385
+
1386
+ ### Example Request Format
1387
+
1388
+ ```json
1389
+ {
1390
+ "commands": [
1391
+ {
1392
+ "type": "add-message",
1393
+ "message": {
1394
+ "role": "user",
1395
+ "parts": [
1396
+ {
1397
+ "type": "text",
1398
+ "text": "Hello, how are you?"
1399
+ }
1400
+ ]
1401
+ }
1402
+ }
1403
+ ],
1404
+ "system": "You are a helpful assistant",
1405
+ "tools": {
1406
+ "get_weather": {
1407
+ "description": "Get weather information",
1408
+ "parameters": {
1409
+ "type": "object",
1410
+ "properties": {
1411
+ "location": { "type": "string" }
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1416
+ }
1417
+ ```
1418
+
1419
+ ## Project Structure
1420
+
1421
+ ```
1422
+ examples/with-assistant-transport/
1423
+ ├── app/
1424
+ │ ├── globals.css # Global styles with Tailwind CSS
1425
+ │ ├── layout.tsx # Root layout component
1426
+ │ ├── MyRuntimeProvider.tsx # Custom runtime provider using useAssistantTransportRuntime
1427
+ │ └── page.tsx # Main page component
1428
+ ├── components/
1429
+ │ └── assistant-ui/
1430
+ │ └── thread.tsx # Thread component for the chat interface
1431
+ ├── package.json # Project dependencies
1432
+ ├── tailwind.config.js # Tailwind CSS configuration
1433
+ ├── tsconfig.json # TypeScript configuration
1434
+ ├── next.config.js # Next.js configuration
1435
+ └── README.md # This file
1436
+ ```
1437
+
1438
+ ## Key Features
1439
+
1440
+ - **Custom Runtime**: Uses `useAssistantTransportRuntime` to connect to any backend
1441
+ - **Streaming Support**: Handles real-time streaming responses from the server
1442
+ - **Tool Support**: Supports tool calling between frontend and backend
1443
+ - **Error Handling**: Includes proper error handling and loading states
1444
+ - **Modern UI**: Built with Tailwind CSS and Radix UI components
1445
+
1446
+ ## Backend Examples
1447
+
1448
+ For a complete working backend example, check out:
1449
+
1450
+ - `examples/ultrathink` - Python FastAPI server with assistant-stream integration
1451
+
1452
+ ## Customization
1453
+
1454
+ ### Modifying the Runtime Configuration
1455
+
1456
+ Edit `app/MyRuntimeProvider.tsx` to customize:
1457
+
1458
+ - **API Endpoint**: Change the `api` URL
1459
+ - **Headers**: Add authentication or other headers
1460
+ - **Body Parameters**: Add additional request parameters
1461
+ - **Event Handlers**: Customize response, error, and completion handling
1462
+ - **State Converter**: Modify how backend state is converted to frontend state
1463
+
1464
+ ### Styling
1465
+
1466
+ The project uses Tailwind CSS for styling. Modify `app/globals.css` and `tailwind.config.js` to customize the appearance.
1467
+
1468
+ ## Troubleshooting
1469
+
1470
+ ### Backend Connection Issues
1471
+
1472
+ 1. Ensure your backend server is running and accessible
1473
+ 2. Check CORS configuration on your backend
1474
+ 3. Verify the API endpoint URL in your `.env.local` file
1475
+ 4. Check the browser console for network errors
1476
+
1477
+ ### Runtime Errors
1478
+
1479
+ 1. Verify the backend response format matches assistant-stream expectations
1480
+ 2. Check that the state converter function properly transforms your backend state
1481
+ 3. Ensure all required dependencies are installed
1482
+
1483
+ ## Learn More
1484
+
1485
+ - [assistant-ui Documentation](https://docs.assistant-ui.com)
1486
+ - [Assistant Transport Runtime API](https://docs.assistant-ui.com/runtimes/assistant-transport)
1487
+ - [Next.js Documentation](https://nextjs.org/docs)
1488
+
1489
+ ```
1490
+
1491
+ ## tailwind.config.js
1492
+
1493
+ ```javascript
1494
+ /** @type {import('tailwindcss').Config} */
1495
+ const config = {
1496
+ darkMode: ["class"],
1497
+ content: [
1498
+ "./pages/**/*.{ts,tsx}",
1499
+ "./components/**/*.{ts,tsx}",
1500
+ "./app/**/*.{ts,tsx}",
1501
+ "./src/**/*.{ts,tsx}",
1502
+ ],
1503
+ prefix: "",
1504
+ theme: {
1505
+ container: {
1506
+ center: true,
1507
+ padding: "2rem",
1508
+ screens: {
1509
+ "2xl": "1400px",
1510
+ },
1511
+ },
1512
+ extend: {
1513
+ colors: {
1514
+ border: "hsl(var(--border))",
1515
+ input: "hsl(var(--input))",
1516
+ ring: "hsl(var(--ring))",
1517
+ background: "hsl(var(--background))",
1518
+ foreground: "hsl(var(--foreground))",
1519
+ primary: {
1520
+ DEFAULT: "hsl(var(--primary))",
1521
+ foreground: "hsl(var(--primary-foreground))",
1522
+ },
1523
+ secondary: {
1524
+ DEFAULT: "hsl(var(--secondary))",
1525
+ foreground: "hsl(var(--secondary-foreground))",
1526
+ },
1527
+ destructive: {
1528
+ DEFAULT: "hsl(var(--destructive))",
1529
+ foreground: "hsl(var(--destructive-foreground))",
1530
+ },
1531
+ muted: {
1532
+ DEFAULT: "hsl(var(--muted))",
1533
+ foreground: "hsl(var(--muted-foreground))",
1534
+ },
1535
+ accent: {
1536
+ DEFAULT: "hsl(var(--accent))",
1537
+ foreground: "hsl(var(--accent-foreground))",
1538
+ },
1539
+ popover: {
1540
+ DEFAULT: "hsl(var(--popover))",
1541
+ foreground: "hsl(var(--popover-foreground))",
1542
+ },
1543
+ card: {
1544
+ DEFAULT: "hsl(var(--card))",
1545
+ foreground: "hsl(var(--card-foreground))",
1546
+ },
1547
+ },
1548
+ borderRadius: {
1549
+ lg: "var(--radius)",
1550
+ md: "calc(var(--radius) - 2px)",
1551
+ sm: "calc(var(--radius) - 4px)",
1552
+ },
1553
+ keyframes: {
1554
+ "accordion-down": {
1555
+ from: { height: "0" },
1556
+ to: { height: "var(--radix-accordion-content-height)" },
1557
+ },
1558
+ "accordion-up": {
1559
+ from: { height: "var(--radix-accordion-content-height)" },
1560
+ to: { height: "0" },
1561
+ },
1562
+ },
1563
+ animation: {
1564
+ "accordion-down": "accordion-down 0.2s ease-out",
1565
+ "accordion-up": "accordion-up 0.2s ease-out",
1566
+ },
1567
+ },
1568
+ },
1569
+ plugins: [],
1570
+ };
1571
+
1572
+ export default config;
1573
+
1574
+ ```
1575
+
1576
+ ## tsconfig.json
1577
+
1578
+ ```json
1579
+ {
1580
+ "extends": "@assistant-ui/x-buildutils/ts/base",
1581
+ "compilerOptions": {
1582
+ "paths": {
1583
+ "@/*": ["./*"]
1584
+ },
1585
+ "allowJs": true,
1586
+ "incremental": true,
1587
+ "jsx": "preserve",
1588
+ "plugins": [
1589
+ {
1590
+ "name": "next"
1591
+ }
1592
+ ]
1593
+ },
1594
+ "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1595
+ "exclude": ["node_modules", "dist"]
1596
+ }
1597
+
1598
+ ```
1599
+