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