@assistant-ui/mcp-docs-server 0.1.14 → 0.1.16

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 (57) hide show
  1. package/.docs/organized/code-examples/store-example.md +628 -0
  2. package/.docs/organized/code-examples/with-ag-ui.md +792 -178
  3. package/.docs/organized/code-examples/with-ai-sdk-v5.md +762 -209
  4. package/.docs/organized/code-examples/with-assistant-transport.md +707 -254
  5. package/.docs/organized/code-examples/with-cloud.md +848 -202
  6. package/.docs/organized/code-examples/with-custom-thread-list.md +1855 -0
  7. package/.docs/organized/code-examples/with-external-store.md +788 -172
  8. package/.docs/organized/code-examples/with-ffmpeg.md +796 -196
  9. package/.docs/organized/code-examples/with-langgraph.md +864 -230
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +785 -255
  11. package/.docs/organized/code-examples/with-react-hook-form.md +804 -226
  12. package/.docs/organized/code-examples/with-tanstack.md +1574 -0
  13. package/.docs/raw/blog/2024-07-29-hello/index.mdx +2 -3
  14. package/.docs/raw/docs/api-reference/overview.mdx +6 -6
  15. package/.docs/raw/docs/api-reference/primitives/ActionBar.mdx +85 -4
  16. package/.docs/raw/docs/api-reference/primitives/AssistantIf.mdx +200 -0
  17. package/.docs/raw/docs/api-reference/primitives/Composer.mdx +0 -20
  18. package/.docs/raw/docs/api-reference/primitives/Message.mdx +0 -45
  19. package/.docs/raw/docs/api-reference/primitives/Thread.mdx +0 -50
  20. package/.docs/raw/docs/cli.mdx +396 -0
  21. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +2 -3
  22. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +2 -3
  23. package/.docs/raw/docs/devtools.mdx +2 -3
  24. package/.docs/raw/docs/getting-started.mdx +37 -1109
  25. package/.docs/raw/docs/guides/Attachments.mdx +3 -25
  26. package/.docs/raw/docs/guides/Branching.mdx +1 -1
  27. package/.docs/raw/docs/guides/Speech.mdx +1 -1
  28. package/.docs/raw/docs/guides/ToolUI.mdx +1 -1
  29. package/.docs/raw/docs/legacy/styled/AssistantModal.mdx +2 -3
  30. package/.docs/raw/docs/legacy/styled/Decomposition.mdx +6 -5
  31. package/.docs/raw/docs/legacy/styled/Markdown.mdx +2 -3
  32. package/.docs/raw/docs/legacy/styled/Thread.mdx +2 -3
  33. package/.docs/raw/docs/react-compatibility.mdx +2 -5
  34. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +3 -4
  35. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +3 -6
  36. package/.docs/raw/docs/runtimes/assistant-transport.mdx +891 -0
  37. package/.docs/raw/docs/runtimes/custom/external-store.mdx +2 -3
  38. package/.docs/raw/docs/runtimes/custom/local.mdx +11 -41
  39. package/.docs/raw/docs/runtimes/data-stream.mdx +15 -11
  40. package/.docs/raw/docs/runtimes/langgraph/index.mdx +4 -4
  41. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
  42. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +2 -3
  43. package/.docs/raw/docs/runtimes/langserve.mdx +2 -3
  44. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +2 -3
  45. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +2 -3
  46. package/.docs/raw/docs/ui/AssistantModal.mdx +3 -25
  47. package/.docs/raw/docs/ui/AssistantSidebar.mdx +2 -24
  48. package/.docs/raw/docs/ui/Attachment.mdx +3 -25
  49. package/.docs/raw/docs/ui/Markdown.mdx +2 -24
  50. package/.docs/raw/docs/ui/Mermaid.mdx +2 -24
  51. package/.docs/raw/docs/ui/Reasoning.mdx +2 -24
  52. package/.docs/raw/docs/ui/Scrollbar.mdx +4 -6
  53. package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +3 -47
  54. package/.docs/raw/docs/ui/Thread.mdx +38 -53
  55. package/.docs/raw/docs/ui/ThreadList.mdx +4 -47
  56. package/.docs/raw/docs/ui/ToolFallback.mdx +2 -24
  57. package/package.json +15 -8
