@assistant-ui/mcp-docs-server 0.1.6 → 0.1.8

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 (47) hide show
  1. package/.docs/organized/code-examples/with-ai-sdk-v5.md +15 -13
  2. package/.docs/organized/code-examples/with-cloud.md +19 -25
  3. package/.docs/organized/code-examples/with-external-store.md +9 -7
  4. package/.docs/organized/code-examples/with-ffmpeg.md +21 -21
  5. package/.docs/organized/code-examples/with-langgraph.md +72 -46
  6. package/.docs/organized/code-examples/with-parent-id-grouping.md +9 -7
  7. package/.docs/organized/code-examples/with-react-hook-form.md +19 -21
  8. package/.docs/raw/docs/api-reference/integrations/react-data-stream.mdx +194 -0
  9. package/.docs/raw/docs/api-reference/overview.mdx +7 -4
  10. package/.docs/raw/docs/api-reference/primitives/Composer.mdx +31 -0
  11. package/.docs/raw/docs/api-reference/primitives/Message.mdx +108 -3
  12. package/.docs/raw/docs/api-reference/primitives/Thread.mdx +59 -0
  13. package/.docs/raw/docs/api-reference/primitives/ThreadList.mdx +128 -0
  14. package/.docs/raw/docs/api-reference/primitives/ThreadListItem.mdx +160 -0
  15. package/.docs/raw/docs/api-reference/runtimes/AssistantRuntime.mdx +0 -11
  16. package/.docs/raw/docs/api-reference/runtimes/ComposerRuntime.mdx +3 -3
  17. package/.docs/raw/docs/copilots/assistant-frame.mdx +397 -0
  18. package/.docs/raw/docs/getting-started.mdx +53 -52
  19. package/.docs/raw/docs/guides/Attachments.mdx +7 -115
  20. package/.docs/raw/docs/guides/ToolUI.mdx +3 -3
  21. package/.docs/raw/docs/guides/Tools.mdx +152 -92
  22. package/.docs/raw/docs/guides/context-api.mdx +574 -0
  23. package/.docs/raw/docs/migrations/v0-12.mdx +125 -0
  24. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +134 -55
  25. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +182 -0
  26. package/.docs/raw/docs/runtimes/custom/local.mdx +16 -3
  27. package/.docs/raw/docs/runtimes/data-stream.mdx +287 -0
  28. package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -1
  29. package/.docs/raw/docs/runtimes/langserve.mdx +9 -11
  30. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +5 -0
  31. package/.docs/raw/docs/ui/ThreadList.mdx +54 -16
  32. package/dist/{chunk-L4K23SWI.js → chunk-NVNFQ5ZO.js} +4 -1
  33. package/dist/index.js +1 -1
  34. package/dist/prepare-docs/prepare.js +1 -1
  35. package/dist/stdio.js +1 -1
  36. package/package.json +7 -7
  37. package/.docs/organized/code-examples/local-ollama.md +0 -1135
  38. package/.docs/organized/code-examples/search-agent-for-e-commerce.md +0 -1721
  39. package/.docs/organized/code-examples/with-ai-sdk.md +0 -1082
  40. package/.docs/organized/code-examples/with-openai-assistants.md +0 -1175
  41. package/.docs/raw/docs/concepts/architecture.mdx +0 -19
  42. package/.docs/raw/docs/concepts/runtime-layer.mdx +0 -163
  43. package/.docs/raw/docs/concepts/why.mdx +0 -9
  44. package/.docs/raw/docs/runtimes/ai-sdk/rsc.mdx +0 -226
  45. package/.docs/raw/docs/runtimes/ai-sdk/use-assistant-hook.mdx +0 -195
  46. package/.docs/raw/docs/runtimes/ai-sdk/use-chat-hook.mdx +0 -138
  47. package/.docs/raw/docs/runtimes/ai-sdk/use-chat-v5.mdx +0 -212
