@assistant-ui/mcp-docs-server 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.docs/organized/code-examples/with-ai-sdk-v5.md +54 -19
  2. package/.docs/organized/code-examples/with-cloud.md +17 -24
  3. package/.docs/organized/code-examples/with-external-store.md +6 -6
  4. package/.docs/organized/code-examples/with-ffmpeg.md +18 -21
  5. package/.docs/organized/code-examples/with-langgraph.md +7 -8
  6. package/.docs/organized/code-examples/with-parent-id-grouping.md +9 -6
  7. package/.docs/organized/code-examples/with-react-hook-form.md +16 -21
  8. package/.docs/raw/docs/api-reference/overview.mdx +1 -4
  9. package/.docs/raw/docs/getting-started.mdx +33 -33
  10. package/.docs/raw/docs/guides/Attachments.mdx +1 -102
  11. package/.docs/raw/docs/guides/Latex.mdx +42 -16
  12. package/.docs/raw/docs/guides/ToolUI.mdx +3 -3
  13. package/.docs/raw/docs/guides/Tools.mdx +101 -84
  14. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +134 -55
  15. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +182 -0
  16. package/.docs/raw/docs/runtimes/custom/local.mdx +1 -1
  17. package/.docs/raw/docs/runtimes/langgraph/index.mdx +0 -1
  18. package/.docs/raw/docs/runtimes/langserve.mdx +9 -11
  19. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +2 -2
  20. package/dist/{chunk-JS4PWCVA.js → chunk-L4K23SWI.js} +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/stdio.js +1 -1
  23. package/package.json +6 -6
  24. package/.docs/organized/code-examples/local-ollama.md +0 -1135
  25. package/.docs/organized/code-examples/search-agent-for-e-commerce.md +0 -1721
  26. package/.docs/organized/code-examples/with-ai-sdk.md +0 -1081
  27. package/.docs/organized/code-examples/with-openai-assistants.md +0 -1175
  28. package/.docs/raw/docs/runtimes/ai-sdk/rsc.mdx +0 -226
  29. package/.docs/raw/docs/runtimes/ai-sdk/use-assistant-hook.mdx +0 -195
  30. package/.docs/raw/docs/runtimes/ai-sdk/use-chat-hook.mdx +0 -138
  31. package/.docs/raw/docs/runtimes/ai-sdk/use-chat-v5.mdx +0 -129
