@assistant-ui/mcp-docs-server 0.1.18 → 0.1.20

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 (148) hide show
  1. package/.docs/organized/code-examples/with-ag-ui.md +69 -1336
  2. package/.docs/organized/code-examples/with-ai-sdk-v6.md +429 -0
  3. package/.docs/organized/code-examples/with-assistant-transport.md +41 -1454
  4. package/.docs/organized/code-examples/with-cloud.md +73 -1442
  5. package/.docs/organized/code-examples/with-custom-thread-list.md +95 -1424
  6. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +674 -0
  7. package/.docs/organized/code-examples/with-external-store.md +68 -1334
  8. package/.docs/organized/code-examples/with-ffmpeg.md +73 -1341
  9. package/.docs/organized/code-examples/with-langgraph.md +52 -1545
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +58 -1364
  11. package/.docs/organized/code-examples/with-react-hook-form.md +73 -1726
  12. package/.docs/organized/code-examples/with-react-router.md +915 -0
  13. package/.docs/organized/code-examples/with-store.md +31 -31
  14. package/.docs/organized/code-examples/with-tanstack.md +78 -862
  15. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +0 -2
  16. package/.docs/raw/docs/{architecture.mdx → (docs)/architecture.mdx} +3 -2
  17. package/.docs/raw/docs/{cli.mdx → (docs)/cli.mdx} +66 -18
  18. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-readable.mdx +1 -0
  19. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-tool-ui.mdx +2 -1
  20. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-tool.mdx +2 -1
  21. package/.docs/raw/docs/{copilots → (docs)/copilots}/model-context.mdx +5 -4
  22. package/.docs/raw/docs/{copilots → (docs)/copilots}/motivation.mdx +4 -3
  23. package/.docs/raw/docs/{copilots → (docs)/copilots}/use-assistant-instructions.mdx +1 -0
  24. package/.docs/raw/docs/{devtools.mdx → (docs)/devtools.mdx} +4 -4
  25. package/.docs/raw/docs/{guides/Attachments.mdx → (docs)/guides/attachments.mdx} +6 -7
  26. package/.docs/raw/docs/{guides/Branching.mdx → (docs)/guides/branching.mdx} +2 -1
  27. package/.docs/raw/docs/{guides → (docs)/guides}/context-api.mdx +118 -117
  28. package/.docs/raw/docs/(docs)/guides/dictation.mdx +370 -0
  29. package/.docs/raw/docs/{guides/Editing.mdx → (docs)/guides/editing.mdx} +1 -0
  30. package/.docs/raw/docs/{guides/Latex.mdx → (docs)/guides/latex.mdx} +1 -2
  31. package/.docs/raw/docs/{guides/Speech.mdx → (docs)/guides/speech.mdx} +9 -10
  32. package/.docs/raw/docs/(docs)/guides/suggestions.mdx +296 -0
  33. package/.docs/raw/docs/{guides/ToolUI.mdx → (docs)/guides/tool-ui.mdx} +15 -14
  34. package/.docs/raw/docs/(docs)/guides/tools.mdx +564 -0
  35. package/.docs/raw/docs/(docs)/index.mdx +74 -0
  36. package/.docs/raw/docs/{getting-started.mdx → (docs)/installation.mdx} +18 -23
  37. package/.docs/raw/docs/(docs)/llm.mdx +209 -0
  38. package/.docs/raw/docs/{api-reference/context-providers/AssistantRuntimeProvider.mdx → (reference)/api-reference/context-providers/assistant-runtime-provider.mdx} +2 -1
  39. package/.docs/raw/docs/{api-reference/context-providers/TextMessagePartProvider.mdx → (reference)/api-reference/context-providers/text-message-part-provider.mdx} +2 -1
  40. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/react-data-stream.mdx +50 -3
  41. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/react-hook-form.mdx +2 -1
  42. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/vercel-ai-sdk.mdx +2 -2
  43. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/overview.mdx +10 -4
  44. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +327 -0
  45. package/.docs/raw/docs/{api-reference/primitives/ActionBar.mdx → (reference)/api-reference/primitives/action-bar.mdx} +7 -5
  46. package/.docs/raw/docs/{api-reference/primitives/AssistantIf.mdx → (reference)/api-reference/primitives/assistant-if.mdx} +51 -51
  47. package/.docs/raw/docs/{api-reference/primitives/AssistantModal.mdx → (reference)/api-reference/primitives/assistant-modal.mdx} +3 -1
  48. package/.docs/raw/docs/{api-reference/primitives/Attachment.mdx → (reference)/api-reference/primitives/attachment.mdx} +3 -2
  49. package/.docs/raw/docs/{api-reference/primitives/BranchPicker.mdx → (reference)/api-reference/primitives/branch-picker.mdx} +2 -1
  50. package/.docs/raw/docs/{api-reference/primitives/Composer.mdx → (reference)/api-reference/primitives/composer.mdx} +101 -2
  51. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/primitives/composition.mdx +1 -0
  52. package/.docs/raw/docs/{api-reference/primitives/Error.mdx → (reference)/api-reference/primitives/error.mdx} +2 -1
  53. package/.docs/raw/docs/{api-reference/primitives/MessagePart.mdx → (reference)/api-reference/primitives/message-part.mdx} +2 -2
  54. package/.docs/raw/docs/{api-reference/primitives/Message.mdx → (reference)/api-reference/primitives/message.mdx} +2 -1
  55. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +153 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +221 -0
  57. package/.docs/raw/docs/{api-reference/primitives/ThreadListItem.mdx → (reference)/api-reference/primitives/thread-list-item.mdx} +2 -1
  58. package/.docs/raw/docs/{api-reference/primitives/ThreadList.mdx → (reference)/api-reference/primitives/thread-list.mdx} +2 -1
  59. package/.docs/raw/docs/{api-reference/primitives/Thread.mdx → (reference)/api-reference/primitives/thread.mdx} +30 -40
  60. package/.docs/raw/docs/{api-reference/runtimes/AssistantRuntime.mdx → (reference)/api-reference/runtimes/assistant-runtime.mdx} +2 -1
  61. package/.docs/raw/docs/{api-reference/runtimes/AttachmentRuntime.mdx → (reference)/api-reference/runtimes/attachment-runtime.mdx} +3 -2
  62. package/.docs/raw/docs/{api-reference/runtimes/ComposerRuntime.mdx → (reference)/api-reference/runtimes/composer-runtime.mdx} +2 -1
  63. package/.docs/raw/docs/{api-reference/runtimes/MessagePartRuntime.mdx → (reference)/api-reference/runtimes/message-part-runtime.mdx} +3 -2
  64. package/.docs/raw/docs/{api-reference/runtimes/MessageRuntime.mdx → (reference)/api-reference/runtimes/message-runtime.mdx} +3 -2
  65. package/.docs/raw/docs/{api-reference/runtimes/ThreadListItemRuntime.mdx → (reference)/api-reference/runtimes/thread-list-item-runtime.mdx} +2 -1
  66. package/.docs/raw/docs/{api-reference/runtimes/ThreadListRuntime.mdx → (reference)/api-reference/runtimes/thread-list-runtime.mdx} +2 -1
  67. package/.docs/raw/docs/{api-reference/runtimes/ThreadRuntime.mdx → (reference)/api-reference/runtimes/thread-runtime.mdx} +3 -5
  68. package/.docs/raw/docs/{legacy/styled/AssistantModal.mdx → (reference)/legacy/styled/assistant-modal.mdx} +2 -3
  69. package/.docs/raw/docs/{legacy/styled/Decomposition.mdx → (reference)/legacy/styled/decomposition.mdx} +6 -5
  70. package/.docs/raw/docs/{legacy/styled/Markdown.mdx → (reference)/legacy/styled/markdown.mdx} +2 -4
  71. package/.docs/raw/docs/{legacy/styled/Scrollbar.mdx → (reference)/legacy/styled/scrollbar.mdx} +2 -1
  72. package/.docs/raw/docs/{legacy/styled/ThreadWidth.mdx → (reference)/legacy/styled/thread-width.mdx} +1 -0
  73. package/.docs/raw/docs/{legacy/styled/Thread.mdx → (reference)/legacy/styled/thread.mdx} +2 -3
  74. package/.docs/raw/docs/{migrations → (reference)/migrations}/deprecation-policy.mdx +1 -0
  75. package/.docs/raw/docs/{migrations → (reference)/migrations}/react-langgraph-v0-7.mdx +1 -2
  76. package/.docs/raw/docs/{migrations → (reference)/migrations}/v0-11.mdx +1 -0
  77. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +300 -0
  78. package/.docs/raw/docs/{react-compatibility.mdx → (reference)/react-compatibility.mdx} +2 -3
  79. package/.docs/raw/docs/cloud/authorization.mdx +1 -0
  80. package/.docs/raw/docs/cloud/overview.mdx +1 -0
  81. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +2 -3
  82. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +5 -7
  83. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +9 -8
  84. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +2 -3
  85. package/.docs/raw/docs/runtimes/assistant-transport.mdx +10 -9
  86. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +7 -8
  87. package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -8
  88. package/.docs/raw/docs/runtimes/custom/local.mdx +55 -42
  89. package/.docs/raw/docs/runtimes/data-stream.mdx +67 -6
  90. package/.docs/raw/docs/runtimes/helicone.mdx +1 -0
  91. package/.docs/raw/docs/runtimes/langgraph/index.mdx +3 -3
  92. package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +1 -0
  93. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +1 -0
  94. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -0
  95. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -0
  96. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +2 -1
  97. package/.docs/raw/docs/runtimes/langserve.mdx +2 -2
  98. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +4 -5
  99. package/.docs/raw/docs/runtimes/mastra/overview.mdx +1 -0
  100. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +3 -4
  101. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +2 -4
  102. package/.docs/raw/docs/ui/accordion.mdx +261 -0
  103. package/.docs/raw/docs/ui/assistant-modal.mdx +163 -0
  104. package/.docs/raw/docs/ui/assistant-sidebar.mdx +90 -0
  105. package/.docs/raw/docs/ui/attachment.mdx +227 -0
  106. package/.docs/raw/docs/ui/badge.mdx +140 -0
  107. package/.docs/raw/docs/ui/file.mdx +153 -0
  108. package/.docs/raw/docs/ui/image.mdx +101 -0
  109. package/.docs/raw/docs/ui/{Markdown.mdx → markdown.mdx} +11 -6
  110. package/.docs/raw/docs/ui/{Mermaid.mdx → mermaid.mdx} +12 -5
  111. package/.docs/raw/docs/ui/model-selector.mdx +226 -0
  112. package/.docs/raw/docs/ui/{PartGrouping.mdx → part-grouping.mdx} +6 -8
  113. package/.docs/raw/docs/ui/reasoning.mdx +150 -0
  114. package/.docs/raw/docs/ui/{Scrollbar.mdx → scrollbar.mdx} +9 -1
  115. package/.docs/raw/docs/ui/select.mdx +247 -0
  116. package/.docs/raw/docs/ui/sources.mdx +89 -0
  117. package/.docs/raw/docs/ui/streamdown.mdx +348 -0
  118. package/.docs/raw/docs/ui/{SyntaxHighlighting.mdx → syntax-highlighting.mdx} +9 -5
  119. package/.docs/raw/docs/ui/tabs.mdx +261 -0
  120. package/.docs/raw/docs/ui/thread-list.mdx +275 -0
  121. package/.docs/raw/docs/ui/{Thread.mdx → thread.mdx} +61 -76
  122. package/.docs/raw/docs/ui/tool-fallback.mdx +112 -0
  123. package/.docs/raw/docs/ui/tool-group.mdx +214 -0
  124. package/README.md +3 -3
  125. package/dist/tools/docs.js +1 -1
  126. package/dist/tools/examples.js +1 -1
  127. package/dist/tools/examples.js.map +1 -1
  128. package/package.json +5 -5
  129. package/src/tools/docs.ts +1 -1
  130. package/src/tools/examples.ts +1 -1
  131. package/src/tools/tests/docs.test.ts +18 -16
  132. package/src/tools/tests/examples.test.ts +6 -6
  133. package/src/tools/tests/path-traversal.test.ts +3 -3
  134. package/src/utils/tests/security.test.ts +3 -3
  135. package/.docs/organized/code-examples/with-ai-sdk-v5.md +0 -1735
  136. package/.docs/raw/docs/about-assistantui.mdx +0 -53
  137. package/.docs/raw/docs/guides/Tools.mdx +0 -738
  138. package/.docs/raw/docs/index.mdx +0 -7
  139. package/.docs/raw/docs/mcp-docs-server.mdx +0 -322
  140. package/.docs/raw/docs/migrations/v0-12.mdx +0 -125
  141. package/.docs/raw/docs/ui/AssistantModal.mdx +0 -45
  142. package/.docs/raw/docs/ui/AssistantSidebar.mdx +0 -41
  143. package/.docs/raw/docs/ui/Attachment.mdx +0 -84
  144. package/.docs/raw/docs/ui/Reasoning.mdx +0 -152
  145. package/.docs/raw/docs/ui/ThreadList.mdx +0 -90
  146. package/.docs/raw/docs/ui/ToolFallback.mdx +0 -63
  147. package/.docs/raw/docs/ui/ToolGroup.mdx +0 -96
  148. /package/.docs/raw/docs/{copilots → (docs)/copilots}/assistant-frame.mdx +0 -0