@@ -1,1175 +0,0 @@
1
- # Example: with-openai-assistants
2
-
3
- ## app/api/assistant/route.ts
4
-
5
- ```typescript
6
- import { AssistantResponse } from "ai";
7
- import OpenAI from "openai";
8
- import type { Run } from "openai/resources/beta/threads/runs/runs";
9
-
10
- // Allow streaming responses up to 30 seconds
11
- export const maxDuration = 30;
12
-
13
- export async function POST(req: Request) {
14
- const openai = new OpenAI();
15
-
16
- // Parse the request body
17
- const input: {
18
- threadId: string | null;
19
- message: string;
20
- } = await req.json();
21
-
22
- // Create a thread if needed
23
- const threadId = input.threadId ?? (await openai.beta.threads.create({})).id;
24
-
25
- // Add a message to the thread
26
- const createdMessage = await openai.beta.threads.messages.create(threadId, {
27
- role: "user",
28
- content: input.message,
29
- });
30
-
31
- return AssistantResponse(
32
- { threadId, messageId: createdMessage.id },
33
- async ({ forwardStream, sendDataMessage }) => {
34
- // Run the assistant on the thread
35
- const runStream = openai.beta.threads.runs.stream(threadId, {
36
- assistant_id:
37
- process.env["ASSISTANT_ID"] ??
38
- (() => {
39
- throw new Error("ASSISTANT_ID is not set");
40
- })(),
41
- });
42
-
43
- // forward run status would stream message deltas
44
- let runResult: Run = await forwardStream(runStream);
45
-
46
- // status can be: queued, in_progress, requires_action, cancelling, cancelled, failed, completed, or expired
47
- while (
48
- runResult?.status === "requires_action" &&
49
- runResult.required_action?.type === "submit_tool_outputs"
50
- ) {
51
- const tool_outputs = await Promise.all(
52
- runResult.required_action.submit_tool_outputs.tool_calls.map(
53
- async (toolCall) => {
54
- const args = JSON.parse(toolCall.function.arguments);
55
-
56
- switch (toolCall.function.name) {
57
- // configure your tool calls here
58
-
59
- case "get_weather":
60
- const { unit } = args;
61
-
62
- sendDataMessage({
63
- role: "data",
64
- data: {
65
- type: "tool-call",
66
- toolCallId: toolCall.id,
67
- toolName: toolCall.function.name,
68
- args,
69
- },
70
- });
71
-
72
- await new Promise((resolve) => setTimeout(resolve, 5000));
73
-
74
- const result = { t: unit === "c" ? 21 : 70 };
75
-
76
- sendDataMessage({
77
- role: "data",
78
- data: {
79
- type: "tool-result",
80
- toolCallId: toolCall.id,
81
- result,
82
- },
83
- });
84
-
85
- return {
86
- tool_call_id: toolCall.id,
87
- output: JSON.stringify(result),
88
- };
89
-
90
- default:
91
- throw new Error(
92
- `Unknown tool call function: ${toolCall.function.name}`,
93
- );
94
- }
95
- },
96
- ),
97
- );
98
-
99
- runResult = await forwardStream(
100
- openai.beta.threads.runs.submitToolOutputsStream(runResult.id, {
101
- thread_id: threadId,
102
- tool_outputs,
103
- }),
104
- );
105
- }
106
- },
107
- );
108
- }
109
-
110
- ```
111
-
112
- ## app/globals.css
113
-
114
- ```css
115
- @import "tailwindcss";
116
- @import "tw-animate-css";
117
-
118
- @custom-variant dark (&:is(.dark *));
119
-
120
- @theme inline {
121
- --color-background: var(--background);
122
- --color-foreground: var(--foreground);
123
- --font-sans: var(--font-geist-sans);
124
- --font-mono: var(--font-geist-mono);
125
- --color-sidebar-ring: var(--sidebar-ring);
126
- --color-sidebar-border: var(--sidebar-border);
127
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
128
- --color-sidebar-accent: var(--sidebar-accent);
129
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
130
- --color-sidebar-primary: var(--sidebar-primary);
131
- --color-sidebar-foreground: var(--sidebar-foreground);
132
- --color-sidebar: var(--sidebar);
133
- --color-chart-5: var(--chart-5);
134
- --color-chart-4: var(--chart-4);
135
- --color-chart-3: var(--chart-3);
136
- --color-chart-2: var(--chart-2);
137
- --color-chart-1: var(--chart-1);
138
- --color-ring: var(--ring);
139
- --color-input: var(--input);
140
- --color-border: var(--border);
141
- --color-destructive: var(--destructive);
142
- --color-accent-foreground: var(--accent-foreground);
143
- --color-accent: var(--accent);
144
- --color-muted-foreground: var(--muted-foreground);
145
- --color-muted: var(--muted);
146
- --color-secondary-foreground: var(--secondary-foreground);
147
- --color-secondary: var(--secondary);
148
- --color-primary-foreground: var(--primary-foreground);
149
- --color-primary: var(--primary);
150
- --color-popover-foreground: var(--popover-foreground);
151
- --color-popover: var(--popover);
152
- --color-card-foreground: var(--card-foreground);
153
- --color-card: var(--card);
154
- --radius-sm: calc(var(--radius) - 4px);
155
- --radius-md: calc(var(--radius) - 2px);
156
- --radius-lg: var(--radius);
157
- --radius-xl: calc(var(--radius) + 4px);
158
- }
159
-
160
- :root {
161
- --radius: 0.625rem;
162
- --background: oklch(1 0 0);
163
- --foreground: oklch(0.141 0.005 285.823);
164
- --card: oklch(1 0 0);
165
- --card-foreground: oklch(0.141 0.005 285.823);
166
- --popover: oklch(1 0 0);
167
- --popover-foreground: oklch(0.141 0.005 285.823);
168
- --primary: oklch(0.21 0.006 285.885);
169
- --primary-foreground: oklch(0.985 0 0);
170
- --secondary: oklch(0.967 0.001 286.375);
171
- --secondary-foreground: oklch(0.21 0.006 285.885);
172
- --muted: oklch(0.967 0.001 286.375);
173
- --muted-foreground: oklch(0.552 0.016 285.938);
174
- --accent: oklch(0.967 0.001 286.375);
175
- --accent-foreground: oklch(0.21 0.006 285.885);
176
- --destructive: oklch(0.577 0.245 27.325);
177
- --border: oklch(0.92 0.004 286.32);
178
- --input: oklch(0.92 0.004 286.32);
179
- --ring: oklch(0.705 0.015 286.067);
180
- --chart-1: oklch(0.646 0.222 41.116);
181
- --chart-2: oklch(0.6 0.118 184.704);
182
- --chart-3: oklch(0.398 0.07 227.392);
183
- --chart-4: oklch(0.828 0.189 84.429);
184
- --chart-5: oklch(0.769 0.188 70.08);
185
- --sidebar: oklch(0.985 0 0);
186
- --sidebar-foreground: oklch(0.141 0.005 285.823);
187
- --sidebar-primary: oklch(0.21 0.006 285.885);
188
- --sidebar-primary-foreground: oklch(0.985 0 0);
189
- --sidebar-accent: oklch(0.967 0.001 286.375);
190
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
191
- --sidebar-border: oklch(0.92 0.004 286.32);
192
- --sidebar-ring: oklch(0.705 0.015 286.067);
193
- }
194
-
195
- .dark {
196
- --background: oklch(0.141 0.005 285.823);
197
- --foreground: oklch(0.985 0 0);
198
- --card: oklch(0.21 0.006 285.885);
199
- --card-foreground: oklch(0.985 0 0);
200
- --popover: oklch(0.21 0.006 285.885);
201
- --popover-foreground: oklch(0.985 0 0);
202
- --primary: oklch(0.92 0.004 286.32);
203
- --primary-foreground: oklch(0.21 0.006 285.885);
204
- --secondary: oklch(0.274 0.006 286.033);
205
- --secondary-foreground: oklch(0.985 0 0);
206
- --muted: oklch(0.274 0.006 286.033);
207
- --muted-foreground: oklch(0.705 0.015 286.067);
208
- --accent: oklch(0.274 0.006 286.033);
209
- --accent-foreground: oklch(0.985 0 0);
210
- --destructive: oklch(0.704 0.191 22.216);
211
- --border: oklch(1 0 0 / 10%);
212
- --input: oklch(1 0 0 / 15%);
213
- --ring: oklch(0.552 0.016 285.938);
214
- --chart-1: oklch(0.488 0.243 264.376);
215
- --chart-2: oklch(0.696 0.17 162.48);
216
- --chart-3: oklch(0.769 0.188 70.08);
217
- --chart-4: oklch(0.627 0.265 303.9);
218
- --chart-5: oklch(0.645 0.246 16.439);
219
- --sidebar: oklch(0.21 0.006 285.885);
220
- --sidebar-foreground: oklch(0.985 0 0);
221
- --sidebar-primary: oklch(0.488 0.243 264.376);
222
- --sidebar-primary-foreground: oklch(0.985 0 0);
223
- --sidebar-accent: oklch(0.274 0.006 286.033);
224
- --sidebar-accent-foreground: oklch(0.985 0 0);
225
- --sidebar-border: oklch(1 0 0 / 10%);
226
- --sidebar-ring: oklch(0.552 0.016 285.938);
227
- }
228
-
229
- @layer base {
230
- * {
231
- @apply border-border outline-ring/50;
232
- }
233
- body {
234
- @apply bg-background text-foreground;
235
- }
236
- }
237
-
238
- ```
239
-
240
- ## app/layout.tsx
241
-
242
- ```tsx
243
- import type { Metadata } from "next";
244
- import { Inter } from "next/font/google";
245
- import { MyRuntimeProvider } from "@/app/MyRuntimeProvider";
246
- import { cn } from "@/lib/utils";
247
-
248
- import "./globals.css";
249
-
250
- const inter = Inter({ subsets: ["latin"] });
251
-
252
- export const metadata: Metadata = {
253
- title: "Create Next App",
254
- description: "Generated by create next app",
255
- };
256
-
257
- export default function RootLayout({
258
- children,
259
- }: Readonly<{
260
- children: React.ReactNode;
261
- }>) {
262
- return (
263
- <MyRuntimeProvider>
264
- <html lang="en" className="h-dvh">
265
- <body className={cn(inter.className, "h-dvh")}>{children}</body>
266
- </html>
267
- </MyRuntimeProvider>
268
- );
269
- }
270
-
271
- ```
272
-
273
- ## app/MyRuntimeProvider.tsx
274
-
275
- ```tsx
276
- "use client";
277
-
278
- import { useAssistant } from "@ai-sdk/react";
279
- import { AssistantRuntimeProvider } from "@assistant-ui/react";
280
- import { useVercelUseAssistantRuntime } from "@assistant-ui/react-ai-sdk-v4";
281
-
282
- export function MyRuntimeProvider({
283
- children,
284
- }: Readonly<{
285
- children: React.ReactNode;
286
- }>) {
287
- const assistant = useAssistant({
288
- api: "/api/assistant",
289
- });
290
-
291
- const runtime = useVercelUseAssistantRuntime(assistant);
292
-
293
- return (
294
- <AssistantRuntimeProvider runtime={runtime}>
295
- {children}
296
- </AssistantRuntimeProvider>
297
- );
298
- }
299
-
300
- ```
301
-
302
- ## app/page.tsx
303
-
304
- ```tsx
305
- "use client";
306
-
307
- import { Thread } from "@/components/assistant-ui/thread";
308
- import { cn } from "@/lib/utils";
309
- import { makeAssistantToolUI } from "@assistant-ui/react";
310
-
311
- type WeatherArgs = {
312
- location: string;
313
- unit: "c" | "f";
314
- };
315
-
316
- type WeatherResult = {
317
- content: string;
318
- };
319
-
320
- const WeatherTool = makeAssistantToolUI<WeatherArgs, WeatherResult>({
321
- toolName: "get_weather",
322
- render: ({ args, result, status }) => {
323
- return (
324
- <p
325
- className={cn(
326
- "my-4 text-center font-mono text-sm font-bold text-blue-500 first:mt-0",
327
- status.type === "running" && "animate-pulse",
328
- )}
329
- >
330
- get_weather({JSON.stringify(args)})
331
- {!!result && <> =&gt; {JSON.stringify(result)}</>}
332
- </p>
333
- );
334
- },
335
- });
336
-
337
- export default function Home() {
338
- return (
339
- <main className="h-dvh">
340
- <Thread />
341
- <WeatherTool />
342
- </main>
343
- );
344
- }
345
-
346
- ```
347
-
348
- ## components.json
349
-
350
- ```json
351
- {
352
- "$schema": "https://ui.shadcn.com/schema.json",
353
- "style": "default",
354
- "rsc": true,
355
- "tsx": true,
356
- "tailwind": {
357
- "config": "",
358
- "css": "app/globals.css",
359
- "baseColor": "zinc",
360
- "cssVariables": true,
361
- "prefix": ""
362
- },
363
- "aliases": {
364
- "components": "@/components",
365
- "utils": "@/lib/utils"
366
- }
367
- }
368
-
369
- ```
370
-
371
- ## components/assistant-ui/markdown-text.tsx
372
-
373
- ```tsx
374
- "use client";
375
-
376
- import "@assistant-ui/react-markdown/styles/dot.css";
377
-
378
- import {
379
- CodeHeaderProps,
380
- MarkdownTextPrimitive,
381
- unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
382
- useIsMarkdownCodeBlock,
383
- } from "@assistant-ui/react-markdown";
384
- import remarkGfm from "remark-gfm";
385
- import { FC, memo, useState } from "react";
386
- import { CheckIcon, CopyIcon } from "lucide-react";
387
-
388
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
389
- import { cn } from "@/lib/utils";
390
-
391
- const MarkdownTextImpl = () => {
392
- return (
393
- <MarkdownTextPrimitive
394
- remarkPlugins={[remarkGfm]}
395
- className="aui-md"
396
- components={defaultComponents}
397
- />
398
- );
399
- };
400
-
401
- export const MarkdownText = memo(MarkdownTextImpl);
402
-
403
- const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
404
- const { isCopied, copyToClipboard } = useCopyToClipboard();
405
- const onCopy = () => {
406
- if (!code || isCopied) return;
407
- copyToClipboard(code);
408
- };
409
-
410
- return (
411
- <div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
412
- <span className="lowercase [&>span]:text-xs">{language}</span>
413
- <TooltipIconButton tooltip="Copy" onClick={onCopy}>
414
- {!isCopied && <CopyIcon />}
415
- {isCopied && <CheckIcon />}
416
- </TooltipIconButton>
417
- </div>
418
- );
419
- };
420
-
421
- const useCopyToClipboard = ({
422
- copiedDuration = 3000,
423
- }: {
424
- copiedDuration?: number;
425
- } = {}) => {
426
- const [isCopied, setIsCopied] = useState<boolean>(false);
427
-
428
- const copyToClipboard = (value: string) => {
429
- if (!value) return;
430
-
431
- navigator.clipboard.writeText(value).then(() => {
432
- setIsCopied(true);
433
- setTimeout(() => setIsCopied(false), copiedDuration);
434
- });
435
- };
436
-
437
- return { isCopied, copyToClipboard };
438
- };
439
-
440
- const defaultComponents = memoizeMarkdownComponents({
441
- h1: ({ className, ...props }) => (
442
- <h1
443
- className={cn(
444
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
445
- className,
446
- )}
447
- {...props}
448
- />
449
- ),
450
- h2: ({ className, ...props }) => (
451
- <h2
452
- className={cn(
453
- "mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
454
- className,
455
- )}
456
- {...props}
457
- />
458
- ),
459
- h3: ({ className, ...props }) => (
460
- <h3
461
- className={cn(
462
- "mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
463
- className,
464
- )}
465
- {...props}
466
- />
467
- ),
468
- h4: ({ className, ...props }) => (
469
- <h4
470
- className={cn(
471
- "mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
472
- className,
473
- )}
474
- {...props}
475
- />
476
- ),
477
- h5: ({ className, ...props }) => (
478
- <h5
479
- className={cn(
480
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
481
- className,
482
- )}
483
- {...props}
484
- />
485
- ),
486
- h6: ({ className, ...props }) => (
487
- <h6
488
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
489
- {...props}
490
- />
491
- ),
492
- p: ({ className, ...props }) => (
493
- <p
494
- className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)}
495
- {...props}
496
- />
497
- ),
498
- a: ({ className, ...props }) => (
499
- <a
500
- className={cn(
501
- "text-primary font-medium underline underline-offset-4",
502
- className,
503
- )}
504
- {...props}
505
- />
506
- ),
507
- blockquote: ({ className, ...props }) => (
508
- <blockquote
509
- className={cn("border-l-2 pl-6 italic", className)}
510
- {...props}
511
- />
512
- ),
513
- ul: ({ className, ...props }) => (
514
- <ul
515
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
516
- {...props}
517
- />
518
- ),
519
- ol: ({ className, ...props }) => (
520
- <ol
521
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
522
- {...props}
523
- />
524
- ),
525
- hr: ({ className, ...props }) => (
526
- <hr className={cn("my-5 border-b", className)} {...props} />
527
- ),
528
- table: ({ className, ...props }) => (
529
- <table
530
- className={cn(
531
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
532
- className,
533
- )}
534
- {...props}
535
- />
536
- ),
537
- th: ({ className, ...props }) => (
538
- <th
539
- className={cn(
540
- "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",
541
- className,
542
- )}
543
- {...props}
544
- />
545
- ),
546
- td: ({ className, ...props }) => (
547
- <td
548
- className={cn(
549
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
550
- className,
551
- )}
552
- {...props}
553
- />
554
- ),
555
- tr: ({ className, ...props }) => (
556
- <tr
557
- className={cn(
558
- "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",
559
- className,
560
- )}
561
- {...props}
562
- />
563
- ),
564
- sup: ({ className, ...props }) => (
565
- <sup
566
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
567
- {...props}
568
- />
569
- ),
570
- pre: ({ className, ...props }) => (
571
- <pre
572
- className={cn(
573
- "overflow-x-auto rounded-b-lg bg-black p-4 text-white",
574
- className,
575
- )}
576
- {...props}
577
- />
578
- ),
579
- code: function Code({ className, ...props }) {
580
- const isCodeBlock = useIsMarkdownCodeBlock();
581
- return (
582
- <code
583
- className={cn(
584
- !isCodeBlock && "bg-muted rounded border font-semibold",
585
- className,
586
- )}
587
- {...props}
588
- />
589
- );
590
- },
591
- CodeHeader,
592
- });
593
-
594
- ```
595
-
596
- ## components/assistant-ui/thread.tsx
597
-
598
- ```tsx
599
- import {
600
- ActionBarPrimitive,
601
- BranchPickerPrimitive,
602
- ComposerPrimitive,
603
- MessagePrimitive,
604
- ThreadPrimitive,
605
- } from "@assistant-ui/react";
606
- import type { FC } from "react";
607
- import {
608
- ArrowDownIcon,
609
- CheckIcon,
610
- ChevronLeftIcon,
611
- ChevronRightIcon,
612
- CopyIcon,
613
- PencilIcon,
614
- RefreshCwIcon,
615
- SendHorizontalIcon,
616
- } from "lucide-react";
617
- import { cn } from "@/lib/utils";
618
-
619
- import { Button } from "@/components/ui/button";
620
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
621
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
622
-
623
- export const Thread: FC = () => {
624
- return (
625
- <ThreadPrimitive.Root
626
- className="bg-background box-border flex h-full flex-col overflow-hidden"
627
- style={{
628
- ["--thread-max-width" as string]: "42rem",
629
- }}
630
- >
631
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
632
- <ThreadWelcome />
633
-
634
- <ThreadPrimitive.Messages
635
- components={{
636
- UserMessage: UserMessage,
637
- EditComposer: EditComposer,
638
- AssistantMessage: AssistantMessage,
639
- }}
640
- />
641
-
642
- <ThreadPrimitive.If empty={false}>
643
- <div className="min-h-8 flex-grow" />
644
- </ThreadPrimitive.If>
645
-
646
- <div className="sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit pb-4">
647
- <ThreadScrollToBottom />
648
- <Composer />
649
- </div>
650
- </ThreadPrimitive.Viewport>
651
- </ThreadPrimitive.Root>
652
- );
653
- };
654
-
655
- const ThreadScrollToBottom: FC = () => {
656
- return (
657
- <ThreadPrimitive.ScrollToBottom asChild>
658
- <TooltipIconButton
659
- tooltip="Scroll to bottom"
660
- variant="outline"
661
- className="absolute -top-8 rounded-full disabled:invisible"
662
- >
663
- <ArrowDownIcon />
664
- </TooltipIconButton>
665
- </ThreadPrimitive.ScrollToBottom>
666
- );
667
- };
668
-
669
- const ThreadWelcome: FC = () => {
670
- return (
671
- <ThreadPrimitive.Empty>
672
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
673
- <div className="flex w-full flex-grow flex-col items-center justify-center">
674
- <p className="mt-4 font-medium">How can I help you today?</p>
675
- </div>
676
- <ThreadWelcomeSuggestions />
677
- </div>
678
- </ThreadPrimitive.Empty>
679
- );
680
- };
681
-
682
- const ThreadWelcomeSuggestions: FC = () => {
683
- return (
684
- <div className="mt-3 flex w-full items-stretch justify-center gap-4">
685
- <ThreadPrimitive.Suggestion
686
- className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
687
- prompt="What is the weather in Tokyo?"
688
- method="replace"
689
- autoSend
690
- >
691
- <span className="line-clamp-2 text-ellipsis text-sm font-semibold">
692
- What is the weather in Tokyo?
693
- </span>
694
- </ThreadPrimitive.Suggestion>
695
- <ThreadPrimitive.Suggestion
696
- className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
697
- prompt="What is assistant-ui?"
698
- method="replace"
699
- autoSend
700
- >
701
- <span className="line-clamp-2 text-ellipsis text-sm font-semibold">
702
- What is assistant-ui?
703
- </span>
704
- </ThreadPrimitive.Suggestion>
705
- </div>
706
- );
707
- };
708
-
709
- const Composer: FC = () => {
710
- return (
711
- <ComposerPrimitive.Root className="focus-within:border-ring/20 flex w-full flex-wrap items-end rounded-lg border bg-inherit px-2.5 shadow-sm transition-colors ease-in">
712
- <ComposerPrimitive.Input
713
- rows={1}
714
- autoFocus
715
- placeholder="Write a message..."
716
- className="placeholder:text-muted-foreground max-h-40 flex-grow resize-none border-none bg-transparent px-2 py-4 text-sm outline-none focus:ring-0 disabled:cursor-not-allowed"
717
- />
718
- <ComposerAction />
719
- </ComposerPrimitive.Root>
720
- );
721
- };
722
-
723
- const ComposerAction: FC = () => {
724
- return (
725
- <>
726
- <ThreadPrimitive.If running={false}>
727
- <ComposerPrimitive.Send asChild>
728
- <TooltipIconButton
729
- tooltip="Send"
730
- variant="default"
731
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
732
- >
733
- <SendHorizontalIcon />
734
- </TooltipIconButton>
735
- </ComposerPrimitive.Send>
736
- </ThreadPrimitive.If>
737
- <ThreadPrimitive.If running>
738
- <ComposerPrimitive.Cancel asChild>
739
- <TooltipIconButton
740
- tooltip="Cancel"
741
- variant="default"
742
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
743
- >
744
- <CircleStopIcon />
745
- </TooltipIconButton>
746
- </ComposerPrimitive.Cancel>
747
- </ThreadPrimitive.If>
748
- </>
749
- );
750
- };
751
-
752
- const UserMessage: FC = () => {
753
- return (
754
- <MessagePrimitive.Root className="grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 py-4 [&:where(>*)]:col-start-2">
755
- <UserActionBar />
756
-
757
- <div className="bg-muted text-foreground col-start-2 row-start-2 max-w-[calc(var(--thread-max-width)*0.8)] break-words rounded-3xl px-5 py-2.5">
758
- <MessagePrimitive.Parts />
759
- </div>
760
-
761
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
762
- </MessagePrimitive.Root>
763
- );
764
- };
765
-
766
- const UserActionBar: FC = () => {
767
- return (
768
- <ActionBarPrimitive.Root
769
- hideWhenRunning
770
- autohide="not-last"
771
- className="col-start-1 row-start-2 mr-3 mt-2.5 flex flex-col items-end"
772
- >
773
- <ActionBarPrimitive.Edit asChild>
774
- <TooltipIconButton tooltip="Edit">
775
- <PencilIcon />
776
- </TooltipIconButton>
777
- </ActionBarPrimitive.Edit>
778
- </ActionBarPrimitive.Root>
779
- );
780
- };
781
-
782
- const EditComposer: FC = () => {
783
- return (
784
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
785
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
786
-
787
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
788
- <ComposerPrimitive.Cancel asChild>
789
- <Button variant="ghost">Cancel</Button>
790
- </ComposerPrimitive.Cancel>
791
- <ComposerPrimitive.Send asChild>
792
- <Button>Send</Button>
793
- </ComposerPrimitive.Send>
794
- </div>
795
- </ComposerPrimitive.Root>
796
- );
797
- };
798
-
799
- const AssistantMessage: FC = () => {
800
- return (
801
- <MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
802
- <div className="text-foreground col-span-2 col-start-2 row-start-1 my-1.5 max-w-[calc(var(--thread-max-width)*0.8)] break-words leading-7">
803
- <MessagePrimitive.Parts components={{ Text: MarkdownText }} />
804
- </div>
805
-
806
- <AssistantActionBar />
807
-
808
- <BranchPicker className="col-start-2 row-start-2 -ml-2 mr-2" />
809
- </MessagePrimitive.Root>
810
- );
811
- };
812
-
813
- const AssistantActionBar: FC = () => {
814
- return (
815
- <ActionBarPrimitive.Root
816
- hideWhenRunning
817
- autohide="not-last"
818
- autohideFloat="single-branch"
819
- className="text-muted-foreground data-[floating]:bg-background col-start-3 row-start-2 -ml-1 flex gap-1 data-[floating]:absolute data-[floating]:rounded-md data-[floating]:border data-[floating]:p-1 data-[floating]:shadow-sm"
820
- >
821
- <ActionBarPrimitive.Copy asChild>
822
- <TooltipIconButton tooltip="Copy">
823
- <MessagePrimitive.If copied>
824
- <CheckIcon />
825
- </MessagePrimitive.If>
826
- <MessagePrimitive.If copied={false}>
827
- <CopyIcon />
828
- </MessagePrimitive.If>
829
- </TooltipIconButton>
830
- </ActionBarPrimitive.Copy>
831
- <ActionBarPrimitive.Reload asChild>
832
- <TooltipIconButton tooltip="Refresh">
833
- <RefreshCwIcon />
834
- </TooltipIconButton>
835
- </ActionBarPrimitive.Reload>
836
- </ActionBarPrimitive.Root>
837
- );
838
- };
839
-
840
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
841
- className,
842
- ...rest
843
- }) => {
844
- return (
845
- <BranchPickerPrimitive.Root
846
- hideWhenSingleBranch
847
- className={cn(
848
- "text-muted-foreground inline-flex items-center text-xs",
849
- className,
850
- )}
851
- {...rest}
852
- >
853
- <BranchPickerPrimitive.Previous asChild>
854
- <TooltipIconButton tooltip="Previous">
855
- <ChevronLeftIcon />
856
- </TooltipIconButton>
857
- </BranchPickerPrimitive.Previous>
858
- <span className="font-medium">
859
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
860
- </span>
861
- <BranchPickerPrimitive.Next asChild>
862
- <TooltipIconButton tooltip="Next">
863
- <ChevronRightIcon />
864
- </TooltipIconButton>
865
- </BranchPickerPrimitive.Next>
866
- </BranchPickerPrimitive.Root>
867
- );
868
- };
869
-
870
- const CircleStopIcon = () => {
871
- return (
872
- <svg
873
- xmlns="http://www.w3.org/2000/svg"
874
- viewBox="0 0 16 16"
875
- fill="currentColor"
876
- width="16"
877
- height="16"
878
- >
879
- <rect width="10" height="10" x="3" y="3" rx="2" />
880
- </svg>
881
- );
882
- };
883
-
884
- ```
885
-
886
- ## components/assistant-ui/tooltip-icon-button.tsx
887
-
888
- ```tsx
889
- "use client";
890
-
891
- import { ComponentPropsWithoutRef, forwardRef } from "react";
892
-
893
- import {
894
- Tooltip,
895
- TooltipContent,
896
- TooltipTrigger,
897
- } from "@/components/ui/tooltip";
898
- import { Button } from "@/components/ui/button";
899
- import { cn } from "@/lib/utils";
900
-
901
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
902
- tooltip: string;
903
- side?: "top" | "bottom" | "left" | "right";
904
- };
905
-
906
- export const TooltipIconButton = forwardRef<
907
- HTMLButtonElement,
908
- TooltipIconButtonProps
909
- >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
910
- return (
911
- <Tooltip>
912
- <TooltipTrigger asChild>
913
- <Button
914
- variant="ghost"
915
- size="icon"
916
- {...rest}
917
- className={cn("size-6 p-1", className)}
918
- ref={ref}
919
- >
920
- {children}
921
- <span className="sr-only">{tooltip}</span>
922
- </Button>
923
- </TooltipTrigger>
924
- <TooltipContent side={side}>{tooltip}</TooltipContent>
925
- </Tooltip>
926
- );
927
- });
928
-
929
- TooltipIconButton.displayName = "TooltipIconButton";
930
-
931
- ```
932
-
933
- ## components/ui/button.tsx
934
-
935
- ```tsx
936
- import * as React from "react";
937
- import { Slot } from "@radix-ui/react-slot";
938
- import { cva, type VariantProps } from "class-variance-authority";
939
-
940
- import { cn } from "@/lib/utils";
941
-
942
- const buttonVariants = cva(
943
- "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",
944
- {
945
- variants: {
946
- variant: {
947
- default:
948
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
949
- destructive:
950
- "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",
951
- outline:
952
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
953
- secondary:
954
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
955
- ghost:
956
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
957
- link: "text-primary underline-offset-4 hover:underline",
958
- },
959
- size: {
960
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
961
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
962
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
963
- icon: "size-9",
964
- },
965
- },
966
- defaultVariants: {
967
- variant: "default",
968
- size: "default",
969
- },
970
- },
971
- );
972
-
973
- function Button({
974
- className,
975
- variant,
976
- size,
977
- asChild = false,
978
- ...props
979
- }: React.ComponentProps<"button"> &
980
- VariantProps<typeof buttonVariants> & {
981
- asChild?: boolean;
982
- }) {
983
- const Comp = asChild ? Slot : "button";
984
-
985
- return (
986
- <Comp
987
- data-slot="button"
988
- className={cn(buttonVariants({ variant, size, className }))}
989
- {...props}
990
- />
991
- );
992
- }
993
-
994
- export { Button, buttonVariants };
995
-
996
- ```
997
-
998
- ## components/ui/tooltip.tsx
999
-
1000
- ```tsx
1001
- "use client";
1002
-
1003
- import * as React from "react";
1004
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1005
-
1006
- import { cn } from "@/lib/utils";
1007
-
1008
- function TooltipProvider({
1009
- delayDuration = 0,
1010
- ...props
1011
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1012
- return (
1013
- <TooltipPrimitive.Provider
1014
- data-slot="tooltip-provider"
1015
- delayDuration={delayDuration}
1016
- {...props}
1017
- />
1018
- );
1019
- }
1020
-
1021
- function Tooltip({
1022
- ...props
1023
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1024
- return (
1025
- <TooltipProvider>
1026
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1027
- </TooltipProvider>
1028
- );
1029
- }
1030
-
1031
- function TooltipTrigger({
1032
- ...props
1033
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
1034
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
1035
- }
1036
-
1037
- function TooltipContent({
1038
- className,
1039
- sideOffset = 0,
1040
- children,
1041
- ...props
1042
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
1043
- return (
1044
- <TooltipPrimitive.Portal>
1045
- <TooltipPrimitive.Content
1046
- data-slot="tooltip-content"
1047
- sideOffset={sideOffset}
1048
- className={cn(
1049
- "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 z-50 w-fit origin-[--radix-tooltip-content-transform-origin] text-balance rounded-md px-3 py-1.5 text-xs",
1050
- className,
1051
- )}
1052
- {...props}
1053
- >
1054
- {children}
1055
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1056
- </TooltipPrimitive.Content>
1057
- </TooltipPrimitive.Portal>
1058
- );
1059
- }
1060
-
1061
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1062
-
1063
- ```
1064
-
1065
- ## eslint.config.ts
1066
-
1067
- ```typescript
1068
- export { default } from "@assistant-ui/x-buildutils/eslint";
1069
-
1070
- ```
1071
-
1072
- ## lib/utils.ts
1073
-
1074
- ```typescript
1075
- import { type ClassValue, clsx } from "clsx";
1076
- import { twMerge } from "tailwind-merge";
1077
-
1078
- export function cn(...inputs: ClassValue[]) {
1079
- return twMerge(clsx(inputs));
1080
- }
1081
-
1082
- ```
1083
-
1084
- ## next.config.ts
1085
-
1086
- ```typescript
1087
- import type { NextConfig } from "next";
1088
-
1089
- const nextConfig: NextConfig = {
1090
- /* config options here */
1091
- };
1092
-
1093
- export default nextConfig;
1094
-
1095
- ```
1096
-
1097
- ## package.json
1098
-
1099
- ```json
1100
- {
1101
- "name": "with-openai-assistants",
1102
- "version": "0.1.0",
1103
- "private": true,
1104
- "scripts": {
1105
- "dev": "next dev --turbo",
1106
- "build": "next build",
1107
- "start": "next start",
1108
- "lint": "next lint"
1109
- },
1110
- "dependencies": {
1111
- "@ai-sdk/openai": "^1.3.22",
1112
- "@ai-sdk/react": "^1.2.12",
1113
- "@assistant-ui/react": "workspace:*",
1114
- "@assistant-ui/react-ai-sdk-v4": "workspace:*",
1115
- "@radix-ui/react-avatar": "^1.1.10",
1116
- "@radix-ui/react-slot": "^1.2.3",
1117
- "@radix-ui/react-tooltip": "^1.2.7",
1118
- "ai": "^4.3.16",
1119
- "class-variance-authority": "^0.7.1",
1120
- "clsx": "^2.1.1",
1121
- "lucide-react": "^0.535.0",
1122
- "next": "15.4.5",
1123
- "openai": "^5.11.0",
1124
- "react": "19.1.1",
1125
- "react-dom": "19.1.1",
1126
- "remark-gfm": "^4.0.1",
1127
- "tailwind-merge": "^3.3.1",
1128
- "tw-animate-css": "^1.3.6"
1129
- },
1130
- "devDependencies": {
1131
- "@assistant-ui/x-buildutils": "workspace:*",
1132
- "@types/node": "^24",
1133
- "@types/react": "^19",
1134
- "@types/react-dom": "^19",
1135
- "eslint": "^9",
1136
- "eslint-config-next": "15.4.5",
1137
- "postcss": "^8",
1138
- "tailwindcss": "^4.1.11",
1139
- "typescript": "^5"
1140
- }
1141
- }
1142
-
1143
- ```
1144
-
1145
- ## tsconfig.json
1146
-
1147
- ```json
1148
- {
1149
- "extends": "@assistant-ui/x-buildutils/ts/base",
1150
- "compilerOptions": {
1151
- "target": "ES6",
1152
- "module": "ESNext",
1153
- "incremental": true,
1154
- "plugins": [
1155
- {
1156
- "name": "next"
1157
- }
1158
- ],
1159
- "allowJs": true,
1160
- "strictNullChecks": true,
1161
- "jsx": "preserve",
1162
- "paths": {
1163
- "@/*": ["./*"],
1164
- "@assistant-ui/*": ["../../packages/*/src"],
1165
- "@assistant-ui/react/*": ["../../packages/react/src/*"],
1166
- "assistant-stream": ["../../packages/assistant-stream/src"],
1167
- "assistant-stream/*": ["../../packages/assistant-stream/src/*"]
1168
- }
1169
- },
1170
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1171
- "exclude": ["node_modules"]
1172
- }
1173
-
1174
- ```
1175
-