@@ -1,1721 +0,0 @@
1
- # Example: search-agent-for-e-commerce
2
-
3
- ## app/actions.tsx
4
-
5
- ```tsx
6
- "use server";
7
-
8
- import { openai } from "@ai-sdk/openai";
9
- import { createAI, getMutableAIState, streamUI } from "ai/rsc";
10
- import { nanoid } from "nanoid";
11
- import type { ReactNode } from "react";
12
- import { z } from "zod";
13
- import { CarouselPlugin } from "../components/ui/productcarousel";
14
- import fs from "fs";
15
- import path from "path";
16
- import { streamText } from "ai";
17
-
18
- export interface ServerMessage {
19
- role: "user" | "assistant";
20
- content: string;
21
- }
22
-
23
- export interface ClientMessage {
24
- id: string;
25
- role: "user" | "assistant";
26
- display: ReactNode;
27
- }
28
-
29
- export async function continueConversation(
30
- input: string,
31
- indexId: string,
32
- ): Promise<ClientMessage> {
33
- "use server";
34
-
35
- const history = getMutableAIState();
36
-
37
- const result = await streamUI({
38
- model: openai("gpt-3.5-turbo"),
39
- temperature: 0,
40
- system: `\
41
- You are a friendly assistant that helps the user with shopping on a ecommerce website ('DUMMY SHOP'). You help users with end-to-end shopping experience
42
- starting from general information about the brands and products, and helping with product discovery, search, and product details, as well as
43
- product purchase, customer support, fitting questions, technical questions.
44
- Your responses are solely based on the provided context about the store and its products.
45
- Right now, the user clicked on the AI assistant widget and your job is to determine their intent.
46
- The user intent migth not be clear, in this case you ask clarifications questions.
47
- The user quesiton might not be complete, in this case you ask for follow up questions.
48
-
49
- Here's a list of user intents to pick from:
50
- - Product search
51
- - Guideline for clothes fitting
52
- - Product specific questions
53
- - Customer support questions (e.g. track purchase, payment issues, order issues)
54
- - Escalate to human agent
55
- - Ask a clarification/follow up question
56
- - Product comparison
57
- - Promotions, hot deals
58
- `,
59
- messages: [...history.get(), { role: "user", content: input, indexId }],
60
- text: ({ content, done }) => {
61
- if (done) {
62
- history.done((messages: ServerMessage[]) => [
63
- ...messages,
64
- { role: "assistant", content },
65
- ]);
66
- }
67
- return <div>{content}</div>;
68
- },
69
- // toolChoice: 'required', // force the model to call a tool
70
- // maxToolRoundtrips: 5, // allow up to 5 tool roundtrips
71
- tools: {
72
- product_search: {
73
- description:
74
- "Search for products on this website using pre-built indices",
75
- parameters: z.object({
76
- query: z
77
- .string()
78
- .describe(
79
- "A clear factual product query, potentially including type, name, qualities, characteristics of the product",
80
- ),
81
- }),
82
- generate: async ({ query }) => {
83
- try {
84
- console.log("query=", query);
85
- const response = await fetch(
86
- `https://dummyjson.com/products/search?q=${query}`,
87
- );
88
- const data = (await response.json()) as {
89
- products: {
90
- thumbnail: string;
91
- title: string;
92
- description: string;
93
- price: string;
94
- url: string;
95
- }[];
96
- };
97
- console.log("data=", data);
98
- if (data.products && data.products.length > 0) {
99
- const products = data.products.map((item) => ({
100
- thumbnail: item.thumbnail,
101
- title: item.title,
102
- description: item.description,
103
- metadata_3: item.price,
104
- link: item.url,
105
- }));
106
- return <CarouselPlugin products={products} />;
107
- } else {
108
- return <p>No products found.</p>;
109
- }
110
- } catch {
111
- return (
112
- <p>
113
- Sorry, we are experiencing some error. Please refresh the chat
114
- and try again.
115
- </p>
116
- );
117
- }
118
- },
119
- },
120
- general_question: {
121
- description: "User questions not related to products directly",
122
- parameters: z.object({
123
- user_question: z
124
- .string()
125
- .describe("User questions not related to products directly"),
126
- }),
127
- generate: async function* ({ user_question }) {
128
- const filePath = path.resolve(process.cwd(), "public/shop_info.txt");
129
- const generalInfo = fs.readFileSync(filePath, "utf-8");
130
- const result = streamText({
131
- model: openai("gpt-3.5-turbo"),
132
- temperature: 0,
133
- prompt: `Generate response to user question ${user_question} based on the context ${generalInfo}`,
134
- });
135
- let textContent = "";
136
-
137
- for await (const textPart of result.textStream) {
138
- textContent += textPart;
139
- yield textContent;
140
- }
141
- return textContent;
142
- },
143
- },
144
- clothes_fitting: {
145
- description:
146
- "Send to user link to guidelines for clothes fitting https://images.app.goo.gl/LECaeXJfXa7gzYCC8 ",
147
- parameters: z.object({}),
148
- generate: async ({}) => {
149
- const fittingGuidelinesLink =
150
- "https://images.app.goo.gl/LECaeXJfXa7gzYCC8 ";
151
- const formattedLink = `<a href="${fittingGuidelinesLink}" target="_blank">Guidelines for clothes fitting</a>`;
152
- const linkStyle = {
153
- color: "blue",
154
- textDecoration: "underline",
155
- };
156
- history.done((messages: ServerMessage[]) => [
157
- ...messages,
158
- {
159
- role: "assistant",
160
- content: formattedLink,
161
- },
162
- ]);
163
-
164
- return (
165
- <a href={fittingGuidelinesLink} target="_blank" style={linkStyle}>
166
- Guidelines for clothes fitting
167
- </a>
168
- );
169
- },
170
- },
171
- escalate: {
172
- description:
173
- "Escalate to human agent if none of the other tools seem relevant or the interaction is repetative, or if the user is getting upset",
174
- parameters: z.object({
175
- identifiable_info: z
176
- .string()
177
- .describe(
178
- "Email, full name, or order number to make the request identifiable",
179
- ),
180
- summary: z.string().describe("Summarize user request concisely"),
181
- }),
182
- generate: async function* ({ identifiable_info, summary }) {
183
- let textContent =
184
- "I am escalating your question to the human assistant\n\n";
185
- yield textContent;
186
- console.log(
187
- "generating answer while escalating, ",
188
- identifiable_info,
189
- );
190
- const filePath = path.resolve(process.cwd(), "public/shop_info.txt");
191
- const generalInfo = fs.readFileSync(filePath, "utf-8");
192
- const result = await streamText({
193
- model: openai("gpt-3.5-turbo"),
194
- temperature: 0,
195
- system: `Generate response to user question ${summary} based on the context ${generalInfo}. Your answer begins with: "While we wait for the human assistant,`,
196
- messages: [
197
- {
198
- role: "user",
199
- content: summary,
200
- },
201
- {
202
- role: "assistant",
203
- content: textContent,
204
- },
205
- ],
206
- });
207
-
208
- for await (const textPart of result.textStream) {
209
- textContent += textPart;
210
- yield textContent;
211
- }
212
- return textContent;
213
- },
214
- },
215
- },
216
- });
217
-
218
- return {
219
- id: nanoid(),
220
- role: "assistant",
221
- display: result.value,
222
- };
223
- }
224
-
225
- export const AI = createAI<ServerMessage[], ClientMessage[]>({
226
- actions: {
227
- continueConversation,
228
- },
229
- initialAIState: [],
230
- initialUIState: [],
231
- });
232
-
233
- ```
234
-
235
- ## app/globals.css
236
-
237
- ```css
238
- @import "tailwindcss";
239
- @import "tw-animate-css";
240
-
241
- @custom-variant dark (&:is(.dark *));
242
-
243
- @theme inline {
244
- --color-background: var(--background);
245
- --color-foreground: var(--foreground);
246
- --font-sans: var(--font-geist-sans);
247
- --font-mono: var(--font-geist-mono);
248
- --color-sidebar-ring: var(--sidebar-ring);
249
- --color-sidebar-border: var(--sidebar-border);
250
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
251
- --color-sidebar-accent: var(--sidebar-accent);
252
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
253
- --color-sidebar-primary: var(--sidebar-primary);
254
- --color-sidebar-foreground: var(--sidebar-foreground);
255
- --color-sidebar: var(--sidebar);
256
- --color-chart-5: var(--chart-5);
257
- --color-chart-4: var(--chart-4);
258
- --color-chart-3: var(--chart-3);
259
- --color-chart-2: var(--chart-2);
260
- --color-chart-1: var(--chart-1);
261
- --color-ring: var(--ring);
262
- --color-input: var(--input);
263
- --color-border: var(--border);
264
- --color-destructive: var(--destructive);
265
- --color-accent-foreground: var(--accent-foreground);
266
- --color-accent: var(--accent);
267
- --color-muted-foreground: var(--muted-foreground);
268
- --color-muted: var(--muted);
269
- --color-secondary-foreground: var(--secondary-foreground);
270
- --color-secondary: var(--secondary);
271
- --color-primary-foreground: var(--primary-foreground);
272
- --color-primary: var(--primary);
273
- --color-popover-foreground: var(--popover-foreground);
274
- --color-popover: var(--popover);
275
- --color-card-foreground: var(--card-foreground);
276
- --color-card: var(--card);
277
- --radius-sm: calc(var(--radius) - 4px);
278
- --radius-md: calc(var(--radius) - 2px);
279
- --radius-lg: var(--radius);
280
- --radius-xl: calc(var(--radius) + 4px);
281
- }
282
-
283
- :root {
284
- --radius: 0.625rem;
285
- --background: oklch(1 0 0);
286
- --foreground: oklch(0.141 0.005 285.823);
287
- --card: oklch(1 0 0);
288
- --card-foreground: oklch(0.141 0.005 285.823);
289
- --popover: oklch(1 0 0);
290
- --popover-foreground: oklch(0.141 0.005 285.823);
291
- --primary: oklch(0.21 0.006 285.885);
292
- --primary-foreground: oklch(0.985 0 0);
293
- --secondary: oklch(0.967 0.001 286.375);
294
- --secondary-foreground: oklch(0.21 0.006 285.885);
295
- --muted: oklch(0.967 0.001 286.375);
296
- --muted-foreground: oklch(0.552 0.016 285.938);
297
- --accent: oklch(0.967 0.001 286.375);
298
- --accent-foreground: oklch(0.21 0.006 285.885);
299
- --destructive: oklch(0.577 0.245 27.325);
300
- --border: oklch(0.92 0.004 286.32);
301
- --input: oklch(0.92 0.004 286.32);
302
- --ring: oklch(0.705 0.015 286.067);
303
- --chart-1: oklch(0.646 0.222 41.116);
304
- --chart-2: oklch(0.6 0.118 184.704);
305
- --chart-3: oklch(0.398 0.07 227.392);
306
- --chart-4: oklch(0.828 0.189 84.429);
307
- --chart-5: oklch(0.769 0.188 70.08);
308
- --sidebar: oklch(0.985 0 0);
309
- --sidebar-foreground: oklch(0.141 0.005 285.823);
310
- --sidebar-primary: oklch(0.21 0.006 285.885);
311
- --sidebar-primary-foreground: oklch(0.985 0 0);
312
- --sidebar-accent: oklch(0.967 0.001 286.375);
313
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
314
- --sidebar-border: oklch(0.92 0.004 286.32);
315
- --sidebar-ring: oklch(0.705 0.015 286.067);
316
- }
317
-
318
- .dark {
319
- --background: oklch(0.141 0.005 285.823);
320
- --foreground: oklch(0.985 0 0);
321
- --card: oklch(0.21 0.006 285.885);
322
- --card-foreground: oklch(0.985 0 0);
323
- --popover: oklch(0.21 0.006 285.885);
324
- --popover-foreground: oklch(0.985 0 0);
325
- --primary: oklch(0.92 0.004 286.32);
326
- --primary-foreground: oklch(0.21 0.006 285.885);
327
- --secondary: oklch(0.274 0.006 286.033);
328
- --secondary-foreground: oklch(0.985 0 0);
329
- --muted: oklch(0.274 0.006 286.033);
330
- --muted-foreground: oklch(0.705 0.015 286.067);
331
- --accent: oklch(0.274 0.006 286.033);
332
- --accent-foreground: oklch(0.985 0 0);
333
- --destructive: oklch(0.704 0.191 22.216);
334
- --border: oklch(1 0 0 / 10%);
335
- --input: oklch(1 0 0 / 15%);
336
- --ring: oklch(0.552 0.016 285.938);
337
- --chart-1: oklch(0.488 0.243 264.376);
338
- --chart-2: oklch(0.696 0.17 162.48);
339
- --chart-3: oklch(0.769 0.188 70.08);
340
- --chart-4: oklch(0.627 0.265 303.9);
341
- --chart-5: oklch(0.645 0.246 16.439);
342
- --sidebar: oklch(0.21 0.006 285.885);
343
- --sidebar-foreground: oklch(0.985 0 0);
344
- --sidebar-primary: oklch(0.488 0.243 264.376);
345
- --sidebar-primary-foreground: oklch(0.985 0 0);
346
- --sidebar-accent: oklch(0.274 0.006 286.033);
347
- --sidebar-accent-foreground: oklch(0.985 0 0);
348
- --sidebar-border: oklch(1 0 0 / 10%);
349
- --sidebar-ring: oklch(0.552 0.016 285.938);
350
- }
351
-
352
- @layer base {
353
- * {
354
- @apply border-border outline-ring/50;
355
- }
356
- body {
357
- @apply bg-background text-foreground;
358
- }
359
- }
360
-
361
- ```
362
-
363
- ## app/layout.tsx
364
-
365
- ```tsx
366
- import type { Metadata } from "next";
367
- import { Inter } from "next/font/google";
368
- import { AI } from "@/app/actions";
369
- import { MyRuntimeProvider } from "@/app/MyRuntimeProvider";
370
- import type React from "react";
371
-
372
- import "./globals.css";
373
-
374
- const inter = Inter({ subsets: ["latin"] });
375
-
376
- export const metadata: Metadata = {
377
- title: "Create Next App",
378
- description: "Generated by create next app",
379
- };
380
-
381
- export default function RootLayout({
382
- children,
383
- }: Readonly<{
384
- children: React.ReactNode;
385
- }>) {
386
- return (
387
- <html lang="en">
388
- <AI>
389
- <MyRuntimeProvider>
390
- <body
391
- className={inter.className}
392
- style={{ backgroundColor: "transparent" }}
393
- >
394
- {children}
395
- </body>
396
- </MyRuntimeProvider>
397
- </AI>
398
- </html>
399
- );
400
- }
401
-
402
- ```
403
-
404
- ## app/MyRuntimeProvider.tsx
405
-
406
- ```tsx
407
- "use client";
408
-
409
- import {
410
- type AppendMessage,
411
- AssistantRuntimeProvider,
412
- } from "@assistant-ui/react";
413
- import { useVercelRSCRuntime } from "@assistant-ui/react-ai-sdk-v4";
414
- import { useActions, useUIState } from "ai/rsc";
415
- import { nanoid } from "nanoid";
416
-
417
- import type { AI } from "@/app/actions";
418
-
419
- export function MyRuntimeProvider({
420
- children,
421
- }: Readonly<{
422
- children: React.ReactNode;
423
- }>) {
424
- const { continueConversation } = useActions();
425
- const [messages, setMessages] = useUIState<typeof AI>();
426
-
427
- const onNew = async (m: AppendMessage) => {
428
- if (m.content[0]?.type !== "text")
429
- throw new Error("Only text messages are supported");
430
-
431
- const input = m.content[0].text;
432
- setMessages((currentConversation) => [
433
- ...currentConversation,
434
- { id: nanoid(), role: "user", display: input },
435
- ]);
436
-
437
- const message = await continueConversation(input);
438
-
439
- setMessages((currentConversation) => [...currentConversation, message]);
440
- };
441
-
442
- const runtime = useVercelRSCRuntime({ messages, onNew });
443
-
444
- return (
445
- <AssistantRuntimeProvider runtime={runtime}>
446
- {children}
447
- </AssistantRuntimeProvider>
448
- );
449
- }
450
-
451
- ```
452
-
453
- ## app/page.tsx
454
-
455
- ```tsx
456
- "use client";
457
-
458
- import { Suspense } from "react";
459
- import { AssistantModal } from "@/components/ui/assistant-ui/assistant-modal";
460
-
461
- function Home() {
462
- return (
463
- <Suspense fallback={<div>Loading...</div>}>
464
- <div className="fixed bottom-4 right-4 size-12 rounded-full shadow">
465
- <AssistantModal />
466
- </div>
467
- </Suspense>
468
- );
469
- }
470
-
471
- export default function Page() {
472
- return (
473
- <Suspense fallback={<div>Loading...</div>}>
474
- <Home />
475
- </Suspense>
476
- );
477
- }
478
-
479
- ```
480
-
481
- ## components.json
482
-
483
- ```json
484
- {
485
- "$schema": "https://ui.shadcn.com/schema.json",
486
- "style": "default",
487
- "rsc": true,
488
- "tsx": true,
489
- "tailwind": {
490
- "config": "",
491
- "css": "src/app/globals.css",
492
- "baseColor": "slate",
493
- "cssVariables": true,
494
- "prefix": ""
495
- },
496
- "aliases": {
497
- "components": "@/components",
498
- "utils": "@/lib/utils"
499
- }
500
- }
501
-
502
- ```
503
-
504
- ## components/ui/assistant-ui/assistant-modal.tsx
505
-
506
- ```tsx
507
- import { BotIcon, ChevronDownIcon } from "lucide-react";
508
- import { Thread } from "@/components/ui/assistant-ui/thread";
509
- import { Button } from "@/components/ui/button";
510
- import {
511
- Popover,
512
- PopoverContent,
513
- PopoverTrigger,
514
- } from "@/components/ui/popover";
515
- import {
516
- Tooltip,
517
- TooltipContent,
518
- TooltipTrigger,
519
- } from "@/components/ui/tooltip";
520
- import { cn } from "@/lib/utils";
521
- import { useEffect, forwardRef, useState } from "react";
522
-
523
- export const AssistantModal = () => {
524
- const [open, setOpen] = useState(false);
525
-
526
- useEffect(() => {
527
- const height = open ? 770 : 70; // Set the height based on the modal state
528
- const width = open ? 720 : 70; // Set the width based on the modal state
529
- window.parent.postMessage(
530
- {
531
- type: "resize",
532
- height: height,
533
- width: width,
534
- },
535
- "*",
536
- );
537
- }, [open]);
538
-
539
- return (
540
- <Popover open={open} onOpenChange={setOpen}>
541
- <PopoverTrigger asChild>
542
- <FloatingAssistantButton />
543
- </PopoverTrigger>
544
- <PopoverContent
545
- side="top"
546
- align="end"
547
- className="fixed bottom-0 right-0 z-50 h-[700px] w-[700px] overflow-y-auto rounded-2xl p-0"
548
- >
549
- <Thread />
550
- </PopoverContent>
551
- </Popover>
552
- );
553
- };
554
-
555
- type FloatingAssistantButton = { "data-state"?: "open" | "closed" };
556
-
557
- const FloatingAssistantButton = forwardRef<
558
- HTMLButtonElement,
559
- FloatingAssistantButton
560
- >(({ "data-state": state, ...rest }, forwardedRef) => {
561
- const tooltip = state === "open" ? "Close Assistant" : "Open Assistant";
562
- return (
563
- <Tooltip>
564
- <TooltipTrigger asChild>
565
- <Button
566
- variant="default"
567
- size="icon"
568
- className="fixed bottom-4 right-4 size-12 rounded-full shadow transition-transform hover:scale-110 active:scale-90"
569
- {...rest}
570
- ref={forwardedRef}
571
- style={{ zIndex: 1000 }}
572
- >
573
- <BotIcon
574
- className={cn(
575
- "absolute size-6 transition-all",
576
- state === "open" && "rotate-90 scale-0",
577
- state === "closed" && "rotate-0 scale-100",
578
- )}
579
- />
580
-
581
- <ChevronDownIcon
582
- className={cn(
583
- "absolute size-6 transition-all",
584
- state === "open" && "rotate-0 scale-100",
585
- state === "closed" && "-rotate-90 scale-0",
586
- )}
587
- />
588
- <span className="sr-only">{tooltip}</span>
589
- </Button>
590
- </TooltipTrigger>
591
- <TooltipContent side="left">{tooltip}</TooltipContent>
592
- </Tooltip>
593
- );
594
- });
595
-
596
- FloatingAssistantButton.displayName = "FloatingAssistantButton";
597
-
598
- ```
599
-
600
- ## components/ui/assistant-ui/thread.tsx
601
-
602
- ```tsx
603
- "use client";
604
-
605
- import {
606
- ComposerPrimitive,
607
- MessagePrimitive,
608
- ThreadPrimitive,
609
- } from "@assistant-ui/react";
610
- import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
611
- import type { ComponentPropsWithRef, FC, PropsWithChildren } from "react";
612
- import { Button } from "@/components/ui/button";
613
- import {
614
- Tooltip,
615
- TooltipContent,
616
- TooltipTrigger,
617
- } from "@/components/ui/tooltip";
618
- import { cn } from "@/lib/utils";
619
- import { ArrowDownIcon, SendHorizontalIcon } from "lucide-react";
620
- import Image from "next/image";
621
- import { RSCDisplay } from "@assistant-ui/react-ai-sdk-v4";
622
-
623
- export const Thread: FC = () => {
624
- return (
625
- <ThreadPrimitive.Root className="flex h-full flex-col items-center pb-3">
626
- <ThreadPrimitive.Viewport className="flex w-full flex-grow flex-col items-center overflow-y-scroll scroll-smooth px-4 pt-12">
627
- <ThreadPrimitive.Empty>
628
- <ThreadEmpty />
629
- </ThreadPrimitive.Empty>
630
-
631
- <ThreadPrimitive.Messages
632
- components={{
633
- UserMessage,
634
- AssistantMessage,
635
- }}
636
- />
637
- <ThreadScrollToBottom />
638
- </ThreadPrimitive.Viewport>
639
-
640
- <Composer />
641
- </ThreadPrimitive.Root>
642
- );
643
- };
644
-
645
- const ThreadEmpty: FC = () => {
646
- return (
647
- <div className="flex w-full max-w-2xl grow flex-col justify-end px-4 py-6">
648
- {" "}
649
- {/* Stick to bottom */}
650
- <div className="mb-1 flex flex-grow flex-col items-center justify-center">
651
- {" "}
652
- {/* Reduced margin-bottom */}
653
- <Image
654
- src="/image.png"
655
- alt="Your Logo"
656
- className="mb-4 w-1/2 max-w-xs"
657
- width={320}
658
- height={164}
659
- />{" "}
660
- {/* Smaller image */}
661
- <div className="flex items-center">
662
- <Avatar className="mr-4" style={{ width: "20px", height: "20px" }}>
663
- {" "}
664
- {/* Adjusted size */}
665
- <AvatarImage src="/favicon.ico" alt="AI" />
666
- <AvatarFallback>AI</AvatarFallback>
667
- </Avatar>
668
- <p className="mt-4">
669
- Hi, do you know what product you are looking for, or you have a
670
- general question?
671
- </p>
672
- </div>
673
- </div>
674
- <div className="flex flex-col gap-4 self-stretch sm:flex-row">
675
- <ThreadSuggestion prompt="I need help with product search">
676
- <p className="mb-2 font-semibold">Product search</p>
677
- </ThreadSuggestion>
678
- <ThreadSuggestion prompt="I need to talk to human agent support">
679
- <p className="mb-2 font-semibold">Human agent</p>
680
- </ThreadSuggestion>
681
- </div>
682
- </div>
683
- );
684
- };
685
-
686
- const ThreadSuggestion: FC<PropsWithChildren<{ prompt: string }>> = ({
687
- prompt,
688
- children,
689
- }) => {
690
- return (
691
- <ThreadPrimitive.Suggestion
692
- prompt={prompt}
693
- method="replace"
694
- autoSend
695
- asChild
696
- >
697
- <Button
698
- variant="outline"
699
- className="text-md flex h-full items-center justify-center sm:basis-full"
700
- >
701
- {children}
702
- </Button>
703
- </ThreadPrimitive.Suggestion>
704
- );
705
- };
706
-
707
- const ThreadScrollToBottom: FC = () => {
708
- return (
709
- <div className="sticky bottom-0">
710
- <ThreadPrimitive.ScrollToBottom asChild>
711
- <IconButton
712
- tooltip="Scroll to bottom"
713
- variant="outline"
714
- className="absolute -top-10 rounded-full disabled:invisible"
715
- >
716
- <ArrowDownIcon className="size-4" />
717
- </IconButton>
718
- </ThreadPrimitive.ScrollToBottom>
719
- </div>
720
- );
721
- };
722
-
723
- const Composer: FC = () => {
724
- return (
725
- <ComposerPrimitive.Root className="flex w-[calc(100%-32px)] max-w-[42rem] items-end rounded-lg border p-0.5 transition-shadow focus-within:shadow-sm">
726
- <ComposerPrimitive.Input
727
- placeholder="Write a message..."
728
- className="placeholder:text-foreground/50 h-12 max-h-40 flex-grow resize-none bg-transparent p-3.5 text-sm outline-none"
729
- />
730
- <ComposerPrimitive.Send className="bg-foreground m-2 flex h-8 w-8 items-center justify-center rounded-md text-2xl font-bold shadow transition-opacity disabled:opacity-10">
731
- <SendHorizontalIcon className="text-background size-4" />
732
- </ComposerPrimitive.Send>
733
- </ComposerPrimitive.Root>
734
- );
735
- };
736
-
737
- const UserMessage: FC = () => {
738
- return (
739
- <MessagePrimitive.Root className="relative mb-6 flex w-full max-w-2xl flex-col items-end gap-2 pl-24">
740
- <div className="relative mr-1 flex items-start gap-3">
741
- <p className="bg-foreground/5 text-foreground max-w-xl whitespace-pre-line break-words rounded-3xl px-5 py-2.5">
742
- <MessagePrimitive.Parts components={{ Text: RSCDisplay }} />
743
- </p>
744
- </div>
745
- </MessagePrimitive.Root>
746
- );
747
- };
748
-
749
- const AssistantMessage: FC = () => {
750
- return (
751
- <MessagePrimitive.Root className="relative mb-6 flex w-full max-w-2xl gap-3">
752
- <Avatar>
753
- <AvatarFallback>A</AvatarFallback>
754
- </Avatar>
755
-
756
- <div className="mt-2 flex-grow">
757
- <p className="text-foreground max-w-xl whitespace-pre-line break-words">
758
- <MessagePrimitive.Parts components={{ Text: RSCDisplay }} />
759
- </p>
760
- </div>
761
- </MessagePrimitive.Root>
762
- );
763
- };
764
-
765
- type IconButton = ComponentPropsWithRef<typeof Button> & { tooltip: string };
766
-
767
- const IconButton: FC<IconButton> = ({
768
- children,
769
- tooltip,
770
- className,
771
- ...rest
772
- }) => {
773
- return (
774
- <Tooltip>
775
- <TooltipTrigger asChild>
776
- <Button
777
- variant="ghost"
778
- size="icon"
779
- className={cn("size-auto p-1", className)}
780
- {...rest}
781
- >
782
- {children}
783
- <span className="sr-only">{tooltip}</span>
784
- </Button>
785
- </TooltipTrigger>
786
- <TooltipContent side="bottom">{tooltip}</TooltipContent>
787
- </Tooltip>
788
- );
789
- };
790
-
791
- ```
792
-
793
- ## components/ui/avatar.tsx
794
-
795
- ```tsx
796
- "use client";
797
-
798
- import * as React from "react";
799
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
800
-
801
- import { cn } from "@/lib/utils";
802
-
803
- const Avatar = React.forwardRef<
804
- React.ComponentRef<typeof AvatarPrimitive.Root>,
805
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
806
- >(({ className, ...props }, ref) => (
807
- <AvatarPrimitive.Root
808
- ref={ref}
809
- className={cn(
810
- "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
811
- className,
812
- )}
813
- {...props}
814
- />
815
- ));
816
- Avatar.displayName = AvatarPrimitive.Root.displayName;
817
-
818
- const AvatarImage = React.forwardRef<
819
- React.ComponentRef<typeof AvatarPrimitive.Image>,
820
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
821
- >(({ className, ...props }, ref) => (
822
- <AvatarPrimitive.Image
823
- ref={ref}
824
- className={cn("aspect-square h-full w-full", className)}
825
- src="/favicon.ico"
826
- alt="AI"
827
- {...props}
828
- />
829
- ));
830
- AvatarImage.displayName = AvatarPrimitive.Image.displayName;
831
-
832
- const AvatarFallback = React.forwardRef<
833
- React.ComponentRef<typeof AvatarPrimitive.Fallback>,
834
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
835
- >(({ className, ...props }, ref) => (
836
- <AvatarPrimitive.Fallback
837
- ref={ref}
838
- className={cn(
839
- "bg-muted flex h-full w-full items-center justify-center rounded-full",
840
- className,
841
- )}
842
- {...props}
843
- />
844
- ));
845
- AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
846
-
847
- export { Avatar, AvatarImage, AvatarFallback };
848
-
849
- ```
850
-
851
- ## components/ui/button.tsx
852
-
853
- ```tsx
854
- import * as React from "react";
855
- import { Slot } from "@radix-ui/react-slot";
856
- import { cva, type VariantProps } from "class-variance-authority";
857
-
858
- import { cn } from "@/lib/utils";
859
-
860
- const buttonVariants = cva(
861
- "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",
862
- {
863
- variants: {
864
- variant: {
865
- default:
866
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
867
- destructive:
868
- "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",
869
- outline:
870
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
871
- secondary:
872
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
873
- ghost:
874
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
875
- link: "text-primary underline-offset-4 hover:underline",
876
- },
877
- size: {
878
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
879
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
880
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
881
- icon: "size-9",
882
- },
883
- },
884
- defaultVariants: {
885
- variant: "default",
886
- size: "default",
887
- },
888
- },
889
- );
890
-
891
- function Button({
892
- className,
893
- variant,
894
- size,
895
- asChild = false,
896
- ...props
897
- }: React.ComponentProps<"button"> &
898
- VariantProps<typeof buttonVariants> & {
899
- asChild?: boolean;
900
- }) {
901
- const Comp = asChild ? Slot : "button";
902
-
903
- return (
904
- <Comp
905
- data-slot="button"
906
- className={cn(buttonVariants({ variant, size, className }))}
907
- {...props}
908
- />
909
- );
910
- }
911
-
912
- export { Button, buttonVariants };
913
-
914
- ```
915
-
916
- ## components/ui/card.tsx
917
-
918
- ```tsx
919
- import * as React from "react";
920
-
921
- import { cn } from "@/lib/utils";
922
-
923
- const Card = React.forwardRef<
924
- HTMLDivElement,
925
- React.HTMLAttributes<HTMLDivElement>
926
- >(({ className, ...props }, ref) => (
927
- <div
928
- ref={ref}
929
- className={cn(
930
- "bg-card text-card-foreground rounded-lg border shadow-sm",
931
- className,
932
- )}
933
- {...props}
934
- />
935
- ));
936
- Card.displayName = "Card";
937
-
938
- const CardHeader = React.forwardRef<
939
- HTMLDivElement,
940
- React.HTMLAttributes<HTMLDivElement>
941
- >(({ className, ...props }, ref) => (
942
- <div
943
- ref={ref}
944
- className={cn("flex flex-col space-y-1.5 p-6", className)}
945
- {...props}
946
- />
947
- ));
948
- CardHeader.displayName = "CardHeader";
949
-
950
- const CardTitle = React.forwardRef<
951
- HTMLParagraphElement,
952
- React.HTMLAttributes<HTMLHeadingElement>
953
- >(({ className, ...props }, ref) => (
954
- <h3
955
- ref={ref}
956
- className={cn(
957
- "text-2xl font-semibold leading-none tracking-tight",
958
- className,
959
- )}
960
- {...props}
961
- />
962
- ));
963
- CardTitle.displayName = "CardTitle";
964
-
965
- const CardDescription = React.forwardRef<
966
- HTMLParagraphElement,
967
- React.HTMLAttributes<HTMLParagraphElement>
968
- >(({ className, ...props }, ref) => (
969
- <p
970
- ref={ref}
971
- className={cn("text-muted-foreground text-sm", className)}
972
- {...props}
973
- />
974
- ));
975
- CardDescription.displayName = "CardDescription";
976
-
977
- const CardContent = React.forwardRef<
978
- HTMLDivElement,
979
- React.HTMLAttributes<HTMLDivElement>
980
- >(({ className, ...props }, ref) => (
981
- <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
982
- ));
983
- CardContent.displayName = "CardContent";
984
-
985
- const CardFooter = React.forwardRef<
986
- HTMLDivElement,
987
- React.HTMLAttributes<HTMLDivElement>
988
- >(({ className, ...props }, ref) => (
989
- <div
990
- ref={ref}
991
- className={cn("flex items-center p-6 pt-0", className)}
992
- {...props}
993
- />
994
- ));
995
- CardFooter.displayName = "CardFooter";
996
-
997
- export {
998
- Card,
999
- CardHeader,
1000
- CardFooter,
1001
- CardTitle,
1002
- CardDescription,
1003
- CardContent,
1004
- };
1005
-
1006
- ```
1007
-
1008
- ## components/ui/carousel.tsx
1009
-
1010
- ```tsx
1011
- "use client";
1012
-
1013
- import * as React from "react";
1014
- import useEmblaCarousel, {
1015
- type UseEmblaCarouselType,
1016
- } from "embla-carousel-react";
1017
- import { ArrowLeft, ArrowRight } from "lucide-react";
1018
-
1019
- import { cn } from "@/lib/utils";
1020
- import { Button } from "@/components/ui/button";
1021
-
1022
- type CarouselApi = UseEmblaCarouselType[1];
1023
- type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
1024
- type CarouselOptions = UseCarouselParameters[0];
1025
- type CarouselPlugin = UseCarouselParameters[1];
1026
-
1027
- type CarouselProps = {
1028
- opts?: CarouselOptions;
1029
- plugins?: CarouselPlugin;
1030
- orientation?: "horizontal" | "vertical";
1031
- setApi?: (api: CarouselApi) => void;
1032
- };
1033
-
1034
- type CarouselContextProps = {
1035
- carouselRef: ReturnType<typeof useEmblaCarousel>[0];
1036
- api: ReturnType<typeof useEmblaCarousel>[1];
1037
- scrollPrev: () => void;
1038
- scrollNext: () => void;
1039
- canScrollPrev: boolean;
1040
- canScrollNext: boolean;
1041
- } & CarouselProps;
1042
-
1043
- const CarouselContext = React.createContext<CarouselContextProps | null>(null);
1044
-
1045
- function useCarousel() {
1046
- const context = React.useContext(CarouselContext);
1047
-
1048
- if (!context) {
1049
- throw new Error("useCarousel must be used within a <Carousel />");
1050
- }
1051
-
1052
- return context;
1053
- }
1054
-
1055
- const Carousel = React.forwardRef<
1056
- HTMLDivElement,
1057
- React.HTMLAttributes<HTMLDivElement> & CarouselProps
1058
- >(
1059
- (
1060
- {
1061
- orientation = "horizontal",
1062
- opts,
1063
- setApi,
1064
- plugins,
1065
- className,
1066
- children,
1067
- ...props
1068
- },
1069
- ref,
1070
- ) => {
1071
- const [carouselRef, api] = useEmblaCarousel(
1072
- {
1073
- ...opts,
1074
- axis: orientation === "horizontal" ? "x" : "y",
1075
- },
1076
- plugins,
1077
- );
1078
- const [canScrollPrev, setCanScrollPrev] = React.useState(false);
1079
- const [canScrollNext, setCanScrollNext] = React.useState(false);
1080
-
1081
- const onSelect = React.useCallback((api: CarouselApi) => {
1082
- if (!api) {
1083
- return;
1084
- }
1085
-
1086
- setCanScrollPrev(api.canScrollPrev());
1087
- setCanScrollNext(api.canScrollNext());
1088
- }, []);
1089
-
1090
- const scrollPrev = React.useCallback(() => {
1091
- api?.scrollPrev();
1092
- }, [api]);
1093
-
1094
- const scrollNext = React.useCallback(() => {
1095
- api?.scrollNext();
1096
- }, [api]);
1097
-
1098
- const handleKeyDown = React.useCallback(
1099
- (event: React.KeyboardEvent<HTMLDivElement>) => {
1100
- if (event.key === "ArrowLeft") {
1101
- event.preventDefault();
1102
- scrollPrev();
1103
- } else if (event.key === "ArrowRight") {
1104
- event.preventDefault();
1105
- scrollNext();
1106
- }
1107
- },
1108
- [scrollPrev, scrollNext],
1109
- );
1110
-
1111
- React.useEffect(() => {
1112
- if (!api || !setApi) {
1113
- return;
1114
- }
1115
-
1116
- setApi(api);
1117
- }, [api, setApi]);
1118
-
1119
- React.useEffect(() => {
1120
- if (!api) {
1121
- return;
1122
- }
1123
-
1124
- onSelect(api);
1125
- api.on("reInit", onSelect);
1126
- api.on("select", onSelect);
1127
-
1128
- return () => {
1129
- api?.off("select", onSelect);
1130
- };
1131
- }, [api, onSelect]);
1132
-
1133
- return (
1134
- <CarouselContext.Provider
1135
- value={{
1136
- carouselRef,
1137
- api: api,
1138
- opts,
1139
- orientation:
1140
- orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
1141
- scrollPrev,
1142
- scrollNext,
1143
- canScrollPrev,
1144
- canScrollNext,
1145
- }}
1146
- >
1147
- <div
1148
- ref={ref}
1149
- onKeyDownCapture={handleKeyDown}
1150
- className={cn("relative", className)}
1151
- role="region"
1152
- aria-roledescription="carousel"
1153
- {...props}
1154
- >
1155
- {children}
1156
- </div>
1157
- </CarouselContext.Provider>
1158
- );
1159
- },
1160
- );
1161
- Carousel.displayName = "Carousel";
1162
-
1163
- const CarouselContent = React.forwardRef<
1164
- HTMLDivElement,
1165
- React.HTMLAttributes<HTMLDivElement>
1166
- >(({ className, ...props }, ref) => {
1167
- const { carouselRef, orientation } = useCarousel();
1168
-
1169
- return (
1170
- <div ref={carouselRef} className="overflow-hidden">
1171
- <div
1172
- ref={ref}
1173
- className={cn(
1174
- "flex",
1175
- orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
1176
- className,
1177
- )}
1178
- {...props}
1179
- />
1180
- </div>
1181
- );
1182
- });
1183
- CarouselContent.displayName = "CarouselContent";
1184
-
1185
- const CarouselItem = React.forwardRef<
1186
- HTMLDivElement,
1187
- React.HTMLAttributes<HTMLDivElement>
1188
- >(({ className, ...props }, ref) => {
1189
- const { orientation } = useCarousel();
1190
-
1191
- return (
1192
- <div
1193
- ref={ref}
1194
- role="group"
1195
- aria-roledescription="slide"
1196
- className={cn(
1197
- "min-w-0 shrink-0 grow-0 basis-full",
1198
- orientation === "horizontal" ? "pl-4" : "pt-4",
1199
- className,
1200
- )}
1201
- {...props}
1202
- />
1203
- );
1204
- });
1205
- CarouselItem.displayName = "CarouselItem";
1206
-
1207
- const CarouselPrevious = React.forwardRef<
1208
- HTMLButtonElement,
1209
- React.ComponentProps<typeof Button>
1210
- >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
1211
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
1212
-
1213
- return (
1214
- <Button
1215
- ref={ref}
1216
- variant={variant}
1217
- size={size}
1218
- className={cn(
1219
- "absolute h-8 w-8 rounded-full",
1220
- orientation === "horizontal"
1221
- ? "-left-12 top-1/2 -translate-y-1/2"
1222
- : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
1223
- className,
1224
- )}
1225
- disabled={!canScrollPrev}
1226
- onClick={scrollPrev}
1227
- {...props}
1228
- >
1229
- <ArrowLeft className="h-4 w-4" />
1230
- <span className="sr-only">Previous slide</span>
1231
- </Button>
1232
- );
1233
- });
1234
- CarouselPrevious.displayName = "CarouselPrevious";
1235
-
1236
- const CarouselNext = React.forwardRef<
1237
- HTMLButtonElement,
1238
- React.ComponentProps<typeof Button>
1239
- >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
1240
- const { orientation, scrollNext, canScrollNext } = useCarousel();
1241
-
1242
- return (
1243
- <Button
1244
- ref={ref}
1245
- variant={variant}
1246
- size={size}
1247
- className={cn(
1248
- "absolute h-8 w-8 rounded-full",
1249
- orientation === "horizontal"
1250
- ? "-right-12 top-1/2 -translate-y-1/2"
1251
- : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
1252
- className,
1253
- )}
1254
- disabled={!canScrollNext}
1255
- onClick={scrollNext}
1256
- {...props}
1257
- >
1258
- <ArrowRight className="h-4 w-4" />
1259
- <span className="sr-only">Next slide</span>
1260
- </Button>
1261
- );
1262
- });
1263
- CarouselNext.displayName = "CarouselNext";
1264
-
1265
- export {
1266
- type CarouselApi,
1267
- Carousel,
1268
- CarouselContent,
1269
- CarouselItem,
1270
- CarouselPrevious,
1271
- CarouselNext,
1272
- };
1273
-
1274
- ```
1275
-
1276
- ## components/ui/popover.tsx
1277
-
1278
- ```tsx
1279
- "use client";
1280
-
1281
- import * as React from "react";
1282
- import * as PopoverPrimitive from "@radix-ui/react-popover";
1283
-
1284
- import { cn } from "@/lib/utils";
1285
-
1286
- const Popover = PopoverPrimitive.Root;
1287
-
1288
- const PopoverTrigger = PopoverPrimitive.Trigger;
1289
-
1290
- const PopoverContent = React.forwardRef<
1291
- React.ComponentRef<typeof PopoverPrimitive.Content>,
1292
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
1293
- >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
1294
- <PopoverPrimitive.Portal>
1295
- <PopoverPrimitive.Content
1296
- ref={ref}
1297
- align={align}
1298
- sideOffset={sideOffset}
1299
- className={cn(
1300
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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-72 rounded-md border p-4 shadow-md outline-none",
1301
- className,
1302
- )}
1303
- {...props}
1304
- />
1305
- </PopoverPrimitive.Portal>
1306
- ));
1307
- PopoverContent.displayName = PopoverPrimitive.Content.displayName;
1308
-
1309
- export { Popover, PopoverTrigger, PopoverContent };
1310
-
1311
- ```
1312
-
1313
- ## components/ui/productcarousel.tsx
1314
-
1315
- ```tsx
1316
- "use client";
1317
-
1318
- import * as React from "react";
1319
- import Autoplay from "embla-carousel-autoplay";
1320
-
1321
- import { Card, CardContent } from "@/components/ui/card";
1322
- import {
1323
- Carousel,
1324
- CarouselContent,
1325
- CarouselItem,
1326
- CarouselNext,
1327
- CarouselPrevious,
1328
- } from "@/components/ui/carousel";
1329
-
1330
- interface Product {
1331
- thumbnail: string;
1332
- title: string;
1333
- description: string;
1334
- metadata_3: string;
1335
- link: string;
1336
- }
1337
-
1338
- interface ProductCarouselProps {
1339
- products: Product[];
1340
- }
1341
-
1342
- const productStyle = {
1343
- display: "flex",
1344
- flexDirection: "column" as const,
1345
- alignItems: "center",
1346
- textAlign: "center" as const,
1347
- };
1348
-
1349
- const imageContainerStyle = {
1350
- justifyContent: "center",
1351
- maxWidth: "100%",
1352
- overflow: "hidden",
1353
- };
1354
-
1355
- const productLinkStyle = {
1356
- display: "flex",
1357
- justifyContent: "center",
1358
- alignItems: "center",
1359
- maxWidth: "100%",
1360
- height: "100%",
1361
- };
1362
-
1363
- const productNameStyle = {
1364
- marginTop: "0.5rem",
1365
- };
1366
-
1367
- const productPriceStyle = {
1368
- marginTop: "0.5rem",
1369
- };
1370
-
1371
- export function CarouselPlugin({ products }: ProductCarouselProps) {
1372
- const plugin = React.useRef(
1373
- Autoplay({ delay: 2000, stopOnInteraction: true }),
1374
- );
1375
-
1376
- return (
1377
- <Carousel
1378
- plugins={[plugin.current]}
1379
- className="relative w-full max-w-2xl"
1380
- onMouseEnter={plugin.current.stop}
1381
- onMouseLeave={plugin.current.reset}
1382
- >
1383
- <CarouselContent className="w-full">
1384
- {products.map((product, index) => (
1385
- <CarouselItem key={index} className="basis-1/2">
1386
- <div className="relative w-full p-1">
1387
- <Card className="h-full w-full">
1388
- <CardContent className="flex flex-col items-center justify-center p-6">
1389
- <div className="product" style={productStyle}>
1390
- <div
1391
- className="image-container"
1392
- style={imageContainerStyle}
1393
- >
1394
- <a
1395
- className="product-link product_image"
1396
- href={product.link}
1397
- style={productLinkStyle}
1398
- data-mpn={product.metadata_3}
1399
- data-query={product.title}
1400
- data-intent="product_search"
1401
- data-order={index}
1402
- >
1403
- {/* eslint-disable-next-line @next/next/no-img-element */}
1404
- <img
1405
- className="img-fluid mb-3"
1406
- src={product.thumbnail}
1407
- alt={product.title}
1408
- style={{ maxWidth: "100%", maxHeight: "100%" }}
1409
- />
1410
- </a>
1411
- <div className="productName" style={productNameStyle}>
1412
- <a
1413
- className="product-name-link"
1414
- href={product.link}
1415
- data-mpn={product.metadata_3}
1416
- >
1417
- {product.title}
1418
- </a>
1419
- </div>
1420
- <p>
1421
- <span
1422
- className="productPrice"
1423
- style={productPriceStyle}
1424
- dangerouslySetInnerHTML={{
1425
- __html: product.metadata_3,
1426
- }}
1427
- />
1428
- </p>
1429
- {/* <div className="actionGroup" style={actionGroupStyle}>
1430
- <a
1431
- className="chat_buy_now"
1432
- href={product.link}
1433
- data-mpn={product.metadata_3}
1434
- >
1435
- Buy Now
1436
- </a>
1437
- </div>
1438
- <div
1439
- className="see_more_products"
1440
- style={seeMoreProductsStyle}
1441
- data-mpn={product.metadata_3}
1442
- data-image={product.thumbnail}
1443
- data-title={product.title}
1444
- >
1445
- See more like this
1446
- </div> */}
1447
- </div>
1448
- </div>
1449
- </CardContent>
1450
- </Card>
1451
- </div>
1452
- </CarouselItem>
1453
- ))}
1454
- </CarouselContent>
1455
- <CarouselPrevious className="absolute left-2 top-1/2 z-10 -translate-y-1/2 transform" />
1456
- <CarouselNext className="absolute right-2 top-1/2 z-10 -translate-y-1/2 transform" />
1457
- </Carousel>
1458
- );
1459
- }
1460
-
1461
- ```
1462
-
1463
- ## components/ui/tooltip.tsx
1464
-
1465
- ```tsx
1466
- "use client";
1467
-
1468
- import * as React from "react";
1469
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1470
-
1471
- import { cn } from "@/lib/utils";
1472
-
1473
- function TooltipProvider({
1474
- delayDuration = 0,
1475
- ...props
1476
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1477
- return (
1478
- <TooltipPrimitive.Provider
1479
- data-slot="tooltip-provider"
1480
- delayDuration={delayDuration}
1481
- {...props}
1482
- />
1483
- );
1484
- }
1485
-
1486
- function Tooltip({
1487
- ...props
1488
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1489
- return (
1490
- <TooltipProvider>
1491
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1492
- </TooltipProvider>
1493
- );
1494
- }
1495
-
1496
- function TooltipTrigger({
1497
- ...props
1498
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
1499
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
1500
- }
1501
-
1502
- function TooltipContent({
1503
- className,
1504
- sideOffset = 0,
1505
- children,
1506
- ...props
1507
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
1508
- return (
1509
- <TooltipPrimitive.Portal>
1510
- <TooltipPrimitive.Content
1511
- data-slot="tooltip-content"
1512
- sideOffset={sideOffset}
1513
- className={cn(
1514
- "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",
1515
- className,
1516
- )}
1517
- {...props}
1518
- >
1519
- {children}
1520
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1521
- </TooltipPrimitive.Content>
1522
- </TooltipPrimitive.Portal>
1523
- );
1524
- }
1525
-
1526
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1527
-
1528
- ```
1529
-
1530
- ## eslint.config.ts
1531
-
1532
- ```typescript
1533
- export { default } from "@assistant-ui/x-buildutils/eslint";
1534
-
1535
- ```
1536
-
1537
- ## lib/utils.ts
1538
-
1539
- ```typescript
1540
- import { type ClassValue, clsx } from "clsx";
1541
- import { twMerge } from "tailwind-merge";
1542
-
1543
- export function cn(...inputs: ClassValue[]) {
1544
- return twMerge(clsx(inputs));
1545
- }
1546
-
1547
- ```
1548
-
1549
- ## next.config.ts
1550
-
1551
- ```typescript
1552
- import type { NextConfig } from "next";
1553
-
1554
- const nextConfig: NextConfig = {
1555
- /* config options here */
1556
- };
1557
-
1558
- export default nextConfig;
1559
-
1560
- ```
1561
-
1562
- ## package.json
1563
-
1564
- ```json
1565
- {
1566
- "name": "search-agent-for-e-commerce",
1567
- "version": "0.1.0",
1568
- "private": true,
1569
- "scripts": {
1570
- "dev": "next dev --turbo",
1571
- "build": "next build",
1572
- "start": "next start",
1573
- "lint": "next lint"
1574
- },
1575
- "dependencies": {
1576
- "@ai-sdk/openai": "^1.3.22",
1577
- "@assistant-ui/react": "workspace:*",
1578
- "@assistant-ui/react-ai-sdk-v4": "workspace:*",
1579
- "@radix-ui/react-avatar": "^1.1.10",
1580
- "@radix-ui/react-popover": "^1.1.14",
1581
- "@radix-ui/react-slot": "^1.2.3",
1582
- "@radix-ui/react-tooltip": "^1.2.7",
1583
- "ai": "^4.3.16",
1584
- "class-variance-authority": "^0.7.1",
1585
- "clsx": "^2.1.1",
1586
- "embla-carousel-autoplay": "^8.6.0",
1587
- "embla-carousel-react": "^8.6.0",
1588
- "lucide-react": "^0.535.0",
1589
- "nanoid": "5.1.5",
1590
- "next": "15.4.5",
1591
- "react": "19.1.1",
1592
- "react-dom": "19.1.1",
1593
- "tailwind-merge": "^3.3.1",
1594
- "tw-animate-css": "^1.3.6",
1595
- "zod": "^4.0.14"
1596
- },
1597
- "devDependencies": {
1598
- "@assistant-ui/x-buildutils": "workspace:*",
1599
- "@types/node": "^24.1.0",
1600
- "@types/react": "^19.1.9",
1601
- "@types/react-dom": "^19.1.7",
1602
- "eslint": "^9",
1603
- "eslint-config-next": "15.4.5",
1604
- "postcss": "^8.5.6",
1605
- "tailwindcss": "^4.1.11",
1606
- "typescript": "^5.9.2"
1607
- }
1608
- }
1609
-
1610
- ```
1611
-
1612
- ## README.md
1613
-
1614
- ```markdown
1615
- ## Getting Started
1616
-
1617
- 1. Clone the repository:
1618
-
1619
- ```sh
1620
- git clone https://github.com/assistant-ui/assistant-ui.git
1621
- ```
1622
-
1623
- 2. Navigate to the project directory:
1624
-
1625
- ```sh
1626
- cd assistant-ui/examples/search-agent-for-e-commerce
1627
- ```
1628
-
1629
- 3. Create a `.env` file with the following variable:
1630
-
1631
- ```sh
1632
- OPENAI_API_KEY="skXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
1633
- ```
1634
-
1635
- 4. Make the `start.sh` script executable:
1636
-
1637
- ```sh
1638
- chmod +x start.sh
1639
- ```
1640
-
1641
- 5. Start the servers:
1642
-
1643
- ```sh
1644
- ./start.sh
1645
- ```
1646
-
1647
- 6. Open the dummy e-commerce website in your browser:
1648
- [http://localhost:8080/dummy-ecommerce-website.html](http://localhost:8080/dummy-ecommerce-website.html)
1649
-
1650
- You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
1651
-
1652
- ## Learn More
1653
-
1654
- This project uses:
1655
-
1656
- - assistant-ui components
1657
- - shadcn components
1658
- - Vercel AI SDK
1659
-
1660
- ```
1661
-
1662
- ## tsconfig.json
1663
-
1664
- ```json
1665
- {
1666
- "extends": "@assistant-ui/x-buildutils/ts/base",
1667
- "compilerOptions": {
1668
- "target": "ES6",
1669
- "module": "ESNext",
1670
- "incremental": true,
1671
- "plugins": [
1672
- {
1673
- "name": "next"
1674
- }
1675
- ],
1676
- "allowJs": true,
1677
- "strictNullChecks": true,
1678
- "jsx": "preserve",
1679
- "paths": {
1680
- "@/*": ["./*"],
1681
- "@assistant-ui/*": ["../../packages/*/src"],
1682
- "@assistant-ui/react/*": ["../../packages/react/src/*"],
1683
- "assistant-stream": ["../../packages/assistant-stream/src"],
1684
- "assistant-stream/*": ["../../packages/assistant-stream/src/*"]
1685
- }
1686
- },
1687
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1688
- "exclude": ["node_modules"]
1689
- }
1690
-
1691
- ```
1692
-
1693
- ## vercel.json
1694
-
1695
- ```json
1696
- {
1697
- "version": 2,
1698
- "builds": [
1699
- {
1700
- "src": "src/app/api/chat/validate.ts",
1701
- "use": "@vercel/node"
1702
- },
1703
- {
1704
- "src": "next.config.ts",
1705
- "use": "@vercel/next"
1706
- }
1707
- ],
1708
- "routes": [
1709
- {
1710
- "src": "/api/validate",
1711
- "dest": "src/app/api/chat/validate.ts"
1712
- },
1713
- {
1714
- "src": "/(.*)",
1715
- "dest": "/"
1716
- }
1717
- ]
1718
- }
1719
-
1720
- ```
1721
-