@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,1819 @@
|
|
|
1
|
+
# Example: with-langgraph
|
|
2
|
+
|
|
3
|
+
## app/api/[..._path]/route.ts
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
7
|
+
|
|
8
|
+
export const runtime = "edge";
|
|
9
|
+
|
|
10
|
+
function getCorsHeaders() {
|
|
11
|
+
return {
|
|
12
|
+
"Access-Control-Allow-Origin": "*",
|
|
13
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
14
|
+
"Access-Control-Allow-Headers": "*",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function handleRequest(req: NextRequest, method: string) {
|
|
19
|
+
try {
|
|
20
|
+
const path = req.nextUrl.pathname.replace(/^\/?api\//, "");
|
|
21
|
+
const url = new URL(req.url);
|
|
22
|
+
const searchParams = new URLSearchParams(url.search);
|
|
23
|
+
searchParams.delete("_path");
|
|
24
|
+
searchParams.delete("nxtP_path");
|
|
25
|
+
const queryString = searchParams.toString()
|
|
26
|
+
? `?${searchParams.toString()}`
|
|
27
|
+
: "";
|
|
28
|
+
|
|
29
|
+
const options: RequestInit = {
|
|
30
|
+
method,
|
|
31
|
+
headers: {
|
|
32
|
+
"x-api-key": process.env["LANGCHAIN_API_KEY"] || "",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
37
|
+
options.body = await req.text();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const res = await fetch(
|
|
41
|
+
`${process.env["LANGGRAPH_API_URL"]}/${path}${queryString}`,
|
|
42
|
+
options,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return new NextResponse(res.body, {
|
|
46
|
+
status: res.status,
|
|
47
|
+
statusText: res.statusText,
|
|
48
|
+
headers: {
|
|
49
|
+
...res.headers,
|
|
50
|
+
...getCorsHeaders(),
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
} catch (e: unknown) {
|
|
54
|
+
if (e instanceof Error) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: e.message },
|
|
57
|
+
{ status: (e as { status?: number }).status ?? 500 },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return NextResponse.json({ error: "Unknown error" }, { status: 500 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const GET = (req: NextRequest) => handleRequest(req, "GET");
|
|
65
|
+
export const POST = (req: NextRequest) => handleRequest(req, "POST");
|
|
66
|
+
export const PUT = (req: NextRequest) => handleRequest(req, "PUT");
|
|
67
|
+
export const PATCH = (req: NextRequest) => handleRequest(req, "PATCH");
|
|
68
|
+
export const DELETE = (req: NextRequest) => handleRequest(req, "DELETE");
|
|
69
|
+
|
|
70
|
+
// Add a new OPTIONS handler
|
|
71
|
+
export const OPTIONS = () => {
|
|
72
|
+
return new NextResponse(null, {
|
|
73
|
+
status: 204,
|
|
74
|
+
headers: {
|
|
75
|
+
...getCorsHeaders(),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## app/api/assistant-ui-token/route.ts
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { customAlphabet } from "nanoid";
|
|
86
|
+
import { cookies } from "next/headers";
|
|
87
|
+
import jwt, { JwtPayload } from "jsonwebtoken";
|
|
88
|
+
import { AssistantCloud } from "@assistant-ui/react";
|
|
89
|
+
|
|
90
|
+
const generateId = customAlphabet(
|
|
91
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
92
|
+
32,
|
|
93
|
+
);
|
|
94
|
+
const randomUserId = () => {
|
|
95
|
+
const userId = "usr_anon_" + generateId();
|
|
96
|
+
return userId;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const getJwtForUser = (userId: string) => {
|
|
100
|
+
return jwt.sign(
|
|
101
|
+
{
|
|
102
|
+
sub: userId,
|
|
103
|
+
iat: Math.floor(Date.now() / 1000),
|
|
104
|
+
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7, // 1 week
|
|
105
|
+
},
|
|
106
|
+
process.env["JWT_SECRET"]!,
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const getUserIdFromJwt = (token: string) => {
|
|
111
|
+
const decoded = jwt.verify(token, process.env["JWT_SECRET"]!) as JwtPayload;
|
|
112
|
+
return decoded.sub!;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const POST = async () => {
|
|
116
|
+
const cookieStore = await cookies();
|
|
117
|
+
const jwtCookie = cookieStore.get("jwt");
|
|
118
|
+
let userId;
|
|
119
|
+
if (!jwtCookie) {
|
|
120
|
+
userId = randomUserId();
|
|
121
|
+
} else {
|
|
122
|
+
userId = getUserIdFromJwt(jwtCookie.value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
cookieStore.set("jwt", getJwtForUser(userId), {
|
|
126
|
+
path: "/",
|
|
127
|
+
httpOnly: true,
|
|
128
|
+
secure: process.env.NODE_ENV === "production",
|
|
129
|
+
sameSite: "strict",
|
|
130
|
+
maxAge: 60 * 60 * 24 * 7, // 1 week
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const client = new AssistantCloud({
|
|
134
|
+
apiKey: process.env["ASSISTANT_API_KEY"]!,
|
|
135
|
+
userId,
|
|
136
|
+
workspaceId: userId,
|
|
137
|
+
});
|
|
138
|
+
const { token } = await client.auth.tokens.create();
|
|
139
|
+
return Response.json({ token });
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## app/globals.css
|
|
145
|
+
|
|
146
|
+
```css
|
|
147
|
+
@import "tailwindcss";
|
|
148
|
+
@import "tw-animate-css";
|
|
149
|
+
|
|
150
|
+
@custom-variant dark (&:is(.dark *));
|
|
151
|
+
|
|
152
|
+
@theme inline {
|
|
153
|
+
--color-background: var(--background);
|
|
154
|
+
--color-foreground: var(--foreground);
|
|
155
|
+
--font-sans: var(--font-geist-sans);
|
|
156
|
+
--font-mono: var(--font-geist-mono);
|
|
157
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
158
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
159
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
160
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
161
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
162
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
163
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
164
|
+
--color-sidebar: var(--sidebar);
|
|
165
|
+
--color-chart-5: var(--chart-5);
|
|
166
|
+
--color-chart-4: var(--chart-4);
|
|
167
|
+
--color-chart-3: var(--chart-3);
|
|
168
|
+
--color-chart-2: var(--chart-2);
|
|
169
|
+
--color-chart-1: var(--chart-1);
|
|
170
|
+
--color-ring: var(--ring);
|
|
171
|
+
--color-input: var(--input);
|
|
172
|
+
--color-border: var(--border);
|
|
173
|
+
--color-destructive: var(--destructive);
|
|
174
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
175
|
+
--color-accent: var(--accent);
|
|
176
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
177
|
+
--color-muted: var(--muted);
|
|
178
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
179
|
+
--color-secondary: var(--secondary);
|
|
180
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
181
|
+
--color-primary: var(--primary);
|
|
182
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
183
|
+
--color-popover: var(--popover);
|
|
184
|
+
--color-card-foreground: var(--card-foreground);
|
|
185
|
+
--color-card: var(--card);
|
|
186
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
187
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
188
|
+
--radius-lg: var(--radius);
|
|
189
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
:root {
|
|
193
|
+
--radius: 0.625rem;
|
|
194
|
+
--background: oklch(1 0 0);
|
|
195
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
196
|
+
--card: oklch(1 0 0);
|
|
197
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
198
|
+
--popover: oklch(1 0 0);
|
|
199
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
200
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
201
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
202
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
203
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
204
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
205
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
206
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
207
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
208
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
209
|
+
--border: oklch(0.92 0.004 286.32);
|
|
210
|
+
--input: oklch(0.92 0.004 286.32);
|
|
211
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
212
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
213
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
214
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
215
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
216
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
217
|
+
--sidebar: oklch(0.985 0 0);
|
|
218
|
+
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
219
|
+
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
220
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
221
|
+
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
222
|
+
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
223
|
+
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
224
|
+
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.dark {
|
|
228
|
+
--background: oklch(0.141 0.005 285.823);
|
|
229
|
+
--foreground: oklch(0.985 0 0);
|
|
230
|
+
--card: oklch(0.21 0.006 285.885);
|
|
231
|
+
--card-foreground: oklch(0.985 0 0);
|
|
232
|
+
--popover: oklch(0.21 0.006 285.885);
|
|
233
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
234
|
+
--primary: oklch(0.92 0.004 286.32);
|
|
235
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
236
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
237
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
238
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
239
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
240
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
241
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
242
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
243
|
+
--border: oklch(1 0 0 / 10%);
|
|
244
|
+
--input: oklch(1 0 0 / 15%);
|
|
245
|
+
--ring: oklch(0.552 0.016 285.938);
|
|
246
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
247
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
248
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
249
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
250
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
251
|
+
--sidebar: oklch(0.21 0.006 285.885);
|
|
252
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
253
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
254
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
255
|
+
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
256
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
257
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
258
|
+
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@layer base {
|
|
262
|
+
* {
|
|
263
|
+
@apply border-border outline-ring/50;
|
|
264
|
+
}
|
|
265
|
+
body {
|
|
266
|
+
@apply bg-background text-foreground;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## app/layout.tsx
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import "./globals.css";
|
|
276
|
+
|
|
277
|
+
import { cn } from "@/lib/utils";
|
|
278
|
+
import { Montserrat } from "next/font/google";
|
|
279
|
+
import { MyRuntimeProvider } from "./MyRuntimeProvider";
|
|
280
|
+
import { Suspense } from "react";
|
|
281
|
+
|
|
282
|
+
const montserrat = Montserrat({ subsets: ["latin"] });
|
|
283
|
+
|
|
284
|
+
export default function RootLayout({
|
|
285
|
+
children,
|
|
286
|
+
}: Readonly<{
|
|
287
|
+
children: React.ReactNode;
|
|
288
|
+
}>) {
|
|
289
|
+
return (
|
|
290
|
+
<MyRuntimeProvider>
|
|
291
|
+
<html lang="en">
|
|
292
|
+
<body className={cn(montserrat.className, "h-dvh")}>
|
|
293
|
+
<Suspense>{children}</Suspense>
|
|
294
|
+
</body>
|
|
295
|
+
</html>
|
|
296
|
+
</MyRuntimeProvider>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## app/MyRuntimeProvider.tsx
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
"use client";
|
|
306
|
+
|
|
307
|
+
import {
|
|
308
|
+
AssistantCloud,
|
|
309
|
+
AssistantRuntimeProvider,
|
|
310
|
+
useCloudThreadListRuntime,
|
|
311
|
+
useThreadListItemRuntime,
|
|
312
|
+
} from "@assistant-ui/react";
|
|
313
|
+
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
|
|
314
|
+
import { createThread, getThreadState, sendMessage } from "@/lib/chatApi";
|
|
315
|
+
import { LangChainMessage } from "@assistant-ui/react-langgraph";
|
|
316
|
+
|
|
317
|
+
const useMyLangGraphRuntime = () => {
|
|
318
|
+
const threadListItemRuntime = useThreadListItemRuntime();
|
|
319
|
+
const runtime = useLangGraphRuntime({
|
|
320
|
+
stream: async function* (messages) {
|
|
321
|
+
const { externalId } = await threadListItemRuntime.initialize();
|
|
322
|
+
if (!externalId) throw new Error("Thread not found");
|
|
323
|
+
|
|
324
|
+
const generator = sendMessage({
|
|
325
|
+
threadId: externalId,
|
|
326
|
+
messages,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
yield* generator;
|
|
330
|
+
},
|
|
331
|
+
onSwitchToThread: async (externalId) => {
|
|
332
|
+
const state = await getThreadState(externalId);
|
|
333
|
+
return {
|
|
334
|
+
messages:
|
|
335
|
+
(state.values as { messages?: LangChainMessage[] }).messages ?? [],
|
|
336
|
+
interrupts: state.tasks[0]?.interrupts ?? [],
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return runtime;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const cloud = new AssistantCloud({
|
|
345
|
+
baseUrl: process.env["NEXT_PUBLIC_ASSISTANT_BASE_URL"]!,
|
|
346
|
+
authToken: () =>
|
|
347
|
+
fetch("/api/assistant-ui-token", { method: "POST" })
|
|
348
|
+
.then((r) => r.json())
|
|
349
|
+
.then((r) => r.token),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
export function MyRuntimeProvider({
|
|
353
|
+
children,
|
|
354
|
+
}: Readonly<{
|
|
355
|
+
children: React.ReactNode;
|
|
356
|
+
}>) {
|
|
357
|
+
const runtime = useCloudThreadListRuntime({
|
|
358
|
+
cloud,
|
|
359
|
+
runtimeHook: useMyLangGraphRuntime,
|
|
360
|
+
create: async () => {
|
|
361
|
+
const { thread_id } = await createThread();
|
|
362
|
+
return { externalId: thread_id };
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
368
|
+
{children}
|
|
369
|
+
</AssistantRuntimeProvider>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## app/page.tsx
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
"use client";
|
|
379
|
+
|
|
380
|
+
import { Thread } from "@/components/assistant-ui/thread";
|
|
381
|
+
import { PriceSnapshotTool } from "@/components/tools/price-snapshot/PriceSnapshotTool";
|
|
382
|
+
import { PurchaseStockTool } from "@/components/tools/purchase-stock/PurchaseStockTool";
|
|
383
|
+
import { ThreadList } from "@/components/assistant-ui/thread-list";
|
|
384
|
+
|
|
385
|
+
export default function Home() {
|
|
386
|
+
return (
|
|
387
|
+
<div className="flex h-dvh">
|
|
388
|
+
<div className="max-w-md">
|
|
389
|
+
<ThreadList />
|
|
390
|
+
</div>
|
|
391
|
+
<div className="flex-grow">
|
|
392
|
+
<Thread />
|
|
393
|
+
<PriceSnapshotTool />
|
|
394
|
+
<PurchaseStockTool />
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## components.json
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{
|
|
406
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
407
|
+
"style": "new-york",
|
|
408
|
+
"rsc": true,
|
|
409
|
+
"tsx": true,
|
|
410
|
+
"tailwind": {
|
|
411
|
+
"config": "",
|
|
412
|
+
"css": "app/globals.css",
|
|
413
|
+
"baseColor": "zinc",
|
|
414
|
+
"cssVariables": true,
|
|
415
|
+
"prefix": ""
|
|
416
|
+
},
|
|
417
|
+
"aliases": {
|
|
418
|
+
"components": "@/components",
|
|
419
|
+
"utils": "@/lib/utils"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## components/assistant-ui/markdown-text.tsx
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
"use client";
|
|
429
|
+
|
|
430
|
+
import "@assistant-ui/react-markdown/styles/dot.css";
|
|
431
|
+
|
|
432
|
+
import {
|
|
433
|
+
CodeHeaderProps,
|
|
434
|
+
MarkdownTextPrimitive,
|
|
435
|
+
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
|
|
436
|
+
useIsMarkdownCodeBlock,
|
|
437
|
+
} from "@assistant-ui/react-markdown";
|
|
438
|
+
import remarkGfm from "remark-gfm";
|
|
439
|
+
import { FC, memo, useState } from "react";
|
|
440
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
441
|
+
|
|
442
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
443
|
+
import { cn } from "@/lib/utils";
|
|
444
|
+
|
|
445
|
+
const MarkdownTextImpl = () => {
|
|
446
|
+
return (
|
|
447
|
+
<MarkdownTextPrimitive
|
|
448
|
+
remarkPlugins={[remarkGfm]}
|
|
449
|
+
className="aui-md"
|
|
450
|
+
components={defaultComponents}
|
|
451
|
+
/>
|
|
452
|
+
);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
export const MarkdownText = memo(MarkdownTextImpl);
|
|
456
|
+
|
|
457
|
+
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
|
|
458
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
459
|
+
const onCopy = () => {
|
|
460
|
+
if (!code || isCopied) return;
|
|
461
|
+
copyToClipboard(code);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<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">
|
|
466
|
+
<span className="lowercase [&>span]:text-xs">{language}</span>
|
|
467
|
+
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
|
|
468
|
+
{!isCopied && <CopyIcon />}
|
|
469
|
+
{isCopied && <CheckIcon />}
|
|
470
|
+
</TooltipIconButton>
|
|
471
|
+
</div>
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const useCopyToClipboard = ({
|
|
476
|
+
copiedDuration = 3000,
|
|
477
|
+
}: {
|
|
478
|
+
copiedDuration?: number;
|
|
479
|
+
} = {}) => {
|
|
480
|
+
const [isCopied, setIsCopied] = useState<boolean>(false);
|
|
481
|
+
|
|
482
|
+
const copyToClipboard = (value: string) => {
|
|
483
|
+
if (!value) return;
|
|
484
|
+
|
|
485
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
486
|
+
setIsCopied(true);
|
|
487
|
+
setTimeout(() => setIsCopied(false), copiedDuration);
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
return { isCopied, copyToClipboard };
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const defaultComponents = memoizeMarkdownComponents({
|
|
495
|
+
h1: ({ className, ...props }) => (
|
|
496
|
+
<h1
|
|
497
|
+
className={cn(
|
|
498
|
+
"mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
|
|
499
|
+
className,
|
|
500
|
+
)}
|
|
501
|
+
{...props}
|
|
502
|
+
/>
|
|
503
|
+
),
|
|
504
|
+
h2: ({ className, ...props }) => (
|
|
505
|
+
<h2
|
|
506
|
+
className={cn(
|
|
507
|
+
"mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
508
|
+
className,
|
|
509
|
+
)}
|
|
510
|
+
{...props}
|
|
511
|
+
/>
|
|
512
|
+
),
|
|
513
|
+
h3: ({ className, ...props }) => (
|
|
514
|
+
<h3
|
|
515
|
+
className={cn(
|
|
516
|
+
"mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
517
|
+
className,
|
|
518
|
+
)}
|
|
519
|
+
{...props}
|
|
520
|
+
/>
|
|
521
|
+
),
|
|
522
|
+
h4: ({ className, ...props }) => (
|
|
523
|
+
<h4
|
|
524
|
+
className={cn(
|
|
525
|
+
"mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
526
|
+
className,
|
|
527
|
+
)}
|
|
528
|
+
{...props}
|
|
529
|
+
/>
|
|
530
|
+
),
|
|
531
|
+
h5: ({ className, ...props }) => (
|
|
532
|
+
<h5
|
|
533
|
+
className={cn(
|
|
534
|
+
"my-4 text-lg font-semibold first:mt-0 last:mb-0",
|
|
535
|
+
className,
|
|
536
|
+
)}
|
|
537
|
+
{...props}
|
|
538
|
+
/>
|
|
539
|
+
),
|
|
540
|
+
h6: ({ className, ...props }) => (
|
|
541
|
+
<h6
|
|
542
|
+
className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
|
|
543
|
+
{...props}
|
|
544
|
+
/>
|
|
545
|
+
),
|
|
546
|
+
p: ({ className, ...props }) => (
|
|
547
|
+
<p
|
|
548
|
+
className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)}
|
|
549
|
+
{...props}
|
|
550
|
+
/>
|
|
551
|
+
),
|
|
552
|
+
a: ({ className, ...props }) => (
|
|
553
|
+
<a
|
|
554
|
+
className={cn(
|
|
555
|
+
"text-primary font-medium underline underline-offset-4",
|
|
556
|
+
className,
|
|
557
|
+
)}
|
|
558
|
+
{...props}
|
|
559
|
+
/>
|
|
560
|
+
),
|
|
561
|
+
blockquote: ({ className, ...props }) => (
|
|
562
|
+
<blockquote
|
|
563
|
+
className={cn("border-l-2 pl-6 italic", className)}
|
|
564
|
+
{...props}
|
|
565
|
+
/>
|
|
566
|
+
),
|
|
567
|
+
ul: ({ className, ...props }) => (
|
|
568
|
+
<ul
|
|
569
|
+
className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
|
|
570
|
+
{...props}
|
|
571
|
+
/>
|
|
572
|
+
),
|
|
573
|
+
ol: ({ className, ...props }) => (
|
|
574
|
+
<ol
|
|
575
|
+
className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
|
|
576
|
+
{...props}
|
|
577
|
+
/>
|
|
578
|
+
),
|
|
579
|
+
hr: ({ className, ...props }) => (
|
|
580
|
+
<hr className={cn("my-5 border-b", className)} {...props} />
|
|
581
|
+
),
|
|
582
|
+
table: ({ className, ...props }) => (
|
|
583
|
+
<table
|
|
584
|
+
className={cn(
|
|
585
|
+
"my-5 w-full border-separate border-spacing-0 overflow-y-auto",
|
|
586
|
+
className,
|
|
587
|
+
)}
|
|
588
|
+
{...props}
|
|
589
|
+
/>
|
|
590
|
+
),
|
|
591
|
+
th: ({ className, ...props }) => (
|
|
592
|
+
<th
|
|
593
|
+
className={cn(
|
|
594
|
+
"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",
|
|
595
|
+
className,
|
|
596
|
+
)}
|
|
597
|
+
{...props}
|
|
598
|
+
/>
|
|
599
|
+
),
|
|
600
|
+
td: ({ className, ...props }) => (
|
|
601
|
+
<td
|
|
602
|
+
className={cn(
|
|
603
|
+
"border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
|
|
604
|
+
className,
|
|
605
|
+
)}
|
|
606
|
+
{...props}
|
|
607
|
+
/>
|
|
608
|
+
),
|
|
609
|
+
tr: ({ className, ...props }) => (
|
|
610
|
+
<tr
|
|
611
|
+
className={cn(
|
|
612
|
+
"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",
|
|
613
|
+
className,
|
|
614
|
+
)}
|
|
615
|
+
{...props}
|
|
616
|
+
/>
|
|
617
|
+
),
|
|
618
|
+
sup: ({ className, ...props }) => (
|
|
619
|
+
<sup
|
|
620
|
+
className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
|
|
621
|
+
{...props}
|
|
622
|
+
/>
|
|
623
|
+
),
|
|
624
|
+
pre: ({ className, ...props }) => (
|
|
625
|
+
<pre
|
|
626
|
+
className={cn(
|
|
627
|
+
"overflow-x-auto rounded-b-lg bg-black p-4 text-white",
|
|
628
|
+
className,
|
|
629
|
+
)}
|
|
630
|
+
{...props}
|
|
631
|
+
/>
|
|
632
|
+
),
|
|
633
|
+
code: function Code({ className, ...props }) {
|
|
634
|
+
const isCodeBlock = useIsMarkdownCodeBlock();
|
|
635
|
+
return (
|
|
636
|
+
<code
|
|
637
|
+
className={cn(
|
|
638
|
+
!isCodeBlock && "bg-muted rounded border font-semibold",
|
|
639
|
+
className,
|
|
640
|
+
)}
|
|
641
|
+
{...props}
|
|
642
|
+
/>
|
|
643
|
+
);
|
|
644
|
+
},
|
|
645
|
+
CodeHeader,
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
## components/assistant-ui/thread-list.tsx
|
|
651
|
+
|
|
652
|
+
```tsx
|
|
653
|
+
import type { FC } from "react";
|
|
654
|
+
import {
|
|
655
|
+
ThreadListItemPrimitive,
|
|
656
|
+
ThreadListPrimitive,
|
|
657
|
+
} from "@assistant-ui/react";
|
|
658
|
+
import { ArchiveIcon, PlusIcon } from "lucide-react";
|
|
659
|
+
|
|
660
|
+
import { Button } from "@/components/ui/button";
|
|
661
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
662
|
+
|
|
663
|
+
export const ThreadList: FC = () => {
|
|
664
|
+
return (
|
|
665
|
+
<ThreadListPrimitive.Root className="flex flex-col items-stretch gap-1.5">
|
|
666
|
+
<ThreadListNew />
|
|
667
|
+
<ThreadListItems />
|
|
668
|
+
</ThreadListPrimitive.Root>
|
|
669
|
+
);
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const ThreadListNew: FC = () => {
|
|
673
|
+
return (
|
|
674
|
+
<ThreadListPrimitive.New asChild>
|
|
675
|
+
<Button
|
|
676
|
+
className="data-[active]:bg-muted hover:bg-muted flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start"
|
|
677
|
+
variant="ghost"
|
|
678
|
+
>
|
|
679
|
+
<PlusIcon />
|
|
680
|
+
New Thread
|
|
681
|
+
</Button>
|
|
682
|
+
</ThreadListPrimitive.New>
|
|
683
|
+
);
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const ThreadListItems: FC = () => {
|
|
687
|
+
return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const ThreadListItem: FC = () => {
|
|
691
|
+
return (
|
|
692
|
+
<ThreadListItemPrimitive.Root className="data-[active]:bg-muted hover:bg-muted focus-visible:bg-muted focus-visible:ring-ring flex items-center gap-2 rounded-lg transition-all focus-visible:outline-none focus-visible:ring-2">
|
|
693
|
+
<ThreadListItemPrimitive.Trigger className="flex-grow px-3 py-2 text-start">
|
|
694
|
+
<ThreadListItemTitle />
|
|
695
|
+
</ThreadListItemPrimitive.Trigger>
|
|
696
|
+
<ThreadListItemArchive />
|
|
697
|
+
</ThreadListItemPrimitive.Root>
|
|
698
|
+
);
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
const ThreadListItemTitle: FC = () => {
|
|
702
|
+
return (
|
|
703
|
+
<p className="text-sm">
|
|
704
|
+
<ThreadListItemPrimitive.Title fallback="New Chat" />
|
|
705
|
+
</p>
|
|
706
|
+
);
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
const ThreadListItemArchive: FC = () => {
|
|
710
|
+
return (
|
|
711
|
+
<ThreadListItemPrimitive.Archive asChild>
|
|
712
|
+
<TooltipIconButton
|
|
713
|
+
className="hover:text-primary text-foreground ml-auto mr-3 size-4 p-0"
|
|
714
|
+
variant="ghost"
|
|
715
|
+
tooltip="Archive thread"
|
|
716
|
+
>
|
|
717
|
+
<ArchiveIcon />
|
|
718
|
+
</TooltipIconButton>
|
|
719
|
+
</ThreadListItemPrimitive.Archive>
|
|
720
|
+
);
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## components/assistant-ui/thread.tsx
|
|
726
|
+
|
|
727
|
+
```tsx
|
|
728
|
+
import {
|
|
729
|
+
ActionBarPrimitive,
|
|
730
|
+
BranchPickerPrimitive,
|
|
731
|
+
ComposerPrimitive,
|
|
732
|
+
MessagePrimitive,
|
|
733
|
+
ThreadPrimitive,
|
|
734
|
+
} from "@assistant-ui/react";
|
|
735
|
+
import type { FC } from "react";
|
|
736
|
+
import {
|
|
737
|
+
ArrowDownIcon,
|
|
738
|
+
CheckIcon,
|
|
739
|
+
ChevronLeftIcon,
|
|
740
|
+
ChevronRightIcon,
|
|
741
|
+
CopyIcon,
|
|
742
|
+
PencilIcon,
|
|
743
|
+
RefreshCwIcon,
|
|
744
|
+
SendHorizontalIcon,
|
|
745
|
+
} from "lucide-react";
|
|
746
|
+
import { cn } from "@/lib/utils";
|
|
747
|
+
|
|
748
|
+
import { Button } from "@/components/ui/button";
|
|
749
|
+
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
|
|
750
|
+
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
|
751
|
+
|
|
752
|
+
export const Thread: FC = () => {
|
|
753
|
+
return (
|
|
754
|
+
<ThreadPrimitive.Root
|
|
755
|
+
className="bg-background box-border flex h-full flex-col overflow-hidden"
|
|
756
|
+
style={{
|
|
757
|
+
["--thread-max-width" as string]: "42rem",
|
|
758
|
+
}}
|
|
759
|
+
>
|
|
760
|
+
<ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
|
|
761
|
+
<ThreadWelcome />
|
|
762
|
+
|
|
763
|
+
<ThreadPrimitive.Messages
|
|
764
|
+
components={{
|
|
765
|
+
UserMessage: UserMessage,
|
|
766
|
+
EditComposer: EditComposer,
|
|
767
|
+
AssistantMessage: AssistantMessage,
|
|
768
|
+
}}
|
|
769
|
+
/>
|
|
770
|
+
|
|
771
|
+
<ThreadPrimitive.If empty={false}>
|
|
772
|
+
<div className="min-h-8 flex-grow" />
|
|
773
|
+
</ThreadPrimitive.If>
|
|
774
|
+
|
|
775
|
+
<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">
|
|
776
|
+
<ThreadScrollToBottom />
|
|
777
|
+
<Composer />
|
|
778
|
+
</div>
|
|
779
|
+
</ThreadPrimitive.Viewport>
|
|
780
|
+
</ThreadPrimitive.Root>
|
|
781
|
+
);
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
const ThreadScrollToBottom: FC = () => {
|
|
785
|
+
return (
|
|
786
|
+
<ThreadPrimitive.ScrollToBottom asChild>
|
|
787
|
+
<TooltipIconButton
|
|
788
|
+
tooltip="Scroll to bottom"
|
|
789
|
+
variant="outline"
|
|
790
|
+
className="absolute -top-8 rounded-full disabled:invisible"
|
|
791
|
+
>
|
|
792
|
+
<ArrowDownIcon />
|
|
793
|
+
</TooltipIconButton>
|
|
794
|
+
</ThreadPrimitive.ScrollToBottom>
|
|
795
|
+
);
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const ThreadWelcome: FC = () => {
|
|
799
|
+
return (
|
|
800
|
+
<ThreadPrimitive.Empty>
|
|
801
|
+
<div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
|
|
802
|
+
<div className="flex w-full flex-grow flex-col items-center justify-center">
|
|
803
|
+
<p className="mt-4 font-medium">How can I help you today?</p>
|
|
804
|
+
</div>
|
|
805
|
+
<ThreadWelcomeSuggestions />
|
|
806
|
+
</div>
|
|
807
|
+
</ThreadPrimitive.Empty>
|
|
808
|
+
);
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
const ThreadWelcomeSuggestions: FC = () => {
|
|
812
|
+
return (
|
|
813
|
+
<div className="mt-3 flex w-full items-stretch justify-center gap-4">
|
|
814
|
+
<ThreadPrimitive.Suggestion
|
|
815
|
+
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"
|
|
816
|
+
prompt="What is the weather in Tokyo?"
|
|
817
|
+
method="replace"
|
|
818
|
+
autoSend
|
|
819
|
+
>
|
|
820
|
+
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
|
821
|
+
What is the weather in Tokyo?
|
|
822
|
+
</span>
|
|
823
|
+
</ThreadPrimitive.Suggestion>
|
|
824
|
+
<ThreadPrimitive.Suggestion
|
|
825
|
+
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"
|
|
826
|
+
prompt="What is assistant-ui?"
|
|
827
|
+
method="replace"
|
|
828
|
+
autoSend
|
|
829
|
+
>
|
|
830
|
+
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
|
|
831
|
+
What is assistant-ui?
|
|
832
|
+
</span>
|
|
833
|
+
</ThreadPrimitive.Suggestion>
|
|
834
|
+
</div>
|
|
835
|
+
);
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const Composer: FC = () => {
|
|
839
|
+
return (
|
|
840
|
+
<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">
|
|
841
|
+
<ComposerPrimitive.Input
|
|
842
|
+
rows={1}
|
|
843
|
+
autoFocus
|
|
844
|
+
placeholder="Write a message..."
|
|
845
|
+
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"
|
|
846
|
+
/>
|
|
847
|
+
<ComposerAction />
|
|
848
|
+
</ComposerPrimitive.Root>
|
|
849
|
+
);
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
const ComposerAction: FC = () => {
|
|
853
|
+
return (
|
|
854
|
+
<>
|
|
855
|
+
<ThreadPrimitive.If running={false}>
|
|
856
|
+
<ComposerPrimitive.Send asChild>
|
|
857
|
+
<TooltipIconButton
|
|
858
|
+
tooltip="Send"
|
|
859
|
+
variant="default"
|
|
860
|
+
className="my-2.5 size-8 p-2 transition-opacity ease-in"
|
|
861
|
+
>
|
|
862
|
+
<SendHorizontalIcon />
|
|
863
|
+
</TooltipIconButton>
|
|
864
|
+
</ComposerPrimitive.Send>
|
|
865
|
+
</ThreadPrimitive.If>
|
|
866
|
+
<ThreadPrimitive.If running>
|
|
867
|
+
<ComposerPrimitive.Cancel asChild>
|
|
868
|
+
<TooltipIconButton
|
|
869
|
+
tooltip="Cancel"
|
|
870
|
+
variant="default"
|
|
871
|
+
className="my-2.5 size-8 p-2 transition-opacity ease-in"
|
|
872
|
+
>
|
|
873
|
+
<CircleStopIcon />
|
|
874
|
+
</TooltipIconButton>
|
|
875
|
+
</ComposerPrimitive.Cancel>
|
|
876
|
+
</ThreadPrimitive.If>
|
|
877
|
+
</>
|
|
878
|
+
);
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
const UserMessage: FC = () => {
|
|
882
|
+
return (
|
|
883
|
+
<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">
|
|
884
|
+
<UserActionBar />
|
|
885
|
+
|
|
886
|
+
<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">
|
|
887
|
+
<MessagePrimitive.Content />
|
|
888
|
+
</div>
|
|
889
|
+
|
|
890
|
+
<BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
|
|
891
|
+
</MessagePrimitive.Root>
|
|
892
|
+
);
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
const UserActionBar: FC = () => {
|
|
896
|
+
return (
|
|
897
|
+
<ActionBarPrimitive.Root
|
|
898
|
+
hideWhenRunning
|
|
899
|
+
autohide="not-last"
|
|
900
|
+
className="col-start-1 row-start-2 mr-3 mt-2.5 flex flex-col items-end"
|
|
901
|
+
>
|
|
902
|
+
<ActionBarPrimitive.Edit asChild>
|
|
903
|
+
<TooltipIconButton tooltip="Edit">
|
|
904
|
+
<PencilIcon />
|
|
905
|
+
</TooltipIconButton>
|
|
906
|
+
</ActionBarPrimitive.Edit>
|
|
907
|
+
</ActionBarPrimitive.Root>
|
|
908
|
+
);
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const EditComposer: FC = () => {
|
|
912
|
+
return (
|
|
913
|
+
<ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
|
|
914
|
+
<ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
|
|
915
|
+
|
|
916
|
+
<div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
|
|
917
|
+
<ComposerPrimitive.Cancel asChild>
|
|
918
|
+
<Button variant="ghost">Cancel</Button>
|
|
919
|
+
</ComposerPrimitive.Cancel>
|
|
920
|
+
<ComposerPrimitive.Send asChild>
|
|
921
|
+
<Button>Send</Button>
|
|
922
|
+
</ComposerPrimitive.Send>
|
|
923
|
+
</div>
|
|
924
|
+
</ComposerPrimitive.Root>
|
|
925
|
+
);
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const AssistantMessage: FC = () => {
|
|
929
|
+
return (
|
|
930
|
+
<MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
|
|
931
|
+
<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">
|
|
932
|
+
<MessagePrimitive.Content components={{ Text: MarkdownText }} />
|
|
933
|
+
</div>
|
|
934
|
+
|
|
935
|
+
<AssistantActionBar />
|
|
936
|
+
|
|
937
|
+
<BranchPicker className="col-start-2 row-start-2 -ml-2 mr-2" />
|
|
938
|
+
</MessagePrimitive.Root>
|
|
939
|
+
);
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
const AssistantActionBar: FC = () => {
|
|
943
|
+
return (
|
|
944
|
+
<ActionBarPrimitive.Root
|
|
945
|
+
hideWhenRunning
|
|
946
|
+
autohide="not-last"
|
|
947
|
+
autohideFloat="single-branch"
|
|
948
|
+
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"
|
|
949
|
+
>
|
|
950
|
+
<ActionBarPrimitive.Copy asChild>
|
|
951
|
+
<TooltipIconButton tooltip="Copy">
|
|
952
|
+
<MessagePrimitive.If copied>
|
|
953
|
+
<CheckIcon />
|
|
954
|
+
</MessagePrimitive.If>
|
|
955
|
+
<MessagePrimitive.If copied={false}>
|
|
956
|
+
<CopyIcon />
|
|
957
|
+
</MessagePrimitive.If>
|
|
958
|
+
</TooltipIconButton>
|
|
959
|
+
</ActionBarPrimitive.Copy>
|
|
960
|
+
<ActionBarPrimitive.Reload asChild>
|
|
961
|
+
<TooltipIconButton tooltip="Refresh">
|
|
962
|
+
<RefreshCwIcon />
|
|
963
|
+
</TooltipIconButton>
|
|
964
|
+
</ActionBarPrimitive.Reload>
|
|
965
|
+
</ActionBarPrimitive.Root>
|
|
966
|
+
);
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
|
|
970
|
+
className,
|
|
971
|
+
...rest
|
|
972
|
+
}) => {
|
|
973
|
+
return (
|
|
974
|
+
<BranchPickerPrimitive.Root
|
|
975
|
+
hideWhenSingleBranch
|
|
976
|
+
className={cn(
|
|
977
|
+
"text-muted-foreground inline-flex items-center text-xs",
|
|
978
|
+
className,
|
|
979
|
+
)}
|
|
980
|
+
{...rest}
|
|
981
|
+
>
|
|
982
|
+
<BranchPickerPrimitive.Previous asChild>
|
|
983
|
+
<TooltipIconButton tooltip="Previous">
|
|
984
|
+
<ChevronLeftIcon />
|
|
985
|
+
</TooltipIconButton>
|
|
986
|
+
</BranchPickerPrimitive.Previous>
|
|
987
|
+
<span className="font-medium">
|
|
988
|
+
<BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
|
|
989
|
+
</span>
|
|
990
|
+
<BranchPickerPrimitive.Next asChild>
|
|
991
|
+
<TooltipIconButton tooltip="Next">
|
|
992
|
+
<ChevronRightIcon />
|
|
993
|
+
</TooltipIconButton>
|
|
994
|
+
</BranchPickerPrimitive.Next>
|
|
995
|
+
</BranchPickerPrimitive.Root>
|
|
996
|
+
);
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
const CircleStopIcon = () => {
|
|
1000
|
+
return (
|
|
1001
|
+
<svg
|
|
1002
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1003
|
+
viewBox="0 0 16 16"
|
|
1004
|
+
fill="currentColor"
|
|
1005
|
+
width="16"
|
|
1006
|
+
height="16"
|
|
1007
|
+
>
|
|
1008
|
+
<rect width="10" height="10" x="3" y="3" rx="2" />
|
|
1009
|
+
</svg>
|
|
1010
|
+
);
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
## components/assistant-ui/tooltip-icon-button.tsx
|
|
1016
|
+
|
|
1017
|
+
```tsx
|
|
1018
|
+
"use client";
|
|
1019
|
+
|
|
1020
|
+
import { ComponentPropsWithoutRef, forwardRef } from "react";
|
|
1021
|
+
|
|
1022
|
+
import {
|
|
1023
|
+
Tooltip,
|
|
1024
|
+
TooltipContent,
|
|
1025
|
+
TooltipProvider,
|
|
1026
|
+
TooltipTrigger,
|
|
1027
|
+
} from "@/components/ui/tooltip";
|
|
1028
|
+
import { Button } from "@/components/ui/button";
|
|
1029
|
+
import { cn } from "@/lib/utils";
|
|
1030
|
+
|
|
1031
|
+
export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
|
|
1032
|
+
tooltip: string;
|
|
1033
|
+
side?: "top" | "bottom" | "left" | "right";
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
export const TooltipIconButton = forwardRef<
|
|
1037
|
+
HTMLButtonElement,
|
|
1038
|
+
TooltipIconButtonProps
|
|
1039
|
+
>(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
|
|
1040
|
+
return (
|
|
1041
|
+
<TooltipProvider>
|
|
1042
|
+
<Tooltip>
|
|
1043
|
+
<TooltipTrigger asChild>
|
|
1044
|
+
<Button
|
|
1045
|
+
variant="ghost"
|
|
1046
|
+
size="icon"
|
|
1047
|
+
{...rest}
|
|
1048
|
+
className={cn("size-6 p-1", className)}
|
|
1049
|
+
ref={ref}
|
|
1050
|
+
>
|
|
1051
|
+
{children}
|
|
1052
|
+
<span className="sr-only">{tooltip}</span>
|
|
1053
|
+
</Button>
|
|
1054
|
+
</TooltipTrigger>
|
|
1055
|
+
<TooltipContent side={side}>{tooltip}</TooltipContent>
|
|
1056
|
+
</Tooltip>
|
|
1057
|
+
</TooltipProvider>
|
|
1058
|
+
);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
TooltipIconButton.displayName = "TooltipIconButton";
|
|
1062
|
+
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
## components/tools/price-snapshot/price-snapshot.tsx
|
|
1066
|
+
|
|
1067
|
+
```tsx
|
|
1068
|
+
"use client";
|
|
1069
|
+
|
|
1070
|
+
import { ArrowDownIcon, ArrowUpIcon } from "lucide-react";
|
|
1071
|
+
|
|
1072
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
1073
|
+
|
|
1074
|
+
type PriceSnapshotToolArgs = {
|
|
1075
|
+
ticker: string;
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
type PriceSnapshotToolResult = {
|
|
1079
|
+
price: number;
|
|
1080
|
+
day_change: number;
|
|
1081
|
+
day_change_percent: number;
|
|
1082
|
+
time: string;
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
export function PriceSnapshot({
|
|
1086
|
+
ticker,
|
|
1087
|
+
price,
|
|
1088
|
+
day_change,
|
|
1089
|
+
day_change_percent,
|
|
1090
|
+
time,
|
|
1091
|
+
}: PriceSnapshotToolArgs & PriceSnapshotToolResult) {
|
|
1092
|
+
const isPositiveChange = day_change >= 0;
|
|
1093
|
+
const changeColor = isPositiveChange ? "text-green-600" : "text-red-600";
|
|
1094
|
+
const ArrowIcon = isPositiveChange ? ArrowUpIcon : ArrowDownIcon;
|
|
1095
|
+
|
|
1096
|
+
return (
|
|
1097
|
+
<Card className="mx-auto w-full max-w-md">
|
|
1098
|
+
<CardHeader>
|
|
1099
|
+
<CardTitle className="text-2xl font-bold">{ticker}</CardTitle>
|
|
1100
|
+
</CardHeader>
|
|
1101
|
+
<CardContent>
|
|
1102
|
+
<div className="grid grid-cols-2 gap-4">
|
|
1103
|
+
<div className="col-span-2">
|
|
1104
|
+
<p className="text-3xl font-semibold">${price?.toFixed(2)}</p>
|
|
1105
|
+
</div>
|
|
1106
|
+
<div>
|
|
1107
|
+
<p className="text-muted-foreground text-sm">Day Change</p>
|
|
1108
|
+
<p
|
|
1109
|
+
className={`flex items-center text-lg font-medium ${changeColor}`}
|
|
1110
|
+
>
|
|
1111
|
+
<ArrowIcon className="mr-1 h-4 w-4" />$
|
|
1112
|
+
{Math.abs(day_change)?.toFixed(2)} (
|
|
1113
|
+
{Math.abs(day_change_percent)?.toFixed(2)}%)
|
|
1114
|
+
</p>
|
|
1115
|
+
</div>
|
|
1116
|
+
<div>
|
|
1117
|
+
<p className="text-muted-foreground text-sm">Last Updated</p>
|
|
1118
|
+
<p className="text-lg font-medium">
|
|
1119
|
+
{new Date(time).toLocaleTimeString()}
|
|
1120
|
+
</p>
|
|
1121
|
+
</div>
|
|
1122
|
+
</div>
|
|
1123
|
+
</CardContent>
|
|
1124
|
+
</Card>
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
## components/tools/price-snapshot/PriceSnapshotTool.tsx
|
|
1131
|
+
|
|
1132
|
+
```tsx
|
|
1133
|
+
"use client";
|
|
1134
|
+
|
|
1135
|
+
import { PriceSnapshot } from "./price-snapshot";
|
|
1136
|
+
import { makeAssistantToolUI } from "@assistant-ui/react";
|
|
1137
|
+
|
|
1138
|
+
type PriceSnapshotToolArgs = {
|
|
1139
|
+
ticker: string;
|
|
1140
|
+
};
|
|
1141
|
+
type PriceSnapshotToolResult = {
|
|
1142
|
+
snapshot: {
|
|
1143
|
+
price: number;
|
|
1144
|
+
day_change: number;
|
|
1145
|
+
day_change_percent: number;
|
|
1146
|
+
time: string;
|
|
1147
|
+
};
|
|
1148
|
+
};
|
|
1149
|
+
export const PriceSnapshotTool = makeAssistantToolUI<
|
|
1150
|
+
PriceSnapshotToolArgs,
|
|
1151
|
+
string
|
|
1152
|
+
>({
|
|
1153
|
+
toolName: "price_snapshot",
|
|
1154
|
+
render: function PriceSnapshotUI({ args, argsText, result }) {
|
|
1155
|
+
const resultObj = result
|
|
1156
|
+
? (JSON.parse(result) as PriceSnapshotToolResult)
|
|
1157
|
+
: undefined;
|
|
1158
|
+
|
|
1159
|
+
return (
|
|
1160
|
+
<div className="mb-4 flex flex-col items-center gap-2">
|
|
1161
|
+
<pre className="whitespace-pre-wrap">price_snapshot({argsText})</pre>
|
|
1162
|
+
{resultObj && (
|
|
1163
|
+
<PriceSnapshot ticker={args.ticker} {...resultObj.snapshot} />
|
|
1164
|
+
)}
|
|
1165
|
+
</div>
|
|
1166
|
+
);
|
|
1167
|
+
},
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
## components/tools/purchase-stock/PurchaseStockTool.tsx
|
|
1173
|
+
|
|
1174
|
+
```tsx
|
|
1175
|
+
"use client";
|
|
1176
|
+
|
|
1177
|
+
import { TransactionConfirmationPending } from "./transaction-confirmation-pending";
|
|
1178
|
+
import { TransactionConfirmationFinal } from "./transaction-confirmation-final";
|
|
1179
|
+
import { makeAssistantToolUI } from "@assistant-ui/react";
|
|
1180
|
+
|
|
1181
|
+
type PurchaseStockArgs = {
|
|
1182
|
+
ticker: string;
|
|
1183
|
+
companyName: string;
|
|
1184
|
+
quantity: number;
|
|
1185
|
+
maxPurchasePrice: number;
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
export const PurchaseStockTool = makeAssistantToolUI<PurchaseStockArgs, string>(
|
|
1189
|
+
{
|
|
1190
|
+
toolName: "purchase_stock",
|
|
1191
|
+
render: function PurchaseStockUI({
|
|
1192
|
+
args,
|
|
1193
|
+
argsText,
|
|
1194
|
+
result,
|
|
1195
|
+
status,
|
|
1196
|
+
addResult,
|
|
1197
|
+
}) {
|
|
1198
|
+
const resultObj = result
|
|
1199
|
+
? (JSON.parse(result) as { transactionId: string })
|
|
1200
|
+
: undefined;
|
|
1201
|
+
|
|
1202
|
+
const handleConfirm = async () => {
|
|
1203
|
+
addResult(JSON.stringify({ confirmed: true }));
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
return (
|
|
1207
|
+
<div className="mb-4 flex flex-col items-center gap-2">
|
|
1208
|
+
<pre className="whitespace-pre-wrap">purchase_stock({argsText})</pre>
|
|
1209
|
+
{!resultObj && status.type !== "running" && (
|
|
1210
|
+
<TransactionConfirmationPending
|
|
1211
|
+
{...args}
|
|
1212
|
+
onConfirm={handleConfirm}
|
|
1213
|
+
/>
|
|
1214
|
+
)}
|
|
1215
|
+
{resultObj && <TransactionConfirmationFinal {...args} />}
|
|
1216
|
+
</div>
|
|
1217
|
+
);
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
## components/tools/purchase-stock/transaction-confirmation-final.tsx
|
|
1225
|
+
|
|
1226
|
+
```tsx
|
|
1227
|
+
"use client";
|
|
1228
|
+
|
|
1229
|
+
import { CheckCircle } from "lucide-react";
|
|
1230
|
+
|
|
1231
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
1232
|
+
|
|
1233
|
+
type TransactionConfirmation = {
|
|
1234
|
+
ticker: string;
|
|
1235
|
+
companyName: string;
|
|
1236
|
+
quantity: number;
|
|
1237
|
+
maxPurchasePrice: number;
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
export function TransactionConfirmationFinal(props: TransactionConfirmation) {
|
|
1241
|
+
const { ticker, companyName, quantity, maxPurchasePrice } = props;
|
|
1242
|
+
|
|
1243
|
+
return (
|
|
1244
|
+
<Card className="mx-auto w-full max-w-md">
|
|
1245
|
+
<CardHeader className="text-center">
|
|
1246
|
+
<CheckCircle className="mx-auto mb-4 h-16 w-16 text-green-500" />
|
|
1247
|
+
<CardTitle className="text-2xl font-bold text-green-700">
|
|
1248
|
+
Transaction Confirmed
|
|
1249
|
+
</CardTitle>
|
|
1250
|
+
</CardHeader>
|
|
1251
|
+
<CardContent className="space-y-4">
|
|
1252
|
+
<div className="rounded-md border border-green-200 bg-green-50 p-4">
|
|
1253
|
+
<h3 className="mb-2 text-lg font-semibold text-green-800">
|
|
1254
|
+
Purchase Summary
|
|
1255
|
+
</h3>
|
|
1256
|
+
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
1257
|
+
<p className="font-medium text-green-700">Ticker:</p>
|
|
1258
|
+
<p className="font-bold text-green-900">{ticker}</p>
|
|
1259
|
+
<p className="font-medium text-green-700">Company:</p>
|
|
1260
|
+
<p className="text-green-900">{companyName}</p>
|
|
1261
|
+
<p className="font-medium text-green-700">Quantity:</p>
|
|
1262
|
+
<p className="text-green-900">{quantity} shares</p>
|
|
1263
|
+
<p className="font-medium text-green-700">Price per Share:</p>
|
|
1264
|
+
<p className="text-green-900">${maxPurchasePrice?.toFixed(2)}</p>
|
|
1265
|
+
</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
<div className="rounded-md border border-green-300 bg-green-100 p-4">
|
|
1268
|
+
<p className="text-lg font-semibold text-green-800">Total Cost:</p>
|
|
1269
|
+
<p className="text-2xl font-bold text-green-900">
|
|
1270
|
+
${(quantity * maxPurchasePrice)?.toFixed(2)}
|
|
1271
|
+
</p>
|
|
1272
|
+
</div>
|
|
1273
|
+
<p className="text-center text-sm text-green-600">
|
|
1274
|
+
Your purchase of {quantity} shares of {companyName} ({ticker}) has
|
|
1275
|
+
been successfully processed.
|
|
1276
|
+
</p>
|
|
1277
|
+
</CardContent>
|
|
1278
|
+
</Card>
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
## components/tools/purchase-stock/transaction-confirmation-pending.tsx
|
|
1285
|
+
|
|
1286
|
+
```tsx
|
|
1287
|
+
"use client";
|
|
1288
|
+
|
|
1289
|
+
import { CheckIcon } from "lucide-react";
|
|
1290
|
+
|
|
1291
|
+
import { Button } from "@/components/ui/button";
|
|
1292
|
+
import {
|
|
1293
|
+
Card,
|
|
1294
|
+
CardContent,
|
|
1295
|
+
CardFooter,
|
|
1296
|
+
CardHeader,
|
|
1297
|
+
CardTitle,
|
|
1298
|
+
} from "@/components/ui/card";
|
|
1299
|
+
|
|
1300
|
+
type TransactionConfirmation = {
|
|
1301
|
+
ticker: string;
|
|
1302
|
+
companyName: string;
|
|
1303
|
+
quantity: number;
|
|
1304
|
+
maxPurchasePrice: number;
|
|
1305
|
+
onConfirm: () => void;
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
export function TransactionConfirmationPending(props: TransactionConfirmation) {
|
|
1309
|
+
const { ticker, companyName, quantity, maxPurchasePrice, onConfirm } = props;
|
|
1310
|
+
|
|
1311
|
+
return (
|
|
1312
|
+
<Card className="mx-auto w-full max-w-md">
|
|
1313
|
+
<CardHeader>
|
|
1314
|
+
<CardTitle className="text-2xl font-bold">
|
|
1315
|
+
Confirm Transaction
|
|
1316
|
+
</CardTitle>
|
|
1317
|
+
</CardHeader>
|
|
1318
|
+
<CardContent className="space-y-4">
|
|
1319
|
+
<div className="grid grid-cols-2 gap-2">
|
|
1320
|
+
<p className="text-muted-foreground text-sm font-medium">Ticker:</p>
|
|
1321
|
+
<p className="text-sm font-bold">{ticker}</p>
|
|
1322
|
+
<p className="text-muted-foreground text-sm font-medium">Company:</p>
|
|
1323
|
+
<p className="text-sm">{companyName}</p>
|
|
1324
|
+
<p className="text-muted-foreground text-sm font-medium">Quantity:</p>
|
|
1325
|
+
<p className="text-sm">{quantity} shares</p>
|
|
1326
|
+
<p className="text-muted-foreground text-sm font-medium">
|
|
1327
|
+
Max Purchase Price:
|
|
1328
|
+
</p>
|
|
1329
|
+
<p className="text-sm">${maxPurchasePrice?.toFixed(2)}</p>
|
|
1330
|
+
</div>
|
|
1331
|
+
<div className="bg-muted rounded-md p-3">
|
|
1332
|
+
<p className="text-sm font-medium">Total Maximum Cost:</p>
|
|
1333
|
+
<p className="text-lg font-bold">
|
|
1334
|
+
${(quantity * maxPurchasePrice)?.toFixed(2)}
|
|
1335
|
+
</p>
|
|
1336
|
+
</div>
|
|
1337
|
+
</CardContent>
|
|
1338
|
+
<CardFooter className="flex justify-end">
|
|
1339
|
+
{/* <Button variant="outline" onClick={onReject}>
|
|
1340
|
+
<X className="mr-2 h-4 w-4" />
|
|
1341
|
+
Reject
|
|
1342
|
+
</Button> */}
|
|
1343
|
+
<Button onClick={onConfirm}>
|
|
1344
|
+
<CheckIcon className="mr-2 h-4 w-4" />
|
|
1345
|
+
Confirm
|
|
1346
|
+
</Button>
|
|
1347
|
+
</CardFooter>
|
|
1348
|
+
</Card>
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
## components/tools/ToolFallback.tsx
|
|
1355
|
+
|
|
1356
|
+
```tsx
|
|
1357
|
+
import { ToolCallContentPartComponent } from "@assistant-ui/react";
|
|
1358
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
1359
|
+
import { useState } from "react";
|
|
1360
|
+
import { Button } from "../ui/button";
|
|
1361
|
+
|
|
1362
|
+
export const ToolFallback: ToolCallContentPartComponent = ({
|
|
1363
|
+
toolName,
|
|
1364
|
+
argsText,
|
|
1365
|
+
result,
|
|
1366
|
+
}) => {
|
|
1367
|
+
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
1368
|
+
return (
|
|
1369
|
+
<div className="mb-4 flex w-full flex-col gap-3 rounded-lg border py-3">
|
|
1370
|
+
<div className="flex items-center gap-2 px-4">
|
|
1371
|
+
<CheckIcon className="size-4" />
|
|
1372
|
+
<p className="">
|
|
1373
|
+
Used tool: <b>{toolName}</b>
|
|
1374
|
+
</p>
|
|
1375
|
+
<div className="flex-grow" />
|
|
1376
|
+
<Button onClick={() => setIsCollapsed(!isCollapsed)}>
|
|
1377
|
+
{isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
|
1378
|
+
</Button>
|
|
1379
|
+
</div>
|
|
1380
|
+
{!isCollapsed && (
|
|
1381
|
+
<div className="flex flex-col gap-2 border-t pt-2">
|
|
1382
|
+
<div className="px-4">
|
|
1383
|
+
<pre className="whitespace-pre-wrap">{argsText}</pre>
|
|
1384
|
+
</div>
|
|
1385
|
+
{result !== undefined && (
|
|
1386
|
+
<div className="border-t border-dashed px-4 pt-2">
|
|
1387
|
+
<p className="font-semibold">Result:</p>
|
|
1388
|
+
<pre className="whitespace-pre-wrap">
|
|
1389
|
+
{typeof result === "string"
|
|
1390
|
+
? result
|
|
1391
|
+
: JSON.stringify(result, null, 2)}
|
|
1392
|
+
</pre>
|
|
1393
|
+
</div>
|
|
1394
|
+
)}
|
|
1395
|
+
</div>
|
|
1396
|
+
)}
|
|
1397
|
+
</div>
|
|
1398
|
+
);
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
```
|
|
1402
|
+
|
|
1403
|
+
## components/ui/button.tsx
|
|
1404
|
+
|
|
1405
|
+
```tsx
|
|
1406
|
+
import * as React from "react";
|
|
1407
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
1408
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1409
|
+
|
|
1410
|
+
import { cn } from "@/lib/utils";
|
|
1411
|
+
|
|
1412
|
+
const buttonVariants = cva(
|
|
1413
|
+
"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",
|
|
1414
|
+
{
|
|
1415
|
+
variants: {
|
|
1416
|
+
variant: {
|
|
1417
|
+
default:
|
|
1418
|
+
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
1419
|
+
destructive:
|
|
1420
|
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
1421
|
+
outline:
|
|
1422
|
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
1423
|
+
secondary:
|
|
1424
|
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
1425
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
1426
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
1427
|
+
},
|
|
1428
|
+
size: {
|
|
1429
|
+
default: "h-9 px-4 py-2",
|
|
1430
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
1431
|
+
lg: "h-10 rounded-md px-8",
|
|
1432
|
+
icon: "h-9 w-9",
|
|
1433
|
+
},
|
|
1434
|
+
},
|
|
1435
|
+
defaultVariants: {
|
|
1436
|
+
variant: "default",
|
|
1437
|
+
size: "default",
|
|
1438
|
+
},
|
|
1439
|
+
},
|
|
1440
|
+
);
|
|
1441
|
+
|
|
1442
|
+
export interface ButtonProps
|
|
1443
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
1444
|
+
VariantProps<typeof buttonVariants> {
|
|
1445
|
+
asChild?: boolean;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
1449
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
1450
|
+
const Comp = asChild ? Slot : "button";
|
|
1451
|
+
return (
|
|
1452
|
+
<Comp
|
|
1453
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
1454
|
+
ref={ref}
|
|
1455
|
+
{...props}
|
|
1456
|
+
/>
|
|
1457
|
+
);
|
|
1458
|
+
},
|
|
1459
|
+
);
|
|
1460
|
+
Button.displayName = "Button";
|
|
1461
|
+
|
|
1462
|
+
export { Button, buttonVariants };
|
|
1463
|
+
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
## components/ui/card.tsx
|
|
1467
|
+
|
|
1468
|
+
```tsx
|
|
1469
|
+
import * as React from "react";
|
|
1470
|
+
|
|
1471
|
+
import { cn } from "@/lib/utils";
|
|
1472
|
+
|
|
1473
|
+
const Card = React.forwardRef<
|
|
1474
|
+
HTMLDivElement,
|
|
1475
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1476
|
+
>(({ className, ...props }, ref) => (
|
|
1477
|
+
<div
|
|
1478
|
+
ref={ref}
|
|
1479
|
+
className={cn(
|
|
1480
|
+
"bg-card text-card-foreground rounded-xl border shadow",
|
|
1481
|
+
className,
|
|
1482
|
+
)}
|
|
1483
|
+
{...props}
|
|
1484
|
+
/>
|
|
1485
|
+
));
|
|
1486
|
+
Card.displayName = "Card";
|
|
1487
|
+
|
|
1488
|
+
const CardHeader = React.forwardRef<
|
|
1489
|
+
HTMLDivElement,
|
|
1490
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1491
|
+
>(({ className, ...props }, ref) => (
|
|
1492
|
+
<div
|
|
1493
|
+
ref={ref}
|
|
1494
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
1495
|
+
{...props}
|
|
1496
|
+
/>
|
|
1497
|
+
));
|
|
1498
|
+
CardHeader.displayName = "CardHeader";
|
|
1499
|
+
|
|
1500
|
+
const CardTitle = React.forwardRef<
|
|
1501
|
+
HTMLDivElement,
|
|
1502
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1503
|
+
>(({ className, ...props }, ref) => (
|
|
1504
|
+
<div
|
|
1505
|
+
ref={ref}
|
|
1506
|
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
1507
|
+
{...props}
|
|
1508
|
+
/>
|
|
1509
|
+
));
|
|
1510
|
+
CardTitle.displayName = "CardTitle";
|
|
1511
|
+
|
|
1512
|
+
const CardDescription = React.forwardRef<
|
|
1513
|
+
HTMLDivElement,
|
|
1514
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1515
|
+
>(({ className, ...props }, ref) => (
|
|
1516
|
+
<div
|
|
1517
|
+
ref={ref}
|
|
1518
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
1519
|
+
{...props}
|
|
1520
|
+
/>
|
|
1521
|
+
));
|
|
1522
|
+
CardDescription.displayName = "CardDescription";
|
|
1523
|
+
|
|
1524
|
+
const CardContent = React.forwardRef<
|
|
1525
|
+
HTMLDivElement,
|
|
1526
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1527
|
+
>(({ className, ...props }, ref) => (
|
|
1528
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
1529
|
+
));
|
|
1530
|
+
CardContent.displayName = "CardContent";
|
|
1531
|
+
|
|
1532
|
+
const CardFooter = React.forwardRef<
|
|
1533
|
+
HTMLDivElement,
|
|
1534
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1535
|
+
>(({ className, ...props }, ref) => (
|
|
1536
|
+
<div
|
|
1537
|
+
ref={ref}
|
|
1538
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
1539
|
+
{...props}
|
|
1540
|
+
/>
|
|
1541
|
+
));
|
|
1542
|
+
CardFooter.displayName = "CardFooter";
|
|
1543
|
+
|
|
1544
|
+
export {
|
|
1545
|
+
Card,
|
|
1546
|
+
CardHeader,
|
|
1547
|
+
CardFooter,
|
|
1548
|
+
CardTitle,
|
|
1549
|
+
CardDescription,
|
|
1550
|
+
CardContent,
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
## components/ui/tooltip.tsx
|
|
1556
|
+
|
|
1557
|
+
```tsx
|
|
1558
|
+
"use client";
|
|
1559
|
+
|
|
1560
|
+
import * as React from "react";
|
|
1561
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
1562
|
+
|
|
1563
|
+
import { cn } from "@/lib/utils";
|
|
1564
|
+
|
|
1565
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
1566
|
+
|
|
1567
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
1568
|
+
|
|
1569
|
+
const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
1570
|
+
|
|
1571
|
+
const TooltipContent = React.forwardRef<
|
|
1572
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
1573
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
1574
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
1575
|
+
<TooltipPrimitive.Portal>
|
|
1576
|
+
<TooltipPrimitive.Content
|
|
1577
|
+
ref={ref}
|
|
1578
|
+
sideOffset={sideOffset}
|
|
1579
|
+
className={cn(
|
|
1580
|
+
"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 origin-[--radix-tooltip-content-transform-origin] overflow-hidden rounded-md px-3 py-1.5 text-xs",
|
|
1581
|
+
className,
|
|
1582
|
+
)}
|
|
1583
|
+
{...props}
|
|
1584
|
+
/>
|
|
1585
|
+
</TooltipPrimitive.Portal>
|
|
1586
|
+
));
|
|
1587
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
1588
|
+
|
|
1589
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
1590
|
+
|
|
1591
|
+
```
|
|
1592
|
+
|
|
1593
|
+
## eslint.config.ts
|
|
1594
|
+
|
|
1595
|
+
```typescript
|
|
1596
|
+
export { default } from "@assistant-ui/x-buildutils/eslint";
|
|
1597
|
+
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
## lib/chatApi.ts
|
|
1601
|
+
|
|
1602
|
+
```typescript
|
|
1603
|
+
import { ThreadState, Client } from "@langchain/langgraph-sdk";
|
|
1604
|
+
import {
|
|
1605
|
+
LangChainMessage,
|
|
1606
|
+
LangGraphMessagesEvent,
|
|
1607
|
+
} from "@assistant-ui/react-langgraph";
|
|
1608
|
+
|
|
1609
|
+
const createClient = () => {
|
|
1610
|
+
const apiUrl =
|
|
1611
|
+
process.env["NEXT_PUBLIC_LANGGRAPH_API_URL"] ||
|
|
1612
|
+
new URL("/api", window.location.href).href;
|
|
1613
|
+
return new Client({
|
|
1614
|
+
apiUrl,
|
|
1615
|
+
});
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
export const createAssistant = async (graphId: string) => {
|
|
1619
|
+
const client = createClient();
|
|
1620
|
+
return client.assistants.create({ graphId });
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
export const createThread = async () => {
|
|
1624
|
+
const client = createClient();
|
|
1625
|
+
return client.threads.create();
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
export const getThreadState = async (
|
|
1629
|
+
threadId: string,
|
|
1630
|
+
): Promise<ThreadState<Record<string, unknown>>> => {
|
|
1631
|
+
const client = createClient();
|
|
1632
|
+
return client.threads.getState(threadId);
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
export const updateState = async (
|
|
1636
|
+
threadId: string,
|
|
1637
|
+
fields: {
|
|
1638
|
+
newState: Record<string, unknown>;
|
|
1639
|
+
asNode?: string;
|
|
1640
|
+
},
|
|
1641
|
+
) => {
|
|
1642
|
+
const client = createClient();
|
|
1643
|
+
return client.threads.updateState(threadId, {
|
|
1644
|
+
values: fields.newState,
|
|
1645
|
+
asNode: fields.asNode!,
|
|
1646
|
+
});
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
export const sendMessage = (params: {
|
|
1650
|
+
threadId: string;
|
|
1651
|
+
messages: LangChainMessage[];
|
|
1652
|
+
}): AsyncGenerator<LangGraphMessagesEvent<LangChainMessage>> => {
|
|
1653
|
+
const client = createClient();
|
|
1654
|
+
|
|
1655
|
+
const input: Record<string, unknown> | null = {
|
|
1656
|
+
messages: params.messages,
|
|
1657
|
+
};
|
|
1658
|
+
const config = {
|
|
1659
|
+
configurable: {
|
|
1660
|
+
model_name: "openai",
|
|
1661
|
+
},
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
return client.runs.stream(
|
|
1665
|
+
params.threadId,
|
|
1666
|
+
process.env["NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID"]!,
|
|
1667
|
+
{
|
|
1668
|
+
input,
|
|
1669
|
+
config,
|
|
1670
|
+
streamMode: "messages",
|
|
1671
|
+
},
|
|
1672
|
+
) as AsyncGenerator<LangGraphMessagesEvent<LangChainMessage>>;
|
|
1673
|
+
};
|
|
1674
|
+
|
|
1675
|
+
```
|
|
1676
|
+
|
|
1677
|
+
## lib/utils.ts
|
|
1678
|
+
|
|
1679
|
+
```typescript
|
|
1680
|
+
import { type ClassValue, clsx } from "clsx";
|
|
1681
|
+
import { twMerge } from "tailwind-merge";
|
|
1682
|
+
|
|
1683
|
+
export function cn(...inputs: ClassValue[]) {
|
|
1684
|
+
return twMerge(clsx(inputs));
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
## next-env.d.ts
|
|
1690
|
+
|
|
1691
|
+
```typescript
|
|
1692
|
+
/// <reference types="next" />
|
|
1693
|
+
/// <reference types="next/image-types/global" />
|
|
1694
|
+
|
|
1695
|
+
// NOTE: This file should not be edited
|
|
1696
|
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
1697
|
+
|
|
1698
|
+
```
|
|
1699
|
+
|
|
1700
|
+
## next.config.ts
|
|
1701
|
+
|
|
1702
|
+
```typescript
|
|
1703
|
+
import type { NextConfig } from "next";
|
|
1704
|
+
|
|
1705
|
+
const nextConfig: NextConfig = {
|
|
1706
|
+
/* config options here */
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
export default nextConfig;
|
|
1710
|
+
|
|
1711
|
+
```
|
|
1712
|
+
|
|
1713
|
+
## package.json
|
|
1714
|
+
|
|
1715
|
+
```json
|
|
1716
|
+
{
|
|
1717
|
+
"name": "with-langgraph",
|
|
1718
|
+
"version": "0.1.0",
|
|
1719
|
+
"private": true,
|
|
1720
|
+
"scripts": {
|
|
1721
|
+
"dev": "next dev --turbo",
|
|
1722
|
+
"build": "next build",
|
|
1723
|
+
"start": "next start",
|
|
1724
|
+
"lint": "next lint"
|
|
1725
|
+
},
|
|
1726
|
+
"dependencies": {
|
|
1727
|
+
"@assistant-ui/react": "workspace:*",
|
|
1728
|
+
"@assistant-ui/react-langgraph": "workspace:*",
|
|
1729
|
+
"@assistant-ui/react-markdown": "workspace:*",
|
|
1730
|
+
"@langchain/langgraph-sdk": "^0.0.81",
|
|
1731
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
1732
|
+
"@radix-ui/react-tooltip": "^1.2.7",
|
|
1733
|
+
"class-variance-authority": "^0.7.1",
|
|
1734
|
+
"clsx": "^2.1.1",
|
|
1735
|
+
"js-cookie": "^3.0.5",
|
|
1736
|
+
"jsonwebtoken": "^9.0.2",
|
|
1737
|
+
"lucide-react": "^0.511.0",
|
|
1738
|
+
"nanoid": "5.1.5",
|
|
1739
|
+
"next": "15.3.3",
|
|
1740
|
+
"react": "19.1.0",
|
|
1741
|
+
"react-dom": "19.1.0",
|
|
1742
|
+
"remark-gfm": "^4.0.1",
|
|
1743
|
+
"tailwind-merge": "^3.3.0",
|
|
1744
|
+
"tw-animate-css": "^1.3.3"
|
|
1745
|
+
},
|
|
1746
|
+
"devDependencies": {
|
|
1747
|
+
"@assistant-ui/x-buildutils": "workspace:*",
|
|
1748
|
+
"@types/js-cookie": "^3.0.6",
|
|
1749
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
1750
|
+
"@types/node": "^22",
|
|
1751
|
+
"@types/react": "^19",
|
|
1752
|
+
"@types/react-dom": "^19",
|
|
1753
|
+
"eslint": "^9",
|
|
1754
|
+
"eslint-config-next": "15.3.3",
|
|
1755
|
+
"postcss": "^8",
|
|
1756
|
+
"tailwindcss": "^4.1.8",
|
|
1757
|
+
"typescript": "^5.8.3"
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
```
|
|
1762
|
+
|
|
1763
|
+
## README.md
|
|
1764
|
+
|
|
1765
|
+
```markdown
|
|
1766
|
+
# LangGraph Example
|
|
1767
|
+
|
|
1768
|
+
[Hosted Demo](https://assistant-ui-langgraph.vercel.app/)
|
|
1769
|
+
|
|
1770
|
+
This example demonstrates how to use LangChain LangGraph with assistant-ui.
|
|
1771
|
+
|
|
1772
|
+
It is meant to be used with the backend found at LangGraph's Stockbroker example: https://github.com/bracesproul/langgraphjs-examples/tree/main/stockbroker
|
|
1773
|
+
|
|
1774
|
+
You need to set the following environment variables:
|
|
1775
|
+
|
|
1776
|
+
```env
|
|
1777
|
+
NEXT_PUBLIC_API_URL=https://stockbrokeragent-bracesprouls-projects.vercel.app/api
|
|
1778
|
+
NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=stockbroker
|
|
1779
|
+
```
|
|
1780
|
+
|
|
1781
|
+
To run the example, run the following commands:
|
|
1782
|
+
|
|
1783
|
+
```sh
|
|
1784
|
+
npm install
|
|
1785
|
+
npm run dev
|
|
1786
|
+
```
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
## tsconfig.json
|
|
1790
|
+
|
|
1791
|
+
```json
|
|
1792
|
+
{
|
|
1793
|
+
"extends": "@assistant-ui/x-buildutils/ts/base",
|
|
1794
|
+
"compilerOptions": {
|
|
1795
|
+
"target": "ES6",
|
|
1796
|
+
"module": "ESNext",
|
|
1797
|
+
"incremental": true,
|
|
1798
|
+
"plugins": [
|
|
1799
|
+
{
|
|
1800
|
+
"name": "next"
|
|
1801
|
+
}
|
|
1802
|
+
],
|
|
1803
|
+
"allowJs": true,
|
|
1804
|
+
"strictNullChecks": true,
|
|
1805
|
+
"jsx": "preserve",
|
|
1806
|
+
"paths": {
|
|
1807
|
+
"@/*": ["./*"],
|
|
1808
|
+
"@assistant-ui/*": ["../../packages/*/src"],
|
|
1809
|
+
"@assistant-ui/react/*": ["../../packages/react/src/*"],
|
|
1810
|
+
"assistant-stream": ["../../packages/assistant-stream/src"],
|
|
1811
|
+
"assistant-stream/*": ["../../packages/assistant-stream/src/*"]
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
1815
|
+
"exclude": ["node_modules"]
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
```
|
|
1819
|
+
|