@assistant-ui/mcp-docs-server 0.1.1
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/local-ollama.md +1135 -0
- package/.docs/organized/code-examples/search-agent-for-e-commerce.md +1721 -0
- package/.docs/organized/code-examples/with-ai-sdk.md +1081 -0
- package/.docs/organized/code-examples/with-cloud.md +1164 -0
- package/.docs/organized/code-examples/with-external-store.md +1064 -0
- package/.docs/organized/code-examples/with-ffmpeg.md +1305 -0
- package/.docs/organized/code-examples/with-langgraph.md +1819 -0
- package/.docs/organized/code-examples/with-openai-assistants.md +1175 -0
- package/.docs/organized/code-examples/with-react-hook-form.md +1727 -0
- package/.docs/organized/code-examples/with-vercel-ai-rsc.md +1157 -0
- package/.docs/raw/blog/2024-07-29-hello/index.mdx +65 -0
- package/.docs/raw/blog/2024-09-11/index.mdx +10 -0
- package/.docs/raw/blog/2024-12-15/index.mdx +10 -0
- package/.docs/raw/blog/2025-01-31-changelog/index.mdx +129 -0
- package/.docs/raw/docs/about-assistantui.mdx +44 -0
- package/.docs/raw/docs/api-reference/context-providers/AssistantRuntimeProvider.mdx +30 -0
- package/.docs/raw/docs/api-reference/context-providers/TextContentPartProvider.mdx +26 -0
- package/.docs/raw/docs/api-reference/integrations/react-hook-form.mdx +103 -0
- package/.docs/raw/docs/api-reference/integrations/vercel-ai-sdk.mdx +145 -0
- package/.docs/raw/docs/api-reference/overview.mdx +583 -0
- package/.docs/raw/docs/api-reference/primitives/ActionBar.mdx +264 -0
- package/.docs/raw/docs/api-reference/primitives/AssistantModal.mdx +129 -0
- package/.docs/raw/docs/api-reference/primitives/Attachment.mdx +96 -0
- package/.docs/raw/docs/api-reference/primitives/BranchPicker.mdx +87 -0
- package/.docs/raw/docs/api-reference/primitives/Composer.mdx +204 -0
- package/.docs/raw/docs/api-reference/primitives/ContentPart.mdx +173 -0
- package/.docs/raw/docs/api-reference/primitives/Error.mdx +70 -0
- package/.docs/raw/docs/api-reference/primitives/Message.mdx +181 -0
- package/.docs/raw/docs/api-reference/primitives/Thread.mdx +197 -0
- package/.docs/raw/docs/api-reference/primitives/composition.mdx +21 -0
- package/.docs/raw/docs/api-reference/runtimes/AssistantRuntime.mdx +33 -0
- package/.docs/raw/docs/api-reference/runtimes/AttachmentRuntime.mdx +46 -0
- package/.docs/raw/docs/api-reference/runtimes/ComposerRuntime.mdx +69 -0
- package/.docs/raw/docs/api-reference/runtimes/ContentPartRuntime.mdx +22 -0
- package/.docs/raw/docs/api-reference/runtimes/MessageRuntime.mdx +49 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadListItemRuntime.mdx +32 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadListRuntime.mdx +31 -0
- package/.docs/raw/docs/api-reference/runtimes/ThreadRuntime.mdx +48 -0
- package/.docs/raw/docs/architecture.mdx +92 -0
- package/.docs/raw/docs/cloud/authorization.mdx +152 -0
- package/.docs/raw/docs/cloud/overview.mdx +55 -0
- package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +54 -0
- package/.docs/raw/docs/cloud/persistence/langgraph.mdx +123 -0
- package/.docs/raw/docs/concepts/architecture.mdx +19 -0
- package/.docs/raw/docs/concepts/runtime-layer.mdx +163 -0
- package/.docs/raw/docs/concepts/why.mdx +9 -0
- package/.docs/raw/docs/copilots/make-assistant-readable.mdx +71 -0
- package/.docs/raw/docs/copilots/make-assistant-tool-ui.mdx +76 -0
- package/.docs/raw/docs/copilots/make-assistant-tool.mdx +117 -0
- package/.docs/raw/docs/copilots/model-context.mdx +135 -0
- package/.docs/raw/docs/copilots/motivation.mdx +191 -0
- package/.docs/raw/docs/copilots/use-assistant-instructions.mdx +62 -0
- package/.docs/raw/docs/getting-started.mdx +1133 -0
- package/.docs/raw/docs/guides/Attachments.mdx +640 -0
- package/.docs/raw/docs/guides/Branching.mdx +59 -0
- package/.docs/raw/docs/guides/Editing.mdx +56 -0
- package/.docs/raw/docs/guides/Speech.mdx +43 -0
- package/.docs/raw/docs/guides/ToolUI.mdx +663 -0
- package/.docs/raw/docs/guides/Tools.mdx +496 -0
- package/.docs/raw/docs/index.mdx +7 -0
- package/.docs/raw/docs/legacy/styled/AssistantModal.mdx +85 -0
- package/.docs/raw/docs/legacy/styled/Decomposition.mdx +633 -0
- package/.docs/raw/docs/legacy/styled/Markdown.mdx +86 -0
- package/.docs/raw/docs/legacy/styled/Scrollbar.mdx +71 -0
- package/.docs/raw/docs/legacy/styled/Thread.mdx +84 -0
- package/.docs/raw/docs/legacy/styled/ThreadWidth.mdx +21 -0
- package/.docs/raw/docs/mcp-docs-server.mdx +324 -0
- package/.docs/raw/docs/migrations/deprecation-policy.mdx +41 -0
- package/.docs/raw/docs/migrations/v0-7.mdx +188 -0
- package/.docs/raw/docs/migrations/v0-8.mdx +160 -0
- package/.docs/raw/docs/migrations/v0-9.mdx +75 -0
- package/.docs/raw/docs/react-compatibility.mdx +208 -0
- package/.docs/raw/docs/runtimes/ai-sdk/rsc.mdx +226 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-assistant-hook.mdx +195 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat-hook.mdx +138 -0
- package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +136 -0
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +1624 -0
- package/.docs/raw/docs/runtimes/custom/local.mdx +1185 -0
- package/.docs/raw/docs/runtimes/helicone.mdx +60 -0
- package/.docs/raw/docs/runtimes/langgraph/index.mdx +320 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +11 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +28 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +120 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +336 -0
- package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +385 -0
- package/.docs/raw/docs/runtimes/langserve.mdx +126 -0
- package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +218 -0
- package/.docs/raw/docs/runtimes/mastra/overview.mdx +17 -0
- package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +196 -0
- package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +222 -0
- package/.docs/raw/docs/ui/AssistantModal.mdx +46 -0
- package/.docs/raw/docs/ui/AssistantSidebar.mdx +42 -0
- package/.docs/raw/docs/ui/Attachment.mdx +82 -0
- package/.docs/raw/docs/ui/Markdown.mdx +72 -0
- package/.docs/raw/docs/ui/Mermaid.mdx +79 -0
- package/.docs/raw/docs/ui/Scrollbar.mdx +59 -0
- package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +253 -0
- package/.docs/raw/docs/ui/Thread.mdx +47 -0
- package/.docs/raw/docs/ui/ThreadList.mdx +49 -0
- package/.docs/raw/docs/ui/ToolFallback.mdx +64 -0
- package/.docs/raw/docs/ui/primitives/Thread.mdx +197 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/chunk-C7O7EFKU.js +38 -0
- package/dist/chunk-CZCDQ3YH.js +420 -0
- package/dist/index.js +1 -0
- package/dist/prepare-docs/prepare.js +199 -0
- package/dist/stdio.js +8 -0
- package/package.json +43 -0
|
@@ -0,0 +1,1305 @@
|
|
|
1
|
+
# Example: with-ffmpeg
|
|
2
|
+
|
|
3
|
+
## app/api/chat/route.ts
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { openai } from "@ai-sdk/openai";
|
|
7
|
+
import { frontendTools } from "@assistant-ui/react-ai-sdk";
|
|
8
|
+
import { streamText } from "ai";
|
|
9
|
+
|
|
10
|
+
export const runtime = "edge";
|
|
11
|
+
export const maxDuration = 30;
|
|
12
|
+
|
|
13
|
+
export async function POST(req: Request) {
|
|
14
|
+
const { messages, system, tools } = await req.json();
|
|
15
|
+
|
|
16
|
+
const result = streamText({
|
|
17
|
+
model: openai("gpt-4o"),
|
|
18
|
+
messages,
|
|
19
|
+
toolCallStreaming: true,
|
|
20
|
+
system,
|
|
21
|
+
tools: {
|
|
22
|
+
...frontendTools(tools),
|
|
23
|
+
// add backend tools here
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return result.toDataStreamResponse();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## app/globals.css
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
@import "tailwindcss";
|
|
36
|
+
@import "tw-animate-css";
|
|
37
|
+
|
|
38
|
+
@custom-variant dark (&:is(.dark *));
|
|
39
|
+
|
|
40
|
+
@theme inline {
|
|
41
|
+
--color-background: var(--background);
|
|
42
|
+
--color-foreground: var(--foreground);
|
|
43
|
+
--font-sans: var(--font-geist-sans);
|
|
44
|
+
--font-mono: var(--font-geist-mono);
|
|
45
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
46
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
47
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
48
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
49
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
50
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
51
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
52
|
+
--color-sidebar: var(--sidebar);
|
|
53
|
+
--color-chart-5: var(--chart-5);
|
|
54
|
+
--color-chart-4: var(--chart-4);
|
|
55
|
+
--color-chart-3: var(--chart-3);
|
|
56
|
+
--color-chart-2: var(--chart-2);
|
|
57
|
+
--color-chart-1: var(--chart-1);
|
|
58
|
+
--color-ring: var(--ring);
|
|
59
|
+
--color-input: var(--input);
|
|
60
|
+
--color-border: var(--border);
|
|
61
|
+
--color-destructive: var(--destructive);
|
|
62
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
63
|
+
--color-accent: var(--accent);
|
|
64
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
65
|
+
--color-muted: var(--muted);
|
|
66
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
67
|
+
--color-secondary: var(--secondary);
|
|
68
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
69
|
+
--color-primary: var(--primary);
|
|
70
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
71
|
+
--color-popover: var(--popover);
|
|
72
|
+
--color-card-foreground: var(--card-foreground);
|
|
73
|
+
--color-card: var(--card);
|
|
74
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
75
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
76
|
+
--radius-lg: var(--radius);
|
|
77
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:root {
|
|
81
|
+
--radius: 0.625rem;
|
|
82
|
+
--background: oklch(1 0 0);
|
|
83
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
84
|
+
--card: oklch(1 0 0);
|
|
85
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
86
|
+
--popover: oklch(1 0 0);
|
|
87
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
88
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
89
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
90
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
91
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
92
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
93
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
94
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
95
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
96
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
97
|
+
--border: oklch(0.92 0.004 286.32);
|
|
98
|
+
--input: oklch(0.92 0.004 286.32);
|
|
99
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
100
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
101
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
102
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
103
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
104
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
105
|
+
--sidebar: oklch(0.985 0 0);
|
|
106
|
+
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
107
|
+
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
108
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
109
|
+
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
110
|
+
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
111
|
+
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
112
|
+
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.dark {
|
|
116
|
+
--background: oklch(0.141 0.005 285.823);
|
|
117
|
+
--foreground: oklch(0.985 0 0);
|
|
118
|
+
--card: oklch(0.21 0.006 285.885);
|
|
119
|
+
--card-foreground: oklch(0.985 0 0);
|
|
120
|
+
--popover: oklch(0.21 0.006 285.885);
|
|
121
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
122
|
+
--primary: oklch(0.92 0.004 286.32);
|
|
123
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
124
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
125
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
126
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
127
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
128
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
129
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
130
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
131
|
+
--border: oklch(1 0 0 / 10%);
|
|
132
|
+
--input: oklch(1 0 0 / 15%);
|
|
133
|
+
--ring: oklch(0.552 0.016 285.938);
|
|
134
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
135
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
136
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
137
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
138
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
139
|
+
--sidebar: oklch(0.21 0.006 285.885);
|
|
140
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
141
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
142
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
143
|
+
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
144
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
145
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
146
|
+
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@layer base {
|
|
150
|
+
* {
|
|
151
|
+
@apply border-border outline-ring/50;
|
|
152
|
+
}
|
|
153
|
+
body {
|
|
154
|
+
@apply bg-background text-foreground;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## app/layout.tsx
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import type { Metadata } from "next";
|
|
164
|
+
import "./globals.css";
|
|
165
|
+
import { MyRuntimeProvider } from "./MyRuntimeProvider";
|
|
166
|
+
|
|
167
|
+
export const metadata: Metadata = {
|
|
168
|
+
title: "ConvertGPT with assistant-ui",
|
|
169
|
+
description: "FFmpeg integration with assistant-ui",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export default function RootLayout({
|
|
173
|
+
children,
|
|
174
|
+
}: Readonly<{
|
|
175
|
+
children: React.ReactNode;
|
|
176
|
+
}>) {
|
|
177
|
+
return (
|
|
178
|
+
<MyRuntimeProvider>
|
|
179
|
+
<html lang="en" className="h-dvh">
|
|
180
|
+
<body className="h-dvh font-sans">{children}</body>
|
|
181
|
+
</html>
|
|
182
|
+
</MyRuntimeProvider>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## app/MyRuntimeProvider.tsx
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
"use client";
|
|
192
|
+
|
|
193
|
+
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
194
|
+
import { AttachmentAdapter } from "@assistant-ui/react";
|
|
195
|
+
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
|
|
196
|
+
import { INTERNAL } from "@assistant-ui/react";
|
|
197
|
+
|
|
198
|
+
const { generateId } = INTERNAL;
|
|
199
|
+
|
|
200
|
+
const attachmentAdapter: AttachmentAdapter = {
|
|
201
|
+
accept: "image/*,video/*,audio/*",
|
|
202
|
+
async add({ file }) {
|
|
203
|
+
return {
|
|
204
|
+
id: generateId(),
|
|
205
|
+
file,
|
|
206
|
+
type: "file",
|
|
207
|
+
name: file.name,
|
|
208
|
+
contentType: file.type,
|
|
209
|
+
status: { type: "requires-action", reason: "composer-send" },
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
async send(attachment) {
|
|
213
|
+
return {
|
|
214
|
+
...attachment,
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: "text",
|
|
218
|
+
text: `[User attached a file: ${attachment.name}]`,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
status: { type: "complete" },
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
async remove() {
|
|
225
|
+
// noop
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export function MyRuntimeProvider({
|
|
230
|
+
children,
|
|
231
|
+
}: Readonly<{
|
|
232
|
+
children: React.ReactNode;
|
|
233
|
+
}>) {
|
|
234
|
+
const runtime = useChatRuntime({
|
|
235
|
+
api: "/api/chat",
|
|
236
|
+
maxSteps: 4,
|
|
237
|
+
adapters: {
|
|
238
|
+
attachments: attachmentAdapter,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
244
|
+
{children}
|
|
245
|
+
</AssistantRuntimeProvider>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## app/NoSSRWrapper.tsx
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import dynamic from "next/dynamic";
|
|
255
|
+
import { FC, PropsWithChildren } from "react";
|
|
256
|
+
|
|
257
|
+
const NoSSRWrapper: FC<PropsWithChildren> = (props) => <>{props.children}</>;
|
|
258
|
+
|
|
259
|
+
export default dynamic(() => Promise.resolve(NoSSRWrapper), {
|
|
260
|
+
ssr: false,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## app/page.tsx
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
"use client";
|
|
269
|
+
|
|
270
|
+
import {
|
|
271
|
+
useAssistantInstructions,
|
|
272
|
+
useAssistantTool,
|
|
273
|
+
useThreadComposer,
|
|
274
|
+
} from "@assistant-ui/react";
|
|
275
|
+
import { z } from "zod";
|
|
276
|
+
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
|
277
|
+
import { toBlobURL } from "@ffmpeg/util";
|
|
278
|
+
import { FC, useEffect, useRef, useState } from "react";
|
|
279
|
+
import {
|
|
280
|
+
CircleCheckIcon,
|
|
281
|
+
RefreshCcwIcon,
|
|
282
|
+
TriangleAlertIcon,
|
|
283
|
+
} from "lucide-react";
|
|
284
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
285
|
+
|
|
286
|
+
// MVP: upload file, enter command
|
|
287
|
+
// MVP: convert command to tool call
|
|
288
|
+
// MVP: tool call: ffmpeg
|
|
289
|
+
|
|
290
|
+
const FfmpegTool: FC<{ file: File }> = ({ file }) => {
|
|
291
|
+
const loadingRef = useRef(false);
|
|
292
|
+
const ffmpegRef = useRef(new FFmpeg());
|
|
293
|
+
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (loadingRef.current) return;
|
|
296
|
+
loadingRef.current = true;
|
|
297
|
+
|
|
298
|
+
const load = async () => {
|
|
299
|
+
const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd";
|
|
300
|
+
const ffmpeg = ffmpegRef.current;
|
|
301
|
+
// toBlobURL is used to bypass CORS issue, urls with the same
|
|
302
|
+
// domain can be used directly.
|
|
303
|
+
await ffmpeg.load({
|
|
304
|
+
coreURL: await toBlobURL(
|
|
305
|
+
`${baseURL}/ffmpeg-core.js`,
|
|
306
|
+
"text/javascript",
|
|
307
|
+
),
|
|
308
|
+
wasmURL: await toBlobURL(
|
|
309
|
+
`${baseURL}/ffmpeg-core.wasm`,
|
|
310
|
+
"application/wasm",
|
|
311
|
+
),
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
load();
|
|
315
|
+
}, []);
|
|
316
|
+
|
|
317
|
+
useAssistantInstructions("The user has attached a file: " + file.name);
|
|
318
|
+
|
|
319
|
+
useAssistantTool({
|
|
320
|
+
toolName: "run_ffmpeg",
|
|
321
|
+
parameters: z.object({
|
|
322
|
+
command: z
|
|
323
|
+
.string()
|
|
324
|
+
.array()
|
|
325
|
+
.describe("The ffmpeg command line arguments to provide"),
|
|
326
|
+
outputFileName: z
|
|
327
|
+
.string()
|
|
328
|
+
.describe(
|
|
329
|
+
"The name of the output file including extension, corresponding to the command provided",
|
|
330
|
+
),
|
|
331
|
+
outputMimeType: z
|
|
332
|
+
.string()
|
|
333
|
+
.describe("The mime type of the output file, e.g. image/png"),
|
|
334
|
+
}),
|
|
335
|
+
execute: async ({ command }) => {
|
|
336
|
+
const transcode = async () => {
|
|
337
|
+
const ffmpeg = ffmpegRef.current;
|
|
338
|
+
|
|
339
|
+
const logs: string[] = [];
|
|
340
|
+
const logger = ({ message }: { message: string }) => {
|
|
341
|
+
logs.push(message);
|
|
342
|
+
};
|
|
343
|
+
ffmpeg.on("log", logger);
|
|
344
|
+
|
|
345
|
+
await ffmpeg.writeFile(
|
|
346
|
+
file.name,
|
|
347
|
+
new Uint8Array(await file.arrayBuffer()),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const code = await ffmpeg.exec(command);
|
|
351
|
+
ffmpeg.off("log", logger);
|
|
352
|
+
|
|
353
|
+
return { code, logs };
|
|
354
|
+
};
|
|
355
|
+
const { code, logs } = await transcode();
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
success: code === 0,
|
|
359
|
+
hint:
|
|
360
|
+
code === 0
|
|
361
|
+
? "note: a download button is appearing in the chat for the user"
|
|
362
|
+
: "some error happened, logs: " + logs.join("\n"),
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
render: function RenderFfmpeg({
|
|
366
|
+
args: { command, outputFileName, outputMimeType },
|
|
367
|
+
result: { success } = {},
|
|
368
|
+
}) {
|
|
369
|
+
const handleDownload = async () => {
|
|
370
|
+
const ffmpeg = ffmpegRef.current;
|
|
371
|
+
const data = (await ffmpeg.readFile(
|
|
372
|
+
outputFileName,
|
|
373
|
+
)) as Uint8Array<ArrayBuffer>;
|
|
374
|
+
window.open(
|
|
375
|
+
URL.createObjectURL(
|
|
376
|
+
new Blob([data.buffer], { type: outputMimeType }),
|
|
377
|
+
),
|
|
378
|
+
"_blank",
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
return (
|
|
382
|
+
<div className="flex flex-col gap-2 rounded-lg border px-5 py-4">
|
|
383
|
+
<div>
|
|
384
|
+
<div className="flex items-center gap-2">
|
|
385
|
+
{success == null && (
|
|
386
|
+
<RefreshCcwIcon className="size-4 animate-spin text-blue-600" />
|
|
387
|
+
)}
|
|
388
|
+
{success === false && (
|
|
389
|
+
<TriangleAlertIcon className="size-4 text-red-600" />
|
|
390
|
+
)}
|
|
391
|
+
{success === true && (
|
|
392
|
+
<CircleCheckIcon className="size-4 text-green-600" />
|
|
393
|
+
)}
|
|
394
|
+
<p>Running ffmpeg</p>
|
|
395
|
+
</div>
|
|
396
|
+
<pre className="font-sm overflow-y-scroll">
|
|
397
|
+
ffmpeg {command?.join(" ")}
|
|
398
|
+
</pre>
|
|
399
|
+
</div>
|
|
400
|
+
{!!success && (
|
|
401
|
+
<div className="mt-2 border-t border-dashed pt-3">
|
|
402
|
+
<button onClick={handleDownload}>
|
|
403
|
+
Download {outputFileName}
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
)}
|
|
407
|
+
{success === false && (
|
|
408
|
+
<div className="mt-2 border-t border-dashed pt-3">
|
|
409
|
+
Encountered an error.
|
|
410
|
+
</div>
|
|
411
|
+
)}
|
|
412
|
+
</div>
|
|
413
|
+
);
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return null;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export default function Home() {
|
|
421
|
+
const [lastFile, setLastFile] = useState<File | null>(null);
|
|
422
|
+
const attachments = useThreadComposer((c) => c.attachments);
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
const lastAttachment = attachments[attachments.length - 1];
|
|
425
|
+
if (!lastAttachment) return;
|
|
426
|
+
setLastFile(lastAttachment.file!);
|
|
427
|
+
}, [attachments]);
|
|
428
|
+
|
|
429
|
+
console.log(lastFile);
|
|
430
|
+
return (
|
|
431
|
+
<div className="flex h-full flex-col">
|
|
432
|
+
<div className="border-b">
|
|
433
|
+
<p className="my-4 ml-8 text-xl font-bold">
|
|
434
|
+
ConvertGPT (built with{" "}
|
|
435
|
+
<a
|
|
436
|
+
href="https://github.com/assistant-ui/assistant-ui"
|
|
437
|
+
className="underline"
|
|
438
|
+
>
|
|
439
|
+
assistant-ui
|
|
440
|
+
</a>
|
|
441
|
+
)
|
|
442
|
+
</p>
|
|
443
|
+
</div>
|
|
444
|
+
<Thread />
|
|
445
|
+
{lastFile && <FfmpegTool file={lastFile} />}
|
|
446
|
+
</div>
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## components.json
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{
|
|
456
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
457
|
+
"style": "new-york",
|
|
458
|
+
"rsc": true,
|
|
459
|
+
"tsx": true,
|
|
460
|
+
"tailwind": {
|
|
461
|
+
"config": "",
|
|
462
|
+
"css": "app/globals.css",
|
|
463
|
+
"baseColor": "zinc",
|
|
464
|
+
"cssVariables": true,
|
|
465
|
+
"prefix": ""
|
|
466
|
+
},
|
|
467
|
+
"aliases": {
|
|
468
|
+
"components": "@/components",
|
|
469
|
+
"utils": "@/lib/utils"
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## components/assistant-ui/markdown-text.tsx
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
"use client";
|
|
479
|
+
|
|
480
|
+
import "@assistant-ui/react-markdown/styles/dot.css";
|
|
481
|
+
|
|
482
|
+
import {
|
|
483
|
+
CodeHeaderProps,
|
|
484
|
+
MarkdownTextPrimitive,
|
|
485
|
+
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
|
|
486
|
+
useIsMarkdownCodeBlock,
|
|
487
|
+
} from "@assistant-ui/react-markdown";
|
|
488
|
+
import remarkGfm from "remark-gfm";
|
|
489
|
+
import { FC, memo, useState } from "react";
|
|
490
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
491
|
+
|
|
492
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
493
|
+
import { cn } from "@/lib/utils";
|
|
494
|
+
|
|
495
|
+
const MarkdownTextImpl = () => {
|
|
496
|
+
return (
|
|
497
|
+
<MarkdownTextPrimitive
|
|
498
|
+
remarkPlugins={[remarkGfm]}
|
|
499
|
+
className="aui-md"
|
|
500
|
+
components={defaultComponents}
|
|
501
|
+
/>
|
|
502
|
+
);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export const MarkdownText = memo(MarkdownTextImpl);
|
|
506
|
+
|
|
507
|
+
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
|
|
508
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
509
|
+
const onCopy = () => {
|
|
510
|
+
if (!code || isCopied) return;
|
|
511
|
+
copyToClipboard(code);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
|
|
516
|
+
<span className="lowercase [&>span]:text-xs">{language}</span>
|
|
517
|
+
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
|
|
518
|
+
{!isCopied && <CopyIcon />}
|
|
519
|
+
{isCopied && <CheckIcon />}
|
|
520
|
+
</TooltipIconButton>
|
|
521
|
+
</div>
|
|
522
|
+
);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const useCopyToClipboard = ({
|
|
526
|
+
copiedDuration = 3000,
|
|
527
|
+
}: {
|
|
528
|
+
copiedDuration?: number;
|
|
529
|
+
} = {}) => {
|
|
530
|
+
const [isCopied, setIsCopied] = useState<boolean>(false);
|
|
531
|
+
|
|
532
|
+
const copyToClipboard = (value: string) => {
|
|
533
|
+
if (!value) return;
|
|
534
|
+
|
|
535
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
536
|
+
setIsCopied(true);
|
|
537
|
+
setTimeout(() => setIsCopied(false), copiedDuration);
|
|
538
|
+
});
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
return { isCopied, copyToClipboard };
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const defaultComponents = memoizeMarkdownComponents({
|
|
545
|
+
h1: ({ className, ...props }) => (
|
|
546
|
+
<h1
|
|
547
|
+
className={cn(
|
|
548
|
+
"mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
|
|
549
|
+
className,
|
|
550
|
+
)}
|
|
551
|
+
{...props}
|
|
552
|
+
/>
|
|
553
|
+
),
|
|
554
|
+
h2: ({ className, ...props }) => (
|
|
555
|
+
<h2
|
|
556
|
+
className={cn(
|
|
557
|
+
"mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
558
|
+
className,
|
|
559
|
+
)}
|
|
560
|
+
{...props}
|
|
561
|
+
/>
|
|
562
|
+
),
|
|
563
|
+
h3: ({ className, ...props }) => (
|
|
564
|
+
<h3
|
|
565
|
+
className={cn(
|
|
566
|
+
"mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
567
|
+
className,
|
|
568
|
+
)}
|
|
569
|
+
{...props}
|
|
570
|
+
/>
|
|
571
|
+
),
|
|
572
|
+
h4: ({ className, ...props }) => (
|
|
573
|
+
<h4
|
|
574
|
+
className={cn(
|
|
575
|
+
"mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
576
|
+
className,
|
|
577
|
+
)}
|
|
578
|
+
{...props}
|
|
579
|
+
/>
|
|
580
|
+
),
|
|
581
|
+
h5: ({ className, ...props }) => (
|
|
582
|
+
<h5
|
|
583
|
+
className={cn(
|
|
584
|
+
"my-4 text-lg font-semibold first:mt-0 last:mb-0",
|
|
585
|
+
className,
|
|
586
|
+
)}
|
|
587
|
+
{...props}
|
|
588
|
+
/>
|
|
589
|
+
),
|
|
590
|
+
h6: ({ className, ...props }) => (
|
|
591
|
+
<h6
|
|
592
|
+
className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
|
|
593
|
+
{...props}
|
|
594
|
+
/>
|
|
595
|
+
),
|
|
596
|
+
p: ({ className, ...props }) => (
|
|
597
|
+
<p
|
|
598
|
+
className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)}
|
|
599
|
+
{...props}
|
|
600
|
+
/>
|
|
601
|
+
),
|
|
602
|
+
a: ({ className, ...props }) => (
|
|
603
|
+
<a
|
|
604
|
+
className={cn(
|
|
605
|
+
"text-primary font-medium underline underline-offset-4",
|
|
606
|
+
className,
|
|
607
|
+
)}
|
|
608
|
+
{...props}
|
|
609
|
+
/>
|
|
610
|
+
),
|
|
611
|
+
blockquote: ({ className, ...props }) => (
|
|
612
|
+
<blockquote
|
|
613
|
+
className={cn("border-l-2 pl-6 italic", className)}
|
|
614
|
+
{...props}
|
|
615
|
+
/>
|
|
616
|
+
),
|
|
617
|
+
ul: ({ className, ...props }) => (
|
|
618
|
+
<ul
|
|
619
|
+
className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
|
|
620
|
+
{...props}
|
|
621
|
+
/>
|
|
622
|
+
),
|
|
623
|
+
ol: ({ className, ...props }) => (
|
|
624
|
+
<ol
|
|
625
|
+
className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
|
|
626
|
+
{...props}
|
|
627
|
+
/>
|
|
628
|
+
),
|
|
629
|
+
hr: ({ className, ...props }) => (
|
|
630
|
+
<hr className={cn("my-5 border-b", className)} {...props} />
|
|
631
|
+
),
|
|
632
|
+
table: ({ className, ...props }) => (
|
|
633
|
+
<table
|
|
634
|
+
className={cn(
|
|
635
|
+
"my-5 w-full border-separate border-spacing-0 overflow-y-auto",
|
|
636
|
+
className,
|
|
637
|
+
)}
|
|
638
|
+
{...props}
|
|
639
|
+
/>
|
|
640
|
+
),
|
|
641
|
+
th: ({ className, ...props }) => (
|
|
642
|
+
<th
|
|
643
|
+
className={cn(
|
|
644
|
+
"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",
|
|
645
|
+
className,
|
|
646
|
+
)}
|
|
647
|
+
{...props}
|
|
648
|
+
/>
|
|
649
|
+
),
|
|
650
|
+
td: ({ className, ...props }) => (
|
|
651
|
+
<td
|
|
652
|
+
className={cn(
|
|
653
|
+
"border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
|
|
654
|
+
className,
|
|
655
|
+
)}
|
|
656
|
+
{...props}
|
|
657
|
+
/>
|
|
658
|
+
),
|
|
659
|
+
tr: ({ className, ...props }) => (
|
|
660
|
+
<tr
|
|
661
|
+
className={cn(
|
|
662
|
+
"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",
|
|
663
|
+
className,
|
|
664
|
+
)}
|
|
665
|
+
{...props}
|
|
666
|
+
/>
|
|
667
|
+
),
|
|
668
|
+
sup: ({ className, ...props }) => (
|
|
669
|
+
<sup
|
|
670
|
+
className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
|
|
671
|
+
{...props}
|
|
672
|
+
/>
|
|
673
|
+
),
|
|
674
|
+
pre: ({ className, ...props }) => (
|
|
675
|
+
<pre
|
|
676
|
+
className={cn(
|
|
677
|
+
"overflow-x-auto rounded-b-lg bg-black p-4 text-white",
|
|
678
|
+
className,
|
|
679
|
+
)}
|
|
680
|
+
{...props}
|
|
681
|
+
/>
|
|
682
|
+
),
|
|
683
|
+
code: function Code({ className, ...props }) {
|
|
684
|
+
const isCodeBlock = useIsMarkdownCodeBlock();
|
|
685
|
+
return (
|
|
686
|
+
<code
|
|
687
|
+
className={cn(
|
|
688
|
+
!isCodeBlock && "bg-muted rounded border font-semibold",
|
|
689
|
+
className,
|
|
690
|
+
)}
|
|
691
|
+
{...props}
|
|
692
|
+
/>
|
|
693
|
+
);
|
|
694
|
+
},
|
|
695
|
+
CodeHeader,
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## components/assistant-ui/thread.tsx
|
|
701
|
+
|
|
702
|
+
```tsx
|
|
703
|
+
import {
|
|
704
|
+
ActionBarPrimitive,
|
|
705
|
+
BranchPickerPrimitive,
|
|
706
|
+
ComposerPrimitive,
|
|
707
|
+
MessagePrimitive,
|
|
708
|
+
ThreadPrimitive,
|
|
709
|
+
} from "@assistant-ui/react";
|
|
710
|
+
import type { FC } from "react";
|
|
711
|
+
import {
|
|
712
|
+
ArrowDownIcon,
|
|
713
|
+
CheckIcon,
|
|
714
|
+
ChevronLeftIcon,
|
|
715
|
+
ChevronRightIcon,
|
|
716
|
+
CopyIcon,
|
|
717
|
+
PencilIcon,
|
|
718
|
+
RefreshCwIcon,
|
|
719
|
+
SendHorizontalIcon,
|
|
720
|
+
} from "lucide-react";
|
|
721
|
+
import { cn } from "@/lib/utils";
|
|
722
|
+
|
|
723
|
+
import { Button } from "@/components/ui/button";
|
|
724
|
+
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
|
|
725
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
726
|
+
|
|
727
|
+
export const Thread: FC = () => {
|
|
728
|
+
return (
|
|
729
|
+
<ThreadPrimitive.Root
|
|
730
|
+
className="bg-background box-border flex h-full flex-col overflow-hidden"
|
|
731
|
+
style={{
|
|
732
|
+
["--thread-max-width" as string]: "42rem",
|
|
733
|
+
}}
|
|
734
|
+
>
|
|
735
|
+
<ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
|
|
736
|
+
<ThreadWelcome />
|
|
737
|
+
|
|
738
|
+
<ThreadPrimitive.Messages
|
|
739
|
+
components={{
|
|
740
|
+
UserMessage: UserMessage,
|
|
741
|
+
EditComposer: EditComposer,
|
|
742
|
+
AssistantMessage: AssistantMessage,
|
|
743
|
+
}}
|
|
744
|
+
/>
|
|
745
|
+
|
|
746
|
+
<ThreadPrimitive.If empty={false}>
|
|
747
|
+
<div className="min-h-8 flex-grow" />
|
|
748
|
+
</ThreadPrimitive.If>
|
|
749
|
+
|
|
750
|
+
<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">
|
|
751
|
+
<ThreadScrollToBottom />
|
|
752
|
+
<Composer />
|
|
753
|
+
</div>
|
|
754
|
+
</ThreadPrimitive.Viewport>
|
|
755
|
+
</ThreadPrimitive.Root>
|
|
756
|
+
);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const ThreadScrollToBottom: FC = () => {
|
|
760
|
+
return (
|
|
761
|
+
<ThreadPrimitive.ScrollToBottom asChild>
|
|
762
|
+
<TooltipIconButton
|
|
763
|
+
tooltip="Scroll to bottom"
|
|
764
|
+
variant="outline"
|
|
765
|
+
className="absolute -top-8 rounded-full disabled:invisible"
|
|
766
|
+
>
|
|
767
|
+
<ArrowDownIcon />
|
|
768
|
+
</TooltipIconButton>
|
|
769
|
+
</ThreadPrimitive.ScrollToBottom>
|
|
770
|
+
);
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const ThreadWelcome: FC = () => {
|
|
774
|
+
return (
|
|
775
|
+
<ThreadPrimitive.Empty>
|
|
776
|
+
<div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
|
|
777
|
+
<div className="flex w-full flex-grow flex-col items-center justify-center">
|
|
778
|
+
<p className="mt-4 font-medium">How can I help you today?</p>
|
|
779
|
+
</div>
|
|
780
|
+
<ThreadWelcomeSuggestions />
|
|
781
|
+
</div>
|
|
782
|
+
</ThreadPrimitive.Empty>
|
|
783
|
+
);
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
const ThreadWelcomeSuggestions: FC = () => {
|
|
787
|
+
return (
|
|
788
|
+
<div className="mt-3 flex w-full items-stretch justify-center gap-4">
|
|
789
|
+
<ThreadPrimitive.Suggestion
|
|
790
|
+
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"
|
|
791
|
+
prompt="What is the weather in Tokyo?"
|
|
792
|
+
method="replace"
|
|
793
|
+
autoSend
|
|
794
|
+
>
|
|
795
|
+
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
|
796
|
+
What is the weather in Tokyo?
|
|
797
|
+
</span>
|
|
798
|
+
</ThreadPrimitive.Suggestion>
|
|
799
|
+
<ThreadPrimitive.Suggestion
|
|
800
|
+
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"
|
|
801
|
+
prompt="What is assistant-ui?"
|
|
802
|
+
method="replace"
|
|
803
|
+
autoSend
|
|
804
|
+
>
|
|
805
|
+
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
|
806
|
+
What is assistant-ui?
|
|
807
|
+
</span>
|
|
808
|
+
</ThreadPrimitive.Suggestion>
|
|
809
|
+
</div>
|
|
810
|
+
);
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const Composer: FC = () => {
|
|
814
|
+
return (
|
|
815
|
+
<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">
|
|
816
|
+
<ComposerPrimitive.Input
|
|
817
|
+
rows={1}
|
|
818
|
+
autoFocus
|
|
819
|
+
placeholder="Write a message..."
|
|
820
|
+
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"
|
|
821
|
+
/>
|
|
822
|
+
<ComposerAction />
|
|
823
|
+
</ComposerPrimitive.Root>
|
|
824
|
+
);
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
const ComposerAction: FC = () => {
|
|
828
|
+
return (
|
|
829
|
+
<>
|
|
830
|
+
<ThreadPrimitive.If running={false}>
|
|
831
|
+
<ComposerPrimitive.Send asChild>
|
|
832
|
+
<TooltipIconButton
|
|
833
|
+
tooltip="Send"
|
|
834
|
+
variant="default"
|
|
835
|
+
className="my-2.5 size-8 p-2 transition-opacity ease-in"
|
|
836
|
+
>
|
|
837
|
+
<SendHorizontalIcon />
|
|
838
|
+
</TooltipIconButton>
|
|
839
|
+
</ComposerPrimitive.Send>
|
|
840
|
+
</ThreadPrimitive.If>
|
|
841
|
+
<ThreadPrimitive.If running>
|
|
842
|
+
<ComposerPrimitive.Cancel asChild>
|
|
843
|
+
<TooltipIconButton
|
|
844
|
+
tooltip="Cancel"
|
|
845
|
+
variant="default"
|
|
846
|
+
className="my-2.5 size-8 p-2 transition-opacity ease-in"
|
|
847
|
+
>
|
|
848
|
+
<CircleStopIcon />
|
|
849
|
+
</TooltipIconButton>
|
|
850
|
+
</ComposerPrimitive.Cancel>
|
|
851
|
+
</ThreadPrimitive.If>
|
|
852
|
+
</>
|
|
853
|
+
);
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const UserMessage: FC = () => {
|
|
857
|
+
return (
|
|
858
|
+
<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">
|
|
859
|
+
<UserActionBar />
|
|
860
|
+
|
|
861
|
+
<div className="bg-muted text-foreground col-start-2 row-start-2 max-w-[calc(var(--thread-max-width)*0.8)] break-words rounded-3xl px-5 py-2.5">
|
|
862
|
+
<MessagePrimitive.Content />
|
|
863
|
+
</div>
|
|
864
|
+
|
|
865
|
+
<BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
|
|
866
|
+
</MessagePrimitive.Root>
|
|
867
|
+
);
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const UserActionBar: FC = () => {
|
|
871
|
+
return (
|
|
872
|
+
<ActionBarPrimitive.Root
|
|
873
|
+
hideWhenRunning
|
|
874
|
+
autohide="not-last"
|
|
875
|
+
className="col-start-1 row-start-2 mr-3 mt-2.5 flex flex-col items-end"
|
|
876
|
+
>
|
|
877
|
+
<ActionBarPrimitive.Edit asChild>
|
|
878
|
+
<TooltipIconButton tooltip="Edit">
|
|
879
|
+
<PencilIcon />
|
|
880
|
+
</TooltipIconButton>
|
|
881
|
+
</ActionBarPrimitive.Edit>
|
|
882
|
+
</ActionBarPrimitive.Root>
|
|
883
|
+
);
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const EditComposer: FC = () => {
|
|
887
|
+
return (
|
|
888
|
+
<ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
|
|
889
|
+
<ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
|
|
890
|
+
|
|
891
|
+
<div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
|
|
892
|
+
<ComposerPrimitive.Cancel asChild>
|
|
893
|
+
<Button variant="ghost">Cancel</Button>
|
|
894
|
+
</ComposerPrimitive.Cancel>
|
|
895
|
+
<ComposerPrimitive.Send asChild>
|
|
896
|
+
<Button>Send</Button>
|
|
897
|
+
</ComposerPrimitive.Send>
|
|
898
|
+
</div>
|
|
899
|
+
</ComposerPrimitive.Root>
|
|
900
|
+
);
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
const AssistantMessage: FC = () => {
|
|
904
|
+
return (
|
|
905
|
+
<MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
|
|
906
|
+
<div className="text-foreground col-span-2 col-start-2 row-start-1 my-1.5 max-w-[calc(var(--thread-max-width)*0.8)] break-words leading-7">
|
|
907
|
+
<MessagePrimitive.Content components={{ Text: MarkdownText }} />
|
|
908
|
+
</div>
|
|
909
|
+
|
|
910
|
+
<AssistantActionBar />
|
|
911
|
+
|
|
912
|
+
<BranchPicker className="col-start-2 row-start-2 -ml-2 mr-2" />
|
|
913
|
+
</MessagePrimitive.Root>
|
|
914
|
+
);
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
const AssistantActionBar: FC = () => {
|
|
918
|
+
return (
|
|
919
|
+
<ActionBarPrimitive.Root
|
|
920
|
+
hideWhenRunning
|
|
921
|
+
autohide="not-last"
|
|
922
|
+
autohideFloat="single-branch"
|
|
923
|
+
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"
|
|
924
|
+
>
|
|
925
|
+
<ActionBarPrimitive.Copy asChild>
|
|
926
|
+
<TooltipIconButton tooltip="Copy">
|
|
927
|
+
<MessagePrimitive.If copied>
|
|
928
|
+
<CheckIcon />
|
|
929
|
+
</MessagePrimitive.If>
|
|
930
|
+
<MessagePrimitive.If copied={false}>
|
|
931
|
+
<CopyIcon />
|
|
932
|
+
</MessagePrimitive.If>
|
|
933
|
+
</TooltipIconButton>
|
|
934
|
+
</ActionBarPrimitive.Copy>
|
|
935
|
+
<ActionBarPrimitive.Reload asChild>
|
|
936
|
+
<TooltipIconButton tooltip="Refresh">
|
|
937
|
+
<RefreshCwIcon />
|
|
938
|
+
</TooltipIconButton>
|
|
939
|
+
</ActionBarPrimitive.Reload>
|
|
940
|
+
</ActionBarPrimitive.Root>
|
|
941
|
+
);
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
|
|
945
|
+
className,
|
|
946
|
+
...rest
|
|
947
|
+
}) => {
|
|
948
|
+
return (
|
|
949
|
+
<BranchPickerPrimitive.Root
|
|
950
|
+
hideWhenSingleBranch
|
|
951
|
+
className={cn(
|
|
952
|
+
"text-muted-foreground inline-flex items-center text-xs",
|
|
953
|
+
className,
|
|
954
|
+
)}
|
|
955
|
+
{...rest}
|
|
956
|
+
>
|
|
957
|
+
<BranchPickerPrimitive.Previous asChild>
|
|
958
|
+
<TooltipIconButton tooltip="Previous">
|
|
959
|
+
<ChevronLeftIcon />
|
|
960
|
+
</TooltipIconButton>
|
|
961
|
+
</BranchPickerPrimitive.Previous>
|
|
962
|
+
<span className="font-medium">
|
|
963
|
+
<BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
|
|
964
|
+
</span>
|
|
965
|
+
<BranchPickerPrimitive.Next asChild>
|
|
966
|
+
<TooltipIconButton tooltip="Next">
|
|
967
|
+
<ChevronRightIcon />
|
|
968
|
+
</TooltipIconButton>
|
|
969
|
+
</BranchPickerPrimitive.Next>
|
|
970
|
+
</BranchPickerPrimitive.Root>
|
|
971
|
+
);
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
const CircleStopIcon = () => {
|
|
975
|
+
return (
|
|
976
|
+
<svg
|
|
977
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
978
|
+
viewBox="0 0 16 16"
|
|
979
|
+
fill="currentColor"
|
|
980
|
+
width="16"
|
|
981
|
+
height="16"
|
|
982
|
+
>
|
|
983
|
+
<rect width="10" height="10" x="3" y="3" rx="2" />
|
|
984
|
+
</svg>
|
|
985
|
+
);
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
## components/assistant-ui/tooltip-icon-button.tsx
|
|
991
|
+
|
|
992
|
+
```tsx
|
|
993
|
+
"use client";
|
|
994
|
+
|
|
995
|
+
import { ComponentPropsWithoutRef, forwardRef } from "react";
|
|
996
|
+
|
|
997
|
+
import {
|
|
998
|
+
Tooltip,
|
|
999
|
+
TooltipContent,
|
|
1000
|
+
TooltipTrigger,
|
|
1001
|
+
} from "@/components/ui/tooltip";
|
|
1002
|
+
import { Button } from "@/components/ui/button";
|
|
1003
|
+
import { cn } from "@/lib/utils";
|
|
1004
|
+
|
|
1005
|
+
export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
|
|
1006
|
+
tooltip: string;
|
|
1007
|
+
side?: "top" | "bottom" | "left" | "right";
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
export const TooltipIconButton = forwardRef<
|
|
1011
|
+
HTMLButtonElement,
|
|
1012
|
+
TooltipIconButtonProps
|
|
1013
|
+
>(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
|
|
1014
|
+
return (
|
|
1015
|
+
<Tooltip>
|
|
1016
|
+
<TooltipTrigger asChild>
|
|
1017
|
+
<Button
|
|
1018
|
+
variant="ghost"
|
|
1019
|
+
size="icon"
|
|
1020
|
+
{...rest}
|
|
1021
|
+
className={cn("size-6 p-1", className)}
|
|
1022
|
+
ref={ref}
|
|
1023
|
+
>
|
|
1024
|
+
{children}
|
|
1025
|
+
<span className="sr-only">{tooltip}</span>
|
|
1026
|
+
</Button>
|
|
1027
|
+
</TooltipTrigger>
|
|
1028
|
+
<TooltipContent side={side}>{tooltip}</TooltipContent>
|
|
1029
|
+
</Tooltip>
|
|
1030
|
+
);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
TooltipIconButton.displayName = "TooltipIconButton";
|
|
1034
|
+
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
## components/ui/button.tsx
|
|
1038
|
+
|
|
1039
|
+
```tsx
|
|
1040
|
+
"use client";
|
|
1041
|
+
|
|
1042
|
+
import * as React from "react";
|
|
1043
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
1044
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1045
|
+
|
|
1046
|
+
import { cn } from "@/lib/utils";
|
|
1047
|
+
|
|
1048
|
+
const buttonVariants = cva(
|
|
1049
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
1050
|
+
{
|
|
1051
|
+
variants: {
|
|
1052
|
+
variant: {
|
|
1053
|
+
default:
|
|
1054
|
+
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
1055
|
+
destructive:
|
|
1056
|
+
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
1057
|
+
outline:
|
|
1058
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
1059
|
+
secondary:
|
|
1060
|
+
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
1061
|
+
ghost:
|
|
1062
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
1063
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
1064
|
+
},
|
|
1065
|
+
size: {
|
|
1066
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
1067
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
1068
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
1069
|
+
icon: "size-9",
|
|
1070
|
+
},
|
|
1071
|
+
},
|
|
1072
|
+
defaultVariants: {
|
|
1073
|
+
variant: "default",
|
|
1074
|
+
size: "default",
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1077
|
+
);
|
|
1078
|
+
|
|
1079
|
+
function Button({
|
|
1080
|
+
className,
|
|
1081
|
+
variant,
|
|
1082
|
+
size,
|
|
1083
|
+
asChild = false,
|
|
1084
|
+
...props
|
|
1085
|
+
}: React.ComponentProps<"button"> &
|
|
1086
|
+
VariantProps<typeof buttonVariants> & {
|
|
1087
|
+
asChild?: boolean;
|
|
1088
|
+
}) {
|
|
1089
|
+
const Comp = asChild ? Slot : "button";
|
|
1090
|
+
|
|
1091
|
+
return (
|
|
1092
|
+
<Comp
|
|
1093
|
+
data-slot="button"
|
|
1094
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
1095
|
+
{...props}
|
|
1096
|
+
/>
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
export { Button, buttonVariants };
|
|
1101
|
+
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
## components/ui/tooltip.tsx
|
|
1105
|
+
|
|
1106
|
+
```tsx
|
|
1107
|
+
"use client";
|
|
1108
|
+
|
|
1109
|
+
import * as React from "react";
|
|
1110
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
1111
|
+
|
|
1112
|
+
import { cn } from "@/lib/utils";
|
|
1113
|
+
|
|
1114
|
+
function TooltipProvider({
|
|
1115
|
+
delayDuration = 0,
|
|
1116
|
+
...props
|
|
1117
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
1118
|
+
return (
|
|
1119
|
+
<TooltipPrimitive.Provider
|
|
1120
|
+
data-slot="tooltip-provider"
|
|
1121
|
+
delayDuration={delayDuration}
|
|
1122
|
+
{...props}
|
|
1123
|
+
/>
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function Tooltip({
|
|
1128
|
+
...props
|
|
1129
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
1130
|
+
return (
|
|
1131
|
+
<TooltipProvider>
|
|
1132
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
1133
|
+
</TooltipProvider>
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
function TooltipTrigger({
|
|
1138
|
+
...props
|
|
1139
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
1140
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function TooltipContent({
|
|
1144
|
+
className,
|
|
1145
|
+
sideOffset = 0,
|
|
1146
|
+
children,
|
|
1147
|
+
...props
|
|
1148
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
1149
|
+
return (
|
|
1150
|
+
<TooltipPrimitive.Portal>
|
|
1151
|
+
<TooltipPrimitive.Content
|
|
1152
|
+
data-slot="tooltip-content"
|
|
1153
|
+
sideOffset={sideOffset}
|
|
1154
|
+
className={cn(
|
|
1155
|
+
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-[--radix-tooltip-content-transform-origin] text-balance rounded-md px-3 py-1.5 text-xs",
|
|
1156
|
+
className,
|
|
1157
|
+
)}
|
|
1158
|
+
{...props}
|
|
1159
|
+
>
|
|
1160
|
+
{children}
|
|
1161
|
+
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
|
1162
|
+
</TooltipPrimitive.Content>
|
|
1163
|
+
</TooltipPrimitive.Portal>
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
1168
|
+
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
## eslint.config.ts
|
|
1172
|
+
|
|
1173
|
+
```typescript
|
|
1174
|
+
export { default } from "@assistant-ui/x-buildutils/eslint";
|
|
1175
|
+
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
## lib/utils.ts
|
|
1179
|
+
|
|
1180
|
+
```typescript
|
|
1181
|
+
import { type ClassValue, clsx } from "clsx";
|
|
1182
|
+
import { twMerge } from "tailwind-merge";
|
|
1183
|
+
|
|
1184
|
+
export function cn(...inputs: ClassValue[]) {
|
|
1185
|
+
return twMerge(clsx(inputs));
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
## next-env.d.ts
|
|
1191
|
+
|
|
1192
|
+
```typescript
|
|
1193
|
+
/// <reference types="next" />
|
|
1194
|
+
/// <reference types="next/image-types/global" />
|
|
1195
|
+
|
|
1196
|
+
// NOTE: This file should not be edited
|
|
1197
|
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
1198
|
+
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
## next.config.ts
|
|
1202
|
+
|
|
1203
|
+
```typescript
|
|
1204
|
+
import type { NextConfig } from "next";
|
|
1205
|
+
|
|
1206
|
+
const nextConfig: NextConfig = {
|
|
1207
|
+
/* config options here */
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
export default nextConfig;
|
|
1211
|
+
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
## package.json
|
|
1215
|
+
|
|
1216
|
+
```json
|
|
1217
|
+
{
|
|
1218
|
+
"name": "with-ffmpeg",
|
|
1219
|
+
"version": "0.1.0",
|
|
1220
|
+
"private": true,
|
|
1221
|
+
"scripts": {
|
|
1222
|
+
"dev": "next dev --turbo",
|
|
1223
|
+
"build": "next build",
|
|
1224
|
+
"start": "next start",
|
|
1225
|
+
"lint": "next lint"
|
|
1226
|
+
},
|
|
1227
|
+
"dependencies": {
|
|
1228
|
+
"@ai-sdk/openai": "^1.3.22",
|
|
1229
|
+
"@assistant-ui/react": "workspace:*",
|
|
1230
|
+
"@assistant-ui/react-ai-sdk": "workspace:*",
|
|
1231
|
+
"@assistant-ui/react-hook-form": "workspace:*",
|
|
1232
|
+
"@assistant-ui/react-markdown": "workspace:*",
|
|
1233
|
+
"@ffmpeg/ffmpeg": "^0.12.15",
|
|
1234
|
+
"@ffmpeg/util": "^0.12.2",
|
|
1235
|
+
"@hookform/resolvers": "^5.0.1",
|
|
1236
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
1237
|
+
"@radix-ui/react-icons": "^1.3.2",
|
|
1238
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
1239
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
1240
|
+
"@radix-ui/react-tabs": "^1.1.12",
|
|
1241
|
+
"@radix-ui/react-tooltip": "^1.2.7",
|
|
1242
|
+
"@react-hook/media-query": "^1.1.1",
|
|
1243
|
+
"ai": "^4.3.16",
|
|
1244
|
+
"class-variance-authority": "^0.7.1",
|
|
1245
|
+
"clsx": "^2.1.1",
|
|
1246
|
+
"json-schema-to-zod": "^2.6.1",
|
|
1247
|
+
"lucide-react": "^0.511.0",
|
|
1248
|
+
"next": "15.3.3",
|
|
1249
|
+
"react": "19.1.0",
|
|
1250
|
+
"react-dom": "19.1.0",
|
|
1251
|
+
"react-hook-form": "^7.57.0",
|
|
1252
|
+
"react-resizable-panels": "^3.0.2",
|
|
1253
|
+
"remark-gfm": "^4.0.1",
|
|
1254
|
+
"tailwind-merge": "^3.3.0",
|
|
1255
|
+
"tw-animate-css": "^1.3.3",
|
|
1256
|
+
"zod": "^3.25.49",
|
|
1257
|
+
"zod-to-json-schema": "^3.24.5",
|
|
1258
|
+
"zustand": "^5.0.5"
|
|
1259
|
+
},
|
|
1260
|
+
"devDependencies": {
|
|
1261
|
+
"@assistant-ui/x-buildutils": "workspace:*",
|
|
1262
|
+
"@types/node": "^22",
|
|
1263
|
+
"@types/react": "^19",
|
|
1264
|
+
"@types/react-dom": "^19",
|
|
1265
|
+
"eslint": "^9",
|
|
1266
|
+
"eslint-config-next": "15.3.3",
|
|
1267
|
+
"postcss": "^8",
|
|
1268
|
+
"tailwindcss": "^4.1.8",
|
|
1269
|
+
"typescript": "^5.8.3"
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
```
|
|
1274
|
+
|
|
1275
|
+
## tsconfig.json
|
|
1276
|
+
|
|
1277
|
+
```json
|
|
1278
|
+
{
|
|
1279
|
+
"extends": "@assistant-ui/x-buildutils/ts/base",
|
|
1280
|
+
"compilerOptions": {
|
|
1281
|
+
"target": "ES6",
|
|
1282
|
+
"module": "ESNext",
|
|
1283
|
+
"incremental": true,
|
|
1284
|
+
"plugins": [
|
|
1285
|
+
{
|
|
1286
|
+
"name": "next"
|
|
1287
|
+
}
|
|
1288
|
+
],
|
|
1289
|
+
"allowJs": true,
|
|
1290
|
+
"strictNullChecks": true,
|
|
1291
|
+
"jsx": "preserve",
|
|
1292
|
+
"paths": {
|
|
1293
|
+
"@/*": ["./*"],
|
|
1294
|
+
"@assistant-ui/*": ["../../packages/*/src"],
|
|
1295
|
+
"@assistant-ui/react/*": ["../../packages/react/src/*"],
|
|
1296
|
+
"assistant-stream": ["../../packages/assistant-stream/src"],
|
|
1297
|
+
"assistant-stream/*": ["../../packages/assistant-stream/src/*"]
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1301
|
+
"exclude": ["node_modules"]
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
```
|
|
1305
|
+
|