@@ -421,6 +421,247 @@ export default function Home() {
421
421
 
422
422
  ```
423
423
 
424
+ ## components/assistant-ui/attachment.tsx
425
+
426
+ ```tsx
427
+ "use client";
428
+
429
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
430
+ import Image from "next/image";
431
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
432
+ import {
433
+ AttachmentPrimitive,
434
+ ComposerPrimitive,
435
+ MessagePrimitive,
436
+ useAssistantState,
437
+ useAssistantApi,
438
+ } from "@assistant-ui/react";
439
+ import { useShallow } from "zustand/shallow";
440
+ import {
441
+ Tooltip,
442
+ TooltipContent,
443
+ TooltipTrigger,
444
+ } from "@/components/ui/tooltip";
445
+ import {
446
+ Dialog,
447
+ DialogTitle,
448
+ DialogContent,
449
+ DialogTrigger,
450
+ } from "@/components/ui/dialog";
451
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
452
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
453
+ import { cn } from "@/lib/utils";
454
+
455
+ const useFileSrc = (file: File | undefined) => {
456
+ const [src, setSrc] = useState<string | undefined>(undefined);
457
+
458
+ useEffect(() => {
459
+ if (!file) {
460
+ setSrc(undefined);
461
+ return;
462
+ }
463
+
464
+ const objectUrl = URL.createObjectURL(file);
465
+ setSrc(objectUrl);
466
+
467
+ return () => {
468
+ URL.revokeObjectURL(objectUrl);
469
+ };
470
+ }, [file]);
471
+
472
+ return src;
473
+ };
474
+
475
+ const useAttachmentSrc = () => {
476
+ const { file, src } = useAssistantState(
477
+ useShallow(({ attachment }): { file?: File; src?: string } => {
478
+ if (attachment.type !== "image") return {};
479
+ if (attachment.file) return { file: attachment.file };
480
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
481
+ ?.image;
482
+ if (!src) return {};
483
+ return { src };
484
+ }),
485
+ );
486
+
487
+ return useFileSrc(file) ?? src;
488
+ };
489
+
490
+ type AttachmentPreviewProps = {
491
+ src: string;
492
+ };
493
+
494
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
495
+ const [isLoaded, setIsLoaded] = useState(false);
496
+ return (
497
+ <Image
498
+ src={src}
499
+ alt="Image Preview"
500
+ width={1}
501
+ height={1}
502
+ className={
503
+ isLoaded
504
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
505
+ : "aui-attachment-preview-image-loading hidden"
506
+ }
507
+ onLoadingComplete={() => setIsLoaded(true)}
508
+ priority={false}
509
+ />
510
+ );
511
+ };
512
+
513
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
514
+ const src = useAttachmentSrc();
515
+
516
+ if (!src) return children;
517
+
518
+ return (
519
+ <Dialog>
520
+ <DialogTrigger
521
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
522
+ asChild
523
+ >
524
+ {children}
525
+ </DialogTrigger>
526
+ <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">
527
+ <DialogTitle className="aui-sr-only sr-only">
528
+ Image Attachment Preview
529
+ </DialogTitle>
530
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
531
+ <AttachmentPreview src={src} />
532
+ </div>
533
+ </DialogContent>
534
+ </Dialog>
535
+ );
536
+ };
537
+
538
+ const AttachmentThumb: FC = () => {
539
+ const isImage = useAssistantState(
540
+ ({ attachment }) => attachment.type === "image",
541
+ );
542
+ const src = useAttachmentSrc();
543
+
544
+ return (
545
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
546
+ <AvatarImage
547
+ src={src}
548
+ alt="Attachment preview"
549
+ className="aui-attachment-tile-image object-cover"
550
+ />
551
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
552
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
553
+ </AvatarFallback>
554
+ </Avatar>
555
+ );
556
+ };
557
+
558
+ const AttachmentUI: FC = () => {
559
+ const api = useAssistantApi();
560
+ const isComposer = api.attachment.source === "composer";
561
+
562
+ const isImage = useAssistantState(
563
+ ({ attachment }) => attachment.type === "image",
564
+ );
565
+ const typeLabel = useAssistantState(({ attachment }) => {
566
+ const type = attachment.type;
567
+ switch (type) {
568
+ case "image":
569
+ return "Image";
570
+ case "document":
571
+ return "Document";
572
+ case "file":
573
+ return "File";
574
+ default:
575
+ const _exhaustiveCheck: never = type;
576
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
577
+ }
578
+ });
579
+
580
+ return (
581
+ <Tooltip>
582
+ <AttachmentPrimitive.Root
583
+ className={cn(
584
+ "aui-attachment-root relative",
585
+ isImage &&
586
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
587
+ )}
588
+ >
589
+ <AttachmentPreviewDialog>
590
+ <TooltipTrigger asChild>
591
+ <div
592
+ className={cn(
593
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
594
+ isComposer &&
595
+ "aui-attachment-tile-composer border-foreground/20",
596
+ )}
597
+ role="button"
598
+ id="attachment-tile"
599
+ aria-label={`${typeLabel} attachment`}
600
+ >
601
+ <AttachmentThumb />
602
+ </div>
603
+ </TooltipTrigger>
604
+ </AttachmentPreviewDialog>
605
+ {isComposer && <AttachmentRemove />}
606
+ </AttachmentPrimitive.Root>
607
+ <TooltipContent side="top">
608
+ <AttachmentPrimitive.Name />
609
+ </TooltipContent>
610
+ </Tooltip>
611
+ );
612
+ };
613
+
614
+ const AttachmentRemove: FC = () => {
615
+ return (
616
+ <AttachmentPrimitive.Remove asChild>
617
+ <TooltipIconButton
618
+ tooltip="Remove file"
619
+ 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"
620
+ side="top"
621
+ >
622
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
623
+ </TooltipIconButton>
624
+ </AttachmentPrimitive.Remove>
625
+ );
626
+ };
627
+
628
+ export const UserMessageAttachments: FC = () => {
629
+ return (
630
+ <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">
631
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
632
+ </div>
633
+ );
634
+ };
635
+
636
+ export const ComposerAttachments: FC = () => {
637
+ return (
638
+ <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">
639
+ <ComposerPrimitive.Attachments
640
+ components={{ Attachment: AttachmentUI }}
641
+ />
642
+ </div>
643
+ );
644
+ };
645
+
646
+ export const ComposerAddAttachment: FC = () => {
647
+ return (
648
+ <ComposerPrimitive.AddAttachment asChild>
649
+ <TooltipIconButton
650
+ tooltip="Add Attachment"
651
+ side="bottom"
652
+ variant="ghost"
653
+ size="icon"
654
+ className="aui-composer-add-attachment size-[34px] rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
655
+ aria-label="Add Attachment"
656
+ >
657
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
658
+ </TooltipIconButton>
659
+ </ComposerPrimitive.AddAttachment>
660
+ );
661
+ };
662
+
663
+ ```
664
+
424
665
  ## components/assistant-ui/markdown-text.tsx
425
666
 
426
667
  ```tsx
@@ -429,13 +670,13 @@ export default function Home() {
429
670
  import "@assistant-ui/react-markdown/styles/dot.css";
430
671
 
431
672
  import {
432
- CodeHeaderProps,
673
+ type CodeHeaderProps,
433
674
  MarkdownTextPrimitive,
434
675
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
435
676
  useIsMarkdownCodeBlock,
436
677
  } from "@assistant-ui/react-markdown";
437
678
  import remarkGfm from "remark-gfm";
438
- import { FC, memo, useState } from "react";
679
+ import { type FC, memo, useState } from "react";
439
680
  import { CheckIcon, CopyIcon } from "lucide-react";
440
681
 
441
682
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -461,8 +702,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
461
702
  };
