@assistant-ui/mcp-docs-server 0.1.19 → 0.1.21

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 (108) hide show
  1. package/.docs/organized/code-examples/with-ag-ui.md +172 -1633
  2. package/.docs/organized/code-examples/with-ai-sdk-v6.md +42 -1640
  3. package/.docs/organized/code-examples/with-assistant-transport.md +40 -1743
  4. package/.docs/organized/code-examples/with-cloud.md +71 -1745
  5. package/.docs/organized/code-examples/with-custom-thread-list.md +87 -1723
  6. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +70 -1637
  7. package/.docs/organized/code-examples/with-external-store.md +67 -1624
  8. package/.docs/organized/code-examples/with-ffmpeg.md +71 -1629
  9. package/.docs/organized/code-examples/with-langgraph.md +95 -1893
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +57 -1654
  11. package/.docs/organized/code-examples/with-react-hook-form.md +220 -2163
  12. package/.docs/organized/code-examples/with-react-router.md +66 -1318
  13. package/.docs/organized/code-examples/with-store.md +31 -31
  14. package/.docs/organized/code-examples/with-tanstack.md +77 -861
  15. package/.docs/organized/code-examples/with-tap-runtime.md +812 -0
  16. package/.docs/raw/docs/(docs)/cli.mdx +66 -0
  17. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +0 -1
  18. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +0 -1
  19. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +4 -4
  20. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +3 -3
  21. package/.docs/raw/docs/(docs)/devtools.mdx +0 -1
  22. package/.docs/raw/docs/(docs)/guides/attachments.mdx +2 -3
  23. package/.docs/raw/docs/(docs)/guides/context-api.mdx +117 -117
  24. package/.docs/raw/docs/(docs)/guides/suggestions.mdx +296 -0
  25. package/.docs/raw/docs/(docs)/guides/tools.mdx +336 -513
  26. package/.docs/raw/docs/(docs)/index.mdx +33 -410
  27. package/.docs/raw/docs/(docs)/installation.mdx +450 -0
  28. package/.docs/raw/docs/(docs)/llm.mdx +209 -0
  29. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +0 -1
  30. package/.docs/raw/docs/(reference)/api-reference/context-providers/text-message-part-provider.mdx +0 -1
  31. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +48 -3
  32. package/.docs/raw/docs/(reference)/api-reference/integrations/react-hook-form.mdx +0 -1
  33. package/.docs/raw/docs/(reference)/api-reference/integrations/vercel-ai-sdk.mdx +0 -1
  34. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +9 -3
  35. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +20 -52
  36. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +16 -39
  37. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +49 -50
  38. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +3 -11
  39. package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +0 -3
  40. package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +0 -1
  41. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +5 -16
  42. package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +0 -1
  43. package/.docs/raw/docs/(reference)/api-reference/primitives/error.mdx +0 -1
  44. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +1 -2
  45. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +0 -1
  46. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +152 -0
  47. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +0 -1
  48. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +1 -2
  49. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +1 -2
  50. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +28 -40
  51. package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +0 -1
  52. package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +1 -2
  53. package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +2 -3
  54. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +1 -2
  55. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +1 -2
  56. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +0 -1
  57. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +0 -1
  58. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +1 -2
  59. package/.docs/raw/docs/(reference)/legacy/styled/assistant-modal.mdx +0 -1
  60. package/.docs/raw/docs/(reference)/legacy/styled/decomposition.mdx +5 -5
  61. package/.docs/raw/docs/(reference)/legacy/styled/markdown.mdx +0 -1
  62. package/.docs/raw/docs/(reference)/legacy/styled/thread.mdx +0 -1
  63. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +207 -33
  64. package/.docs/raw/docs/(reference)/react-compatibility.mdx +0 -1
  65. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +0 -1
  66. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +0 -1
  67. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +0 -1
  68. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +118 -0
  69. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +198 -0
  70. package/.docs/raw/docs/runtimes/assistant-transport.mdx +3 -3
  71. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +5 -6
  72. package/.docs/raw/docs/runtimes/custom/external-store.mdx +9 -11
  73. package/.docs/raw/docs/runtimes/custom/local.mdx +43 -36
  74. package/.docs/raw/docs/runtimes/data-stream.mdx +35 -3
  75. package/.docs/raw/docs/runtimes/langgraph/index.mdx +1 -2
  76. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +0 -1
  77. package/.docs/raw/docs/runtimes/langserve.mdx +0 -1
  78. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +0 -1
  79. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +0 -1
  80. package/.docs/raw/docs/ui/accordion.mdx +259 -0
  81. package/.docs/raw/docs/ui/assistant-modal.mdx +1 -3
  82. package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -3
  83. package/.docs/raw/docs/ui/attachment.mdx +0 -2
  84. package/.docs/raw/docs/ui/badge.mdx +138 -0
  85. package/.docs/raw/docs/ui/diff-viewer.mdx +279 -0
  86. package/.docs/raw/docs/ui/file.mdx +152 -0
  87. package/.docs/raw/docs/ui/image.mdx +100 -0
  88. package/.docs/raw/docs/ui/markdown.mdx +0 -1
  89. package/.docs/raw/docs/ui/mermaid.mdx +0 -1
  90. package/.docs/raw/docs/ui/model-selector.mdx +224 -0
  91. package/.docs/raw/docs/ui/part-grouping.mdx +4 -5
  92. package/.docs/raw/docs/ui/reasoning.mdx +6 -5
  93. package/.docs/raw/docs/ui/scrollbar.mdx +26 -9
  94. package/.docs/raw/docs/ui/select.mdx +245 -0
  95. package/.docs/raw/docs/ui/sources.mdx +6 -5
  96. package/.docs/raw/docs/ui/streamdown.mdx +348 -0
  97. package/.docs/raw/docs/ui/syntax-highlighting.mdx +8 -63
  98. package/.docs/raw/docs/ui/tabs.mdx +259 -0
  99. package/.docs/raw/docs/ui/thread-list.mdx +98 -16
  100. package/.docs/raw/docs/ui/thread.mdx +57 -73
  101. package/.docs/raw/docs/ui/tool-fallback.mdx +0 -1
  102. package/.docs/raw/docs/ui/tool-group.mdx +1 -3
  103. package/README.md +3 -3
  104. package/package.json +4 -4
  105. package/src/tools/tests/examples.test.ts +1 -1
  106. package/.docs/raw/docs/(docs)/about-assistantui.mdx +0 -54
  107. package/.docs/raw/docs/(docs)/mcp-docs-server.mdx +0 -321
  108. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +0 -219