@@ -1,1735 +0,0 @@
1
- # Example: with-ai-sdk-v5
2
-
3
- ## app/api/chat/route.ts
4
-
5
- ```typescript
6
- import { openai } from "@ai-sdk/openai";
7
- import {
8
- streamText,
9
- UIMessage,
10
- convertToModelMessages,
11
- tool,
12
- stepCountIs,
13
- } from "ai";
14
- import { z } from "zod";
15
-
16
- // Allow streaming responses up to 30 seconds
17
- export const maxDuration = 30;
18
-
19
- export async function POST(req: Request) {
20
- const { messages }: { messages: UIMessage[] } = await req.json();
21
-
22
- const result = streamText({
23
- model: openai("gpt-4o"),
24
- messages: convertToModelMessages(messages),
25
- stopWhen: stepCountIs(10),
26
- tools: {
27
- get_current_weather: tool({
28
- name: "",
29
- description: "Get the current weather",
30
- inputSchema: z.object({
31
- city: z.string(),
32
- }),
33
- execute: async ({ city }) => {
34
- return `The weather in ${city} is sunny`;
35
- },
36
- }),
37
- },
38
- });
39
-
40
- return result.toUIMessageStreamResponse();
41
- }
42
-
43
- ```
44
-
45
- ## app/globals.css
46
-
47
- ```css
48
- @import "tailwindcss";
49
- @import "tw-animate-css";
50
-
51
- @custom-variant dark (&:is(.dark *));
52
-
53
- @theme inline {
54
- --radius-sm: calc(var(--radius) - 4px);
55
- --radius-md: calc(var(--radius) - 2px);
56
- --radius-lg: var(--radius);
57
- --radius-xl: calc(var(--radius) + 4px);
58
- --color-background: var(--background);
59
- --color-foreground: var(--foreground);
60
- --color-card: var(--card);
61
- --color-card-foreground: var(--card-foreground);
62
- --color-popover: var(--popover);
63
- --color-popover-foreground: var(--popover-foreground);
64
- --color-primary: var(--primary);
65
- --color-primary-foreground: var(--primary-foreground);
66
- --color-secondary: var(--secondary);
67
- --color-secondary-foreground: var(--secondary-foreground);
68
- --color-muted: var(--muted);
69
- --color-muted-foreground: var(--muted-foreground);
70
- --color-accent: var(--accent);
71
- --color-accent-foreground: var(--accent-foreground);
72
- --color-destructive: var(--destructive);
73
- --color-border: var(--border);
74
- --color-input: var(--input);
75
- --color-ring: var(--ring);
76
- --color-chart-1: var(--chart-1);
77
- --color-chart-2: var(--chart-2);
78
- --color-chart-3: var(--chart-3);
79
- --color-chart-4: var(--chart-4);
80
- --color-chart-5: var(--chart-5);
81
- --color-sidebar: var(--sidebar);
82
- --color-sidebar-foreground: var(--sidebar-foreground);
83
- --color-sidebar-primary: var(--sidebar-primary);
84
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
85
- --color-sidebar-accent: var(--sidebar-accent);
86
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
87
- --color-sidebar-border: var(--sidebar-border);
88
- --color-sidebar-ring: var(--sidebar-ring);
89
- }
90
-
91
- :root {
92
- --radius: 0.625rem;
93
- --background: oklch(1 0 0);
94
- --foreground: oklch(0.141 0.005 285.823);
95
- --card: oklch(1 0 0);
96
- --card-foreground: oklch(0.141 0.005 285.823);
97
- --popover: oklch(1 0 0);
98
- --popover-foreground: oklch(0.141 0.005 285.823);
99
- --primary: oklch(0.21 0.006 285.885);
100
- --primary-foreground: oklch(0.985 0 0);
101
- --secondary: oklch(0.967 0.001 286.375);
102
- --secondary-foreground: oklch(0.21 0.006 285.885);
103
- --muted: oklch(0.967 0.001 286.375);
104
- --muted-foreground: oklch(0.552 0.016 285.938);
105
- --accent: oklch(0.967 0.001 286.375);
106
- --accent-foreground: oklch(0.21 0.006 285.885);
107
- --destructive: oklch(0.577 0.245 27.325);
108
- --border: oklch(0.92 0.004 286.32);
109
- --input: oklch(0.92 0.004 286.32);
110
- --ring: oklch(0.705 0.015 286.067);
111
- --chart-1: oklch(0.646 0.222 41.116);
112
- --chart-2: oklch(0.6 0.118 184.704);
113
- --chart-3: oklch(0.398 0.07 227.392);
114
- --chart-4: oklch(0.828 0.189 84.429);
115
- --chart-5: oklch(0.769 0.188 70.08);
116
- --sidebar: oklch(0.985 0 0);
117
- --sidebar-foreground: oklch(0.141 0.005 285.823);
118
- --sidebar-primary: oklch(0.21 0.006 285.885);
119
- --sidebar-primary-foreground: oklch(0.985 0 0);
120
- --sidebar-accent: oklch(0.967 0.001 286.375);
121
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
122
- --sidebar-border: oklch(0.92 0.004 286.32);
123
- --sidebar-ring: oklch(0.705 0.015 286.067);
124
- }
125
-
126
- .dark {
127
- --background: oklch(0.141 0.005 285.823);
128
- --foreground: oklch(0.985 0 0);
129
- --card: oklch(0.21 0.006 285.885);
130
- --card-foreground: oklch(0.985 0 0);
131
- --popover: oklch(0.21 0.006 285.885);
132
- --popover-foreground: oklch(0.985 0 0);
133
- --primary: oklch(0.92 0.004 286.32);
134
- --primary-foreground: oklch(0.21 0.006 285.885);
135
- --secondary: oklch(0.274 0.006 286.033);
136
- --secondary-foreground: oklch(0.985 0 0);
137
- --muted: oklch(0.274 0.006 286.033);
138
- --muted-foreground: oklch(0.705 0.015 286.067);
139
- --accent: oklch(0.274 0.006 286.033);
140
- --accent-foreground: oklch(0.985 0 0);
141
- --destructive: oklch(0.704 0.191 22.216);
142
- --border: oklch(1 0 0 / 10%);
143
- --input: oklch(1 0 0 / 15%);
144
- --ring: oklch(0.552 0.016 285.938);
145
- --chart-1: oklch(0.488 0.243 264.376);
146
- --chart-2: oklch(0.696 0.17 162.48);
147
- --chart-3: oklch(0.769 0.188 70.08);
148
- --chart-4: oklch(0.627 0.265 303.9);
149
- --chart-5: oklch(0.645 0.246 16.439);
150
- --sidebar: oklch(0.21 0.006 285.885);
151
- --sidebar-foreground: oklch(0.985 0 0);
152
- --sidebar-primary: oklch(0.488 0.243 264.376);
153
- --sidebar-primary-foreground: oklch(0.985 0 0);
154
- --sidebar-accent: oklch(0.274 0.006 286.033);
155
- --sidebar-accent-foreground: oklch(0.985 0 0);
156
- --sidebar-border: oklch(1 0 0 / 10%);
157
- --sidebar-ring: oklch(0.552 0.016 285.938);
158
- }
159
-
160
- @layer base {
161
- * {
162
- @apply border-border outline-ring/50;
163
- }
164
- body {
165
- @apply bg-background text-foreground;
166
- }
167
- }
168
-
169
- ```
170
-
171
- ## app/layout.tsx
172
-
173
- ```tsx
174
- import type { Metadata } from "next";
175
- import "./globals.css";
176
-
177
- export const metadata: Metadata = {
178
- title: "AI SDK v5 Example",
179
- description: "Example using @assistant-ui/react with AI SDK v5",
180
- };
181
-
182
- export default function RootLayout({
183
- children,
184
- }: Readonly<{
185
- children: React.ReactNode;
186
- }>) {
187
- return (
188
- <html lang="en">
189
- <body className="h-dvh">{children}</body>
190
- </html>
191
- );
192
- }
193
-
194
- ```
195
-
196
- ## app/page.tsx
197
-
198
- ```tsx
199
- "use client";
200
-
201
- import { Thread } from "@/components/assistant-ui/thread";
202
- import { AssistantRuntimeProvider } from "@assistant-ui/react";
203
- import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
204
-
205
- export default function Home() {
206
- // Using the new simplified useChatRuntime hook
207
- const runtime = useChatRuntime();
208
-
209
- return (
210
- <AssistantRuntimeProvider runtime={runtime}>
211
- <div className="h-full">
212
- <Thread />
213
- </div>
214
- </AssistantRuntimeProvider>
215
- );
216
- }
217
-
218
- ```
219
-
220
- ## components.json
221
-
222
- ```json
223
- {
224
- "$schema": "https://ui.shadcn.com/schema.json",
225
- "style": "new-york",
226
- "rsc": true,
227
- "tsx": true,
228
- "tailwind": {
229
- "config": "",
230
- "css": "app/globals.css",
231
- "baseColor": "zinc",
232
- "cssVariables": true,
233
- "prefix": ""
234
- },
235
- "aliases": {
236
- "components": "@/components",
237
- "utils": "@/lib/utils",
238
- "ui": "@/components/ui",
239
- "lib": "@/lib",
240
- "hooks": "@/hooks"
241
- },
242
- "iconLibrary": "lucide"
243
- }
244
-
245
- ```
246
-
247
- ## components/assistant-ui/attachment.tsx
248
-
249
- ```tsx
250
- "use client";
251
-
252
- import { PropsWithChildren, useEffect, useState, type FC } from "react";
253
- import Image from "next/image";
254
- import { XIcon, PlusIcon, FileText } from "lucide-react";
255
- import {
256
- AttachmentPrimitive,
257
- ComposerPrimitive,
258
- MessagePrimitive,
259
- useAssistantState,
260
- useAssistantApi,
261
- } from "@assistant-ui/react";
262
- import { useShallow } from "zustand/shallow";
263
- import {
264
- Tooltip,
265
- TooltipContent,
266
- TooltipTrigger,
267
- } from "@/components/ui/tooltip";
268
- import {
269
- Dialog,
270
- DialogTitle,
271
- DialogContent,
272
- DialogTrigger,
273
- } from "@/components/ui/dialog";
274
- import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
275
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
276
- import { cn } from "@/lib/utils";
277
-
278
- const useFileSrc = (file: File | undefined) => {
279
- const [src, setSrc] = useState<string | undefined>(undefined);
280
-
281
- useEffect(() => {
282
- if (!file) {
283
- setSrc(undefined);
284
- return;
285
- }
286
-
287
- const objectUrl = URL.createObjectURL(file);
288
- setSrc(objectUrl);
289
-
290
- return () => {
291
- URL.revokeObjectURL(objectUrl);
292
- };
293
- }, [file]);
294
-
295
- return src;
296
- };
297
-
298
- const useAttachmentSrc = () => {
299
- const { file, src } = useAssistantState(
300
- useShallow(({ attachment }): { file?: File; src?: string } => {
301
- if (attachment.type !== "image") return {};
302
- if (attachment.file) return { file: attachment.file };
303
- const src = attachment.content?.filter((c) => c.type === "image")[0]
304
- ?.image;
305
- if (!src) return {};
306
- return { src };
307
- }),
308
- );
309
-
310
- return useFileSrc(file) ?? src;
311
- };
312
-
313
- type AttachmentPreviewProps = {
314
- src: string;
315
- };
316
-
317
- const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
318
- const [isLoaded, setIsLoaded] = useState(false);
319
- return (
320
- <Image
321
- src={src}
322
- alt="Image Preview"
323
- width={1}
324
- height={1}
325
- className={
326
- isLoaded
327
- ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
328
- : "aui-attachment-preview-image-loading hidden"
329
- }
330
- onLoadingComplete={() => setIsLoaded(true)}
331
- priority={false}
332
- />
333
- );
334
- };
335
-
336
- const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
337
- const src = useAttachmentSrc();
338
-
339
- if (!src) return children;
340
-
341
- return (
342
- <Dialog>
343
- <DialogTrigger
344
- className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
345
- asChild
346
- >
347
- {children}
348
- </DialogTrigger>
349
- <DialogContent className="aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive">
350
- <DialogTitle className="aui-sr-only sr-only">
351
- Image Attachment Preview
352
- </DialogTitle>
353
- <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
354
- <AttachmentPreview src={src} />
355
- </div>
356
- </DialogContent>
357
- </Dialog>
358
- );
359
- };
360
-
361
- const AttachmentThumb: FC = () => {
362
- const isImage = useAssistantState(
363
- ({ attachment }) => attachment.type === "image",
364
- );
365
- const src = useAttachmentSrc();
366
-
367
- return (
368
- <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
369
- <AvatarImage
370
- src={src}
371
- alt="Attachment preview"
372
- className="aui-attachment-tile-image object-cover"
373
- />
374
- <AvatarFallback delayMs={isImage ? 200 : 0}>
375
- <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
376
- </AvatarFallback>
377
- </Avatar>
378
- );
379
- };
380
-
381
- const AttachmentUI: FC = () => {
382
- const api = useAssistantApi();
383
- const isComposer = api.attachment.source === "composer";
384
-
385
- const isImage = useAssistantState(
386
- ({ attachment }) => attachment.type === "image",
387
- );
388
- const typeLabel = useAssistantState(({ attachment }) => {
389
- const type = attachment.type;
390
- switch (type) {
391
- case "image":
392
- return "Image";
393
- case "document":
394
- return "Document";
395
- case "file":
396
- return "File";
397
- default:
398
- const _exhaustiveCheck: never = type;
399
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
400
- }
401
- });
402
-
403
- return (
404
- <Tooltip>
405
- <AttachmentPrimitive.Root
406
- className={cn(
407
- "aui-attachment-root relative",
408
- isImage &&
409
- "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
410
- )}
411
- >
412
- <AttachmentPreviewDialog>
413
- <TooltipTrigger asChild>
414
- <div
415
- className={cn(
416
- "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
417
- isComposer &&
418
- "aui-attachment-tile-composer border-foreground/20",
419
- )}
420
- role="button"
421
- id="attachment-tile"
422
- aria-label={`${typeLabel} attachment`}
423
- >
424
- <AttachmentThumb />
425
- </div>
426
- </TooltipTrigger>
427
- </AttachmentPreviewDialog>
428
- {isComposer && <AttachmentRemove />}
429
- </AttachmentPrimitive.Root>
430
- <TooltipContent side="top">
431
- <AttachmentPrimitive.Name />
432
- </TooltipContent>
433
- </Tooltip>
434
- );
435
- };
436
-
437
- const AttachmentRemove: FC = () => {
438
- return (
439
- <AttachmentPrimitive.Remove asChild>
440
- <TooltipIconButton
441
- tooltip="Remove file"
442
- className="aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive"
443
- side="top"
444
- >
445
- <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
446
- </TooltipIconButton>
447
- </AttachmentPrimitive.Remove>
448
- );
449
- };
450
-
451
- export const UserMessageAttachments: FC = () => {
452
- return (
453
- <div className="aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2">
454
- <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
455
- </div>
456
- );
457
- };
458
-
459
- export const ComposerAttachments: FC = () => {
460
- return (
461
- <div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden">
462
- <ComposerPrimitive.Attachments
463
- components={{ Attachment: AttachmentUI }}
464
- />
465
- </div>
466
- );
467
- };
468
-
469
- export const ComposerAddAttachment: FC = () => {
470
- return (
471
- <ComposerPrimitive.AddAttachment asChild>
472
- <TooltipIconButton
473
- tooltip="Add Attachment"
474
- side="bottom"
475
- variant="ghost"
476
- size="icon"
477
- className="aui-composer-add-attachment size-[34px] rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
478
- aria-label="Add Attachment"
479
- >
480
- <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
481
- </TooltipIconButton>
482
- </ComposerPrimitive.AddAttachment>
483
- );
484
- };
485
-
486
- ```
487
-
488
- ## components/assistant-ui/markdown-text.tsx
489
-
490
- ```tsx
491
- "use client";
492
-
493
- import "@assistant-ui/react-markdown/styles/dot.css";
494
-
495
- import {
496
- type CodeHeaderProps,
497
- MarkdownTextPrimitive,
498
- unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
499
- useIsMarkdownCodeBlock,
500
- } from "@assistant-ui/react-markdown";
501
- import remarkGfm from "remark-gfm";
502
- import { type FC, memo, useState } from "react";
503
- import { CheckIcon, CopyIcon } from "lucide-react";
504
-
505
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
506
- import { cn } from "@/lib/utils";
507
-
508
- const MarkdownTextImpl = () => {
509
- return (
510
- <MarkdownTextPrimitive
511
- remarkPlugins={[remarkGfm]}
512
- className="aui-md"
513
- components={defaultComponents}
514
- />
515
- );
516
- };
517
-
518
- export const MarkdownText = memo(MarkdownTextImpl);
519
-
520
- const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
521
- const { isCopied, copyToClipboard } = useCopyToClipboard();
522
- const onCopy = () => {
523
- if (!code || isCopied) return;
524
- copyToClipboard(code);
525
- };
526
-
527
- return (
528
- <div className="aui-code-header-root mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-muted-foreground/15 px-4 py-2 font-semibold text-foreground text-sm dark:bg-muted-foreground/20">
529
- <span className="aui-code-header-language lowercase [&>span]:text-xs">
530
- {language}
531
- </span>
532
- <TooltipIconButton tooltip="Copy" onClick={onCopy}>
533
- {!isCopied && <CopyIcon />}
534
- {isCopied && <CheckIcon />}
535
- </TooltipIconButton>
536
- </div>
537
- );
538
- };
539
-
540
- const useCopyToClipboard = ({
541
- copiedDuration = 3000,
542
- }: {
543
- copiedDuration?: number;
544
- } = {}) => {
545
- const [isCopied, setIsCopied] = useState<boolean>(false);
546
-
547
- const copyToClipboard = (value: string) => {
548
- if (!value) return;
549
-
550
- navigator.clipboard.writeText(value).then(() => {
551
- setIsCopied(true);
552
- setTimeout(() => setIsCopied(false), copiedDuration);
553
- });
554
- };
555
-
556
- return { isCopied, copyToClipboard };
557
- };
558
-
559
- const defaultComponents = memoizeMarkdownComponents({
560
- h1: ({ className, ...props }) => (
561
- <h1
562
- className={cn(
563
- "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
564
- className,
565
- )}
566
- {...props}
567
- />
568
- ),
569
- h2: ({ className, ...props }) => (
570
- <h2
571
- className={cn(
572
- "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
573
- className,
574
- )}
575
- {...props}
576
- />
577
- ),
578
- h3: ({ className, ...props }) => (
579
- <h3
580
- className={cn(
581
- "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
582
- className,
583
- )}
584
- {...props}
585
- />
586
- ),
587
- h4: ({ className, ...props }) => (
588
- <h4
589
- className={cn(
590
- "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
591
- className,
592
- )}
593
- {...props}
594
- />
595
- ),
596
- h5: ({ className, ...props }) => (
597
- <h5
598
- className={cn(
599
- "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
600
- className,
601
- )}
602
- {...props}
603
- />
604
- ),
605
- h6: ({ className, ...props }) => (
606
- <h6
607
- className={cn(
608
- "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
609
- className,
610
- )}
611
- {...props}
612
- />
613
- ),
614
- p: ({ className, ...props }) => (
615
- <p
616
- className={cn(
617
- "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
618
- className,
619
- )}
620
- {...props}
621
- />
622
- ),
623
- a: ({ className, ...props }) => (
624
- <a
625
- className={cn(
626
- "aui-md-a font-medium text-primary underline underline-offset-4",
627
- className,
628
- )}
629
- {...props}
630
- />
631
- ),
632
- blockquote: ({ className, ...props }) => (
633
- <blockquote
634
- className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
635
- {...props}
636
- />
637
- ),
638
- ul: ({ className, ...props }) => (
639
- <ul
640
- className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
641
- {...props}
642
- />
643
- ),
644
- ol: ({ className, ...props }) => (
645
- <ol
646
- className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
647
- {...props}
648
- />
649
- ),
650
- hr: ({ className, ...props }) => (
651
- <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
652
- ),
653
- table: ({ className, ...props }) => (
654
- <table
655
- className={cn(
656
- "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
657
- className,
658
- )}
659
- {...props}
660
- />
661
- ),
662
- th: ({ className, ...props }) => (
663
- <th
664
- className={cn(
665
- "aui-md-th 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",
666
- className,
667
- )}
668
- {...props}
669
- />
670
- ),
671
- td: ({ className, ...props }) => (
672
- <td
673
- className={cn(
674
- "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
675
- className,
676
- )}
677
- {...props}
678
- />
679
- ),
680
- tr: ({ className, ...props }) => (
681
- <tr
682
- className={cn(
683
- "aui-md-tr 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",
684
- className,
685
- )}
686
- {...props}
687
- />
688
- ),
689
- sup: ({ className, ...props }) => (
690
- <sup
691
- className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
692
- {...props}
693
- />
694
- ),
695
- pre: ({ className, ...props }) => (
696
- <pre
697
- className={cn(
698
- "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
699
- className,
700
- )}
701
- {...props}
702
- />
703
- ),
704
- code: function Code({ className, ...props }) {
705
- const isCodeBlock = useIsMarkdownCodeBlock();
706
- return (
707
- <code
708
- className={cn(
709
- !isCodeBlock &&
710
- "aui-md-inline-code rounded border bg-muted font-semibold",
711
- className,
712
- )}
713
- {...props}
714
- />
715
- );
716
- },
717
- CodeHeader,
718
- });
719
-
720
- ```
721
-
722
- ## components/assistant-ui/thread.tsx
723
-
724
- ```tsx
725
- import {
726
- ComposerAddAttachment,
727
- ComposerAttachments,
728
- UserMessageAttachments,
729
- } from "@/components/assistant-ui/attachment";
730
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
731
- import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
732
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
733
- import { Button } from "@/components/ui/button";
734
- import { cn } from "@/lib/utils";
735
- import {
736
- ActionBarPrimitive,
737
- AssistantIf,
738
- BranchPickerPrimitive,
739
- ComposerPrimitive,
740
- ErrorPrimitive,
741
- MessagePrimitive,
742
- ThreadPrimitive,
743
- } from "@assistant-ui/react";
744
- import {
745
- ArrowDownIcon,
746
- ArrowUpIcon,
747
- CheckIcon,
748
- ChevronLeftIcon,
749
- ChevronRightIcon,
750
- CopyIcon,
751
- DownloadIcon,
752
- PencilIcon,
753
- RefreshCwIcon,
754
- SquareIcon,
755
- } from "lucide-react";
756
- import type { FC } from "react";
757
-
758
- export const Thread: FC = () => {
759
- return (
760
- <ThreadPrimitive.Root
761
- className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
762
- style={{
763
- ["--thread-max-width" as string]: "44rem",
764
- }}
765
- >
766
- <ThreadPrimitive.Viewport
767
- turnAnchor="top"
768
- className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
769
- >
770
- <AssistantIf condition={({ thread }) => thread.isEmpty}>
771
- <ThreadWelcome />
772
- </AssistantIf>
773
-
774
- <ThreadPrimitive.Messages
775
- components={{
776
- UserMessage,
777
- EditComposer,
778
- AssistantMessage,
779
- }}
780
- />
781
-
782
- <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
783
- <ThreadScrollToBottom />
784
- <Composer />
785
- </ThreadPrimitive.ViewportFooter>
786
- </ThreadPrimitive.Viewport>
787
- </ThreadPrimitive.Root>
788
- );
789
- };
790
-
791
- const ThreadScrollToBottom: FC = () => {
792
- return (
793
- <ThreadPrimitive.ScrollToBottom asChild>
794
- <TooltipIconButton
795
- tooltip="Scroll to bottom"
796
- variant="outline"
797
- className="aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
798
- >
799
- <ArrowDownIcon />
800
- </TooltipIconButton>
801
- </ThreadPrimitive.ScrollToBottom>
802
- );
803
- };
804
-
805
- const ThreadWelcome: FC = () => {
806
- return (
807
- <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
808
- <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
809
- <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
810
- <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
811
- Hello there!
812
- </h1>
813
- <p className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in text-muted-foreground text-xl delay-75 duration-200">
814
- How can I help you today?
815
- </p>
816
- </div>
817
- </div>
818
- <ThreadSuggestions />
819
- </div>
820
- );
821
- };
822
-
823
- const SUGGESTIONS = [
824
- {
825
- title: "What's the weather",
826
- label: "in San Francisco?",
827
- prompt: "What's the weather in San Francisco?",
828
- },
829
- {
830
- title: "Explain React hooks",
831
- label: "like useState and useEffect",
832
- prompt: "Explain React hooks like useState and useEffect",
833
- },
834
- ] as const;
835
-
836
- const ThreadSuggestions: FC = () => {
837
- return (
838
- <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
839
- {SUGGESTIONS.map((suggestion, index) => (
840
- <div
841
- key={suggestion.prompt}
842
- className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200"
843
- style={{ animationDelay: `${100 + index * 50}ms` }}
844
- >
845
- <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
846
- <Button
847
- variant="ghost"
848
- className="aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted"
849
- aria-label={suggestion.prompt}
850
- >
851
- <span className="aui-thread-welcome-suggestion-text-1 font-medium">
852
- {suggestion.title}
853
- </span>
854
- <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
855
- {suggestion.label}
856
- </span>
857
- </Button>
858
- </ThreadPrimitive.Suggestion>
859
- </div>
860
- ))}
861
- </div>
862
- );
863
- };
864
-
865
- const Composer: FC = () => {
866
- return (
867
- <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
868
- <ComposerPrimitive.AttachmentDropzone className="aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50">
869
- <ComposerAttachments />
870
- <ComposerPrimitive.Input
871
- placeholder="Send a message..."
872
- className="aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0"
873
- rows={1}
874
- autoFocus
875
- aria-label="Message input"
876
- />
877
- <ComposerAction />
878
- </ComposerPrimitive.AttachmentDropzone>
879
- </ComposerPrimitive.Root>
880
- );
881
- };
882
-
883
- const ComposerAction: FC = () => {
884
- return (
885
- <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
886
- <ComposerAddAttachment />
887
-
888
- <AssistantIf condition={({ thread }) => !thread.isRunning}>
889
- <ComposerPrimitive.Send asChild>
890
- <TooltipIconButton
891
- tooltip="Send message"
892
- side="bottom"
893
- type="submit"
894
- variant="default"
895
- size="icon"
896
- className="aui-composer-send size-8 rounded-full"
897
- aria-label="Send message"
898
- >
899
- <ArrowUpIcon className="aui-composer-send-icon size-4" />
900
- </TooltipIconButton>
901
- </ComposerPrimitive.Send>
902
- </AssistantIf>
903
-
904
- <AssistantIf condition={({ thread }) => thread.isRunning}>
905
- <ComposerPrimitive.Cancel asChild>
906
- <Button
907
- type="button"
908
- variant="default"
909
- size="icon"
910
- className="aui-composer-cancel size-8 rounded-full"
911
- aria-label="Stop generating"
912
- >
913
- <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
914
- </Button>
915
- </ComposerPrimitive.Cancel>
916
- </AssistantIf>
917
- </div>
918
- );
919
- };
920
-
921
- const MessageError: FC = () => {
922
- return (
923
- <MessagePrimitive.Error>
924
- <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200">
925
- <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
926
- </ErrorPrimitive.Root>
927
- </MessagePrimitive.Error>
928
- );
929
- };
930
-
931
- const AssistantMessage: FC = () => {
932
- return (
933
- <MessagePrimitive.Root
934
- className="aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150"
935
- data-role="assistant"
936
- >
937
- <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
938
- <MessagePrimitive.Parts
939
- components={{
940
- Text: MarkdownText,
941
- tools: { Fallback: ToolFallback },
942
- }}
943
- />
944
- <MessageError />
945
- </div>
946
-
947
- <div className="aui-assistant-message-footer mt-1 ml-2 flex">
948
- <BranchPicker />
949
- <AssistantActionBar />
950
- </div>
951
- </MessagePrimitive.Root>
952
- );
953
- };
954
-
955
- const AssistantActionBar: FC = () => {
956
- return (
957
- <ActionBarPrimitive.Root
958
- hideWhenRunning
959
- autohide="not-last"
960
- autohideFloat="single-branch"
961
- className="aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
962
- >
963
- <ActionBarPrimitive.Copy asChild>
964
- <TooltipIconButton tooltip="Copy">
965
- <AssistantIf condition={({ message }) => message.isCopied}>
966
- <CheckIcon />
967
- </AssistantIf>
968
- <AssistantIf condition={({ message }) => !message.isCopied}>
969
- <CopyIcon />
970
- </AssistantIf>
971
- </TooltipIconButton>
972
- </ActionBarPrimitive.Copy>
973
- <ActionBarPrimitive.ExportMarkdown asChild>
974
- <TooltipIconButton tooltip="Export as Markdown">
975
- <DownloadIcon />
976
- </TooltipIconButton>
977
- </ActionBarPrimitive.ExportMarkdown>
978
- <ActionBarPrimitive.Reload asChild>
979
- <TooltipIconButton tooltip="Refresh">
980
- <RefreshCwIcon />
981
- </TooltipIconButton>
982
- </ActionBarPrimitive.Reload>
983
- </ActionBarPrimitive.Root>
984
- );
985
- };
986
-
987
- const UserMessage: FC = () => {
988
- return (
989
- <MessagePrimitive.Root
990
- className="aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-3 duration-150 [&:where(>*)]:col-start-2"
991
- data-role="user"
992
- >
993
- <UserMessageAttachments />
994
-
995
- <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
996
- <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
997
- <MessagePrimitive.Parts />
998
- </div>
999
- <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
1000
- <UserActionBar />
1001
- </div>
1002
- </div>
1003
-
1004
- <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1005
- </MessagePrimitive.Root>
1006
- );
1007
- };
1008
-
1009
- const UserActionBar: FC = () => {
1010
- return (
1011
- <ActionBarPrimitive.Root
1012
- hideWhenRunning
1013
- autohide="not-last"
1014
- className="aui-user-action-bar-root flex flex-col items-end"
1015
- >
1016
- <ActionBarPrimitive.Edit asChild>
1017
- <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1018
- <PencilIcon />
1019
- </TooltipIconButton>
1020
- </ActionBarPrimitive.Edit>
1021
- </ActionBarPrimitive.Root>
1022
- );
1023
- };
1024
-
1025
- const EditComposer: FC = () => {
1026
- return (
1027
- <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1028
- <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1029
- <ComposerPrimitive.Input
1030
- className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1031
- autoFocus
1032
- />
1033
- <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1034
- <ComposerPrimitive.Cancel asChild>
1035
- <Button variant="ghost" size="sm">
1036
- Cancel
1037
- </Button>
1038
- </ComposerPrimitive.Cancel>
1039
- <ComposerPrimitive.Send asChild>
1040
- <Button size="sm">Update</Button>
1041
- </ComposerPrimitive.Send>
1042
- </div>
1043
- </ComposerPrimitive.Root>
1044
- </MessagePrimitive.Root>
1045
- );
1046
- };
1047
-
1048
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1049
- className,
1050
- ...rest
1051
- }) => {
1052
- return (
1053
- <BranchPickerPrimitive.Root
1054
- hideWhenSingleBranch
1055
- className={cn(
1056
- "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-muted-foreground text-xs",
1057
- className,
1058
- )}
1059
- {...rest}
1060
- >
1061
- <BranchPickerPrimitive.Previous asChild>
1062
- <TooltipIconButton tooltip="Previous">
1063
- <ChevronLeftIcon />
1064
- </TooltipIconButton>
1065
- </BranchPickerPrimitive.Previous>
1066
- <span className="aui-branch-picker-state font-medium">
1067
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
1068
- </span>
1069
- <BranchPickerPrimitive.Next asChild>
1070
- <TooltipIconButton tooltip="Next">
1071
- <ChevronRightIcon />
1072
- </TooltipIconButton>
1073
- </BranchPickerPrimitive.Next>
1074
- </BranchPickerPrimitive.Root>
1075
- );
1076
- };
1077
-
1078
- ```
1079
-
1080
- ## components/assistant-ui/tool-fallback.tsx
1081
-
1082
- ```tsx
1083
- import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1084
- import {
1085
- CheckIcon,
1086
- ChevronDownIcon,
1087
- ChevronUpIcon,
1088
- XCircleIcon,
1089
- } from "lucide-react";
1090
- import { useState } from "react";
1091
- import { Button } from "@/components/ui/button";
1092
- import { cn } from "@/lib/utils";
1093
-
1094
- export const ToolFallback: ToolCallMessagePartComponent = ({
1095
- toolName,
1096
- argsText,
1097
- result,
1098
- status,
1099
- }) => {
1100
- const [isCollapsed, setIsCollapsed] = useState(true);
1101
-
1102
- const isCancelled =
1103
- status?.type === "incomplete" && status.reason === "cancelled";
1104
- const cancelledReason =
1105
- isCancelled && status.error
1106
- ? typeof status.error === "string"
1107
- ? status.error
1108
- : JSON.stringify(status.error)
1109
- : null;
1110
-
1111
- return (
1112
- <div
1113
- className={cn(
1114
- "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1115
- isCancelled && "border-muted-foreground/30 bg-muted/30",
1116
- )}
1117
- >
1118
- <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1119
- {isCancelled ? (
1120
- <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1121
- ) : (
1122
- <CheckIcon className="aui-tool-fallback-icon size-4" />
1123
- )}
1124
- <p
1125
- className={cn(
1126
- "aui-tool-fallback-title grow",
1127
- isCancelled && "text-muted-foreground line-through",
1128
- )}
1129
- >
1130
- {isCancelled ? "Cancelled tool: " : "Used tool: "}
1131
- <b>{toolName}</b>
1132
- </p>
1133
- <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1134
- {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1135
- </Button>
1136
- </div>
1137
- {!isCollapsed && (
1138
- <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1139
- {cancelledReason && (
1140
- <div className="aui-tool-fallback-cancelled-root px-4">
1141
- <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1142
- Cancelled reason:
1143
- </p>
1144
- <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1145
- {cancelledReason}
1146
- </p>
1147
- </div>
1148
- )}
1149
- <div
1150
- className={cn(
1151
- "aui-tool-fallback-args-root px-4",
1152
- isCancelled && "opacity-60",
1153
- )}
1154
- >
1155
- <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1156
- {argsText}
1157
- </pre>
1158
- </div>
1159
- {!isCancelled && result !== undefined && (
1160
- <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1161
- <p className="aui-tool-fallback-result-header font-semibold">
1162
- Result:
1163
- </p>
1164
- <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1165
- {typeof result === "string"
1166
- ? result
1167
- : JSON.stringify(result, null, 2)}
1168
- </pre>
1169
- </div>
1170
- )}
1171
- </div>
1172
- )}
1173
- </div>
1174
- );
1175
- };
1176
-
1177
- ```
1178
-
1179
- ## components/assistant-ui/tooltip-icon-button.tsx
1180
-
1181
- ```tsx
1182
- "use client";
1183
-
1184
- import { ComponentPropsWithRef, forwardRef } from "react";
1185
- import { Slottable } from "@radix-ui/react-slot";
1186
-
1187
- import {
1188
- Tooltip,
1189
- TooltipContent,
1190
- TooltipTrigger,
1191
- } from "@/components/ui/tooltip";
1192
- import { Button } from "@/components/ui/button";
1193
- import { cn } from "@/lib/utils";
1194
-
1195
- export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1196
- tooltip: string;
1197
- side?: "top" | "bottom" | "left" | "right";
1198
- };
1199
-
1200
- export const TooltipIconButton = forwardRef<
1201
- HTMLButtonElement,
1202
- TooltipIconButtonProps
1203
- >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
1204
- return (
1205
- <Tooltip>
1206
- <TooltipTrigger asChild>
1207
- <Button
1208
- variant="ghost"
1209
- size="icon"
1210
- {...rest}
1211
- className={cn("aui-button-icon size-6 p-1", className)}
1212
- ref={ref}
1213
- >
1214
- <Slottable>{children}</Slottable>
1215
- <span className="aui-sr-only sr-only">{tooltip}</span>
1216
- </Button>
1217
- </TooltipTrigger>
1218
- <TooltipContent side={side}>{tooltip}</TooltipContent>
1219
- </Tooltip>
1220
- );
1221
- });
1222
-
1223
- TooltipIconButton.displayName = "TooltipIconButton";
1224
-
1225
- ```
1226
-
1227
- ## components/ui/avatar.tsx
1228
-
1229
- ```tsx
1230
- "use client";
1231
-
1232
- import * as React from "react";
1233
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
1234
-
1235
- import { cn } from "@/lib/utils";
1236
-
1237
- function Avatar({
1238
- className,
1239
- ...props
1240
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
1241
- return (
1242
- <AvatarPrimitive.Root
1243
- data-slot="avatar"
1244
- className={cn(
1245
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
1246
- className,
1247
- )}
1248
- {...props}
1249
- />
1250
- );
1251
- }
1252
-
1253
- function AvatarImage({
1254
- className,
1255
- ...props
1256
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
1257
- return (
1258
- <AvatarPrimitive.Image
1259
- data-slot="avatar-image"
1260
- className={cn("aspect-square size-full", className)}
1261
- {...props}
1262
- />
1263
- );
1264
- }
1265
-
1266
- function AvatarFallback({
1267
- className,
1268
- ...props
1269
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
1270
- return (
1271
- <AvatarPrimitive.Fallback
1272
- data-slot="avatar-fallback"
1273
- className={cn(
1274
- "flex size-full items-center justify-center rounded-full bg-muted",
1275
- className,
1276
- )}
1277
- {...props}
1278
- />
1279
- );
1280
- }
1281
-
1282
- export { Avatar, AvatarImage, AvatarFallback };
1283
-
1284
- ```
1285
-
1286
- ## components/ui/button.tsx
1287
-
1288
- ```tsx
1289
- import * as React from "react";
1290
- import { Slot } from "@radix-ui/react-slot";
1291
- import { cva, type VariantProps } from "class-variance-authority";
1292
-
1293
- import { cn } from "@/lib/utils";
1294
-
1295
- const buttonVariants = cva(
1296
- "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
1297
- {
1298
- variants: {
1299
- variant: {
1300
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
1301
- destructive:
1302
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1303
- outline:
1304
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1305
- secondary:
1306
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1307
- ghost:
1308
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1309
- link: "text-primary underline-offset-4 hover:underline",
1310
- },
1311
- size: {
1312
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
1313
- sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1314
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1315
- icon: "size-9",
1316
- "icon-sm": "size-8",
1317
- "icon-lg": "size-10",
1318
- },
1319
- },
1320
- defaultVariants: {
1321
- variant: "default",
1322
- size: "default",
1323
- },
1324
- },
1325
- );
1326
-
1327
- function Button({
1328
- className,
1329
- variant = "default",
1330
- size = "default",
1331
- asChild = false,
1332
- ...props
1333
- }: React.ComponentProps<"button"> &
1334
- VariantProps<typeof buttonVariants> & {
1335
- asChild?: boolean;
1336
- }) {
1337
- const Comp = asChild ? Slot : "button";
1338
-
1339
- return (
1340
- <Comp
1341
- data-slot="button"
1342
- data-variant={variant}
1343
- data-size={size}
1344
- className={cn(buttonVariants({ variant, size, className }))}
1345
- {...props}
1346
- />
1347
- );
1348
- }
1349
-
1350
- export { Button, buttonVariants };
1351
-
1352
- ```
1353
-
1354
- ## components/ui/dialog.tsx
1355
-
1356
- ```tsx
1357
- "use client";
1358
-
1359
- import * as React from "react";
1360
- import * as DialogPrimitive from "@radix-ui/react-dialog";
1361
- import { XIcon } from "lucide-react";
1362
-
1363
- import { cn } from "@/lib/utils";
1364
-
1365
- function Dialog({
1366
- ...props
1367
- }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1368
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1369
- }
1370
-
1371
- function DialogTrigger({
1372
- ...props
1373
- }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1374
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1375
- }
1376
-
1377
- function DialogPortal({
1378
- ...props
1379
- }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1380
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1381
- }
1382
-
1383
- function DialogClose({
1384
- ...props
1385
- }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1386
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1387
- }
1388
-
1389
- function DialogOverlay({
1390
- className,
1391
- ...props
1392
- }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1393
- return (
1394
- <DialogPrimitive.Overlay
1395
- data-slot="dialog-overlay"
1396
- className={cn(
1397
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in",
1398
- className,
1399
- )}
1400
- {...props}
1401
- />
1402
- );
1403
- }
1404
-
1405
- function DialogContent({
1406
- className,
1407
- children,
1408
- showCloseButton = true,
1409
- ...props
1410
- }: React.ComponentProps<typeof DialogPrimitive.Content> & {
1411
- showCloseButton?: boolean;
1412
- }) {
1413
- return (
1414
- <DialogPortal data-slot="dialog-portal">
1415
- <DialogOverlay />
1416
- <DialogPrimitive.Content
1417
- data-slot="dialog-content"
1418
- className={cn(
1419
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg outline-none duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
1420
- className,
1421
- )}
1422
- {...props}
1423
- >
1424
- {children}
1425
- {showCloseButton && (
1426
- <DialogPrimitive.Close
1427
- data-slot="dialog-close"
1428
- className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
1429
- >
1430
- <XIcon />
1431
- <span className="sr-only">Close</span>
1432
- </DialogPrimitive.Close>
1433
- )}
1434
- </DialogPrimitive.Content>
1435
- </DialogPortal>
1436
- );
1437
- }
1438
-
1439
- function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1440
- return (
1441
- <div
1442
- data-slot="dialog-header"
1443
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1444
- {...props}
1445
- />
1446
- );
1447
- }
1448
-
1449
- function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1450
- return (
1451
- <div
1452
- data-slot="dialog-footer"
1453
- className={cn(
1454
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1455
- className,
1456
- )}
1457
- {...props}
1458
- />
1459
- );
1460
- }
1461
-
1462
- function DialogTitle({
1463
- className,
1464
- ...props
1465
- }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1466
- return (
1467
- <DialogPrimitive.Title
1468
- data-slot="dialog-title"
1469
- className={cn("font-semibold text-lg leading-none", className)}
1470
- {...props}
1471
- />
1472
- );
1473
- }
1474
-
1475
- function DialogDescription({
1476
- className,
1477
- ...props
1478
- }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1479
- return (
1480
- <DialogPrimitive.Description
1481
- data-slot="dialog-description"
1482
- className={cn("text-muted-foreground text-sm", className)}
1483
- {...props}
1484
- />
1485
- );
1486
- }
1487
-
1488
- export {
1489
- Dialog,
1490
- DialogClose,
1491
- DialogContent,
1492
- DialogDescription,
1493
- DialogFooter,
1494
- DialogHeader,
1495
- DialogOverlay,
1496
- DialogPortal,
1497
- DialogTitle,
1498
- DialogTrigger,
1499
- };
1500
-
1501
- ```
1502
-
1503
- ## components/ui/tooltip.tsx
1504
-
1505
- ```tsx
1506
- "use client";
1507
-
1508
- import * as React from "react";
1509
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1510
-
1511
- import { cn } from "@/lib/utils";
1512
-
1513
- function TooltipProvider({
1514
- delayDuration = 0,
1515
- ...props
1516
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1517
- return (
1518
- <TooltipPrimitive.Provider
1519
- data-slot="tooltip-provider"
1520
- delayDuration={delayDuration}
1521
- {...props}
1522
- />
1523
- );
1524
- }
1525
-
1526
- function Tooltip({
1527
- ...props
1528
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1529
- return (
1530
- <TooltipProvider>
1531
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1532
- </TooltipProvider>
1533
- );
1534
- }
1535
-
1536
- function TooltipTrigger({
1537
- ...props
1538
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
1539
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
1540
- }
1541
-
1542
- function TooltipContent({
1543
- className,
1544
- sideOffset = 0,
1545
- children,
1546
- ...props
1547
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
1548
- return (
1549
- <TooltipPrimitive.Portal>
1550
- <TooltipPrimitive.Content
1551
- data-slot="tooltip-content"
1552
- sideOffset={sideOffset}
1553
- className={cn(
1554
- "fade-in-0 zoom-in-95 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) animate-in text-balance rounded-md bg-foreground px-3 py-1.5 text-background text-xs data-[state=closed]:animate-out",
1555
- className,
1556
- )}
1557
- {...props}
1558
- >
1559
- {children}
1560
- <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
1561
- </TooltipPrimitive.Content>
1562
- </TooltipPrimitive.Portal>
1563
- );
1564
- }
1565
-
1566
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1567
-
1568
- ```
1569
-
1570
- ## lib/utils.ts
1571
-
1572
- ```typescript
1573
- import { clsx, type ClassValue } from "clsx";
1574
- import { twMerge } from "tailwind-merge";
1575
-
1576
- export function cn(...inputs: ClassValue[]) {
1577
- return twMerge(clsx(inputs));
1578
- }
1579
-
1580
- ```
1581
-
1582
- ## next.config.js
1583
-
1584
- ```javascript
1585
- /** @type {import('next').NextConfig} */
1586
- const nextConfig = {
1587
- transpilePackages: ["@assistant-ui/react", "@assistant-ui/react-ai-sdk"],
1588
- };
1589
-
1590
- export default nextConfig;
1591
-
1592
- ```
1593
-
1594
- ## package.json
1595
-
1596
- ```json
1597
- {
1598
- "name": "with-ai-sdk-v5",
1599
- "version": "0.0.0",
1600
- "private": true,
1601
- "type": "module",
1602
- "scripts": {
1603
- "dev": "next dev",
1604
- "build": "next build",
1605
- "start": "next start"
1606
- },
1607
- "dependencies": {
1608
- "@ai-sdk/openai": "^2.0.88",
1609
- "@assistant-ui/react": "workspace:*",
1610
- "@assistant-ui/react-ai-sdk": "workspace:*",
1611
- "@assistant-ui/react-markdown": "workspace:*",
1612
- "@radix-ui/react-avatar": "^1.1.11",
1613
- "@radix-ui/react-dialog": "^1.1.15",
1614
- "@radix-ui/react-slot": "^1.2.4",
1615
- "@radix-ui/react-tooltip": "^1.2.8",
1616
- "ai": "^5.0.116",
1617
- "class-variance-authority": "^0.7.1",
1618
- "clsx": "^2.1.1",
1619
- "lucide-react": "^0.562.0",
1620
- "next": "16.1.0",
1621
- "react": "19.2.3",
1622
- "react-dom": "19.2.3",
1623
- "remark-gfm": "^4.0.1",
1624
- "tailwind-merge": "^3.4.0",
1625
- "zod": "^4.2.1",
1626
- "zustand": "^5.0.9"
1627
- },
1628
- "devDependencies": {
1629
- "@assistant-ui/x-buildutils": "workspace:*",
1630
- "@tailwindcss/postcss": "^4.1.18",
1631
- "@types/node": "^25.0.3",
1632
- "@types/react": "^19.2.7",
1633
- "@types/react-dom": "^19.2.3",
1634
- "postcss": "^8.5.6",
1635
- "tailwindcss": "^4.1.18",
1636
- "tw-animate-css": "^1.4.0",
1637
- "typescript": "^5.9.3"
1638
- }
1639
- }
1640
-
1641
- ```
1642
-
1643
- ## README.md
1644
-
1645
- ```markdown
1646
- # AI SDK v5 Example
1647
-
1648
- This example demonstrates how to use `@assistant-ui/react-ai-sdk-v5` with the Vercel AI SDK v5.
1649
-
1650
- ## Getting Started
1651
-
1652
- 1. Install dependencies:
1653
-
1654
- ```bash
1655
- npm install
1656
- ```
1657
-
1658
- 2. Set up your environment variables:
1659
-
1660
- ```bash
1661
- cp .env.example .env.local
1662
- ```
1663
-
1664
- Add your Anthropic API key to `.env.local`:
1665
-
1666
- ```
1667
- ANTHROPIC_API_KEY=your-api-key-here
1668
- ```
1669
-
1670
- 3. Run the development server:
1671
-
1672
- ```bash
1673
- npm run dev
1674
- ```
1675
-
1676
- Open [http://localhost:3000](http://localhost:3000) to see the result.
1677
-
1678
- ## Key Features
1679
-
1680
- - Uses the new AI SDK v5 with `@ai-sdk/react` and `@ai-sdk/anthropic`
1681
- - Integrates with `@assistant-ui/react` using the new `useChatRuntime` hook
1682
- - No RSC support (client-side only)
1683
- - Simplified integration with the `useChatRuntime` hook that wraps AI SDK v5's `useChat`
1684
- - Automatically uses `AssistantChatTransport` to pass system messages and frontend tools to the backend
1685
-
1686
- ## Custom Transport Configuration
1687
-
1688
- By default, `useChatRuntime` uses `AssistantChatTransport` which automatically forwards system messages and frontend tools to the backend.
1689
-
1690
- ### Custom API URL with Forwarding
1691
-
1692
- When customizing the API URL, you must explicitly use `AssistantChatTransport` to keep system/tools forwarding:
1693
-
1694
- ```typescript
1695
- import { AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
1696
-
1697
- const runtime = useChatRuntime({
1698
- transport: new AssistantChatTransport({
1699
- api: "/my-custom-api/chat", // Custom URL with system/tools forwarding
1700
- }),
1701
- });
1702
- ```
1703
-
1704
- ### Disable System/Tools Forwarding
1705
-
1706
- To use the standard AI SDK transport without forwarding:
1707
-
1708
- ```typescript
1709
- import { DefaultChatTransport } from "ai";
1710
-
1711
- const runtime = useChatRuntime({
1712
- transport: new DefaultChatTransport(), // No system/tools forwarding
1713
- });
1714
- ```
1715
-
1716
- ## API Route
1717
-
1718
- The API route at `/api/chat` uses the new `streamText` function from AI SDK v5 to handle chat completions.
1719
-
1720
- ```
1721
-
1722
- ## tsconfig.json
1723
-
1724
- ```json
1725
- {
1726
- "extends": "@assistant-ui/x-buildutils/ts/next",
1727
- "compilerOptions": {
1728
- "paths": { "@/*": ["./*"] }
1729
- },
1730
- "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1731
- "exclude": ["node_modules"]
1732
- }
1733
-
1734
- ```
1735
-