462
703
 
463
704
  return (
464
- <div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
465
- <span className="lowercase [&>span]:text-xs">{language}</span>
705
+ <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">
706
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
707
+ {language}
708
+ </span>
466
709
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
467
710
  {!isCopied && <CopyIcon />}
468
711
  {isCopied && <CheckIcon />}
@@ -494,7 +737,7 @@ const defaultComponents = memoizeMarkdownComponents({
494
737
  h1: ({ className, ...props }) => (
495
738
  <h1
496
739
  className={cn(
497
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
740
+ "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
498
741
  className,
499
742
  )}
500
743
  {...props}
@@ -503,7 +746,7 @@ const defaultComponents = memoizeMarkdownComponents({
503
746
  h2: ({ className, ...props }) => (
504
747
  <h2
505
748
  className={cn(
506
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
749
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
507
750
  className,
508
751
  )}
509
752
  {...props}
@@ -512,7 +755,7 @@ const defaultComponents = memoizeMarkdownComponents({
512
755
  h3: ({ className, ...props }) => (
513
756
  <h3
514
757
  className={cn(
515
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
758
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
516
759
  className,
517
760
  )}
518
761
  {...props}
@@ -521,7 +764,7 @@ const defaultComponents = memoizeMarkdownComponents({
521
764
  h4: ({ className, ...props }) => (
522
765
  <h4
523
766
  className={cn(
524
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
767
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
525
768
  className,
526
769
  )}
527
770
  {...props}
@@ -530,7 +773,7 @@ const defaultComponents = memoizeMarkdownComponents({
530
773
  h5: ({ className, ...props }) => (
531
774
  <h5
532
775
  className={cn(
533
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
776
+ "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
534
777
  className,
535
778
  )}
536
779
  {...props}
@@ -538,20 +781,26 @@ const defaultComponents = memoizeMarkdownComponents({
538
781
  ),
539
782
  h6: ({ className, ...props }) => (
540
783
  <h6
541
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
784
+ className={cn(
785
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
786
+ className,
787
+ )}
542
788
  {...props}
543
789
  />
544
790
  ),
545
791
  p: ({ className, ...props }) => (
546
792
  <p
547
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
793
+ className={cn(
794
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
795
+ className,
796
+ )}
548
797
  {...props}
549
798
  />
550
799
  ),
551
800
  a: ({ className, ...props }) => (
552
801
  <a
553
802
  className={cn(
554
- "text-primary font-medium underline underline-offset-4",
803
+ "aui-md-a font-medium text-primary underline underline-offset-4",
555
804
  className,
556
805
  )}
557
806
  {...props}
@@ -559,29 +808,29 @@ const defaultComponents = memoizeMarkdownComponents({
559
808
  ),
560
809
  blockquote: ({ className, ...props }) => (
561
810
  <blockquote
562
- className={cn("border-l-2 pl-6 italic", className)}
811
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
563
812
  {...props}
564
813
  />
565
814
  ),
566
815
  ul: ({ className, ...props }) => (
567
816
  <ul
568
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
817
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
569
818
  {...props}
570
819
  />
571
820
  ),
572
821
  ol: ({ className, ...props }) => (
573
822
  <ol
574
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
823
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
575
824
  {...props}
576
825
  />
577
826
  ),
578
827
  hr: ({ className, ...props }) => (
579
- <hr className={cn("my-5 border-b", className)} {...props} />
828
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
580
829
  ),
581
830
  table: ({ className, ...props }) => (
582
831
  <table
583
832
  className={cn(
584
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
833
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
585
834
  className,
586
835
  )}
587
836
  {...props}
@@ -590,7 +839,7 @@ const defaultComponents = memoizeMarkdownComponents({
590
839
  th: ({ className, ...props }) => (
591
840
  <th
592
841
  className={cn(
593
- "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",
842
+ "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",
594
843
  className,
595
844
  )}
596
845
  {...props}
@@ -599,7 +848,7 @@ const defaultComponents = memoizeMarkdownComponents({
599
848
  td: ({ className, ...props }) => (
600
849
  <td
601
850
  className={cn(
602
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
851
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
603
852
  className,
604
853
  )}
605
854
  {...props}
@@ -608,7 +857,7 @@ const defaultComponents = memoizeMarkdownComponents({
608
857
  tr: ({ className, ...props }) => (
609
858
  <tr
610
859
  className={cn(
611
- "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",
860
+ "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",
612
861
  className,
613
862
  )}
614
863
  {...props}
@@ -616,14 +865,14 @@ const defaultComponents = memoizeMarkdownComponents({
616
865
  ),
617
866
  sup: ({ className, ...props }) => (
618
867
  <sup
619
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
868
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
620
869
  {...props}
621
870
  />
622
871
  ),
623
872
  pre: ({ className, ...props }) => (
624
873
  <pre
625
874
  className={cn(
626
- "overflow-x-auto rounded-b-lg bg-black p-4 text-white",
875
+ "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
627
876
  className,
628
877
  )}
629
878
  {...props}
@@ -634,7 +883,8 @@ const defaultComponents = memoizeMarkdownComponents({
634
883
  return (
635
884
  <code
636
885
  className={cn(
637
- !isCodeBlock && "bg-muted rounded border font-semibold",
886
+ !isCodeBlock &&
887
+ "aui-md-inline-code rounded border bg-muted font-semibold",
638
888
  className,
639
889
  )}
640
890
  {...props}
@@ -649,60 +899,67 @@ const defaultComponents = memoizeMarkdownComponents({
649
899
  ## components/assistant-ui/thread.tsx
650
900
 
651
901
  ```tsx
902
+ import {
903
+ ComposerAddAttachment,
904
+ ComposerAttachments,
905
+ UserMessageAttachments,
906
+ } from "@/components/assistant-ui/attachment";
907
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
908
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
909
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
910
+ import { Button } from "@/components/ui/button";
911
+ import { cn } from "@/lib/utils";
652
912
  import {
653
913
  ActionBarPrimitive,
914
+ AssistantIf,
654
915
  BranchPickerPrimitive,
655
916
  ComposerPrimitive,
917
+ ErrorPrimitive,
656
918
  MessagePrimitive,
657
919
  ThreadPrimitive,
658
920
  } from "@assistant-ui/react";
659
- import type { FC, PropsWithChildren } from "react";
660
921
  import {
661
922
  ArrowDownIcon,
923
+ ArrowUpIcon,
662
924
  CheckIcon,
663
925
  ChevronLeftIcon,
664
926
  ChevronRightIcon,
665
927
  CopyIcon,
928
+ DownloadIcon,
666
929
  PencilIcon,
667
930
  RefreshCwIcon,
668
- SendHorizontalIcon,
669
- ChevronDownIcon,
670
- ChevronUpIcon,
931
+ SquareIcon,
671
932
  } from "lucide-react";
672
- import { cn } from "@/lib/utils";
673
-
674
- import { Button } from "@/components/ui/button";
675
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
676
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
677
- import { useState } from "react";
933
+ import type { FC } from "react";
678
934
 
679
935
  export const Thread: FC = () => {
680
936
  return (
681
937
  <ThreadPrimitive.Root
682
- className="bg-background box-border flex h-full flex-col overflow-hidden"
938
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
683
939
  style={{
684
- ["--thread-max-width" as string]: "42rem",
940
+ ["--thread-max-width" as string]: "44rem",
685
941
  }}
686
942
  >
687
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
688
- <ThreadWelcome />
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>
689
950
 
690
951
  <ThreadPrimitive.Messages
691
952
  components={{
692
- UserMessage: UserMessage,
693
- EditComposer: EditComposer,
694
- AssistantMessage: AssistantMessage,
953
+ UserMessage,
954
+ EditComposer,
955
+ AssistantMessage,
695
956
  }}
696
957
  />
697
958
 
698
- <ThreadPrimitive.If empty={false}>
699
- <div className="min-h-8 flex-grow" />
700
- </ThreadPrimitive.If>
701
-
702
- <div className="sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit pb-4">
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">
703
960
  <ThreadScrollToBottom />
704
961
  <Composer />
705
- </div>
962
+ </ThreadPrimitive.ViewportFooter>
706
963
  </ThreadPrimitive.Viewport>
707
964
  </ThreadPrimitive.Root>
708
965
  );
@@ -714,7 +971,7 @@ const ThreadScrollToBottom: FC = () => {
714
971
  <TooltipIconButton
715
972
  tooltip="Scroll to bottom"
716
973
  variant="outline"
717
- className="absolute -top-8 rounded-full disabled:invisible"
974
+ className="aui-thread-scroll-to-bottom -top-12 absolute z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
718
975
  >
719
976
  <ArrowDownIcon />
720
977
  </TooltipIconButton>
@@ -724,227 +981,150 @@ const ThreadScrollToBottom: FC = () => {
724
981
 
725
982
  const ThreadWelcome: FC = () => {
726
983
  return (
727
- <ThreadPrimitive.Empty>
728
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
729
- <div className="flex w-full flex-grow flex-col items-center justify-center">
730
- <p className="mt-4 font-medium">Parent ID Grouping Demo</p>
731
- <p className="text-muted-foreground mt-2 text-sm">
732
- This example demonstrates how message parts can be grouped by parent
733
- ID
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?
734
992
  </p>
735
993
  </div>
736
- <ThreadWelcomeSuggestions />
737
994
  </div>
738
- </ThreadPrimitive.Empty>
995
+ <ThreadSuggestions />
996
+ </div>
739
997
  );
740
998
  };
741
999
 
742
- const ThreadWelcomeSuggestions: FC = () => {
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 = () => {
743
1014
  return (
744
- <div className="mt-3 flex w-full items-stretch justify-center gap-4">
745
- <ThreadPrimitive.Suggestion
746
- className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
747
- prompt="Tell me more about this"
748
- method="replace"
749
- autoSend
750
- >
751
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
752
- Tell me more about this
753
- </span>
754
- </ThreadPrimitive.Suggestion>
755
- <ThreadPrimitive.Suggestion
756
- className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
757
- prompt="What are the latest updates?"
758
- method="replace"
759
- autoSend
760
- >
761
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
762
- What are the latest updates?
763
- </span>
764
- </ThreadPrimitive.Suggestion>
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
+ ))}
765
1038
  </div>
766
1039
  );
767
1040
  };
768
1041
 
769
1042
  const Composer: FC = () => {
770
1043
  return (
771
- <ComposerPrimitive.Root className="focus-within:border-ring/20 flex w-full flex-wrap items-end rounded-lg border bg-inherit px-2.5 shadow-sm transition-colors ease-in">
772
- <ComposerPrimitive.Input
773
- rows={1}
774
- autoFocus
775
- placeholder="Write a message..."
776
- className="placeholder:text-muted-foreground max-h-40 flex-grow resize-none border-none bg-transparent px-2 py-4 text-sm outline-none focus:ring-0 disabled:cursor-not-allowed"
777
- />
778
- <ComposerAction />
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>
779
1056
  </ComposerPrimitive.Root>
780
1057
  );
781
1058
  };
782
1059
 
783
1060
  const ComposerAction: FC = () => {
784
1061
  return (
785
- <>
786
- <ThreadPrimitive.If running={false}>
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}>
787
1066
  <ComposerPrimitive.Send asChild>
788
1067
  <TooltipIconButton
789
- tooltip="Send"
1068
+ tooltip="Send message"
1069
+ side="bottom"
1070
+ type="submit"
790
1071
  variant="default"
791
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
1072
+ size="icon"
1073
+ className="aui-composer-send size-8 rounded-full"
1074
+ aria-label="Send message"
792
1075
  >
793
- <SendHorizontalIcon />
1076
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
794
1077
  </TooltipIconButton>
795
1078
  </ComposerPrimitive.Send>
796
- </ThreadPrimitive.If>
797
- <ThreadPrimitive.If running>
1079
+ </AssistantIf>
1080
+
1081
+ <AssistantIf condition={({ thread }) => thread.isRunning}>
798
1082
  <ComposerPrimitive.Cancel asChild>
799
- <TooltipIconButton
800
- tooltip="Cancel"
1083
+ <Button
1084
+ type="button"
801
1085
  variant="default"
802
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
1086
+ size="icon"
1087
+ className="aui-composer-cancel size-8 rounded-full"
1088
+ aria-label="Stop generating"
803
1089
  >
804
- <CircleStopIcon />
805
- </TooltipIconButton>
1090
+ <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1091
+ </Button>
806
1092
  </ComposerPrimitive.Cancel>
807
- </ThreadPrimitive.If>
808
- </>
809
- );
810
- };
811
-
812
- const UserMessage: FC = () => {
813
- return (
814
- <MessagePrimitive.Root className="grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 py-4 [&:where(>*)]:col-start-2">
815
- <UserActionBar />
816
-
817
- <div className="bg-muted text-foreground col-start-2 row-start-2 max-w-[calc(var(--thread-max-width)*0.8)] rounded-3xl px-5 py-2.5 break-words">
818
- <MessagePrimitive.Parts />
819
- </div>
820
-
821
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
822
- </MessagePrimitive.Root>
823
- );
824
- };
825
-
826
- const UserActionBar: FC = () => {
827
- return (
828
- <ActionBarPrimitive.Root
829
- hideWhenRunning
830
- autohide="not-last"
831
- className="col-start-1 row-start-2 mt-2.5 mr-3 flex flex-col items-end"
832
- >
833
- <ActionBarPrimitive.Edit asChild>
834
- <TooltipIconButton tooltip="Edit">
835
- <PencilIcon />
836
- </TooltipIconButton>
837
- </ActionBarPrimitive.Edit>
838
- </ActionBarPrimitive.Root>
839
- );
840
- };
841
-
842
- const EditComposer: FC = () => {
843
- return (
844
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
845
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
846
-
847
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
848
- <ComposerPrimitive.Cancel asChild>
849
- <Button variant="ghost">Cancel</Button>
850
- </ComposerPrimitive.Cancel>
851
- <ComposerPrimitive.Send asChild>
852
- <Button>Send</Button>
853
- </ComposerPrimitive.Send>
854
- </div>
855
- </ComposerPrimitive.Root>
1093
+ </AssistantIf>
1094
+ </div>
856
1095
  );
857
1096
  };
858
1097
 
859
- // Custom Group component for parent ID grouping
860
- const ParentIdGroup: FC<
861
- PropsWithChildren<{ groupKey: string | undefined; indices: number[] }>
862
- > = ({ groupKey, indices, children }) => {
863
- const [isCollapsed, setIsCollapsed] = useState(false);
864
-
865
- if (!groupKey) {
866
- // Ungrouped parts - just render them directly
867
- return <>{children}</>;
868
- }
869
-
1098
+ const MessageError: FC = () => {
870
1099
  return (
871
- <div className="border-border/50 bg-muted/20 my-2 overflow-hidden rounded-lg border">
872
- <button
873
- onClick={() => setIsCollapsed(!isCollapsed)}
874
- className="hover:bg-muted/40 flex w-full items-center justify-between px-4 py-2 text-sm font-medium transition-colors"
875
- >
876
- <span className="flex items-center gap-2">
877
- <span className="text-muted-foreground">Research Group:</span>
878
- <span className="text-foreground">
879
- {groupKey === "research-climate-causes" && "Climate Change Causes"}
880
- {groupKey === "research-climate-effects" &&
881
- "Climate Change Effects"}
882
- {groupKey === "new-research" && "Recent Research"}
883
- {![
884
- "research-climate-causes",
885
- "research-climate-effects",
886
- "new-research",
887
- ].includes(groupKey) && groupKey}
888
- </span>
889
- <span className="text-muted-foreground text-xs">
890
- ({indices.length} parts)
891
- </span>
892
- </span>
893
- {isCollapsed ? (
894
- <ChevronDownIcon className="h-4 w-4" />
895
- ) : (
896
- <ChevronUpIcon className="h-4 w-4" />
897
- )}
898
- </button>
899
- {!isCollapsed && <div className="space-y-2 px-4 py-2">{children}</div>}
900
- </div>
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>
901
1105
  );
902
1106
  };
903
1107
 
904
1108
  const AssistantMessage: FC = () => {
905
1109
  return (
906
- <MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
907
- <div className="text-foreground col-span-2 col-start-2 row-start-1 my-1.5 max-w-[calc(var(--thread-max-width)*0.8)] leading-7 break-words">
908
- <MessagePrimitive.Unstable_PartsGroupedByParentId
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
909
1116
  components={{
910
1117
  Text: MarkdownText,
911
- Group: ParentIdGroup,
912
- Source: ({ url, title }) => (
913
- <div className="text-muted-foreground text-sm">
914
- <a
915
- href={url}
916
- target="_blank"
917
- rel="noopener noreferrer"
918
- className="hover:underline"
919
- >
920
- 📄 {title || url}
921
- </a>
922
- </div>
923
- ),
924
- tools: {
925
- Fallback: ({ toolName, args, result }) => (
926
- <div className="bg-muted/40 my-1 rounded-md p-2 text-sm">
927
- <div className="text-muted-foreground font-medium">
928
- 🔧 {toolName}
929
- </div>
930
- <div className="text-muted-foreground mt-1 text-xs">
931
- <details>
932
- <summary className="cursor-pointer">View details</summary>
933
- <pre className="mt-2 overflow-x-auto">
934
- {JSON.stringify({ args, result }, null, 2)}
935
- </pre>
936
- </details>
937
- </div>
938
- </div>
939
- ),
940
- },
1118
+ tools: { Fallback: ToolFallback },
941
1119
  }}
942
1120
  />
1121
+ <MessageError />
943
1122
  </div>
944
1123
 
945
- <AssistantActionBar />
946
-
947
- <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
1124
+ <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1125
+ <BranchPicker />
1126
+ <AssistantActionBar />
1127
+ </div>
948
1128
  </MessagePrimitive.Root>
949
1129
  );
950
1130
  };
@@ -955,18 +1135,23 @@ const AssistantActionBar: FC = () => {
955
1135
  hideWhenRunning
956
1136
  autohide="not-last"
957
1137
  autohideFloat="single-branch"
958
- className="text-muted-foreground data-[floating]:bg-background col-start-3 row-start-2 -ml-1 flex gap-1 data-[floating]:absolute data-[floating]:rounded-md data-[floating]:border data-[floating]:p-1 data-[floating]:shadow-sm"
1138
+ className="aui-assistant-action-bar-root -ml-1 col-start-3 row-start-2 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"
959
1139
  >
960
1140
  <ActionBarPrimitive.Copy asChild>
961
1141
  <TooltipIconButton tooltip="Copy">
962
- <MessagePrimitive.If copied>
1142
+ <AssistantIf condition={({ message }) => message.isCopied}>
963
1143
  <CheckIcon />
964
- </MessagePrimitive.If>
965
- <MessagePrimitive.If copied={false}>
1144
+ </AssistantIf>
1145
+ <AssistantIf condition={({ message }) => !message.isCopied}>
966
1146
  <CopyIcon />
967
- </MessagePrimitive.If>
1147
+ </AssistantIf>
968
1148
  </TooltipIconButton>
969
1149
  </ActionBarPrimitive.Copy>
1150
+ <ActionBarPrimitive.ExportMarkdown asChild>
1151
+ <TooltipIconButton tooltip="Export as Markdown">
1152
+ <DownloadIcon />
1153
+ </TooltipIconButton>
1154
+ </ActionBarPrimitive.ExportMarkdown>
970
1155
  <ActionBarPrimitive.Reload asChild>
971
1156
  <TooltipIconButton tooltip="Refresh">
972
1157
  <RefreshCwIcon />
@@ -976,6 +1161,67 @@ const AssistantActionBar: FC = () => {
976
1161
  );
977
1162
  };
978
1163
 
1164
+ const UserMessage: FC = () => {
1165
+ return (
1166
+ <MessagePrimitive.Root
1167
+ 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"
1168
+ data-role="user"
1169
+ >
1170
+ <UserMessageAttachments />
1171
+
1172
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1173
+ <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1174
+ <MessagePrimitive.Parts />
1175
+ </div>
1176
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
1177
+ <UserActionBar />
1178
+ </div>
1179
+ </div>
1180
+
1181
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
1182
+ </MessagePrimitive.Root>
1183
+ );
1184
+ };
1185
+
1186
+ const UserActionBar: FC = () => {
1187
+ return (
1188
+ <ActionBarPrimitive.Root
1189
+ hideWhenRunning
1190
+ autohide="not-last"
1191
+ className="aui-user-action-bar-root flex flex-col items-end"
1192
+ >
1193
+ <ActionBarPrimitive.Edit asChild>
1194
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1195
+ <PencilIcon />
1196
+ </TooltipIconButton>
1197
+ </ActionBarPrimitive.Edit>
1198
+ </ActionBarPrimitive.Root>
1199
+ );
1200
+ };
1201
+
1202
+ const EditComposer: FC = () => {
1203
+ return (
1204
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1205
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1206
+ <ComposerPrimitive.Input
1207
+ className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1208
+ autoFocus
1209
+ />
1210
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1211
+ <ComposerPrimitive.Cancel asChild>
1212
+ <Button variant="ghost" size="sm">
1213
+ Cancel
1214
+ </Button>
1215
+ </ComposerPrimitive.Cancel>
1216
+ <ComposerPrimitive.Send asChild>
1217
+ <Button size="sm">Update</Button>
1218
+ </ComposerPrimitive.Send>
1219
+ </div>
1220
+ </ComposerPrimitive.Root>
1221
+ </MessagePrimitive.Root>
1222
+ );
1223
+ };
1224
+
979
1225
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
980
1226
  className,
981
1227
  ...rest
@@ -984,7 +1230,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
984
1230
  <BranchPickerPrimitive.Root
985
1231
  hideWhenSingleBranch
986
1232
  className={cn(
987
- "text-muted-foreground inline-flex items-center text-xs",
1233
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
988
1234
  className,
989
1235
  )}
990
1236
  {...rest}
@@ -994,7 +1240,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
994
1240
  <ChevronLeftIcon />
995
1241
  </TooltipIconButton>
996
1242
  </BranchPickerPrimitive.Previous>
997
- <span className="font-medium">
1243
+ <span className="aui-branch-picker-state font-medium">
998
1244
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
999
1245
  </span>
1000
1246
  <BranchPickerPrimitive.Next asChild>
@@ -1006,17 +1252,102 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1006
1252
  );
1007
1253
  };
1008
1254
 
1009
- const CircleStopIcon = () => {
1255
+ ```
1256
+
1257
+ ## components/assistant-ui/tool-fallback.tsx
1258
+
1259
+ ```tsx
1260
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1261
+ import {
1262
+ CheckIcon,
1263
+ ChevronDownIcon,
1264
+ ChevronUpIcon,
1265
+ XCircleIcon,
1266
+ } from "lucide-react";
1267
+ import { useState } from "react";
1268
+ import { Button } from "@/components/ui/button";
1269
+ import { cn } from "@/lib/utils";
1270
+
1271
+ export const ToolFallback: ToolCallMessagePartComponent = ({
1272
+ toolName,
1273
+ argsText,
1274
+ result,
1275
+ status,
1276
+ }) => {
1277
+ const [isCollapsed, setIsCollapsed] = useState(true);
1278
+
1279
+ const isCancelled =
1280
+ status?.type === "incomplete" && status.reason === "cancelled";
1281
+ const cancelledReason =
1282
+ isCancelled && status.error
1283
+ ? typeof status.error === "string"
1284
+ ? status.error
1285
+ : JSON.stringify(status.error)
1286
+ : null;
1287
+
1010
1288
  return (
1011
- <svg
1012
- xmlns="http://www.w3.org/2000/svg"
1013
- viewBox="0 0 16 16"
1014
- fill="currentColor"
1015
- width="16"
1016
- height="16"
1289
+ <div
1290
+ className={cn(
1291
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1292
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1293
+ )}
1017
1294
  >
1018
- <rect width="10" height="10" x="3" y="3" rx="2" />
1019
- </svg>
1295
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1296
+ {isCancelled ? (
1297
+ <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1298
+ ) : (
1299
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1300
+ )}
1301
+ <p
1302
+ className={cn(
1303
+ "aui-tool-fallback-title grow",
1304
+ isCancelled && "text-muted-foreground line-through",
1305
+ )}
1306
+ >
1307
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1308
+ <b>{toolName}</b>
1309
+ </p>
1310
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1311
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1312
+ </Button>
1313
+ </div>
1314
+ {!isCollapsed && (
1315
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1316
+ {cancelledReason && (
1317
+ <div className="aui-tool-fallback-cancelled-root px-4">
1318
+ <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1319
+ Cancelled reason:
1320
+ </p>
1321
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1322
+ {cancelledReason}
1323
+ </p>
1324
+ </div>
1325
+ )}
1326
+ <div
1327
+ className={cn(
1328
+ "aui-tool-fallback-args-root px-4",
1329
+ isCancelled && "opacity-60",
1330
+ )}
1331
+ >
1332
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1333
+ {argsText}
1334
+ </pre>
1335
+ </div>
1336
+ {!isCancelled && result !== undefined && (
1337
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1338
+ <p className="aui-tool-fallback-result-header font-semibold">
1339
+ Result:
1340
+ </p>
1341
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1342
+ {typeof result === "string"
1343
+ ? result
1344
+ : JSON.stringify(result, null, 2)}
1345
+ </pre>
1346
+ </div>
1347
+ )}
1348
+ </div>
1349
+ )}
1350
+ </div>
1020
1351
  );
1021
1352
  };
1022
1353
 
@@ -1027,7 +1358,7 @@ const CircleStopIcon = () => {
1027
1358
  ```tsx
1028
1359
  "use client";
1029
1360
 
1030
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1361
+ import { ComponentPropsWithRef, forwardRef } from "react";
1031
1362
  import { Slottable } from "@radix-ui/react-slot";
1032
1363
 
1033
1364
  import {
@@ -1038,7 +1369,7 @@ import {
1038
1369
  import { Button } from "@/components/ui/button";
1039
1370
  import { cn } from "@/lib/utils";
1040
1371
 
1041
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1372
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1042
1373
  tooltip: string;
1043
1374
  side?: "top" | "bottom" | "left" | "right";
1044
1375
  };
@@ -1054,11 +1385,11 @@ export const TooltipIconButton = forwardRef<
1054
1385
  variant="ghost"
1055
1386
  size="icon"
1056
1387
  {...rest}
1057
- className={cn("size-6 p-1", className)}
1388
+ className={cn("aui-button-icon size-6 p-1", className)}
1058
1389
  ref={ref}
1059
1390
  >
1060
1391
  <Slottable>{children}</Slottable>
1061
- <span className="sr-only">{tooltip}</span>
1392
+ <span className="aui-sr-only sr-only">{tooltip}</span>
1062
1393
  </Button>
1063
1394
  </TooltipTrigger>
1064
1395
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -1070,6 +1401,62 @@ TooltipIconButton.displayName = "TooltipIconButton";
1070
1401
 
1071
1402
  ```
1072
1403
 
1404
+ ## components/ui/avatar.tsx
1405
+
1406
+ ```tsx
1407
+ "use client";
1408
+
1409
+ import * as React from "react";
1410
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1411
+
1412
+ import { cn } from "@/lib/utils";
1413
+
1414
+ const Avatar = React.forwardRef<
1415
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1416
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1417
+ >(({ className, ...props }, ref) => (
1418
+ <AvatarPrimitive.Root
1419
+ ref={ref}
1420
+ className={cn(
1421
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1422
+ className,
1423
+ )}
1424
+ {...props}
1425
+ />
1426
+ ));
1427
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1428
+
1429
+ const AvatarImage = React.forwardRef<
1430
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1431
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1432
+ >(({ className, ...props }, ref) => (
1433
+ <AvatarPrimitive.Image
1434
+ ref={ref}
1435
+ className={cn("aspect-square h-full w-full", className)}
1436
+ {...props}
1437
+ />
1438
+ ));
1439
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1440
+
1441
+ const AvatarFallback = React.forwardRef<
1442
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1443
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1444
+ >(({ className, ...props }, ref) => (
1445
+ <AvatarPrimitive.Fallback
1446
+ ref={ref}
1447
+ className={cn(
1448
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
1449
+ className,
1450
+ )}
1451
+ {...props}
1452
+ />
1453
+ ));
1454
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1455
+
1456
+ export { Avatar, AvatarImage, AvatarFallback };
1457
+
1458
+ ```
1459
+
1073
1460
  ## components/ui/button.tsx
1074
1461
 
1075
1462
  ```tsx
@@ -1082,7 +1469,7 @@ import { cva, type VariantProps } from "class-variance-authority";
1082
1469
  import { cn } from "@/lib/utils";
1083
1470
 
1084
1471
  const buttonVariants = cva(
1085
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
1472
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
1086
1473
  {
1087
1474
  variants: {
1088
1475
  variant: {
@@ -1135,6 +1522,147 @@ export { Button, buttonVariants };
1135
1522
 
1136
1523
  ```
1137
1524
 
1525
+ ## components/ui/dialog.tsx
1526
+
1527
+ ```tsx
1528
+ "use client";
1529
+
1530
+ import * as React from "react";
1531
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1532
+ import { XIcon } from "lucide-react";
1533
+
1534
+ import { cn } from "@/lib/utils";
1535
+
1536
+ function Dialog({
1537
+ ...props
1538
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1539
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1540
+ }
1541
+
1542
+ function DialogTrigger({
1543
+ ...props
1544
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1545
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1546
+ }
1547
+
1548
+ function DialogPortal({
1549
+ ...props
1550
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1551
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1552
+ }
1553
+
1554
+ function DialogClose({
1555
+ ...props
1556
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1557
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1558
+ }
1559
+
1560
+ function DialogOverlay({
1561
+ className,
1562
+ ...props
1563
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1564
+ return (
1565
+ <DialogPrimitive.Overlay
1566
+ data-slot="dialog-overlay"
1567
+ className={cn(
1568
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
1569
+ className,
1570
+ )}
1571
+ {...props}
1572
+ />
1573
+ );
1574
+ }
1575
+
1576
+ function DialogContent({
1577
+ className,
1578
+ children,
1579
+ ...props
1580
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
1581
+ return (
1582
+ <DialogPortal data-slot="dialog-portal">
1583
+ <DialogOverlay />
1584
+ <DialogPrimitive.Content
1585
+ data-slot="dialog-content"
1586
+ className={cn(
1587
+ "data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 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 duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
1588
+ className,
1589
+ )}
1590
+ {...props}
1591
+ >
1592
+ {children}
1593
+ <DialogPrimitive.Close 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">
1594
+ <XIcon />
1595
+ <span className="sr-only">Close</span>
1596
+ </DialogPrimitive.Close>
1597
+ </DialogPrimitive.Content>
1598
+ </DialogPortal>
1599
+ );
1600
+ }
1601
+
1602
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1603
+ return (
1604
+ <div
1605
+ data-slot="dialog-header"
1606
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1607
+ {...props}
1608
+ />
1609
+ );
1610
+ }
1611
+
1612
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1613
+ return (
1614
+ <div
1615
+ data-slot="dialog-footer"
1616
+ className={cn(
1617
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1618
+ className,
1619
+ )}
1620
+ {...props}
1621
+ />
1622
+ );
1623
+ }
1624
+
1625
+ function DialogTitle({
1626
+ className,
1627
+ ...props
1628
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1629
+ return (
1630
+ <DialogPrimitive.Title
1631
+ data-slot="dialog-title"
1632
+ className={cn("font-semibold text-lg leading-none", className)}
1633
+ {...props}
1634
+ />
1635
+ );
1636
+ }
1637
+
1638
+ function DialogDescription({
1639
+ className,
1640
+ ...props
1641
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1642
+ return (
1643
+ <DialogPrimitive.Description
1644
+ data-slot="dialog-description"
1645
+ className={cn("text-muted-foreground text-sm", className)}
1646
+ {...props}
1647
+ />
1648
+ );
1649
+ }
1650
+
1651
+ export {
1652
+ Dialog,
1653
+ DialogClose,
1654
+ DialogContent,
1655
+ DialogDescription,
1656
+ DialogFooter,
1657
+ DialogHeader,
1658
+ DialogOverlay,
1659
+ DialogPortal,
1660
+ DialogTitle,
1661
+ DialogTrigger,
1662
+ };
1663
+
1664
+ ```
1665
+
1138
1666
  ## components/ui/tooltip.tsx
1139
1667
 
1140
1668
  ```tsx
@@ -1186,13 +1714,13 @@ function TooltipContent({
1186
1714
  data-slot="tooltip-content"
1187
1715
  sideOffset={sideOffset}
1188
1716
  className={cn(
1189
- "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-[--radix-tooltip-content-transform-origin] rounded-md px-3 py-1.5 text-xs text-balance",
1717
+ "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-primary px-3 py-1.5 text-primary-foreground text-xs data-[state=closed]:animate-out",
1190
1718
  className,
1191
1719
  )}
1192
1720
  {...props}
1193
1721
  >
1194
1722
  {children}
1195
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1723
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
1196
1724
  </TooltipPrimitive.Content>
1197
1725
  </TooltipPrimitive.Portal>
1198
1726
  );
@@ -1237,24 +1765,26 @@ export default nextConfig;
1237
1765
  "scripts": {
1238
1766
  "dev": "next dev --turbo",
1239
1767
  "build": "next build",
1240
- "start": "next start",
1241
- "lint": "eslint ."
1768
+ "start": "next start"
1242
1769
  },
1243
1770
  "dependencies": {
1244
- "@ai-sdk/openai": "^2.0.68",
1771
+ "@ai-sdk/openai": "^2.0.77",
1245
1772
  "@assistant-ui/react": "workspace:*",
1246
1773
  "@assistant-ui/react-markdown": "workspace:*",
1774
+ "@radix-ui/react-avatar": "^1.1.11",
1775
+ "@radix-ui/react-dialog": "^1.1.15",
1247
1776
  "@radix-ui/react-slot": "^1.2.4",
1248
1777
  "@radix-ui/react-tooltip": "^1.2.8",
1249
1778
  "class-variance-authority": "^0.7.1",
1250
1779
  "clsx": "^2.1.1",
1251
- "lucide-react": "^0.554.0",
1252
- "next": "16.0.3",
1253
- "react": "19.2.0",
1254
- "react-dom": "19.2.0",
1780
+ "lucide-react": "^0.556.0",
1781
+ "next": "16.0.7",
1782
+ "react": "19.2.1",
1783
+ "react-dom": "19.2.1",
1255
1784
  "remark-gfm": "^4.0.1",
1256
1785
  "tailwind-merge": "^3.4.0",
1257
- "tw-animate-css": "^1.4.0"
1786
+ "tw-animate-css": "^1.4.0",
1787
+ "zustand": "^5.0.9"
1258
1788
  },
1259
1789
  "devDependencies": {
1260
1790
  "@assistant-ui/x-buildutils": "workspace:*",