@@ -83,6 +83,8 @@ export const OPTIONS = () => {
83
83
  @import "tailwindcss";
84
84
  @import "tw-animate-css";
85
85
 
86
+ @source "../../../packages/ui/src";
87
+
86
88
  @custom-variant dark (&:is(.dark *));
87
89
 
88
90
  @theme inline {
@@ -278,1393 +280,65 @@ export function MyRuntimeProvider({
278
280
  <AssistantRuntimeProvider runtime={runtime}>
279
281
  {children}
280
282
  </AssistantRuntimeProvider>
281
- );
282
- }
283
-
284
- ```
285
-
286
- ## app/page.tsx
287
-
288
- ```tsx
289
- "use client";
290
-
291
- import { Thread } from "@/components/assistant-ui/thread";
292
- import { PriceSnapshotTool } from "@/components/tools/price-snapshot/PriceSnapshotTool";
293
- import { PurchaseStockTool } from "@/components/tools/purchase-stock/PurchaseStockTool";
294
- import { ThreadList } from "@/components/assistant-ui/thread-list";
295
-
296
- export default function Home() {
297
- return (
298
- <div className="flex h-dvh">
299
- <div className="max-w-md">
300
- <ThreadList />
301
- </div>
302
- <div className="flex-grow">
303
- <Thread />
304
- <PriceSnapshotTool />
305
- <PurchaseStockTool />
306
- </div>
307
- </div>
308
- );
309
- }
310
-
311
- ```
312
-
313
- ## components.json
314
-
315
- ```json
316
- {
317
- "$schema": "https://ui.shadcn.com/schema.json",
318
- "style": "new-york",
319
- "rsc": true,
320
- "tsx": true,
321
- "tailwind": {
322
- "config": "",
323
- "css": "app/globals.css",
324
- "baseColor": "zinc",
325
- "cssVariables": true,
326
- "prefix": ""
327
- },
328
- "aliases": {
329
- "components": "@/components",
330
- "utils": "@/lib/utils",
331
- "ui": "@/components/ui",
332
- "lib": "@/lib",
333
- "hooks": "@/hooks"
334
- },
335
- "iconLibrary": "lucide"
336
- }
337
-
338
- ```
339
-
340
- ## components/assistant-ui/attachment.tsx
341
-
342
- ```tsx
343
- "use client";
344
-
345
- import { PropsWithChildren, useEffect, useState, type FC } from "react";
346
- import Image from "next/image";
347
- import { XIcon, PlusIcon, FileText } from "lucide-react";
348
- import {
349
- AttachmentPrimitive,
350
- ComposerPrimitive,
351
- MessagePrimitive,
352
- useAssistantState,
353
- useAssistantApi,
354
- } from "@assistant-ui/react";
355
- import { useShallow } from "zustand/shallow";
356
- import {
357
- Tooltip,
358
- TooltipContent,
359
- TooltipTrigger,
360
- } from "@/components/ui/tooltip";
361
- import {
362
- Dialog,
363
- DialogTitle,
364
- DialogContent,
365
- DialogTrigger,
366
- } from "@/components/ui/dialog";
367
- import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
368
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
369
- import { cn } from "@/lib/utils";
370
-
371
- const useFileSrc = (file: File | undefined) => {
372
- const [src, setSrc] = useState<string | undefined>(undefined);
373
-
374
- useEffect(() => {
375
- if (!file) {
376
- setSrc(undefined);
377
- return;
378
- }
379
-
380
- const objectUrl = URL.createObjectURL(file);
381
- setSrc(objectUrl);
382
-
383
- return () => {
384
- URL.revokeObjectURL(objectUrl);
385
- };
386
- }, [file]);
387
-
388
- return src;
389
- };
390
-
391
- const useAttachmentSrc = () => {
392
- const { file, src } = useAssistantState(
393
- useShallow(({ attachment }): { file?: File; src?: string } => {
394
- if (attachment.type !== "image") return {};
395
- if (attachment.file) return { file: attachment.file };
396
- const src = attachment.content?.filter((c) => c.type === "image")[0]
397
- ?.image;
398
- if (!src) return {};
399
- return { src };
400
- }),
401
- );
402
-
403
- return useFileSrc(file) ?? src;
404
- };
405
-
406
- type AttachmentPreviewProps = {
407
- src: string;
408
- };
409
-
410
- const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
411
- const [isLoaded, setIsLoaded] = useState(false);
412
- return (
413
- <Image
414
- src={src}
415
- alt="Image Preview"
416
- width={1}
417
- height={1}
418
- className={
419
- isLoaded
420
- ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
421
- : "aui-attachment-preview-image-loading hidden"
422
- }
423
- onLoadingComplete={() => setIsLoaded(true)}
424
- priority={false}
425
- />
426
- );
427
- };
428
-
429
- const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
430
- const src = useAttachmentSrc();
431
-
432
- if (!src) return children;
433
-
434
- return (
435
- <Dialog>
436
- <DialogTrigger
437
- className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
438
- asChild
439
- >
440
- {children}
441
- </DialogTrigger>
442
- <DialogContent className="aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive">
443
- <DialogTitle className="aui-sr-only sr-only">
444
- Image Attachment Preview
445
- </DialogTitle>
446
- <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
447
- <AttachmentPreview src={src} />
448
- </div>
449
- </DialogContent>
450
- </Dialog>
451
- );
452
- };
453
-
454
- const AttachmentThumb: FC = () => {
455
- const isImage = useAssistantState(
456
- ({ attachment }) => attachment.type === "image",
457
- );
458
- const src = useAttachmentSrc();
459
-
460
- return (
461
- <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
462
- <AvatarImage
463
- src={src}
464
- alt="Attachment preview"
465
- className="aui-attachment-tile-image object-cover"
466
- />
467
- <AvatarFallback delayMs={isImage ? 200 : 0}>
468
- <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
469
- </AvatarFallback>
470
- </Avatar>
471
- );
472
- };
473
-
474
- const AttachmentUI: FC = () => {
475
- const api = useAssistantApi();
476
- const isComposer = api.attachment.source === "composer";
477
-
478
- const isImage = useAssistantState(
479
- ({ attachment }) => attachment.type === "image",
480
- );
481
- const typeLabel = useAssistantState(({ attachment }) => {
482
- const type = attachment.type;
483
- switch (type) {
484
- case "image":
485
- return "Image";
486
- case "document":
487
- return "Document";
488
- case "file":
489
- return "File";
490
- default:
491
- const _exhaustiveCheck: never = type;
492
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
493
- }
494
- });
495
-
496
- return (
497
- <Tooltip>
498
- <AttachmentPrimitive.Root
499
- className={cn(
500
- "aui-attachment-root relative",
501
- isImage &&
502
- "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
503
- )}
504
- >
505
- <AttachmentPreviewDialog>
506
- <TooltipTrigger asChild>
507
- <div
508
- className={cn(
509
- "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
510
- isComposer &&
511
- "aui-attachment-tile-composer border-foreground/20",
512
- )}
513
- role="button"
514
- id="attachment-tile"
515
- aria-label={`${typeLabel} attachment`}
516
- >
517
- <AttachmentThumb />
518
- </div>
519
- </TooltipTrigger>
520
- </AttachmentPreviewDialog>
521
- {isComposer && <AttachmentRemove />}
522
- </AttachmentPrimitive.Root>
523
- <TooltipContent side="top">
524
- <AttachmentPrimitive.Name />
525
- </TooltipContent>
526
- </Tooltip>
527
- );
528
- };
529
-
530
- const AttachmentRemove: FC = () => {
531
- return (
532
- <AttachmentPrimitive.Remove asChild>
533
- <TooltipIconButton
534
- tooltip="Remove file"
535
- className="aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive"
536
- side="top"
537
- >
538
- <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
539
- </TooltipIconButton>
540
- </AttachmentPrimitive.Remove>
541
- );
542
- };
543
-
544
- export const UserMessageAttachments: FC = () => {
545
- return (
546
- <div className="aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2">
547
- <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
548
- </div>
549
- );
550
- };
551
-
552
- export const ComposerAttachments: FC = () => {
553
- return (
554
- <div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden">
555
- <ComposerPrimitive.Attachments
556
- components={{ Attachment: AttachmentUI }}
557
- />
558
- </div>
559
- );
560
- };
561
-
562
- export const ComposerAddAttachment: FC = () => {
563
- return (
564
- <ComposerPrimitive.AddAttachment asChild>
565
- <TooltipIconButton
566
- tooltip="Add Attachment"
567
- side="bottom"
568
- variant="ghost"
569
- size="icon"
570
- className="aui-composer-add-attachment size-8.5 rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
571
- aria-label="Add Attachment"
572
- >
573
- <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
574
- </TooltipIconButton>
575
- </ComposerPrimitive.AddAttachment>
576
- );
577
- };
578
-
579
- ```
580
-
581
- ## components/assistant-ui/markdown-text.tsx
582
-
583
- ```tsx
584
- "use client";
585
-
586
- import "@assistant-ui/react-markdown/styles/dot.css";
587
-
588
- import {
589
- type CodeHeaderProps,
590
- MarkdownTextPrimitive,
591
- unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
592
- useIsMarkdownCodeBlock,
593
- } from "@assistant-ui/react-markdown";
594
- import remarkGfm from "remark-gfm";
595
- import { type FC, memo, useState } from "react";
596
- import { CheckIcon, CopyIcon } from "lucide-react";
597
-
598
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
599
- import { cn } from "@/lib/utils";
600
-
601
- const MarkdownTextImpl = () => {
602
- return (
603
- <MarkdownTextPrimitive
604
- remarkPlugins={[remarkGfm]}
605
- className="aui-md"
606
- components={defaultComponents}
607
- />
608
- );
609
- };
610
-
611
- export const MarkdownText = memo(MarkdownTextImpl);
612
-
613
- const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
614
- const { isCopied, copyToClipboard } = useCopyToClipboard();
615
- const onCopy = () => {
616
- if (!code || isCopied) return;
617
- copyToClipboard(code);
618
- };
619
-
620
- return (
621
- <div className="aui-code-header-root mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-muted-foreground/15 px-4 py-2 font-semibold text-foreground text-sm dark:bg-muted-foreground/20">
622
- <span className="aui-code-header-language lowercase [&>span]:text-xs">
623
- {language}
624
- </span>
625
- <TooltipIconButton tooltip="Copy" onClick={onCopy}>
626
- {!isCopied && <CopyIcon />}
627
- {isCopied && <CheckIcon />}
628
- </TooltipIconButton>
629
- </div>
630
- );
631
- };
632
-
633
- const useCopyToClipboard = ({
634
- copiedDuration = 3000,
635
- }: {
636
- copiedDuration?: number;
637
- } = {}) => {
638
- const [isCopied, setIsCopied] = useState<boolean>(false);
639
-
640
- const copyToClipboard = (value: string) => {
641
- if (!value) return;
642
-
643
- navigator.clipboard.writeText(value).then(() => {
644
- setIsCopied(true);
645
- setTimeout(() => setIsCopied(false), copiedDuration);
646
- });
647
- };
648
-
649
- return { isCopied, copyToClipboard };
650
- };
651
-
652
- const defaultComponents = memoizeMarkdownComponents({
653
- h1: ({ className, ...props }) => (
654
- <h1
655
- className={cn(
656
- "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
657
- className,
658
- )}
659
- {...props}
660
- />
661
- ),
662
- h2: ({ className, ...props }) => (
663
- <h2
664
- className={cn(
665
- "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
666
- className,
667
- )}
668
- {...props}
669
- />
670
- ),
671
- h3: ({ className, ...props }) => (
672
- <h3
673
- className={cn(
674
- "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
675
- className,
676
- )}
677
- {...props}
678
- />
679
- ),
680
- h4: ({ className, ...props }) => (
681
- <h4
682
- className={cn(
683
- "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
684
- className,
685
- )}
686
- {...props}
687
- />
688
- ),
689
- h5: ({ className, ...props }) => (
690
- <h5
691
- className={cn(
692
- "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
693
- className,
694
- )}
695
- {...props}
696
- />
697
- ),
698
- h6: ({ className, ...props }) => (
699
- <h6
700
- className={cn(
701
- "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
702
- className,
703
- )}
704
- {...props}
705
- />
706
- ),
707
- p: ({ className, ...props }) => (
708
- <p
709
- className={cn(
710
- "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
711
- className,
712
- )}
713
- {...props}
714
- />
715
- ),
716
- a: ({ className, ...props }) => (
717
- <a
718
- className={cn(
719
- "aui-md-a font-medium text-primary underline underline-offset-4",
720
- className,
721
- )}
722
- {...props}
723
- />
724
- ),
725
- blockquote: ({ className, ...props }) => (
726
- <blockquote
727
- className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
728
- {...props}
729
- />
730
- ),
731
- ul: ({ className, ...props }) => (
732
- <ul
733
- className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
734
- {...props}
735
- />
736
- ),
737
- ol: ({ className, ...props }) => (
738
- <ol
739
- className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
740
- {...props}
741
- />
742
- ),
743
- hr: ({ className, ...props }) => (
744
- <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
745
- ),
746
- table: ({ className, ...props }) => (
747
- <table
748
- className={cn(
749
- "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
750
- className,
751
- )}
752
- {...props}
753
- />
754
- ),
755
- th: ({ className, ...props }) => (
756
- <th
757
- className={cn(
758
- "aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [[align=center]]:text-center [[align=right]]:text-right",
759
- className,
760
- )}
761
- {...props}
762
- />
763
- ),
764
- td: ({ className, ...props }) => (
765
- <td
766
- className={cn(
767
- "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
768
- className,
769
- )}
770
- {...props}
771
- />
772
- ),
773
- tr: ({ className, ...props }) => (
774
- <tr
775
- className={cn(
776
- "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
777
- className,
778
- )}
779
- {...props}
780
- />
781
- ),
782
- sup: ({ className, ...props }) => (
783
- <sup
784
- className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
785
- {...props}
786
- />
787
- ),
788
- pre: ({ className, ...props }) => (
789
- <pre
790
- className={cn(
791
- "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
792
- className,
793
- )}
794
- {...props}
795
- />
796
- ),
797
- code: function Code({ className, ...props }) {
798
- const isCodeBlock = useIsMarkdownCodeBlock();
799
- return (
800
- <code
801
- className={cn(
802
- !isCodeBlock &&
803
- "aui-md-inline-code rounded border bg-muted font-semibold",
804
- className,
805
- )}
806
- {...props}
807
- />
808
- );
809
- },
810
- CodeHeader,
811
- });
812
-
813
- ```
814
-
815
- ## components/assistant-ui/thread-list.tsx
816
-
817
- ```tsx
818
- import { Button } from "@/components/ui/button";
819
- import { Skeleton } from "@/components/ui/skeleton";
820
- import {
821
- AssistantIf,
822
- ThreadListItemMorePrimitive,
823
- ThreadListItemPrimitive,
824
- ThreadListPrimitive,
825
- } from "@assistant-ui/react";
826
- import { ArchiveIcon, MoreHorizontalIcon, PlusIcon } from "lucide-react";
827
- import type { FC } from "react";
828
-
829
- export const ThreadList: FC = () => {
830
- return (
831
- <ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col gap-1">
832
- <ThreadListNew />
833
- <AssistantIf condition={({ threads }) => threads.isLoading}>
834
- <ThreadListSkeleton />
835
- </AssistantIf>
836
- <AssistantIf condition={({ threads }) => !threads.isLoading}>
837
- <ThreadListPrimitive.Items components={{ ThreadListItem }} />
838
- </AssistantIf>
839
- </ThreadListPrimitive.Root>
840
- );
841
- };
842
-
843
- const ThreadListNew: FC = () => {
844
- return (
845
- <ThreadListPrimitive.New asChild>
846
- <Button
847
- variant="outline"
848
- className="aui-thread-list-new h-9 justify-start gap-2 rounded-lg px-3 text-sm hover:bg-muted data-active:bg-muted"
849
- >
850
- <PlusIcon className="size-4" />
851
- New Thread
852
- </Button>
853
- </ThreadListPrimitive.New>
854
- );
855
- };
856
-
857
- const ThreadListSkeleton: FC = () => {
858
- return (
859
- <div className="flex flex-col gap-1">
860
- {Array.from({ length: 5 }, (_, i) => (
861
- <div
862
- key={i}
863
- role="status"
864
- aria-label="Loading threads"
865
- className="aui-thread-list-skeleton-wrapper flex h-9 items-center px-3"
866
- >
867
- <Skeleton className="aui-thread-list-skeleton h-4 w-full" />
868
- </div>
869
- ))}
870
- </div>
871
- );
872
- };
873
-
874
- const ThreadListItem: FC = () => {
875
- return (
876
- <ThreadListItemPrimitive.Root className="aui-thread-list-item group flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none data-active:bg-muted">
877
- <ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm">
878
- <ThreadListItemPrimitive.Title fallback="New Chat" />
879
- </ThreadListItemPrimitive.Trigger>
880
- <ThreadListItemMore />
881
- </ThreadListItemPrimitive.Root>
882
- );
883
- };
884
-
885
- const ThreadListItemMore: FC = () => {
886
- return (
887
- <ThreadListItemMorePrimitive.Root>
888
- <ThreadListItemMorePrimitive.Trigger asChild>
889
- <Button
890
- variant="ghost"
891
- size="icon"
892
- className="aui-thread-list-item-more mr-2 size-7 p-0 opacity-0 transition-opacity group-hover:opacity-100 data-[state=open]:bg-accent data-[state=open]:opacity-100 group-data-active:opacity-100"
893
- >
894
- <MoreHorizontalIcon className="size-4" />
895
- <span className="sr-only">More options</span>
896
- </Button>
897
- </ThreadListItemMorePrimitive.Trigger>
898
- <ThreadListItemMorePrimitive.Content
899
- side="bottom"
900
- align="start"
901
- className="aui-thread-list-item-more-content z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
902
- >
903
- <ThreadListItemPrimitive.Archive asChild>
904
- <ThreadListItemMorePrimitive.Item className="aui-thread-list-item-more-item flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground">
905
- <ArchiveIcon className="size-4" />
906
- Archive
907
- </ThreadListItemMorePrimitive.Item>
908
- </ThreadListItemPrimitive.Archive>
909
- </ThreadListItemMorePrimitive.Content>
910
- </ThreadListItemMorePrimitive.Root>
911
- );
912
- };
913
-
914
- ```
915
-
916
- ## components/assistant-ui/thread.tsx
917
-
918
- ```tsx
919
- import {
920
- ComposerAddAttachment,
921
- ComposerAttachments,
922
- UserMessageAttachments,
923
- } from "@/components/assistant-ui/attachment";
924
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
925
- import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
926
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
927
- import { Button } from "@/components/ui/button";
928
- import { cn } from "@/lib/utils";
929
- import {
930
- ActionBarMorePrimitive,
931
- ActionBarPrimitive,
932
- AssistantIf,
933
- BranchPickerPrimitive,
934
- ComposerPrimitive,
935
- ErrorPrimitive,
936
- MessagePrimitive,
937
- ThreadPrimitive,
938
- } from "@assistant-ui/react";
939
- import {
940
- ArrowDownIcon,
941
- ArrowUpIcon,
942
- CheckIcon,
943
- ChevronLeftIcon,
944
- ChevronRightIcon,
945
- CopyIcon,
946
- DownloadIcon,
947
- MoreHorizontalIcon,
948
- PencilIcon,
949
- RefreshCwIcon,
950
- SquareIcon,
951
- } from "lucide-react";
952
- import type { FC } from "react";
953
-
954
- export const Thread: FC = () => {
955
- return (
956
- <ThreadPrimitive.Root
957
- className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
958
- style={{
959
- ["--thread-max-width" as string]: "44rem",
960
- }}
961
- >
962
- <ThreadPrimitive.Viewport
963
- turnAnchor="top"
964
- className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
965
- >
966
- <AssistantIf condition={({ thread }) => thread.isEmpty}>
967
- <ThreadWelcome />
968
- </AssistantIf>
969
-
970
- <ThreadPrimitive.Messages
971
- components={{
972
- UserMessage,
973
- EditComposer,
974
- AssistantMessage,
975
- }}
976
- />
977
-
978
- <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
979
- <ThreadScrollToBottom />
980
- <Composer />
981
- </ThreadPrimitive.ViewportFooter>
982
- </ThreadPrimitive.Viewport>
983
- </ThreadPrimitive.Root>
984
- );
985
- };
986
-
987
- const ThreadScrollToBottom: FC = () => {
988
- return (
989
- <ThreadPrimitive.ScrollToBottom asChild>
990
- <TooltipIconButton
991
- tooltip="Scroll to bottom"
992
- variant="outline"
993
- className="aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
994
- >
995
- <ArrowDownIcon />
996
- </TooltipIconButton>
997
- </ThreadPrimitive.ScrollToBottom>
998
- );
999
- };
1000
-
1001
- const ThreadWelcome: FC = () => {
1002
- return (
1003
- <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
1004
- <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
1005
- <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
1006
- <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
1007
- Hello there!
1008
- </h1>
1009
- <p className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in text-muted-foreground text-xl delay-75 duration-200">
1010
- How can I help you today?
1011
- </p>
1012
- </div>
1013
- </div>
1014
- <ThreadSuggestions />
1015
- </div>
1016
- );
1017
- };
1018
-
1019
- const SUGGESTIONS = [
1020
- {
1021
- title: "What's the weather",
1022
- label: "in San Francisco?",
1023
- prompt: "What's the weather in San Francisco?",
1024
- },
1025
- {
1026
- title: "Explain React hooks",
1027
- label: "like useState and useEffect",
1028
- prompt: "Explain React hooks like useState and useEffect",
1029
- },
1030
- ] as const;
1031
-
1032
- const ThreadSuggestions: FC = () => {
1033
- return (
1034
- <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
1035
- {SUGGESTIONS.map((suggestion, index) => (
1036
- <div
1037
- key={suggestion.prompt}
1038
- className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200"
1039
- style={{ animationDelay: `${100 + index * 50}ms` }}
1040
- >
1041
- <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
1042
- <Button
1043
- variant="ghost"
1044
- className="aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted"
1045
- aria-label={suggestion.prompt}
1046
- >
1047
- <span className="aui-thread-welcome-suggestion-text-1 font-medium">
1048
- {suggestion.title}
1049
- </span>
1050
- <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
1051
- {suggestion.label}
1052
- </span>
1053
- </Button>
1054
- </ThreadPrimitive.Suggestion>
1055
- </div>
1056
- ))}
1057
- </div>
1058
- );
1059
- };
1060
-
1061
- const Composer: FC = () => {
1062
- return (
1063
- <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
1064
- <ComposerPrimitive.AttachmentDropzone className="aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50">
1065
- <ComposerAttachments />
1066
- <ComposerPrimitive.Input
1067
- placeholder="Send a message..."
1068
- className="aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0"
1069
- rows={1}
1070
- autoFocus
1071
- aria-label="Message input"
1072
- />
1073
- <ComposerAction />
1074
- </ComposerPrimitive.AttachmentDropzone>
1075
- </ComposerPrimitive.Root>
1076
- );
1077
- };
1078
-
1079
- const ComposerAction: FC = () => {
1080
- return (
1081
- <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
1082
- <ComposerAddAttachment />
1083
-
1084
- <AssistantIf condition={({ thread }) => !thread.isRunning}>
1085
- <ComposerPrimitive.Send asChild>
1086
- <TooltipIconButton
1087
- tooltip="Send message"
1088
- side="bottom"
1089
- type="submit"
1090
- variant="default"
1091
- size="icon"
1092
- className="aui-composer-send size-8 rounded-full"
1093
- aria-label="Send message"
1094
- >
1095
- <ArrowUpIcon className="aui-composer-send-icon size-4" />
1096
- </TooltipIconButton>
1097
- </ComposerPrimitive.Send>
1098
- </AssistantIf>
1099
-
1100
- <AssistantIf condition={({ thread }) => thread.isRunning}>
1101
- <ComposerPrimitive.Cancel asChild>
1102
- <Button
1103
- type="button"
1104
- variant="default"
1105
- size="icon"
1106
- className="aui-composer-cancel size-8 rounded-full"
1107
- aria-label="Stop generating"
1108
- >
1109
- <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1110
- </Button>
1111
- </ComposerPrimitive.Cancel>
1112
- </AssistantIf>
1113
- </div>
1114
- );
1115
- };
1116
-
1117
- const MessageError: FC = () => {
1118
- return (
1119
- <MessagePrimitive.Error>
1120
- <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200">
1121
- <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
1122
- </ErrorPrimitive.Root>
1123
- </MessagePrimitive.Error>
1124
- );
1125
- };
1126
-
1127
- const AssistantMessage: FC = () => {
1128
- return (
1129
- <MessagePrimitive.Root
1130
- className="aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150"
1131
- data-role="assistant"
1132
- >
1133
- <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
1134
- <MessagePrimitive.Parts
1135
- components={{
1136
- Text: MarkdownText,
1137
- tools: { Fallback: ToolFallback },
1138
- }}
1139
- />
1140
- <MessageError />
1141
- </div>
1142
-
1143
- <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1144
- <BranchPicker />
1145
- <AssistantActionBar />
1146
- </div>
1147
- </MessagePrimitive.Root>
1148
- );
1149
- };
1150
-
1151
- const AssistantActionBar: FC = () => {
1152
- return (
1153
- <ActionBarPrimitive.Root
1154
- hideWhenRunning
1155
- autohide="not-last"
1156
- autohideFloat="single-branch"
1157
- className="aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
1158
- >
1159
- <ActionBarPrimitive.Copy asChild>
1160
- <TooltipIconButton tooltip="Copy">
1161
- <AssistantIf condition={({ message }) => message.isCopied}>
1162
- <CheckIcon />
1163
- </AssistantIf>
1164
- <AssistantIf condition={({ message }) => !message.isCopied}>
1165
- <CopyIcon />
1166
- </AssistantIf>
1167
- </TooltipIconButton>
1168
- </ActionBarPrimitive.Copy>
1169
- <ActionBarPrimitive.Reload asChild>
1170
- <TooltipIconButton tooltip="Refresh">
1171
- <RefreshCwIcon />
1172
- </TooltipIconButton>
1173
- </ActionBarPrimitive.Reload>
1174
- <ActionBarMorePrimitive.Root>
1175
- <ActionBarMorePrimitive.Trigger asChild>
1176
- <TooltipIconButton
1177
- tooltip="More"
1178
- className="data-[state=open]:bg-accent"
1179
- >
1180
- <MoreHorizontalIcon />
1181
- </TooltipIconButton>
1182
- </ActionBarMorePrimitive.Trigger>
1183
- <ActionBarMorePrimitive.Content
1184
- side="bottom"
1185
- align="start"
1186
- className="aui-action-bar-more-content z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
1187
- >
1188
- <ActionBarPrimitive.ExportMarkdown asChild>
1189
- <ActionBarMorePrimitive.Item className="aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground">
1190
- <DownloadIcon className="size-4" />
1191
- Export as Markdown
1192
- </ActionBarMorePrimitive.Item>
1193
- </ActionBarPrimitive.ExportMarkdown>
1194
- </ActionBarMorePrimitive.Content>
1195
- </ActionBarMorePrimitive.Root>
1196
- </ActionBarPrimitive.Root>
1197
- );
1198
- };
1199
-
1200
- const UserMessage: FC = () => {
1201
- return (
1202
- <MessagePrimitive.Root
1203
- className="aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-3 duration-150 [&:where(>*)]:col-start-2"
1204
- data-role="user"
1205
- >
1206
- <UserMessageAttachments />
1207
-
1208
- <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1209
- <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1210
- <MessagePrimitive.Parts />
1211
- </div>
1212
- <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
1213
- <UserActionBar />
1214
- </div>
1215
- </div>
1216
-
1217
- <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1218
- </MessagePrimitive.Root>
1219
- );
1220
- };
1221
-
1222
- const UserActionBar: FC = () => {
1223
- return (
1224
- <ActionBarPrimitive.Root
1225
- hideWhenRunning
1226
- autohide="not-last"
1227
- className="aui-user-action-bar-root flex flex-col items-end"
1228
- >
1229
- <ActionBarPrimitive.Edit asChild>
1230
- <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1231
- <PencilIcon />
1232
- </TooltipIconButton>
1233
- </ActionBarPrimitive.Edit>
1234
- </ActionBarPrimitive.Root>
1235
- );
1236
- };
1237
-
1238
- const EditComposer: FC = () => {
1239
- return (
1240
- <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1241
- <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1242
- <ComposerPrimitive.Input
1243
- className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1244
- autoFocus
1245
- />
1246
- <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1247
- <ComposerPrimitive.Cancel asChild>
1248
- <Button variant="ghost" size="sm">
1249
- Cancel
1250
- </Button>
1251
- </ComposerPrimitive.Cancel>
1252
- <ComposerPrimitive.Send asChild>
1253
- <Button size="sm">Update</Button>
1254
- </ComposerPrimitive.Send>
1255
- </div>
1256
- </ComposerPrimitive.Root>
1257
- </MessagePrimitive.Root>
1258
- );
1259
- };
1260
-
1261
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1262
- className,
1263
- ...rest
1264
- }) => {
1265
- return (
1266
- <BranchPickerPrimitive.Root
1267
- hideWhenSingleBranch
1268
- className={cn(
1269
- "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-muted-foreground text-xs",
1270
- className,
1271
- )}
1272
- {...rest}
1273
- >
1274
- <BranchPickerPrimitive.Previous asChild>
1275
- <TooltipIconButton tooltip="Previous">
1276
- <ChevronLeftIcon />
1277
- </TooltipIconButton>
1278
- </BranchPickerPrimitive.Previous>
1279
- <span className="aui-branch-picker-state font-medium">
1280
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
1281
- </span>
1282
- <BranchPickerPrimitive.Next asChild>
1283
- <TooltipIconButton tooltip="Next">
1284
- <ChevronRightIcon />
1285
- </TooltipIconButton>
1286
- </BranchPickerPrimitive.Next>
1287
- </BranchPickerPrimitive.Root>
1288
- );
1289
- };
1290
-
1291
- ```
1292
-
1293
- ## components/assistant-ui/tool-fallback.tsx
1294
-
1295
- ```tsx
1296
- "use client";
1297
-
1298
- import { memo, useCallback, useRef, useState } from "react";
1299
- import {
1300
- AlertCircleIcon,
1301
- CheckIcon,
1302
- ChevronDownIcon,
1303
- LoaderIcon,
1304
- XCircleIcon,
1305
- } from "lucide-react";
1306
- import {
1307
- useScrollLock,
1308
- type ToolCallMessagePartStatus,
1309
- type ToolCallMessagePartComponent,
1310
- } from "@assistant-ui/react";
1311
- import {
1312
- Collapsible,
1313
- CollapsibleContent,
1314
- CollapsibleTrigger,
1315
- } from "@/components/ui/collapsible";
1316
- import { cn } from "@/lib/utils";
1317
-
1318
- const ANIMATION_DURATION = 200;
1319
-
1320
- export type ToolFallbackRootProps = Omit<
1321
- React.ComponentProps<typeof Collapsible>,
1322
- "open" | "onOpenChange"
1323
- > & {
1324
- open?: boolean;
1325
- onOpenChange?: (open: boolean) => void;
1326
- defaultOpen?: boolean;
1327
- };
1328
-
1329
- function ToolFallbackRoot({
1330
- className,
1331
- open: controlledOpen,
1332
- onOpenChange: controlledOnOpenChange,
1333
- defaultOpen = false,
1334
- children,
1335
- ...props
1336
- }: ToolFallbackRootProps) {
1337
- const collapsibleRef = useRef<HTMLDivElement>(null);
1338
- const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
1339
- const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
1340
-
1341
- const isControlled = controlledOpen !== undefined;
1342
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
1343
-
1344
- const handleOpenChange = useCallback(
1345
- (open: boolean) => {
1346
- if (!open) {
1347
- lockScroll();
1348
- }
1349
- if (!isControlled) {
1350
- setUncontrolledOpen(open);
1351
- }
1352
- controlledOnOpenChange?.(open);
1353
- },
1354
- [lockScroll, isControlled, controlledOnOpenChange],
1355
- );
1356
-
1357
- return (
1358
- <Collapsible
1359
- ref={collapsibleRef}
1360
- data-slot="tool-fallback-root"
1361
- open={isOpen}
1362
- onOpenChange={handleOpenChange}
1363
- className={cn(
1364
- "aui-tool-fallback-root group/tool-fallback-root w-full rounded-lg border py-3",
1365
- className,
1366
- )}
1367
- style={
1368
- {
1369
- "--animation-duration": `${ANIMATION_DURATION}ms`,
1370
- } as React.CSSProperties
1371
- }
1372
- {...props}
1373
- >
1374
- {children}
1375
- </Collapsible>
1376
- );
1377
- }
1378
-
1379
- type ToolStatus = ToolCallMessagePartStatus["type"];
1380
-
1381
- const statusIconMap: Record<ToolStatus, React.ElementType> = {
1382
- running: LoaderIcon,
1383
- complete: CheckIcon,
1384
- incomplete: XCircleIcon,
1385
- "requires-action": AlertCircleIcon,
1386
- };
1387
-
1388
- function ToolFallbackTrigger({
1389
- toolName,
1390
- status,
1391
- className,
1392
- ...props
1393
- }: React.ComponentProps<typeof CollapsibleTrigger> & {
1394
- toolName: string;
1395
- status?: ToolCallMessagePartStatus;
1396
- }) {
1397
- const statusType = status?.type ?? "complete";
1398
- const isRunning = statusType === "running";
1399
- const isCancelled =
1400
- status?.type === "incomplete" && status.reason === "cancelled";
1401
-
1402
- const Icon = statusIconMap[statusType];
1403
- const label = isCancelled ? "Cancelled tool" : "Used tool";
1404
-
1405
- return (
1406
- <CollapsibleTrigger
1407
- data-slot="tool-fallback-trigger"
1408
- className={cn(
1409
- "aui-tool-fallback-trigger group/trigger flex w-full items-center gap-2 px-4 text-sm transition-colors",
1410
- className,
1411
- )}
1412
- {...props}
1413
- >
1414
- <Icon
1415
- data-slot="tool-fallback-trigger-icon"
1416
- className={cn(
1417
- "aui-tool-fallback-trigger-icon size-4 shrink-0",
1418
- isCancelled && "text-muted-foreground",
1419
- isRunning && "animate-spin",
1420
- )}
1421
- />
1422
- <span
1423
- data-slot="tool-fallback-trigger-label"
1424
- className={cn(
1425
- "aui-tool-fallback-trigger-label-wrapper relative inline-block grow text-left leading-none",
1426
- isCancelled && "text-muted-foreground line-through",
1427
- )}
1428
- >
1429
- <span>
1430
- {label}: <b>{toolName}</b>
1431
- </span>
1432
- {isRunning && (
1433
- <span
1434
- aria-hidden
1435
- data-slot="tool-fallback-trigger-shimmer"
1436
- className="aui-tool-fallback-trigger-shimmer shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none"
1437
- >
1438
- {label}: <b>{toolName}</b>
1439
- </span>
1440
- )}
1441
- </span>
1442
- <ChevronDownIcon
1443
- data-slot="tool-fallback-trigger-chevron"
1444
- className={cn(
1445
- "aui-tool-fallback-trigger-chevron size-4 shrink-0",
1446
- "transition-transform duration-(--animation-duration) ease-out",
1447
- "group-data-[state=closed]/trigger:-rotate-90",
1448
- "group-data-[state=open]/trigger:rotate-0",
1449
- )}
1450
- />
1451
- </CollapsibleTrigger>
1452
- );
1453
- }
1454
-
1455
- function ToolFallbackContent({
1456
- className,
1457
- children,
1458
- ...props
1459
- }: React.ComponentProps<typeof CollapsibleContent>) {
1460
- return (
1461
- <CollapsibleContent
1462
- data-slot="tool-fallback-content"
1463
- className={cn(
1464
- "aui-tool-fallback-content relative overflow-hidden text-sm outline-none",
1465
- "group/collapsible-content ease-out",
1466
- "data-[state=closed]:animate-collapsible-up",
1467
- "data-[state=open]:animate-collapsible-down",
1468
- "data-[state=closed]:fill-mode-forwards",
1469
- "data-[state=closed]:pointer-events-none",
1470
- "data-[state=open]:duration-(--animation-duration)",
1471
- "data-[state=closed]:duration-(--animation-duration)",
1472
- className,
1473
- )}
1474
- {...props}
1475
- >
1476
- <div className="mt-3 flex flex-col gap-2 border-t pt-2">{children}</div>
1477
- </CollapsibleContent>
1478
- );
1479
- }
1480
-
1481
- function ToolFallbackArgs({
1482
- argsText,
1483
- className,
1484
- ...props
1485
- }: React.ComponentProps<"div"> & {
1486
- argsText?: string;
1487
- }) {
1488
- if (!argsText) return null;
1489
-
1490
- return (
1491
- <div
1492
- data-slot="tool-fallback-args"
1493
- className={cn("aui-tool-fallback-args px-4", className)}
1494
- {...props}
1495
- >
1496
- <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1497
- {argsText}
1498
- </pre>
1499
- </div>
1500
- );
1501
- }
1502
-
1503
- function ToolFallbackResult({
1504
- result,
1505
- className,
1506
- ...props
1507
- }: React.ComponentProps<"div"> & {
1508
- result?: unknown;
1509
- }) {
1510
- if (result === undefined) return null;
1511
-
1512
- return (
1513
- <div
1514
- data-slot="tool-fallback-result"
1515
- className={cn(
1516
- "aui-tool-fallback-result border-t border-dashed px-4 pt-2",
1517
- className,
1518
- )}
1519
- {...props}
1520
- >
1521
- <p className="aui-tool-fallback-result-header font-semibold">Result:</p>
1522
- <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1523
- {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
1524
- </pre>
1525
- </div>
1526
- );
1527
- }
1528
-
1529
- function ToolFallbackError({
1530
- status,
1531
- className,
1532
- ...props
1533
- }: React.ComponentProps<"div"> & {
1534
- status?: ToolCallMessagePartStatus;
1535
- }) {
1536
- if (status?.type !== "incomplete") return null;
1537
-
1538
- const error = status.error;
1539
- const errorText = error
1540
- ? typeof error === "string"
1541
- ? error
1542
- : JSON.stringify(error)
1543
- : null;
1544
-
1545
- if (!errorText) return null;
1546
-
1547
- const isCancelled = status.reason === "cancelled";
1548
- const headerText = isCancelled ? "Cancelled reason:" : "Error:";
1549
-
1550
- return (
1551
- <div
1552
- data-slot="tool-fallback-error"
1553
- className={cn("aui-tool-fallback-error px-4", className)}
1554
- {...props}
1555
- >
1556
- <p className="aui-tool-fallback-error-header font-semibold text-muted-foreground">
1557
- {headerText}
1558
- </p>
1559
- <p className="aui-tool-fallback-error-reason text-muted-foreground">
1560
- {errorText}
1561
- </p>
1562
- </div>
1563
- );
1564
- }
1565
-
1566
- const ToolFallbackImpl: ToolCallMessagePartComponent = ({
1567
- toolName,
1568
- argsText,
1569
- result,
1570
- status,
1571
- }) => {
1572
- const isCancelled =
1573
- status?.type === "incomplete" && status.reason === "cancelled";
1574
-
1575
- return (
1576
- <ToolFallbackRoot
1577
- className={cn(isCancelled && "border-muted-foreground/30 bg-muted/30")}
1578
- >
1579
- <ToolFallbackTrigger toolName={toolName} status={status} />
1580
- <ToolFallbackContent>
1581
- <ToolFallbackError status={status} />
1582
- <ToolFallbackArgs
1583
- argsText={argsText}
1584
- className={cn(isCancelled && "opacity-60")}
1585
- />
1586
- {!isCancelled && <ToolFallbackResult result={result} />}
1587
- </ToolFallbackContent>
1588
- </ToolFallbackRoot>
1589
- );
1590
- };
1591
-
1592
- const ToolFallback = memo(
1593
- ToolFallbackImpl,
1594
- ) as unknown as ToolCallMessagePartComponent & {
1595
- Root: typeof ToolFallbackRoot;
1596
- Trigger: typeof ToolFallbackTrigger;
1597
- Content: typeof ToolFallbackContent;
1598
- Args: typeof ToolFallbackArgs;
1599
- Result: typeof ToolFallbackResult;
1600
- Error: typeof ToolFallbackError;
1601
- };
1602
-
1603
- ToolFallback.displayName = "ToolFallback";
1604
- ToolFallback.Root = ToolFallbackRoot;
1605
- ToolFallback.Trigger = ToolFallbackTrigger;
1606
- ToolFallback.Content = ToolFallbackContent;
1607
- ToolFallback.Args = ToolFallbackArgs;
1608
- ToolFallback.Result = ToolFallbackResult;
1609
- ToolFallback.Error = ToolFallbackError;
1610
-
1611
- export {
1612
- ToolFallback,
1613
- ToolFallbackRoot,
1614
- ToolFallbackTrigger,
1615
- ToolFallbackContent,
1616
- ToolFallbackArgs,
1617
- ToolFallbackResult,
1618
- ToolFallbackError,
1619
- };
283
+ );
284
+ }
1620
285
 
