@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
@@ -6,6 +6,8 @@
6
6
  @import "tailwindcss";
7
7
  @import "tw-animate-css";
8
8
 
9
+ @source "../../../packages/ui/src";
10
+
9
11
  @custom-variant dark (&:is(.dark *));
10
12
 
11
13
  @theme inline {
@@ -414,1621 +416,12 @@ export default function Home() {
414
416
  "lib": "@/lib",
415
417
  "hooks": "@/hooks"
416
418
  },
417
- "iconLibrary": "lucide"
418
- }
419
-
420
- ```
421
-
422
- ## components/assistant-ui/attachment.tsx
423
-
424
- ```tsx
425
- "use client";
426
-
427
- import { PropsWithChildren, useEffect, useState, type FC } from "react";
428
- import Image from "next/image";
429
- import { XIcon, PlusIcon, FileText } from "lucide-react";
430
- import {
431
- AttachmentPrimitive,
432
- ComposerPrimitive,
433
- MessagePrimitive,
434
- useAssistantState,
435
- useAssistantApi,
436
- } from "@assistant-ui/react";
437
- import { useShallow } from "zustand/shallow";
438
- import {
439
- Tooltip,
440
- TooltipContent,
441
- TooltipTrigger,
442
- } from "@/components/ui/tooltip";
443
- import {
444
- Dialog,
445
- DialogTitle,
446
- DialogContent,
447
- DialogTrigger,
448
- } from "@/components/ui/dialog";
449
- import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
450
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
451
- import { cn } from "@/lib/utils";
452
-
453
- const useFileSrc = (file: File | undefined) => {
454
- const [src, setSrc] = useState<string | undefined>(undefined);
455
-
456
- useEffect(() => {
457
- if (!file) {
458
- setSrc(undefined);
459
- return;
460
- }
461
-
462
- const objectUrl = URL.createObjectURL(file);
463
- setSrc(objectUrl);
464
-
465
- return () => {
466
- URL.revokeObjectURL(objectUrl);
467
- };
468
- }, [file]);
469
-
470
- return src;
471
- };
472
-
473
- const useAttachmentSrc = () => {
474
- const { file, src } = useAssistantState(
475
- useShallow(({ attachment }): { file?: File; src?: string } => {
476
- if (attachment.type !== "image") return {};
477
- if (attachment.file) return { file: attachment.file };
478
- const src = attachment.content?.filter((c) => c.type === "image")[0]
479
- ?.image;
480
- if (!src) return {};
481
- return { src };
482
- }),
483
- );
484
-
485
- return useFileSrc(file) ?? src;
486
- };
487
-
488
- type AttachmentPreviewProps = {
489
- src: string;
490
- };
491
-
492
- const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
493
- const [isLoaded, setIsLoaded] = useState(false);
494
- return (
495
- <Image
496
- src={src}
497
- alt="Image Preview"
498
- width={1}
499
- height={1}
500
- className={
501
- isLoaded
502
- ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
503
- : "aui-attachment-preview-image-loading hidden"
504
- }
505
- onLoadingComplete={() => setIsLoaded(true)}
506
- priority={false}
507
- />
508
- );
509
- };
510
-
511
- const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
512
- const src = useAttachmentSrc();
513
-
514
- if (!src) return children;
515
-
516
- return (
517
- <Dialog>
518
- <DialogTrigger
519
- className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
520
- asChild
521
- >
522
- {children}
523
- </DialogTrigger>
524
- <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">
525
- <DialogTitle className="aui-sr-only sr-only">
526
- Image Attachment Preview
527
- </DialogTitle>
528
- <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
529
- <AttachmentPreview src={src} />
530
- </div>
531
- </DialogContent>
532
- </Dialog>
533
- );
534
- };
535
-
536
- const AttachmentThumb: FC = () => {
537
- const isImage = useAssistantState(
538
- ({ attachment }) => attachment.type === "image",
539
- );
540
- const src = useAttachmentSrc();
541
-
542
- return (
543
- <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
544
- <AvatarImage
545
- src={src}
546
- alt="Attachment preview"
547
- className="aui-attachment-tile-image object-cover"
548
- />
549
- <AvatarFallback delayMs={isImage ? 200 : 0}>
550
- <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
551
- </AvatarFallback>
552
- </Avatar>
553
- );
554
- };
555
-
556
- const AttachmentUI: FC = () => {
557
- const api = useAssistantApi();
558
- const isComposer = api.attachment.source === "composer";
559
-
560
- const isImage = useAssistantState(
561
- ({ attachment }) => attachment.type === "image",
562
- );
563
- const typeLabel = useAssistantState(({ attachment }) => {
564
- const type = attachment.type;
565
- switch (type) {
566
- case "image":
567
- return "Image";
568
- case "document":
569
- return "Document";
570
- case "file":
571
- return "File";
572
- default:
573
- const _exhaustiveCheck: never = type;
574
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
575
- }
576
- });
577
-
578
- return (
579
- <Tooltip>
580
- <AttachmentPrimitive.Root
581
- className={cn(
582
- "aui-attachment-root relative",
583
- isImage &&
584
- "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
585
- )}
586
- >
587
- <AttachmentPreviewDialog>
588
- <TooltipTrigger asChild>
589
- <div
590
- className={cn(
591
- "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
592
- isComposer &&
593
- "aui-attachment-tile-composer border-foreground/20",
594
- )}
595
- role="button"
596
- id="attachment-tile"
597
- aria-label={`${typeLabel} attachment`}
598
- >
599
- <AttachmentThumb />
600
- </div>
601
- </TooltipTrigger>
602
- </AttachmentPreviewDialog>
603
- {isComposer && <AttachmentRemove />}
604
- </AttachmentPrimitive.Root>
605
- <TooltipContent side="top">
606
- <AttachmentPrimitive.Name />
607
- </TooltipContent>
608
- </Tooltip>
609
- );
610
- };
611
-
612
- const AttachmentRemove: FC = () => {
613
- return (
614
- <AttachmentPrimitive.Remove asChild>
615
- <TooltipIconButton
616
- tooltip="Remove file"
617
- 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"
618
- side="top"
619
- >
620
- <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
621
- </TooltipIconButton>
622
- </AttachmentPrimitive.Remove>
623
- );
624
- };
625
-
626
- export const UserMessageAttachments: FC = () => {
627
- return (
628
- <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">
629
- <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
630
- </div>
631
- );
632
- };
633
-
634
- export const ComposerAttachments: FC = () => {
635
- return (
636
- <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">
637
- <ComposerPrimitive.Attachments
638
- components={{ Attachment: AttachmentUI }}
639
- />
640
- </div>
641
- );
642
- };
643
-
644
- export const ComposerAddAttachment: FC = () => {
645
- return (
646
- <ComposerPrimitive.AddAttachment asChild>
647
- <TooltipIconButton
648
- tooltip="Add Attachment"
649
- side="bottom"
650
- variant="ghost"
651
- size="icon"
652
- 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"
653
- aria-label="Add Attachment"
654
- >
655
- <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
656
- </TooltipIconButton>
657
- </ComposerPrimitive.AddAttachment>
658
- );
659
- };
660
-
661
- ```
662
-
663
- ## components/assistant-ui/markdown-text.tsx
664
-
665
- ```tsx
666
- "use client";
667
-
668
- import "@assistant-ui/react-markdown/styles/dot.css";
669
-
670
- import {
671
- type CodeHeaderProps,
672
- MarkdownTextPrimitive,
673
- unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
674
- useIsMarkdownCodeBlock,
675
- } from "@assistant-ui/react-markdown";
676
- import remarkGfm from "remark-gfm";
677
- import { type FC, memo, useState } from "react";
678
- import { CheckIcon, CopyIcon } from "lucide-react";
679
-
680
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
681
- import { cn } from "@/lib/utils";
682
-
683
- const MarkdownTextImpl = () => {
684
- return (
685
- <MarkdownTextPrimitive
686
- remarkPlugins={[remarkGfm]}
687
- className="aui-md"
688
- components={defaultComponents}
689
- />
690
- );
691
- };
692
-
693
- export const MarkdownText = memo(MarkdownTextImpl);
694
-
695
- const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
696
- const { isCopied, copyToClipboard } = useCopyToClipboard();
697
- const onCopy = () => {
698
- if (!code || isCopied) return;
699
- copyToClipboard(code);
700
- };
701
-
702
- return (
703
- <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">
704
- <span className="aui-code-header-language lowercase [&>span]:text-xs">
705
- {language}
706
- </span>
707
- <TooltipIconButton tooltip="Copy" onClick={onCopy}>
708
- {!isCopied && <CopyIcon />}
709
- {isCopied && <CheckIcon />}
710
- </TooltipIconButton>
711
- </div>
712
- );
713
- };
714
-
715
- const useCopyToClipboard = ({
716
- copiedDuration = 3000,
717
- }: {
718
- copiedDuration?: number;
719
- } = {}) => {
720
- const [isCopied, setIsCopied] = useState<boolean>(false);
721
-
722
- const copyToClipboard = (value: string) => {
723
- if (!value) return;
724
-
725
- navigator.clipboard.writeText(value).then(() => {
726
- setIsCopied(true);
727
- setTimeout(() => setIsCopied(false), copiedDuration);
728
- });
729
- };
730
-
731
- return { isCopied, copyToClipboard };
732
- };
733
-
734
- const defaultComponents = memoizeMarkdownComponents({
735
- h1: ({ className, ...props }) => (
736
- <h1
737
- className={cn(
738
- "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
739
- className,
740
- )}
741
- {...props}
742
- />
743
- ),
744
- h2: ({ className, ...props }) => (
745
- <h2
746
- className={cn(
747
- "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
748
- className,
749
- )}
750
- {...props}
751
- />
752
- ),
753
- h3: ({ className, ...props }) => (
754
- <h3
755
- className={cn(
756
- "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
757
- className,
758
- )}
759
- {...props}
760
- />
761
- ),
762
- h4: ({ className, ...props }) => (
763
- <h4
764
- className={cn(
765
- "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
766
- className,
767
- )}
768
- {...props}
769
- />
770
- ),
771
- h5: ({ className, ...props }) => (
772
- <h5
773
- className={cn(
774
- "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
775
- className,
776
- )}
777
- {...props}
778
- />
779
- ),
780
- h6: ({ className, ...props }) => (
781
- <h6
782
- className={cn(
783
- "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
784
- className,
785
- )}
786
- {...props}
787
- />
788
- ),
789
- p: ({ className, ...props }) => (
790
- <p
791
- className={cn(
792
- "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
793
- className,
794
- )}
795
- {...props}
796
- />
797
- ),
798
- a: ({ className, ...props }) => (
799
- <a
800
- className={cn(
801
- "aui-md-a font-medium text-primary underline underline-offset-4",
802
- className,
803
- )}
804
- {...props}
805
- />
806
- ),
807
- blockquote: ({ className, ...props }) => (
808
- <blockquote
809
- className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
810
- {...props}
811
- />
812
- ),
813
- ul: ({ className, ...props }) => (
814
- <ul
815
- className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
816
- {...props}
817
- />
818
- ),
819
- ol: ({ className, ...props }) => (
820
- <ol
821
- className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
822
- {...props}
823
- />
824
- ),
825
- hr: ({ className, ...props }) => (
826
- <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
827
- ),
828
- table: ({ className, ...props }) => (
829
- <table
830
- className={cn(
831
- "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
832
- className,
833
- )}
834
- {...props}
835
- />
836
- ),
837
- th: ({ className, ...props }) => (
838
- <th
839
- className={cn(
840
- "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",
841
- className,
842
- )}
843
- {...props}
844
- />
845
- ),
846
- td: ({ className, ...props }) => (
847
- <td
848
- className={cn(
849
- "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
850
- className,
851
- )}
852
- {...props}
853
- />
854
- ),
855
- tr: ({ className, ...props }) => (
856
- <tr
857
- className={cn(
858
- "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",
859
- className,
860
- )}
861
- {...props}
862
- />
863
- ),
864
- sup: ({ className, ...props }) => (
865
- <sup
866
- className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
867
- {...props}
868
- />
869
- ),
870
- pre: ({ className, ...props }) => (
871
- <pre
872
- className={cn(
873
- "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
874
- className,
875
- )}
876
- {...props}
877
- />
878
- ),
879
- code: function Code({ className, ...props }) {
880
- const isCodeBlock = useIsMarkdownCodeBlock();
881
- return (
882
- <code
883
- className={cn(
884
- !isCodeBlock &&
885
- "aui-md-inline-code rounded border bg-muted font-semibold",
886
- className,
887
- )}
888
- {...props}
889
- />
890
- );
891
- },
892
- CodeHeader,
893
- });
894
-
895
- ```
896
-
897
- ## components/assistant-ui/thread.tsx
898
-
899
- ```tsx
900
- import {
901
- ComposerAddAttachment,
902
- ComposerAttachments,
903
- UserMessageAttachments,
904
- } from "@/components/assistant-ui/attachment";
905
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
906
- import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
907
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
908
- import { Button } from "@/components/ui/button";
909
- import { cn } from "@/lib/utils";
910
- import {
911
- ActionBarMorePrimitive,
912
- ActionBarPrimitive,
913
- AssistantIf,
914
- BranchPickerPrimitive,
915
- ComposerPrimitive,
916
- ErrorPrimitive,
917
- MessagePrimitive,
918
- ThreadPrimitive,
919
- } from "@assistant-ui/react";
920
- import {
921
- ArrowDownIcon,
922
- ArrowUpIcon,
923
- CheckIcon,
924
- ChevronLeftIcon,
925
- ChevronRightIcon,
926
- CopyIcon,
927
- DownloadIcon,
928
- MoreHorizontalIcon,
929
- PencilIcon,
930
- RefreshCwIcon,
931
- SquareIcon,
932
- } from "lucide-react";
933
- import type { FC } from "react";
934
-
935
- export const Thread: FC = () => {
936
- return (
937
- <ThreadPrimitive.Root
938
- className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
939
- style={{
940
- ["--thread-max-width" as string]: "44rem",
941
- }}
942
- >
943
- <ThreadPrimitive.Viewport
944
- turnAnchor="top"
945
- className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
946
- >
947
- <AssistantIf condition={({ thread }) => thread.isEmpty}>
948
- <ThreadWelcome />
949
- </AssistantIf>
950
-
951
- <ThreadPrimitive.Messages
952
- components={{
953
- UserMessage,
954
- EditComposer,
955
- AssistantMessage,
956
- }}
957
- />
958
-
959
- <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">
960
- <ThreadScrollToBottom />
961
- <Composer />
962
- </ThreadPrimitive.ViewportFooter>
963
- </ThreadPrimitive.Viewport>
964
- </ThreadPrimitive.Root>
965
- );
966
- };
967
-
968
- const ThreadScrollToBottom: FC = () => {
969
- return (
970
- <ThreadPrimitive.ScrollToBottom asChild>
971
- <TooltipIconButton
972
- tooltip="Scroll to bottom"
973
- variant="outline"
974
- 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"
975
- >
976
- <ArrowDownIcon />
977
- </TooltipIconButton>
978
- </ThreadPrimitive.ScrollToBottom>
979
- );
980
- };
981
-
982
- const ThreadWelcome: FC = () => {
983
- return (
984
- <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
985
- <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
986
- <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
987
- <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
988
- Hello there!
989
- </h1>
990
- <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">
991
- How can I help you today?
992
- </p>
993
- </div>
994
- </div>
995
- <ThreadSuggestions />
996
- </div>
997
- );
998
- };
999
-
1000
- const SUGGESTIONS = [
1001
- {
1002
- title: "What's the weather",
1003
- label: "in San Francisco?",
1004
- prompt: "What's the weather in San Francisco?",
1005
- },
1006
- {
1007
- title: "Explain React hooks",
1008
- label: "like useState and useEffect",
1009
- prompt: "Explain React hooks like useState and useEffect",
1010
- },
1011
- ] as const;
1012
-
1013
- const ThreadSuggestions: FC = () => {
1014
- return (
1015
- <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
1016
- {SUGGESTIONS.map((suggestion, index) => (
1017
- <div
1018
- key={suggestion.prompt}
1019
- 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"
1020
- style={{ animationDelay: `${100 + index * 50}ms` }}
1021
- >
1022
- <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
1023
- <Button
1024
- variant="ghost"
1025
- 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"
1026
- aria-label={suggestion.prompt}
1027
- >
1028
- <span className="aui-thread-welcome-suggestion-text-1 font-medium">
1029
- {suggestion.title}
1030
- </span>
1031
- <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
1032
- {suggestion.label}
1033
- </span>
1034
- </Button>
1035
- </ThreadPrimitive.Suggestion>
1036
- </div>
1037
- ))}
1038
- </div>
1039
- );
1040
- };
1041
-
1042
- const Composer: FC = () => {
1043
- return (
1044
- <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
1045
- <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">
1046
- <ComposerAttachments />
1047
- <ComposerPrimitive.Input
1048
- placeholder="Send a message..."
1049
- 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"
1050
- rows={1}
1051
- autoFocus
1052
- aria-label="Message input"
1053
- />
1054
- <ComposerAction />
1055
- </ComposerPrimitive.AttachmentDropzone>
1056
- </ComposerPrimitive.Root>
1057
- );
1058
- };
1059
-
1060
- const ComposerAction: FC = () => {
1061
- return (
1062
- <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
1063
- <ComposerAddAttachment />
1064
-
1065
- <AssistantIf condition={({ thread }) => !thread.isRunning}>
1066
- <ComposerPrimitive.Send asChild>
1067
- <TooltipIconButton
1068
- tooltip="Send message"
1069
- side="bottom"
1070
- type="submit"
1071
- variant="default"
1072
- size="icon"
1073
- className="aui-composer-send size-8 rounded-full"
1074
- aria-label="Send message"
1075
- >
1076
- <ArrowUpIcon className="aui-composer-send-icon size-4" />
1077
- </TooltipIconButton>
1078
- </ComposerPrimitive.Send>
1079
- </AssistantIf>
1080
-
1081
- <AssistantIf condition={({ thread }) => thread.isRunning}>
1082
- <ComposerPrimitive.Cancel asChild>
1083
- <Button
1084
- type="button"
1085
- variant="default"
1086
- size="icon"
1087
- className="aui-composer-cancel size-8 rounded-full"
1088
- aria-label="Stop generating"
1089
- >
1090
- <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1091
- </Button>
1092
- </ComposerPrimitive.Cancel>
1093
- </AssistantIf>
1094
- </div>
1095
- );
1096
- };
1097
-
1098
- const MessageError: FC = () => {
1099
- return (
1100
- <MessagePrimitive.Error>
1101
- <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">
1102
- <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
1103
- </ErrorPrimitive.Root>
1104
- </MessagePrimitive.Error>
1105
- );
1106
- };
1107
-
1108
- const AssistantMessage: FC = () => {
1109
- return (
1110
- <MessagePrimitive.Root
1111
- 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"
1112
- data-role="assistant"
1113
- >
1114
- <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
1115
- <MessagePrimitive.Parts
1116
- components={{
1117
- Text: MarkdownText,
1118
- tools: { Fallback: ToolFallback },
1119
- }}
1120
- />
1121
- <MessageError />
1122
- </div>
1123
-
1124
- <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1125
- <BranchPicker />
1126
- <AssistantActionBar />
1127
- </div>
1128
- </MessagePrimitive.Root>
1129
- );
1130
- };
1131
-
1132
- const AssistantActionBar: FC = () => {
1133
- return (
1134
- <ActionBarPrimitive.Root
1135
- hideWhenRunning
1136
- autohide="not-last"
1137
- autohideFloat="single-branch"
1138
- 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"
1139
- >
1140
- <ActionBarPrimitive.Copy asChild>
1141
- <TooltipIconButton tooltip="Copy">
1142
- <AssistantIf condition={({ message }) => message.isCopied}>
1143
- <CheckIcon />
1144
- </AssistantIf>
1145
- <AssistantIf condition={({ message }) => !message.isCopied}>
1146
- <CopyIcon />
1147
- </AssistantIf>
1148
- </TooltipIconButton>
1149
- </ActionBarPrimitive.Copy>
1150
- <ActionBarPrimitive.Reload asChild>
1151
- <TooltipIconButton tooltip="Refresh">
1152
- <RefreshCwIcon />
1153
- </TooltipIconButton>
1154
- </ActionBarPrimitive.Reload>
1155
- <ActionBarMorePrimitive.Root>
1156
- <ActionBarMorePrimitive.Trigger asChild>
1157
- <TooltipIconButton
1158
- tooltip="More"
1159
- className="data-[state=open]:bg-accent"
1160
- >
1161
- <MoreHorizontalIcon />
1162
- </TooltipIconButton>
1163
- </ActionBarMorePrimitive.Trigger>
1164
- <ActionBarMorePrimitive.Content
1165
- side="bottom"
1166
- align="start"
1167
- 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"
1168
- >
1169
- <ActionBarPrimitive.ExportMarkdown asChild>
1170
- <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">
1171
- <DownloadIcon className="size-4" />
1172
- Export as Markdown
1173
- </ActionBarMorePrimitive.Item>
1174
- </ActionBarPrimitive.ExportMarkdown>
1175
- </ActionBarMorePrimitive.Content>
1176
- </ActionBarMorePrimitive.Root>
1177
- </ActionBarPrimitive.Root>
1178
- );
1179
- };
1180
-
1181
- const UserMessage: FC = () => {
1182
- return (
1183
- <MessagePrimitive.Root
1184
- 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"
1185
- data-role="user"
1186
- >
1187
- <UserMessageAttachments />
1188
-
1189
- <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1190
- <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1191
- <MessagePrimitive.Parts />
1192
- </div>
1193
- <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
1194
- <UserActionBar />
1195
- </div>
1196
- </div>
1197
-
1198
- <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1199
- </MessagePrimitive.Root>
1200
- );
1201
- };
1202
-
1203
- const UserActionBar: FC = () => {
1204
- return (
1205
- <ActionBarPrimitive.Root
1206
- hideWhenRunning
1207
- autohide="not-last"
1208
- className="aui-user-action-bar-root flex flex-col items-end"
1209
- >
1210
- <ActionBarPrimitive.Edit asChild>
1211
- <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1212
- <PencilIcon />
1213
- </TooltipIconButton>
1214
- </ActionBarPrimitive.Edit>
1215
- </ActionBarPrimitive.Root>
1216
- );
1217
- };
1218
-
1219
- const EditComposer: FC = () => {
1220
- return (
1221
- <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1222
- <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1223
- <ComposerPrimitive.Input
1224
- className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1225
- autoFocus
1226
- />
1227
- <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1228
- <ComposerPrimitive.Cancel asChild>
1229
- <Button variant="ghost" size="sm">
1230
- Cancel
1231
- </Button>
1232
- </ComposerPrimitive.Cancel>
1233
- <ComposerPrimitive.Send asChild>
1234
- <Button size="sm">Update</Button>
1235
- </ComposerPrimitive.Send>
1236
- </div>
1237
- </ComposerPrimitive.Root>
1238
- </MessagePrimitive.Root>
1239
- );
1240
- };
1241
-
1242
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1243
- className,
1244
- ...rest
1245
- }) => {
1246
- return (
1247
- <BranchPickerPrimitive.Root
1248
- hideWhenSingleBranch
1249
- className={cn(
1250
- "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-muted-foreground text-xs",
1251
- className,
1252
- )}
1253
- {...rest}
1254
- >
1255
- <BranchPickerPrimitive.Previous asChild>
1256
- <TooltipIconButton tooltip="Previous">
1257
- <ChevronLeftIcon />
1258
- </TooltipIconButton>
1259
- </BranchPickerPrimitive.Previous>
1260
- <span className="aui-branch-picker-state font-medium">
1261
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
1262
- </span>
1263
- <BranchPickerPrimitive.Next asChild>
1264
- <TooltipIconButton tooltip="Next">
1265
- <ChevronRightIcon />
1266
- </TooltipIconButton>
1267
- </BranchPickerPrimitive.Next>
1268
- </BranchPickerPrimitive.Root>
1269
- );
1270
- };
1271
-
1272
- ```
1273
-
1274
- ## components/assistant-ui/tool-fallback.tsx
1275
-
1276
- ```tsx
1277
- "use client";
1278
-
1279
- import { memo, useCallback, useRef, useState } from "react";
1280
- import {
1281
- AlertCircleIcon,
1282
- CheckIcon,
1283
- ChevronDownIcon,
1284
- LoaderIcon,
1285
- XCircleIcon,
1286
- } from "lucide-react";
1287
- import {
1288
- useScrollLock,
1289
- type ToolCallMessagePartStatus,
1290
- type ToolCallMessagePartComponent,
1291
- } from "@assistant-ui/react";
1292
- import {
1293
- Collapsible,
1294
- CollapsibleContent,
1295
- CollapsibleTrigger,
1296
- } from "@/components/ui/collapsible";
1297
- import { cn } from "@/lib/utils";
1298
-
1299
- const ANIMATION_DURATION = 200;
1300
-
1301
- export type ToolFallbackRootProps = Omit<
1302
- React.ComponentProps<typeof Collapsible>,
1303
- "open" | "onOpenChange"
1304
- > & {
1305
- open?: boolean;
1306
- onOpenChange?: (open: boolean) => void;
1307
- defaultOpen?: boolean;
1308
- };
1309
-
1310
- function ToolFallbackRoot({
1311
- className,
1312
- open: controlledOpen,
1313
- onOpenChange: controlledOnOpenChange,
1314
- defaultOpen = false,
1315
- children,
1316
- ...props
1317
- }: ToolFallbackRootProps) {
1318
- const collapsibleRef = useRef<HTMLDivElement>(null);
1319
- const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
1320
- const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
1321
-
1322
- const isControlled = controlledOpen !== undefined;
1323
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
1324
-
1325
- const handleOpenChange = useCallback(
1326
- (open: boolean) => {
1327
- if (!open) {
1328
- lockScroll();
1329
- }
1330
- if (!isControlled) {
1331
- setUncontrolledOpen(open);
1332
- }
1333
- controlledOnOpenChange?.(open);
1334
- },
1335
- [lockScroll, isControlled, controlledOnOpenChange],
1336
- );
1337
-
1338
- return (
1339
- <Collapsible
1340
- ref={collapsibleRef}
1341
- data-slot="tool-fallback-root"
1342
- open={isOpen}
1343
- onOpenChange={handleOpenChange}
1344
- className={cn(
1345
- "aui-tool-fallback-root group/tool-fallback-root w-full rounded-lg border py-3",
1346
- className,
1347
- )}
1348
- style={
1349
- {
1350
- "--animation-duration": `${ANIMATION_DURATION}ms`,
1351
- } as React.CSSProperties
1352
- }
1353
- {...props}
1354
- >
1355
- {children}
1356
- </Collapsible>
1357
- );
1358
- }
1359
-
1360
- type ToolStatus = ToolCallMessagePartStatus["type"];
1361
-
1362
- const statusIconMap: Record<ToolStatus, React.ElementType> = {
1363
- running: LoaderIcon,
1364
- complete: CheckIcon,
1365
- incomplete: XCircleIcon,
1366
- "requires-action": AlertCircleIcon,
1367
- };
1368
-
1369
- function ToolFallbackTrigger({
1370
- toolName,
1371
- status,
1372
- className,
1373
- ...props
1374
- }: React.ComponentProps<typeof CollapsibleTrigger> & {
1375
- toolName: string;
1376
- status?: ToolCallMessagePartStatus;
1377
- }) {
1378
- const statusType = status?.type ?? "complete";
1379
- const isRunning = statusType === "running";
1380
- const isCancelled =
1381
- status?.type === "incomplete" && status.reason === "cancelled";
1382
-
1383
- const Icon = statusIconMap[statusType];
1384
- const label = isCancelled ? "Cancelled tool" : "Used tool";
1385
-
1386
- return (
1387
- <CollapsibleTrigger
1388
- data-slot="tool-fallback-trigger"
1389
- className={cn(
1390
- "aui-tool-fallback-trigger group/trigger flex w-full items-center gap-2 px-4 text-sm transition-colors",
1391
- className,
1392
- )}
1393
- {...props}
1394
- >
1395
- <Icon
1396
- data-slot="tool-fallback-trigger-icon"
1397
- className={cn(
1398
- "aui-tool-fallback-trigger-icon size-4 shrink-0",
1399
- isCancelled && "text-muted-foreground",
1400
- isRunning && "animate-spin",
1401
- )}
1402
- />
1403
- <span
1404
- data-slot="tool-fallback-trigger-label"
1405
- className={cn(
1406
- "aui-tool-fallback-trigger-label-wrapper relative inline-block grow text-left leading-none",
1407
- isCancelled && "text-muted-foreground line-through",
1408
- )}
1409
- >
1410
- <span>
1411
- {label}: <b>{toolName}</b>
1412
- </span>
1413
- {isRunning && (
1414
- <span
1415
- aria-hidden
1416
- data-slot="tool-fallback-trigger-shimmer"
1417
- className="aui-tool-fallback-trigger-shimmer shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none"
1418
- >
1419
- {label}: <b>{toolName}</b>
1420
- </span>
1421
- )}
1422
- </span>
1423
- <ChevronDownIcon
1424
- data-slot="tool-fallback-trigger-chevron"
1425
- className={cn(
1426
- "aui-tool-fallback-trigger-chevron size-4 shrink-0",
1427
- "transition-transform duration-(--animation-duration) ease-out",
1428
- "group-data-[state=closed]/trigger:-rotate-90",
1429
- "group-data-[state=open]/trigger:rotate-0",
1430
- )}
1431
- />
1432
- </CollapsibleTrigger>
1433
- );
1434
- }
1435
-
1436
- function ToolFallbackContent({
1437
- className,
1438
- children,
1439
- ...props
1440
- }: React.ComponentProps<typeof CollapsibleContent>) {
1441
- return (
1442
- <CollapsibleContent
1443
- data-slot="tool-fallback-content"
1444
- className={cn(
1445
- "aui-tool-fallback-content relative overflow-hidden text-sm outline-none",
1446
- "group/collapsible-content ease-out",
1447
- "data-[state=closed]:animate-collapsible-up",
1448
- "data-[state=open]:animate-collapsible-down",
1449
- "data-[state=closed]:fill-mode-forwards",
1450
- "data-[state=closed]:pointer-events-none",
1451
- "data-[state=open]:duration-(--animation-duration)",
1452
- "data-[state=closed]:duration-(--animation-duration)",
1453
- className,
1454
- )}
1455
- {...props}
1456
- >
1457
- <div className="mt-3 flex flex-col gap-2 border-t pt-2">{children}</div>
1458
- </CollapsibleContent>
1459
- );
1460
- }
1461
-
1462
- function ToolFallbackArgs({
1463
- argsText,
1464
- className,
1465
- ...props
1466
- }: React.ComponentProps<"div"> & {
1467
- argsText?: string;
1468
- }) {
1469
- if (!argsText) return null;
1470
-
1471
- return (
1472
- <div
1473
- data-slot="tool-fallback-args"
1474
- className={cn("aui-tool-fallback-args px-4", className)}
1475
- {...props}
1476
- >
1477
- <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1478
- {argsText}
1479
- </pre>
1480
- </div>
1481
- );
1482
- }
1483
-
1484
- function ToolFallbackResult({
1485
- result,
1486
- className,
1487
- ...props
1488
- }: React.ComponentProps<"div"> & {
1489
- result?: unknown;
1490
- }) {
1491
- if (result === undefined) return null;
1492
-
1493
- return (
1494
- <div
1495
- data-slot="tool-fallback-result"
1496
- className={cn(
1497
- "aui-tool-fallback-result border-t border-dashed px-4 pt-2",
1498
- className,
1499
- )}
1500
- {...props}
1501
- >
1502
- <p className="aui-tool-fallback-result-header font-semibold">Result:</p>
1503
- <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1504
- {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
1505
- </pre>
1506
- </div>
1507
- );
1508
- }
1509
-
1510
- function ToolFallbackError({
1511
- status,
1512
- className,
1513
- ...props
1514
- }: React.ComponentProps<"div"> & {
1515
- status?: ToolCallMessagePartStatus;
1516
- }) {
1517
- if (status?.type !== "incomplete") return null;
1518
-
1519
- const error = status.error;
1520
- const errorText = error
1521
- ? typeof error === "string"
1522
- ? error
1523
- : JSON.stringify(error)
1524
- : null;
1525
-
1526
- if (!errorText) return null;
1527
-
1528
- const isCancelled = status.reason === "cancelled";
1529
- const headerText = isCancelled ? "Cancelled reason:" : "Error:";
1530
-
1531
- return (
1532
- <div
1533
- data-slot="tool-fallback-error"
1534
- className={cn("aui-tool-fallback-error px-4", className)}
1535
- {...props}
1536
- >
1537
- <p className="aui-tool-fallback-error-header font-semibold text-muted-foreground">
1538
- {headerText}
1539
- </p>
1540
- <p className="aui-tool-fallback-error-reason text-muted-foreground">
1541
- {errorText}
1542
- </p>
1543
- </div>
1544
- );
1545
- }
1546
-
1547
- const ToolFallbackImpl: ToolCallMessagePartComponent = ({
1548
- toolName,
1549
- argsText,
1550
- result,
1551
- status,
1552
- }) => {
1553
- const isCancelled =
1554
- status?.type === "incomplete" && status.reason === "cancelled";
1555
-
1556
- return (
1557
- <ToolFallbackRoot
1558
- className={cn(isCancelled && "border-muted-foreground/30 bg-muted/30")}
1559
- >
1560
- <ToolFallbackTrigger toolName={toolName} status={status} />
1561
- <ToolFallbackContent>
1562
- <ToolFallbackError status={status} />
1563
- <ToolFallbackArgs
1564
- argsText={argsText}
1565
- className={cn(isCancelled && "opacity-60")}
1566
- />
1567
- {!isCancelled && <ToolFallbackResult result={result} />}
1568
- </ToolFallbackContent>
1569
- </ToolFallbackRoot>
1570
- );
1571
- };
1572
-
1573
- const ToolFallback = memo(
1574
- ToolFallbackImpl,
1575
- ) as unknown as ToolCallMessagePartComponent & {
1576
- Root: typeof ToolFallbackRoot;
1577
- Trigger: typeof ToolFallbackTrigger;
1578
- Content: typeof ToolFallbackContent;
1579
- Args: typeof ToolFallbackArgs;
1580
- Result: typeof ToolFallbackResult;
1581
- Error: typeof ToolFallbackError;
1582
- };
1583
-
1584
- ToolFallback.displayName = "ToolFallback";
1585
- ToolFallback.Root = ToolFallbackRoot;
1586
- ToolFallback.Trigger = ToolFallbackTrigger;
1587
- ToolFallback.Content = ToolFallbackContent;
1588
- ToolFallback.Args = ToolFallbackArgs;
1589
- ToolFallback.Result = ToolFallbackResult;
1590
- ToolFallback.Error = ToolFallbackError;
1591
-
1592
- export {
1593
- ToolFallback,
1594
- ToolFallbackRoot,
1595
- ToolFallbackTrigger,
1596
- ToolFallbackContent,
1597
- ToolFallbackArgs,
1598
- ToolFallbackResult,
1599
- ToolFallbackError,
1600
- };
1601
-
1602
- ```
1603
-
1604
- ## components/assistant-ui/tooltip-icon-button.tsx
1605
-
1606
- ```tsx
1607
- "use client";
1608
-
1609
- import { ComponentPropsWithRef, forwardRef } from "react";
1610
- import { Slottable } from "@radix-ui/react-slot";
1611
-
1612
- import {
1613
- Tooltip,
1614
- TooltipContent,
1615
- TooltipTrigger,
1616
- } from "@/components/ui/tooltip";
1617
- import { Button } from "@/components/ui/button";
1618
- import { cn } from "@/lib/utils";
1619
-
1620
- export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1621
- tooltip: string;
1622
- side?: "top" | "bottom" | "left" | "right";
1623
- };
1624
-
1625
- export const TooltipIconButton = forwardRef<
1626
- HTMLButtonElement,
1627
- TooltipIconButtonProps
1628
- >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
1629
- return (
1630
- <Tooltip>
1631
- <TooltipTrigger asChild>
1632
- <Button
1633
- variant="ghost"
1634
- size="icon"
1635
- {...rest}
1636
- className={cn("aui-button-icon size-6 p-1", className)}
1637
- ref={ref}
1638
- >
1639
- <Slottable>{children}</Slottable>
1640
- <span className="aui-sr-only sr-only">{tooltip}</span>
1641
- </Button>
1642
- </TooltipTrigger>
1643
- <TooltipContent side={side}>{tooltip}</TooltipContent>
1644
- </Tooltip>
1645
- );
1646
- });
1647
-
1648
- TooltipIconButton.displayName = "TooltipIconButton";
1649
-
1650
- ```
1651
-
1652
- ## components/ui/avatar.tsx
1653
-
1654
- ```tsx
1655
- "use client";
1656
-
1657
- import * as React from "react";
1658
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
1659
-
1660
- import { cn } from "@/lib/utils";
1661
-
1662
- function Avatar({
1663
- className,
1664
- ...props
1665
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
1666
- return (
1667
- <AvatarPrimitive.Root
1668
- data-slot="avatar"
1669
- className={cn(
1670
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
1671
- className,
1672
- )}
1673
- {...props}
1674
- />
1675
- );
1676
- }
1677
-
1678
- function AvatarImage({
1679
- className,
1680
- ...props
1681
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
1682
- return (
1683
- <AvatarPrimitive.Image
1684
- data-slot="avatar-image"
1685
- className={cn("aspect-square size-full", className)}
1686
- {...props}
1687
- />
1688
- );
1689
- }
1690
-
1691
- function AvatarFallback({
1692
- className,
1693
- ...props
1694
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
1695
- return (
1696
- <AvatarPrimitive.Fallback
1697
- data-slot="avatar-fallback"
1698
- className={cn(
1699
- "flex size-full items-center justify-center rounded-full bg-muted",
1700
- className,
1701
- )}
1702
- {...props}
1703
- />
1704
- );
1705
- }
1706
-
1707
- export { Avatar, AvatarImage, AvatarFallback };
1708
-
1709
- ```
1710
-
1711
- ## components/ui/button.tsx
1712
-
1713
- ```tsx
1714
- import * as React from "react";
1715
- import { Slot } from "@radix-ui/react-slot";
1716
- import { cva, type VariantProps } from "class-variance-authority";
1717
-
1718
- import { cn } from "@/lib/utils";
1719
-
1720
- const buttonVariants = cva(
1721
- "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",
1722
- {
1723
- variants: {
1724
- variant: {
1725
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
1726
- destructive:
1727
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1728
- outline:
1729
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1730
- secondary:
1731
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1732
- ghost:
1733
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1734
- link: "text-primary underline-offset-4 hover:underline",
1735
- },
1736
- size: {
1737
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
1738
- sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1739
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1740
- icon: "size-9",
1741
- "icon-sm": "size-8",
1742
- "icon-lg": "size-10",
1743
- },
1744
- },
1745
- defaultVariants: {
1746
- variant: "default",
1747
- size: "default",
1748
- },
1749
- },
1750
- );
1751
-
1752
- function Button({
1753
- className,
1754
- variant = "default",
1755
- size = "default",
1756
- asChild = false,
1757
- ...props
1758
- }: React.ComponentProps<"button"> &
1759
- VariantProps<typeof buttonVariants> & {
1760
- asChild?: boolean;
1761
- }) {
1762
- const Comp = asChild ? Slot : "button";
1763
-
1764
- return (
1765
- <Comp
1766
- data-slot="button"
1767
- data-variant={variant}
1768
- data-size={size}
1769
- className={cn(buttonVariants({ variant, size, className }))}
1770
- {...props}
1771
- />
1772
- );
1773
- }
1774
-
1775
- export { Button, buttonVariants };
1776
-
1777
- ```
1778
-
1779
- ## components/ui/collapsible.tsx
1780
-
1781
- ```tsx
1782
- "use client";
1783
-
1784
- import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
1785
-
1786
- function Collapsible({
1787
- ...props
1788
- }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
1789
- return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
1790
- }
1791
-
1792
- function CollapsibleTrigger({
1793
- ...props
1794
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
1795
- return (
1796
- <CollapsiblePrimitive.CollapsibleTrigger
1797
- data-slot="collapsible-trigger"
1798
- {...props}
1799
- />
1800
- );
1801
- }
1802
-
1803
- function CollapsibleContent({
1804
- ...props
1805
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
1806
- return (
1807
- <CollapsiblePrimitive.CollapsibleContent
1808
- data-slot="collapsible-content"
1809
- {...props}
1810
- />
1811
- );
1812
- }
1813
-
1814
- export { Collapsible, CollapsibleTrigger, CollapsibleContent };
1815
-
1816
- ```
1817
-
1818
- ## components/ui/dialog.tsx
1819
-
1820
- ```tsx
1821
- "use client";
1822
-
1823
- import * as React from "react";
1824
- import * as DialogPrimitive from "@radix-ui/react-dialog";
1825
- import { XIcon } from "lucide-react";
1826
-
1827
- import { cn } from "@/lib/utils";
1828
-
1829
- function Dialog({
1830
- ...props
1831
- }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1832
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1833
- }
1834
-
1835
- function DialogTrigger({
1836
- ...props
1837
- }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1838
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1839
- }
1840
-
1841
- function DialogPortal({
1842
- ...props
1843
- }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1844
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1845
- }
1846
-
1847
- function DialogClose({
1848
- ...props
1849
- }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1850
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1851
- }
1852
-
1853
- function DialogOverlay({
1854
- className,
1855
- ...props
1856
- }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1857
- return (
1858
- <DialogPrimitive.Overlay
1859
- data-slot="dialog-overlay"
1860
- className={cn(
1861
- "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",
1862
- className,
1863
- )}
1864
- {...props}
1865
- />
1866
- );
1867
- }
1868
-
1869
- function DialogContent({
1870
- className,
1871
- children,
1872
- showCloseButton = true,
1873
- ...props
1874
- }: React.ComponentProps<typeof DialogPrimitive.Content> & {
1875
- showCloseButton?: boolean;
1876
- }) {
1877
- return (
1878
- <DialogPortal data-slot="dialog-portal">
1879
- <DialogOverlay />
1880
- <DialogPrimitive.Content
1881
- data-slot="dialog-content"
1882
- className={cn(
1883
- "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",
1884
- className,
1885
- )}
1886
- {...props}
1887
- >
1888
- {children}
1889
- {showCloseButton && (
1890
- <DialogPrimitive.Close
1891
- data-slot="dialog-close"
1892
- 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"
1893
- >
1894
- <XIcon />
1895
- <span className="sr-only">Close</span>
1896
- </DialogPrimitive.Close>
1897
- )}
1898
- </DialogPrimitive.Content>
1899
- </DialogPortal>
1900
- );
1901
- }
1902
-
1903
- function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1904
- return (
1905
- <div
1906
- data-slot="dialog-header"
1907
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1908
- {...props}
1909
- />
1910
- );
1911
- }
1912
-
1913
- function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1914
- return (
1915
- <div
1916
- data-slot="dialog-footer"
1917
- className={cn(
1918
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1919
- className,
1920
- )}
1921
- {...props}
1922
- />
1923
- );
1924
- }
1925
-
1926
- function DialogTitle({
1927
- className,
1928
- ...props
1929
- }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1930
- return (
1931
- <DialogPrimitive.Title
1932
- data-slot="dialog-title"
1933
- className={cn("font-semibold text-lg leading-none", className)}
1934
- {...props}
1935
- />
1936
- );
1937
- }
1938
-
1939
- function DialogDescription({
1940
- className,
1941
- ...props
1942
- }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1943
- return (
1944
- <DialogPrimitive.Description
1945
- data-slot="dialog-description"
1946
- className={cn("text-muted-foreground text-sm", className)}
1947
- {...props}
1948
- />
1949
- );
1950
- }
1951
-
1952
- export {
1953
- Dialog,
1954
- DialogClose,
1955
- DialogContent,
1956
- DialogDescription,
1957
- DialogFooter,
1958
- DialogHeader,
1959
- DialogOverlay,
1960
- DialogPortal,
1961
- DialogTitle,
1962
- DialogTrigger,
1963
- };
1964
-
1965
- ```
1966
-
1967
- ## components/ui/tooltip.tsx
1968
-
1969
- ```tsx
1970
- "use client";
1971
-
1972
- import * as React from "react";
1973
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1974
-
1975
- import { cn } from "@/lib/utils";
1976
-
1977
- function TooltipProvider({
1978
- delayDuration = 0,
1979
- ...props
1980
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1981
- return (
1982
- <TooltipPrimitive.Provider
1983
- data-slot="tooltip-provider"
1984
- delayDuration={delayDuration}
1985
- {...props}
1986
- />
1987
- );
1988
- }
1989
-
1990
- function Tooltip({
1991
- ...props
1992
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1993
- return (
1994
- <TooltipProvider>
1995
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1996
- </TooltipProvider>
1997
- );
1998
- }
1999
-
2000
- function TooltipTrigger({
2001
- ...props
2002
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
2003
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
2004
- }
2005
-
2006
- function TooltipContent({
2007
- className,
2008
- sideOffset = 0,
2009
- children,
2010
- ...props
2011
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
2012
- return (
2013
- <TooltipPrimitive.Portal>
2014
- <TooltipPrimitive.Content
2015
- data-slot="tooltip-content"
2016
- sideOffset={sideOffset}
2017
- className={cn(
2018
- "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",
2019
- className,
2020
- )}
2021
- {...props}
2022
- >
2023
- {children}
2024
- <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
2025
- </TooltipPrimitive.Content>
2026
- </TooltipPrimitive.Portal>
2027
- );
419
+ "iconLibrary": "lucide",
420
+ "registries": {
421
+ "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
422
+ }
2028
423
  }
2029
424
 
2030
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
2031
-
2032
425
  ```
2033
426
 
2034
427
  ## lib/utils.ts
@@ -2070,9 +463,10 @@ export default nextConfig;
2070
463
  "start": "next start"
2071
464
  },
2072
465
  "dependencies": {
2073
- "@ai-sdk/openai": "^3.0.13",
466
+ "@ai-sdk/openai": "^3.0.25",
2074
467
  "@assistant-ui/react": "workspace:*",
2075
468
  "@assistant-ui/react-markdown": "workspace:*",
469
+ "@assistant-ui/ui": "workspace:*",
2076
470
  "@radix-ui/react-avatar": "^1.1.11",
2077
471
  "@radix-ui/react-collapsible": "^1.1.12",
2078
472
  "@radix-ui/react-dialog": "^1.1.15",
@@ -2080,19 +474,17 @@ export default nextConfig;
2080
474
  "@radix-ui/react-tooltip": "^1.2.8",
2081
475
  "class-variance-authority": "^0.7.1",
2082
476
  "clsx": "^2.1.1",
2083
- "lucide-react": "^0.562.0",
2084
- "next": "^16.1.4",
2085
- "react": "^19.2.3",
2086
- "react-dom": "^19.2.3",
2087
- "remark-gfm": "^4.0.1",
2088
- "tailwind-merge": "^3.4.0",
2089
- "zustand": "^5.0.10"
477
+ "lucide-react": "^0.563.0",
478
+ "next": "^16.1.6",
479
+ "react": "^19.2.4",
480
+ "react-dom": "^19.2.4",
481
+ "tailwind-merge": "^3.4.0"
2090
482
  },
2091
483
  "devDependencies": {
2092
484
  "@assistant-ui/x-buildutils": "workspace:*",
2093
485
  "@tailwindcss/postcss": "^4.1.18",
2094
- "@types/node": "^25.0.9",
2095
- "@types/react": "^19.2.9",
486
+ "@types/node": "^25.2.0",
487
+ "@types/react": "^19.2.10",
2096
488
  "@types/react-dom": "^19.2.3",
2097
489
  "postcss": "^8.5.6",
2098
490
  "tailwindcss": "^4.1.18",
@@ -2110,15 +502,37 @@ export default nextConfig;
2110
502
 
2111
503
  This example demonstrates how to use the parent ID feature in assistant-ui to group related message parts together.
2112
504
 
505
+ ## Quick Start
506
+
507
+ ### Using CLI (Recommended)
508
+
509
+ ```bash
510
+ npx assistant-ui@latest create my-app --example with-parent-id-grouping
511
+ cd my-app
512
+ ```
513
+
514
+ ### Environment Variables
515
+
516
+ Create `.env.local`:
517
+
518
+ ```
519
+ OPENAI_API_KEY=sk-...
520
+ ```
521
+
522
+ ### Run
523
+
524
+ ```bash
525
+ npm install
526
+ npm run dev
527
+ ```
528
+
529
+ Open [http://localhost:3000](http://localhost:3000) to see the example.
530
+
2113
531
  ## Features
2114
532
 
2115
533
  - **Parent ID Support**: Message parts can have a `parentId` field that groups them together
2116
534
  - **Visual Grouping**: Related parts are displayed in collapsible groups
2117
- - **Custom Group Component**: The example shows how to create a custom Group component that:
2118
- - Shows grouped parts in a bordered container
2119
- - Provides expand/collapse functionality
2120
- - Displays meaningful labels for each group
2121
- - Leaves ungrouped parts (without parentId) as-is
535
+ - **Custom Group Component**: Shows grouped parts in a bordered container with expand/collapse functionality
2122
536
 
2123
537
  ## How it works
2124
538
 
@@ -2132,32 +546,9 @@ This example demonstrates how to use the parent ID feature in assistant-ui to gr
2132
546
  }
2133
547
  ```
2134
548
 
2135
- 2. **Grouping Component**: Uses `MessagePrimitive.Unstable_PartsGroupedByParentId` which automatically:
2136
- - Groups parts by their `parentId`
2137
- - Maintains order based on first occurrence
2138
- - Places ungrouped parts after grouped ones
2139
-
2140
- 3. **Custom Rendering**: The `ParentIdGroup` component provides:
2141
- - Collapsible sections for each group
2142
- - Custom styling with borders and backgrounds
2143
- - Meaningful labels based on the parent ID
2144
-
2145
- ## Running the Example
2146
-
2147
- ```bash
2148
- # Install dependencies
2149
- npm install
2150
-
2151
- # Run the development server
2152
- npm run dev
2153
- ```
2154
-
2155
- Open [http://localhost:3000](http://localhost:3000) to see the example.
2156
-
2157
- ## Key Components
549
+ 2. **Grouping Component**: Uses `MessagePrimitive.Unstable_PartsGroupedByParentId` which automatically groups parts by their `parentId`
2158
550
 
2159
- - `MyRuntimeProvider.tsx`: Sets up the external store with dummy messages containing parent IDs
2160
- - `thread.tsx`: Contains the custom `ParentIdGroup` component and uses `Unstable_PartsGroupedByParentId`
551
+ 3. **Custom Rendering**: The `ParentIdGroup` component provides collapsible sections for each group
2161
552
 
2162
553
  ## Use Cases
2163
554
 
@@ -2168,6 +559,11 @@ This pattern is useful for:
2168
559
  - Creating hierarchical content structures
2169
560
  - Showing related content in collapsible sections
2170
561
 
562
+ ## Related Documentation
563
+
564
+ - [assistant-ui Documentation](https://www.assistant-ui.com/docs)
565
+ - [Message Structure Guide](https://www.assistant-ui.com/docs/concepts/messages)
566
+
2171
567
  ```
2172
568
 
2173
569
  ## tsconfig.json
@@ -2176,7 +572,14 @@ This pattern is useful for:
2176
572
  {
2177
573
  "extends": "@assistant-ui/x-buildutils/ts/next",
2178
574
  "compilerOptions": {
2179
- "paths": { "@/*": ["./*"] }
575
+ "paths": {
576
+ "@/*": ["./*"],
577
+ "@/components/assistant-ui/*": [
578
+ "../../packages/ui/src/components/assistant-ui/*"
579
+ ],
580
+ "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
581
+ "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
582
+ }
2180
583
  },
2181
584
  "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2182
585
  "exclude": ["node_modules"]