1621
286
  ```
1622
287
 
1623
- ## components/assistant-ui/tooltip-icon-button.tsx
288
+ ## app/page.tsx
1624
289
 
1625
290
  ```tsx
1626
291
  "use client";
1627
292
 
1628
- import { ComponentPropsWithRef, forwardRef } from "react";
1629
- import { Slottable } from "@radix-ui/react-slot";
1630
-
1631
- import {
1632
- Tooltip,
1633
- TooltipContent,
1634
- TooltipTrigger,
1635
- } from "@/components/ui/tooltip";
1636
- import { Button } from "@/components/ui/button";
1637
- import { cn } from "@/lib/utils";
1638
-
1639
- export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1640
- tooltip: string;
1641
- side?: "top" | "bottom" | "left" | "right";
1642
- };
293
+ import { Thread } from "@/components/assistant-ui/thread";
294
+ import { PriceSnapshotTool } from "@/components/tools/price-snapshot/PriceSnapshotTool";
295
+ import { PurchaseStockTool } from "@/components/tools/purchase-stock/PurchaseStockTool";
296
+ import { ThreadList } from "@/components/assistant-ui/thread-list";
1643
297
 
1644
- export const TooltipIconButton = forwardRef<
1645
- HTMLButtonElement,
1646
- TooltipIconButtonProps
1647
- >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
298
+ export default function Home() {
1648
299
  return (
1649
- <Tooltip>
1650
- <TooltipTrigger asChild>
1651
- <Button
1652
- variant="ghost"
1653
- size="icon"
1654
- {...rest}
1655
- className={cn("aui-button-icon size-6 p-1", className)}
1656
- ref={ref}
1657
- >
1658
- <Slottable>{children}</Slottable>
1659
- <span className="aui-sr-only sr-only">{tooltip}</span>
1660
- </Button>
1661
- </TooltipTrigger>
1662
- <TooltipContent side={side}>{tooltip}</TooltipContent>
1663
- </Tooltip>
300
+ <div className="flex h-dvh">
301
+ <div className="max-w-md">
302
+ <ThreadList />
303
+ </div>
304
+ <div className="flex-grow">
305
+ <Thread />
306
+ <PriceSnapshotTool />
307
+ <PurchaseStockTool />
308
+ </div>
309
+ </div>
1664
310
  );
1665
- });
311
+ }
312
+
313
+ ```
314
+
315
+ ## components.json
1666
316
 
1667
- TooltipIconButton.displayName = "TooltipIconButton";
317
+ ```json
318
+ {
319
+ "$schema": "https://ui.shadcn.com/schema.json",
320
+ "style": "new-york",
321
+ "rsc": true,
322
+ "tsx": true,
323
+ "tailwind": {
324
+ "config": "",
325
+ "css": "app/globals.css",
326
+ "baseColor": "zinc",
327
+ "cssVariables": true,
328
+ "prefix": ""
329
+ },
330
+ "aliases": {
331
+ "components": "@/components",
332
+ "utils": "@/lib/utils",
333
+ "ui": "@/components/ui",
334
+ "lib": "@/lib",
335
+ "hooks": "@/hooks"
336
+ },
337
+ "iconLibrary": "lucide",
338
+ "registries": {
339
+ "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
340
+ }
341
+ }
1668
342
 
1669
343
  ```
1670
344
 
@@ -2006,505 +680,6 @@ export const ToolFallback: ToolCallMessagePartComponent = ({
2006
680
 
2007
681
  ```
2008
682
 
2009
- ## components/ui/avatar.tsx
2010
-
2011
- ```tsx
2012
- "use client";
2013
-
2014
- import * as React from "react";
2015
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
2016
-
2017
- import { cn } from "@/lib/utils";
2018
-
2019
- function Avatar({
2020
- className,
2021
- ...props
2022
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
2023
- return (
2024
- <AvatarPrimitive.Root
2025
- data-slot="avatar"
2026
- className={cn(
2027
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
2028
- className,
2029
- )}
2030
- {...props}
2031
- />
2032
- );
2033
- }
2034
-
2035
- function AvatarImage({
2036
- className,
2037
- ...props
2038
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
2039
- return (
2040
- <AvatarPrimitive.Image
2041
- data-slot="avatar-image"
2042
- className={cn("aspect-square size-full", className)}
2043
- {...props}
2044
- />
2045
- );
2046
- }
2047
-
2048
- function AvatarFallback({
2049
- className,
2050
- ...props
2051
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
2052
- return (
2053
- <AvatarPrimitive.Fallback
2054
- data-slot="avatar-fallback"
2055
- className={cn(
2056
- "flex size-full items-center justify-center rounded-full bg-muted",
2057
- className,
2058
- )}
2059
- {...props}
2060
- />
2061
- );
2062
- }
2063
-
2064
- export { Avatar, AvatarImage, AvatarFallback };
2065
-
2066
- ```
2067
-
2068
- ## components/ui/button.tsx
2069
-
2070
- ```tsx
2071
- import * as React from "react";
2072
- import { Slot } from "@radix-ui/react-slot";
2073
- import { cva, type VariantProps } from "class-variance-authority";
2074
-
2075
- import { cn } from "@/lib/utils";
2076
-
2077
- const buttonVariants = cva(
2078
- "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
2079
- {
2080
- variants: {
2081
- variant: {
2082
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
2083
- destructive:
2084
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
2085
- outline:
2086
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
2087
- secondary:
2088
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
2089
- ghost:
2090
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
2091
- link: "text-primary underline-offset-4 hover:underline",
2092
- },
2093
- size: {
2094
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
2095
- sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
2096
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
2097
- icon: "size-9",
2098
- "icon-sm": "size-8",
2099
- "icon-lg": "size-10",
2100
- },
2101
- },
2102
- defaultVariants: {
2103
- variant: "default",
2104
- size: "default",
2105
- },
2106
- },
2107
- );
2108
-
2109
- function Button({
2110
- className,
2111
- variant = "default",
2112
- size = "default",
2113
- asChild = false,
2114
- ...props
2115
- }: React.ComponentProps<"button"> &
2116
- VariantProps<typeof buttonVariants> & {
2117
- asChild?: boolean;
2118
- }) {
2119
- const Comp = asChild ? Slot : "button";
2120
-
2121
- return (
2122
- <Comp
2123
- data-slot="button"
2124
- data-variant={variant}
2125
- data-size={size}
2126
- className={cn(buttonVariants({ variant, size, className }))}
2127
- {...props}
2128
- />
2129
- );
2130
- }
2131
-
2132
- export { Button, buttonVariants };
2133
-
2134
- ```
2135
-
2136
- ## components/ui/card.tsx
2137
-
2138
- ```tsx
2139
- import * as React from "react";
2140
-
2141
- import { cn } from "@/lib/utils";
2142
-
2143
- function Card({ className, ...props }: React.ComponentProps<"div">) {
2144
- return (
2145
- <div
2146
- data-slot="card"
2147
- className={cn(
2148
- "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
2149
- className,
2150
- )}
2151
- {...props}
2152
- />
2153
- );
2154
- }
2155
-
2156
- function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
2157
- return (
2158
- <div
2159
- data-slot="card-header"
2160
- className={cn(
2161
- "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
2162
- className,
2163
- )}
2164
- {...props}
2165
- />
2166
- );
2167
- }
2168
-
2169
- function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
2170
- return (
2171
- <div
2172
- data-slot="card-title"
2173
- className={cn("font-semibold leading-none", className)}
2174
- {...props}
2175
- />
2176
- );
2177
- }
2178
-
2179
- function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
2180
- return (
2181
- <div
2182
- data-slot="card-description"
2183
- className={cn("text-muted-foreground text-sm", className)}
2184
- {...props}
2185
- />
2186
- );
2187
- }
2188
-
2189
- function CardAction({ className, ...props }: React.ComponentProps<"div">) {
2190
- return (
2191
- <div
2192
- data-slot="card-action"
2193
- className={cn(
2194
- "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
2195
- className,
2196
- )}
2197
- {...props}
2198
- />
2199
- );
2200
- }
2201
-
2202
- function CardContent({ className, ...props }: React.ComponentProps<"div">) {
2203
- return (
2204
- <div
2205
- data-slot="card-content"
2206
- className={cn("px-6", className)}
2207
- {...props}
2208
- />
2209
- );
2210
- }
2211
-
2212
- function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
2213
- return (
2214
- <div
2215
- data-slot="card-footer"
2216
- className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
2217
- {...props}
2218
- />
2219
- );
2220
- }
2221
-
2222
- export {
2223
- Card,
2224
- CardHeader,
2225
- CardFooter,
2226
- CardTitle,
2227
- CardAction,
2228
- CardDescription,
2229
- CardContent,
2230
- };
2231
-
2232
- ```
2233
-
2234
- ## components/ui/collapsible.tsx
2235
-
2236
- ```tsx
2237
- "use client";
2238
-
2239
- import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
2240
-
2241
- function Collapsible({
2242
- ...props
2243
- }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
2244
- return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
2245
- }
2246
-
2247
- function CollapsibleTrigger({
2248
- ...props
2249
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
2250
- return (
2251
- <CollapsiblePrimitive.CollapsibleTrigger
2252
- data-slot="collapsible-trigger"
2253
- {...props}
2254
- />
2255
- );
2256
- }
2257
-
2258
- function CollapsibleContent({
2259
- ...props
2260
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
2261
- return (
2262
- <CollapsiblePrimitive.CollapsibleContent
2263
- data-slot="collapsible-content"
2264
- {...props}
2265
- />
2266
- );
2267
- }
2268
-
2269
- export { Collapsible, CollapsibleTrigger, CollapsibleContent };
2270
-
2271
- ```
2272
-
2273
- ## components/ui/dialog.tsx
2274
-
2275
- ```tsx
2276
- "use client";
2277
-
2278
- import * as React from "react";
2279
- import * as DialogPrimitive from "@radix-ui/react-dialog";
2280
- import { XIcon } from "lucide-react";
2281
-
2282
- import { cn } from "@/lib/utils";
2283
-
2284
- function Dialog({
2285
- ...props
2286
- }: React.ComponentProps<typeof DialogPrimitive.Root>) {
2287
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
2288
- }
2289
-
2290
- function DialogTrigger({
2291
- ...props
2292
- }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
2293
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
2294
- }
2295
-
2296
- function DialogPortal({
2297
- ...props
2298
- }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
2299
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
2300
- }
2301
-
2302
- function DialogClose({
2303
- ...props
2304
- }: React.ComponentProps<typeof DialogPrimitive.Close>) {
2305
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
2306
- }
2307
-
2308
- function DialogOverlay({
2309
- className,
2310
- ...props
2311
- }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
2312
- return (
2313
- <DialogPrimitive.Overlay
2314
- data-slot="dialog-overlay"
2315
- className={cn(
2316
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in",
2317
- className,
2318
- )}
2319
- {...props}
2320
- />
2321
- );
2322
- }
2323
-
2324
- function DialogContent({
2325
- className,
2326
- children,
2327
- showCloseButton = true,
2328
- ...props
2329
- }: React.ComponentProps<typeof DialogPrimitive.Content> & {
2330
- showCloseButton?: boolean;
2331
- }) {
2332
- return (
2333
- <DialogPortal data-slot="dialog-portal">
2334
- <DialogOverlay />
2335
- <DialogPrimitive.Content
2336
- data-slot="dialog-content"
2337
- className={cn(
2338
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg outline-none duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
2339
- className,
2340
- )}
2341
- {...props}
2342
- >
2343
- {children}
2344
- {showCloseButton && (
2345
- <DialogPrimitive.Close
2346
- data-slot="dialog-close"
2347
- className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
2348
- >
2349
- <XIcon />
2350
- <span className="sr-only">Close</span>
2351
- </DialogPrimitive.Close>
2352
- )}
2353
- </DialogPrimitive.Content>
2354
- </DialogPortal>
2355
- );
2356
- }
2357
-
2358
- function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
2359
- return (
2360
- <div
2361
- data-slot="dialog-header"
2362
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
2363
- {...props}
2364
- />
2365
- );
2366
- }
2367
-
2368
- function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
2369
- return (
2370
- <div
2371
- data-slot="dialog-footer"
2372
- className={cn(
2373
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
2374
- className,
2375
- )}
2376
- {...props}
2377
- />
2378
- );
2379
- }
2380
-
2381
- function DialogTitle({
2382
- className,
2383
- ...props
2384
- }: React.ComponentProps<typeof DialogPrimitive.Title>) {
2385
- return (
2386
- <DialogPrimitive.Title
2387
- data-slot="dialog-title"
2388
- className={cn("font-semibold text-lg leading-none", className)}
2389
- {...props}
2390
- />
2391
- );
2392
- }
2393
-
2394
- function DialogDescription({
2395
- className,
2396
- ...props
2397
- }: React.ComponentProps<typeof DialogPrimitive.Description>) {
2398
- return (
2399
- <DialogPrimitive.Description
2400
- data-slot="dialog-description"
2401
- className={cn("text-muted-foreground text-sm", className)}
2402
- {...props}
2403
- />
2404
- );
2405
- }
2406
-
2407
- export {
2408
- Dialog,
2409
- DialogClose,
2410
- DialogContent,
2411
- DialogDescription,
2412
- DialogFooter,
2413
- DialogHeader,
2414
- DialogOverlay,
2415
- DialogPortal,
2416
- DialogTitle,
2417
- DialogTrigger,
2418
- };
2419
-
2420
- ```
2421
-
2422
- ## components/ui/skeleton.tsx
2423
-
2424
- ```tsx
2425
- import { cn } from "@/lib/utils";
2426
-
2427
- function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
2428
- return (
2429
- <div
2430
- data-slot="skeleton"
2431
- className={cn("animate-pulse rounded-md bg-accent", className)}
2432
- {...props}
2433
- />
2434
- );
2435
- }
2436
-
2437
- export { Skeleton };
2438
-
2439
- ```
2440
-
2441
- ## components/ui/tooltip.tsx
2442
-
2443
- ```tsx
2444
- "use client";
2445
-
2446
- import * as React from "react";
2447
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2448
-
2449
- import { cn } from "@/lib/utils";
2450
-
2451
- function TooltipProvider({
2452
- delayDuration = 0,
2453
- ...props
2454
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
2455
- return (
2456
- <TooltipPrimitive.Provider
2457
- data-slot="tooltip-provider"
2458
- delayDuration={delayDuration}
2459
- {...props}
2460
- />
2461
- );
2462
- }
2463
-
2464
- function Tooltip({
2465
- ...props
2466
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
2467
- return (
2468
- <TooltipProvider>
2469
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
2470
- </TooltipProvider>
2471
- );
2472
- }
2473
-
2474
- function TooltipTrigger({
2475
- ...props
2476
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
2477
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
2478
- }
2479
-
2480
- function TooltipContent({
2481
- className,
2482
- sideOffset = 0,
2483
- children,
2484
- ...props
2485
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
2486
- return (
2487
- <TooltipPrimitive.Portal>
2488
- <TooltipPrimitive.Content
2489
- data-slot="tooltip-content"
2490
- sideOffset={sideOffset}
2491
- className={cn(
2492
- "fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in text-balance rounded-md bg-foreground px-3 py-1.5 text-background text-xs data-[state=closed]:animate-out",
2493
- className,
2494
- )}
2495
- {...props}
2496
- >
2497
- {children}
2498
- <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
2499
- </TooltipPrimitive.Content>
2500
- </TooltipPrimitive.Portal>
2501
- );
2502
- }
2503
-
2504
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
2505
-
2506
- ```
2507
-
2508
683
  ## lib/chatApi.ts
2509
684
 
2510
685
  ```typescript
@@ -2631,7 +806,8 @@ export default nextConfig;
2631
806
  "@assistant-ui/react": "workspace:*",
2632
807
  "@assistant-ui/react-langgraph": "workspace:*",
2633
808
  "@assistant-ui/react-markdown": "workspace:*",
2634
- "@langchain/langgraph-sdk": "^1.5.4",
809
+ "@assistant-ui/ui": "workspace:*",
810
+ "@langchain/langgraph-sdk": "^1.5.5",
2635
811
  "@radix-ui/react-avatar": "^1.1.11",
2636
812
  "@radix-ui/react-collapsible": "^1.1.12",
2637
813
  "@radix-ui/react-dialog": "^1.1.15",
@@ -2639,19 +815,17 @@ export default nextConfig;
2639
815
  "@radix-ui/react-tooltip": "^1.2.8",
2640
816
  "class-variance-authority": "^0.7.1",
2641
817
  "clsx": "^2.1.1",
2642
- "lucide-react": "^0.562.0",
2643
- "next": "^16.1.4",
2644
- "react": "^19.2.3",
2645
- "react-dom": "^19.2.3",
2646
- "remark-gfm": "^4.0.1",
2647
- "tailwind-merge": "^3.4.0",
2648
- "zustand": "^5.0.10"
818
+ "lucide-react": "^0.563.0",
819
+ "next": "^16.1.6",
820
+ "react": "^19.2.4",
821
+ "react-dom": "^19.2.4",
822
+ "tailwind-merge": "^3.4.0"
2649
823
  },
2650
824
  "devDependencies": {
2651
825
  "@assistant-ui/x-buildutils": "workspace:*",
2652
826
  "@tailwindcss/postcss": "^4.1.18",
2653
- "@types/node": "^25.0.9",
2654
- "@types/react": "^19.2.9",
827
+ "@types/node": "^25.2.0",
828
+ "@types/react": "^19.2.10",
2655
829
  "@types/react-dom": "^19.2.3",
2656
830
  "postcss": "^8.5.6",
2657
831
  "tailwindcss": "^4.1.18",
@@ -2665,28 +839,49 @@ export default nextConfig;
2665
839
  ## README.md
2666
840
 
2667
841
  ```markdown
2668
- # LangGraph Example
842
+ # LangGraph Integration
2669
843
 
2670
844
  [Hosted Demo](https://assistant-ui-langgraph.vercel.app/)
2671
845
 
2672
- This example demonstrates how to use LangChain LangGraph with assistant-ui.
846
+ This example demonstrates how to use LangChain LangGraph with assistant-ui. It is meant to be used with the backend found at LangGraph's Stockbroker example: https://github.com/bracesproul/langgraphjs-examples/tree/main/stockbroker
847
+
848
+ ## Quick Start
849
+
850
+ ### Using CLI (Recommended)
2673
851
 
2674
- It is meant to be used with the backend found at LangGraph's Stockbroker example: https://github.com/bracesproul/langgraphjs-examples/tree/main/stockbroker
852
+ ```bash
853
+ npx assistant-ui@latest create my-app --example with-langgraph
854
+ cd my-app
855
+ ```
856
+
857
+ ### Environment Variables
2675
858
 
2676
- You need to set the following environment variables:
859
+ Create `.env.local`:
2677
860
 
2678
- ```env
861
+ ```
2679
862
  NEXT_PUBLIC_API_URL=https://stockbrokeragent-bracesprouls-projects.vercel.app/api
2680
863
  NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID=stockbroker
2681
864
  ```
2682
865
 
2683
- To run the example, run the following commands:
866
+ ### Run
2684
867
 
2685
- ```sh
868
+ ```bash
2686
869
  npm install
2687
870
  npm run dev
2688
871
  ```
2689
872
 
873
+ ## Features
874
+
875
+ - LangGraph agent integration via `@assistant-ui/react-langgraph`
876
+ - Thread list for conversation management
877
+ - Custom tool UI for stock price snapshots
878
+ - Custom tool UI for stock purchases
879
+
880
+ ## Related Documentation
881
+
882
+ - [assistant-ui Documentation](https://www.assistant-ui.com/docs)
883
+ - [LangGraph Integration Guide](https://www.assistant-ui.com/docs/runtimes/langgraph)
884
+
2690
885
  ```
2691
886
 
2692
887
  ## tsconfig.json
@@ -2695,7 +890,14 @@ npm run dev
2695
890
  {
2696
891
  "extends": "@assistant-ui/x-buildutils/ts/next",
2697
892
  "compilerOptions": {
2698
- "paths": { "@/*": ["./*"] }
893
+ "paths": {
894
+ "@/*": ["./*"],
895
+ "@/components/assistant-ui/*": [
896
+ "../../packages/ui/src/components/assistant-ui/*"
897
+ ],
898
+ "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
899
+ "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
900
+ }
2699
901
  },
2700
902
  "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2701
903
  "exclude": ["node_modules"]