@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
@@ -312,7 +312,7 @@ const FfmpegTool: FC<{ file: File }> = ({ file }) => {
312
312
  load();
313
313
  }, []);
314
314
 
315
- useAssistantInstructions("The user has attached a file: " + file.name);
315
+ useAssistantInstructions(`The user has attached a file: ${file.name}`);
316
316
 
317
317
  useAssistantTool({
318
318
  toolName: "run_ffmpeg",
@@ -357,7 +357,7 @@ const FfmpegTool: FC<{ file: File }> = ({ file }) => {
357
357
  hint:
358
358
  code === 0
359
359
  ? "note: a download button is appearing in the chat for the user"
360
- : "some error happened, logs: " + logs.join("\n"),
360
+ : `some error happened, logs: ${logs.join("\n")}`,
361
361
  };
362
362
  },
363
363
  render: function RenderFfmpeg({
@@ -391,7 +391,7 @@ const FfmpegTool: FC<{ file: File }> = ({ file }) => {
391
391
  )}
392
392
  <p>Running ffmpeg</p>
393
393
  </div>
394
- <pre className="font-sm overflow-y-scroll">
394
+ <pre className="overflow-y-scroll font-sm">
395
395
  ffmpeg {command?.join(" ")}
396
396
  </pre>
397
397
  </div>
@@ -426,11 +426,10 @@ export default function Home() {
426
426
  setLastFile(lastAttachment.file!);
427
427
  }, [attachments]);
428
428
 
429
- console.log(lastFile);
430
429
  return (
431
430
  <div className="flex h-full flex-col">
432
431
  <div className="border-b">
433
- <p className="my-4 ml-8 text-xl font-bold">
432
+ <p className="my-4 ml-8 font-bold text-xl">
434
433
  ConvertGPT (built with{" "}
435
434
  <a
436
435
  href="https://github.com/assistant-ui/assistant-ui"
@@ -472,6 +471,247 @@ export default function Home() {
472
471
 
473
472
  ```
474
473
 
474
+ ## components/assistant-ui/attachment.tsx
475
+
476
+ ```tsx
477
+ "use client";
478
+
479
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
480
+ import Image from "next/image";
481
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
482
+ import {
483
+ AttachmentPrimitive,
484
+ ComposerPrimitive,
485
+ MessagePrimitive,
486
+ useAssistantState,
487
+ useAssistantApi,
488
+ } from "@assistant-ui/react";
489
+ import { useShallow } from "zustand/shallow";
490
+ import {
491
+ Tooltip,
492
+ TooltipContent,
493
+ TooltipTrigger,
494
+ } from "@/components/ui/tooltip";
495
+ import {
496
+ Dialog,
497
+ DialogTitle,
498
+ DialogContent,
499
+ DialogTrigger,
500
+ } from "@/components/ui/dialog";
501
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
502
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
503
+ import { cn } from "@/lib/utils";
504
+
505
+ const useFileSrc = (file: File | undefined) => {
506
+ const [src, setSrc] = useState<string | undefined>(undefined);
507
+
508
+ useEffect(() => {
509
+ if (!file) {
510
+ setSrc(undefined);
511
+ return;
512
+ }
513
+
514
+ const objectUrl = URL.createObjectURL(file);
515
+ setSrc(objectUrl);
516
+
517
+ return () => {
518
+ URL.revokeObjectURL(objectUrl);
519
+ };
520
+ }, [file]);
521
+
522
+ return src;
523
+ };
524
+
525
+ const useAttachmentSrc = () => {
526
+ const { file, src } = useAssistantState(
527
+ useShallow(({ attachment }): { file?: File; src?: string } => {
528
+ if (attachment.type !== "image") return {};
529
+ if (attachment.file) return { file: attachment.file };
530
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
531
+ ?.image;
532
+ if (!src) return {};
533
+ return { src };
534
+ }),
535
+ );
536
+
537
+ return useFileSrc(file) ?? src;
538
+ };
539
+
540
+ type AttachmentPreviewProps = {
541
+ src: string;
542
+ };
543
+
544
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
545
+ const [isLoaded, setIsLoaded] = useState(false);
546
+ return (
547
+ <Image
548
+ src={src}
549
+ alt="Image Preview"
550
+ width={1}
551
+ height={1}
552
+ className={
553
+ isLoaded
554
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
555
+ : "aui-attachment-preview-image-loading hidden"
556
+ }
557
+ onLoadingComplete={() => setIsLoaded(true)}
558
+ priority={false}
559
+ />
560
+ );
561
+ };
562
+
563
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
564
+ const src = useAttachmentSrc();
565
+
566
+ if (!src) return children;
567
+
568
+ return (
569
+ <Dialog>
570
+ <DialogTrigger
571
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
572
+ asChild
573
+ >
574
+ {children}
575
+ </DialogTrigger>
576
+ <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">
577
+ <DialogTitle className="aui-sr-only sr-only">
578
+ Image Attachment Preview
579
+ </DialogTitle>
580
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
581
+ <AttachmentPreview src={src} />
582
+ </div>
583
+ </DialogContent>
584
+ </Dialog>
585
+ );
586
+ };
587
+
588
+ const AttachmentThumb: FC = () => {
589
+ const isImage = useAssistantState(
590
+ ({ attachment }) => attachment.type === "image",
591
+ );
592
+ const src = useAttachmentSrc();
593
+
594
+ return (
595
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
596
+ <AvatarImage
597
+ src={src}
598
+ alt="Attachment preview"
599
+ className="aui-attachment-tile-image object-cover"
600
+ />
601
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
602
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
603
+ </AvatarFallback>
604
+ </Avatar>
605
+ );
606
+ };
607
+
608
+ const AttachmentUI: FC = () => {
609
+ const api = useAssistantApi();
610
+ const isComposer = api.attachment.source === "composer";
611
+
612
+ const isImage = useAssistantState(
613
+ ({ attachment }) => attachment.type === "image",
614
+ );
615
+ const typeLabel = useAssistantState(({ attachment }) => {
616
+ const type = attachment.type;
617
+ switch (type) {
618
+ case "image":
619
+ return "Image";
620
+ case "document":
621
+ return "Document";
622
+ case "file":
623
+ return "File";
624
+ default:
625
+ const _exhaustiveCheck: never = type;
626
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
627
+ }
628
+ });
629
+
630
+ return (
631
+ <Tooltip>
632
+ <AttachmentPrimitive.Root
633
+ className={cn(
634
+ "aui-attachment-root relative",
635
+ isImage &&
636
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
637
+ )}
638
+ >
639
+ <AttachmentPreviewDialog>
640
+ <TooltipTrigger asChild>
641
+ <div
642
+ className={cn(
643
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
644
+ isComposer &&
645
+ "aui-attachment-tile-composer border-foreground/20",
646
+ )}
647
+ role="button"
648
+ id="attachment-tile"
649
+ aria-label={`${typeLabel} attachment`}
650
+ >
651
+ <AttachmentThumb />
652
+ </div>
653
+ </TooltipTrigger>
654
+ </AttachmentPreviewDialog>
655
+ {isComposer && <AttachmentRemove />}
656
+ </AttachmentPrimitive.Root>
657
+ <TooltipContent side="top">
658
+ <AttachmentPrimitive.Name />
659
+ </TooltipContent>
660
+ </Tooltip>
661
+ );
662
+ };
663
+
664
+ const AttachmentRemove: FC = () => {
665
+ return (
666
+ <AttachmentPrimitive.Remove asChild>
667
+ <TooltipIconButton
668
+ tooltip="Remove file"
669
+ 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"
670
+ side="top"
671
+ >
672
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
673
+ </TooltipIconButton>
674
+ </AttachmentPrimitive.Remove>
675
+ );
676
+ };
677
+
678
+ export const UserMessageAttachments: FC = () => {
679
+ return (
680
+ <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">
681
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
682
+ </div>
683
+ );
684
+ };
685
+
686
+ export const ComposerAttachments: FC = () => {
687
+ return (
688
+ <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">
689
+ <ComposerPrimitive.Attachments
690
+ components={{ Attachment: AttachmentUI }}
691
+ />
692
+ </div>
693
+ );
694
+ };
695
+
696
+ export const ComposerAddAttachment: FC = () => {
697
+ return (
698
+ <ComposerPrimitive.AddAttachment asChild>
699
+ <TooltipIconButton
700
+ tooltip="Add Attachment"
701
+ side="bottom"
702
+ variant="ghost"
703
+ size="icon"
704
+ 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"
705
+ aria-label="Add Attachment"
706
+ >
707
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
708
+ </TooltipIconButton>
709
+ </ComposerPrimitive.AddAttachment>
710
+ );
711
+ };
712
+
713
+ ```
714
+
475
715
  ## components/assistant-ui/markdown-text.tsx
476
716
 
477
717
  ```tsx
@@ -480,13 +720,13 @@ export default function Home() {
480
720
  import "@assistant-ui/react-markdown/styles/dot.css";
481
721
 
482
722
  import {
483
- CodeHeaderProps,
723
+ type CodeHeaderProps,
484
724
  MarkdownTextPrimitive,
485
725
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
486
726
  useIsMarkdownCodeBlock,
487
727
  } from "@assistant-ui/react-markdown";
488
728
  import remarkGfm from "remark-gfm";
489
- import { FC, memo, useState } from "react";
729
+ import { type FC, memo, useState } from "react";
490
730
  import { CheckIcon, CopyIcon } from "lucide-react";
491
731
 
492
732
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -512,8 +752,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
512
752
  };
513
753
 
514
754
  return (
515
- <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">
516
- <span className="lowercase [&>span]:text-xs">{language}</span>
755
+ <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">
756
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
757
+ {language}
758
+ </span>
517
759
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
518
760
  {!isCopied && <CopyIcon />}
519
761
  {isCopied && <CheckIcon />}
@@ -545,7 +787,7 @@ const defaultComponents = memoizeMarkdownComponents({
545
787
  h1: ({ className, ...props }) => (
546
788
  <h1
547
789
  className={cn(
548
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
790
+ "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
549
791
  className,
550
792
  )}
551
793
  {...props}
@@ -554,7 +796,7 @@ const defaultComponents = memoizeMarkdownComponents({
554
796
  h2: ({ className, ...props }) => (
555
797
  <h2
556
798
  className={cn(
557
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
799
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
558
800
  className,
559
801
  )}
560
802
  {...props}
@@ -563,7 +805,7 @@ const defaultComponents = memoizeMarkdownComponents({
563
805
  h3: ({ className, ...props }) => (
564
806
  <h3
565
807
  className={cn(
566
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
808
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
567
809
  className,
568
810
  )}
569
811
  {...props}
@@ -572,7 +814,7 @@ const defaultComponents = memoizeMarkdownComponents({
572
814
  h4: ({ className, ...props }) => (
573
815
  <h4
574
816
  className={cn(
575
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
817
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
576
818
  className,
577
819
  )}
578
820
  {...props}
@@ -581,7 +823,7 @@ const defaultComponents = memoizeMarkdownComponents({
581
823
  h5: ({ className, ...props }) => (
582
824
  <h5
583
825
  className={cn(
584
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
826
+ "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
585
827
  className,
586
828
  )}
587
829
  {...props}
@@ -589,20 +831,26 @@ const defaultComponents = memoizeMarkdownComponents({
589
831
  ),
590
832
  h6: ({ className, ...props }) => (
591
833
  <h6
592
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
834
+ className={cn(
835
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
836
+ className,
837
+ )}
593
838
  {...props}
594
839
  />
595
840
  ),
596
841
  p: ({ className, ...props }) => (
597
842
  <p
598
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
843
+ className={cn(
844
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
845
+ className,
846
+ )}
599
847
  {...props}
600
848
  />
601
849
  ),
602
850
  a: ({ className, ...props }) => (
603
851
  <a
604
852
  className={cn(
605
- "text-primary font-medium underline underline-offset-4",
853
+ "aui-md-a font-medium text-primary underline underline-offset-4",
606
854
  className,
607
855
  )}
608
856
  {...props}
@@ -610,29 +858,29 @@ const defaultComponents = memoizeMarkdownComponents({
610
858
  ),
611
859
  blockquote: ({ className, ...props }) => (
612
860
  <blockquote
613
- className={cn("border-l-2 pl-6 italic", className)}
861
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
614
862
  {...props}
615
863
  />
616
864
  ),
617
865
  ul: ({ className, ...props }) => (
618
866
  <ul
619
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
867
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
620
868
  {...props}
621
869
  />
622
870
  ),
623
871
  ol: ({ className, ...props }) => (
624
872
  <ol
625
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
873
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
626
874
  {...props}
627
875
  />
628
876
  ),
629
877
  hr: ({ className, ...props }) => (
630
- <hr className={cn("my-5 border-b", className)} {...props} />
878
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
631
879
  ),
632
880
  table: ({ className, ...props }) => (
633
881
  <table
634
882
  className={cn(
635
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
883
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
636
884
  className,
637
885
  )}
638
886
  {...props}
@@ -641,7 +889,7 @@ const defaultComponents = memoizeMarkdownComponents({
641
889
  th: ({ className, ...props }) => (
642
890
  <th
643
891
  className={cn(
644
- "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",
892
+ "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",
645
893
  className,
646
894
  )}
647
895
  {...props}
@@ -650,7 +898,7 @@ const defaultComponents = memoizeMarkdownComponents({
650
898
  td: ({ className, ...props }) => (
651
899
  <td
652
900
  className={cn(
653
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
901
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
654
902
  className,
655
903
  )}
656
904
  {...props}
@@ -659,7 +907,7 @@ const defaultComponents = memoizeMarkdownComponents({
659
907
  tr: ({ className, ...props }) => (
660
908
  <tr
661
909
  className={cn(
662
- "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",
910
+ "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",
663
911
  className,
664
912
  )}
665
913
  {...props}
@@ -667,14 +915,14 @@ const defaultComponents = memoizeMarkdownComponents({
667
915
  ),
668
916
  sup: ({ className, ...props }) => (
669
917
  <sup
670
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
918
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
671
919
  {...props}
672
920
  />
673
921
  ),
674
922
  pre: ({ className, ...props }) => (
675
923
  <pre
676
924
  className={cn(
677
- "overflow-x-auto rounded-b-lg bg-black p-4 text-white",
925
+ "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
678
926
  className,
679
927
  )}
680
928
  {...props}
@@ -685,7 +933,8 @@ const defaultComponents = memoizeMarkdownComponents({
685
933
  return (
686
934
  <code
687
935
  className={cn(
688
- !isCodeBlock && "bg-muted rounded border font-semibold",
936
+ !isCodeBlock &&
937
+ "aui-md-inline-code rounded border bg-muted font-semibold",
689
938
  className,
690
939
  )}
691
940
  {...props}
@@ -700,57 +949,67 @@ const defaultComponents = memoizeMarkdownComponents({
700
949
  ## components/assistant-ui/thread.tsx
701
950
 
702
951
  ```tsx
952
+ import {
953
+ ComposerAddAttachment,
954
+ ComposerAttachments,
955
+ UserMessageAttachments,
956
+ } from "@/components/assistant-ui/attachment";
957
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
958
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
959
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
960
+ import { Button } from "@/components/ui/button";
961
+ import { cn } from "@/lib/utils";
703
962
  import {
704
963
  ActionBarPrimitive,
964
+ AssistantIf,
705
965
  BranchPickerPrimitive,
706
966
  ComposerPrimitive,
967
+ ErrorPrimitive,
707
968
  MessagePrimitive,
708
969
  ThreadPrimitive,
709
970
  } from "@assistant-ui/react";
710
- import type { FC } from "react";
711
971
  import {
712
972
  ArrowDownIcon,
973
+ ArrowUpIcon,
713
974
  CheckIcon,
714
975
  ChevronLeftIcon,
715
976
  ChevronRightIcon,
716
977
  CopyIcon,
978
+ DownloadIcon,
717
979
  PencilIcon,
718
980
  RefreshCwIcon,
719
- SendHorizontalIcon,
981
+ SquareIcon,
720
982
  } from "lucide-react";
721
- import { cn } from "@/lib/utils";
722
-
723
- import { Button } from "@/components/ui/button";
724
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
725
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
983
+ import type { FC } from "react";
726
984
 
727
985
  export const Thread: FC = () => {
728
986
  return (
729
987
  <ThreadPrimitive.Root
730
- className="bg-background box-border flex h-full flex-col overflow-hidden"
988
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
731
989
  style={{
732
- ["--thread-max-width" as string]: "42rem",
990
+ ["--thread-max-width" as string]: "44rem",
733
991
  }}
734
992
  >
735
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
736
- <ThreadWelcome />
993
+ <ThreadPrimitive.Viewport
994
+ turnAnchor="top"
995
+ className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
996
+ >
997
+ <AssistantIf condition={({ thread }) => thread.isEmpty}>
998
+ <ThreadWelcome />
999
+ </AssistantIf>
737
1000
 
738
1001
  <ThreadPrimitive.Messages
739
1002
  components={{
740
- UserMessage: UserMessage,
741
- EditComposer: EditComposer,
742
- AssistantMessage: AssistantMessage,
1003
+ UserMessage,
1004
+ EditComposer,
1005
+ AssistantMessage,
743
1006
  }}
744
1007
  />
745
1008
 
746
- <ThreadPrimitive.If empty={false}>
747
- <div className="min-h-8 flex-grow" />
748
- </ThreadPrimitive.If>
749
-
750
- <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">
1009
+ <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">
751
1010
  <ThreadScrollToBottom />
752
1011
  <Composer />
753
- </div>
1012
+ </ThreadPrimitive.ViewportFooter>
754
1013
  </ThreadPrimitive.Viewport>
755
1014
  </ThreadPrimitive.Root>
756
1015
  );
@@ -762,7 +1021,7 @@ const ThreadScrollToBottom: FC = () => {
762
1021
  <TooltipIconButton
763
1022
  tooltip="Scroll to bottom"
764
1023
  variant="outline"
765
- className="absolute -top-8 rounded-full disabled:invisible"
1024
+ 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"
766
1025
  >
767
1026
  <ArrowDownIcon />
768
1027
  </TooltipIconButton>
@@ -772,175 +1031,247 @@ const ThreadScrollToBottom: FC = () => {
772
1031
 
773
1032
  const ThreadWelcome: FC = () => {
774
1033
  return (
775
- <ThreadPrimitive.Empty>
776
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
777
- <div className="flex w-full flex-grow flex-col items-center justify-center">
778
- <p className="mt-4 font-medium">How can I help you today?</p>
1034
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
1035
+ <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
1036
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
1037
+ <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
1038
+ Hello there!
1039
+ </h1>
1040
+ <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">
1041
+ How can I help you today?
1042
+ </p>
779
1043
  </div>
780
- <ThreadWelcomeSuggestions />
781
1044
  </div>
782
- </ThreadPrimitive.Empty>
1045
+ <ThreadSuggestions />
1046
+ </div>
783
1047
  );
784
1048
  };
785
1049
 
786
- const ThreadWelcomeSuggestions: FC = () => {
1050
+ const SUGGESTIONS = [
1051
+ {
1052
+ title: "What's the weather",
1053
+ label: "in San Francisco?",
1054
+ prompt: "What's the weather in San Francisco?",
1055
+ },
1056
+ {
1057
+ title: "Explain React hooks",
1058
+ label: "like useState and useEffect",
1059
+ prompt: "Explain React hooks like useState and useEffect",
1060
+ },
1061
+ ] as const;
1062
+
1063
+ const ThreadSuggestions: FC = () => {
787
1064
  return (
788
- <div className="mt-3 flex w-full items-stretch justify-center gap-4">
789
- <ThreadPrimitive.Suggestion
790
- 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"
791
- prompt="What is the weather in Tokyo?"
792
- method="replace"
793
- autoSend
794
- >
795
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
796
- What is the weather in Tokyo?
797
- </span>
798
- </ThreadPrimitive.Suggestion>
799
- <ThreadPrimitive.Suggestion
800
- 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"
801
- prompt="What is assistant-ui?"
802
- method="replace"
803
- autoSend
804
- >
805
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
806
- What is assistant-ui?
807
- </span>
808
- </ThreadPrimitive.Suggestion>
1065
+ <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
1066
+ {SUGGESTIONS.map((suggestion, index) => (
1067
+ <div
1068
+ key={suggestion.prompt}
1069
+ 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"
1070
+ style={{ animationDelay: `${100 + index * 50}ms` }}
1071
+ >
1072
+ <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
1073
+ <Button
1074
+ variant="ghost"
1075
+ 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"
1076
+ aria-label={suggestion.prompt}
1077
+ >
1078
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
1079
+ {suggestion.title}
1080
+ </span>
1081
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
1082
+ {suggestion.label}
1083
+ </span>
1084
+ </Button>
1085
+ </ThreadPrimitive.Suggestion>
1086
+ </div>
1087
+ ))}
809
1088
  </div>
810
1089
  );
811
1090
  };
812
1091
 
813
1092
  const Composer: FC = () => {
814
1093
  return (
815
- <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">
816
- <ComposerPrimitive.Input
817
- rows={1}
818
- autoFocus
819
- placeholder="Write a message..."
820
- 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"
821
- />
822
- <ComposerAction />
1094
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
1095
+ <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">
1096
+ <ComposerAttachments />
1097
+ <ComposerPrimitive.Input
1098
+ placeholder="Send a message..."
1099
+ 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"
1100
+ rows={1}
1101
+ autoFocus
1102
+ aria-label="Message input"
1103
+ />
1104
+ <ComposerAction />
1105
+ </ComposerPrimitive.AttachmentDropzone>
823
1106
  </ComposerPrimitive.Root>
824
1107
  );
825
1108
  };
826
1109
 
827
1110
  const ComposerAction: FC = () => {
828
1111
  return (
829
- <>
830
- <ThreadPrimitive.If running={false}>
1112
+ <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
1113
+ <ComposerAddAttachment />
1114
+
1115
+ <AssistantIf condition={({ thread }) => !thread.isRunning}>
831
1116
  <ComposerPrimitive.Send asChild>
832
1117
  <TooltipIconButton
833
- tooltip="Send"
1118
+ tooltip="Send message"
1119
+ side="bottom"
1120
+ type="submit"
834
1121
  variant="default"
835
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
1122
+ size="icon"
1123
+ className="aui-composer-send size-8 rounded-full"
1124
+ aria-label="Send message"
836
1125
  >
837
- <SendHorizontalIcon />
1126
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
838
1127
  </TooltipIconButton>
839
1128
  </ComposerPrimitive.Send>
840
- </ThreadPrimitive.If>
841
- <ThreadPrimitive.If running>
1129
+ </AssistantIf>
1130
+
1131
+ <AssistantIf condition={({ thread }) => thread.isRunning}>
842
1132
  <ComposerPrimitive.Cancel asChild>
843
- <TooltipIconButton
844
- tooltip="Cancel"
1133
+ <Button
1134
+ type="button"
845
1135
  variant="default"
846
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
1136
+ size="icon"
1137
+ className="aui-composer-cancel size-8 rounded-full"
1138
+ aria-label="Stop generating"
847
1139
  >
848
- <CircleStopIcon />
849
- </TooltipIconButton>
1140
+ <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1141
+ </Button>
850
1142
  </ComposerPrimitive.Cancel>
851
- </ThreadPrimitive.If>
852
- </>
1143
+ </AssistantIf>
1144
+ </div>
853
1145
  );
854
1146
  };
855
1147
 
856
- const UserMessage: FC = () => {
1148
+ const MessageError: FC = () => {
857
1149
  return (
858
- <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">
859
- <UserActionBar />
1150
+ <MessagePrimitive.Error>
1151
+ <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">
1152
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
1153
+ </ErrorPrimitive.Root>
1154
+ </MessagePrimitive.Error>
1155
+ );
1156
+ };
860
1157
 
861
- <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">
862
- <MessagePrimitive.Parts />
1158
+ const AssistantMessage: FC = () => {
1159
+ return (
1160
+ <MessagePrimitive.Root
1161
+ 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"
1162
+ data-role="assistant"
1163
+ >
1164
+ <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
1165
+ <MessagePrimitive.Parts
1166
+ components={{
1167
+ Text: MarkdownText,
1168
+ tools: { Fallback: ToolFallback },
1169
+ }}
1170
+ />
1171
+ <MessageError />
863
1172
  </div>
864
1173
 
865
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1174
+ <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1175
+ <BranchPicker />
1176
+ <AssistantActionBar />
1177
+ </div>
866
1178
  </MessagePrimitive.Root>
867
1179
  );
868
1180
  };
869
1181
 
870
- const UserActionBar: FC = () => {
1182
+ const AssistantActionBar: FC = () => {
871
1183
  return (
872
1184
  <ActionBarPrimitive.Root
873
1185
  hideWhenRunning
874
1186
  autohide="not-last"
875
- className="col-start-1 row-start-2 mt-2.5 mr-3 flex flex-col items-end"
1187
+ autohideFloat="single-branch"
1188
+ 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"
876
1189
  >
877
- <ActionBarPrimitive.Edit asChild>
878
- <TooltipIconButton tooltip="Edit">
879
- <PencilIcon />
1190
+ <ActionBarPrimitive.Copy asChild>
1191
+ <TooltipIconButton tooltip="Copy">
1192
+ <AssistantIf condition={({ message }) => message.isCopied}>
1193
+ <CheckIcon />
1194
+ </AssistantIf>
1195
+ <AssistantIf condition={({ message }) => !message.isCopied}>
1196
+ <CopyIcon />
1197
+ </AssistantIf>
880
1198
  </TooltipIconButton>
881
- </ActionBarPrimitive.Edit>
1199
+ </ActionBarPrimitive.Copy>
1200
+ <ActionBarPrimitive.ExportMarkdown asChild>
1201
+ <TooltipIconButton tooltip="Export as Markdown">
1202
+ <DownloadIcon />
1203
+ </TooltipIconButton>
1204
+ </ActionBarPrimitive.ExportMarkdown>
1205
+ <ActionBarPrimitive.Reload asChild>
1206
+ <TooltipIconButton tooltip="Refresh">
1207
+ <RefreshCwIcon />
1208
+ </TooltipIconButton>
1209
+ </ActionBarPrimitive.Reload>
882
1210
  </ActionBarPrimitive.Root>
883
1211
  );
884
1212
  };
885
1213
 
886
- const EditComposer: FC = () => {
1214
+ const UserMessage: FC = () => {
887
1215
  return (
888
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
889
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
890
-
891
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
892
- <ComposerPrimitive.Cancel asChild>
893
- <Button variant="ghost">Cancel</Button>
894
- </ComposerPrimitive.Cancel>
895
- <ComposerPrimitive.Send asChild>
896
- <Button>Send</Button>
897
- </ComposerPrimitive.Send>
898
- </div>
899
- </ComposerPrimitive.Root>
900
- );
901
- };
1216
+ <MessagePrimitive.Root
1217
+ 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"
1218
+ data-role="user"
1219
+ >
1220
+ <UserMessageAttachments />
902
1221
 
903
- const AssistantMessage: FC = () => {
904
- return (
905
- <MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
906
- <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">
907
- <MessagePrimitive.Parts components={{ Text: MarkdownText }} />
1222
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1223
+ <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1224
+ <MessagePrimitive.Parts />
1225
+ </div>
1226
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
1227
+ <UserActionBar />
1228
+ </div>
908
1229
  </div>
909
1230
 
910
- <AssistantActionBar />
911
-
912
- <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
1231
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
913
1232
  </MessagePrimitive.Root>
914
1233
  );
915
1234
  };
916
1235
 
917
- const AssistantActionBar: FC = () => {
1236
+ const UserActionBar: FC = () => {
918
1237
  return (
919
1238
  <ActionBarPrimitive.Root
920
1239
  hideWhenRunning
921
1240
  autohide="not-last"
922
- autohideFloat="single-branch"
923
- 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"
1241
+ className="aui-user-action-bar-root flex flex-col items-end"
924
1242
  >
925
- <ActionBarPrimitive.Copy asChild>
926
- <TooltipIconButton tooltip="Copy">
927
- <MessagePrimitive.If copied>
928
- <CheckIcon />
929
- </MessagePrimitive.If>
930
- <MessagePrimitive.If copied={false}>
931
- <CopyIcon />
932
- </MessagePrimitive.If>
933
- </TooltipIconButton>
934
- </ActionBarPrimitive.Copy>
935
- <ActionBarPrimitive.Reload asChild>
936
- <TooltipIconButton tooltip="Refresh">
937
- <RefreshCwIcon />
1243
+ <ActionBarPrimitive.Edit asChild>
1244
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1245
+ <PencilIcon />
938
1246
  </TooltipIconButton>
939
- </ActionBarPrimitive.Reload>
1247
+ </ActionBarPrimitive.Edit>
940
1248
  </ActionBarPrimitive.Root>
941
1249
  );
942
1250
  };
943
1251
 
1252
+ const EditComposer: FC = () => {
1253
+ return (
1254
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1255
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1256
+ <ComposerPrimitive.Input
1257
+ className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1258
+ autoFocus
1259
+ />
1260
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1261
+ <ComposerPrimitive.Cancel asChild>
1262
+ <Button variant="ghost" size="sm">
1263
+ Cancel
1264
+ </Button>
1265
+ </ComposerPrimitive.Cancel>
1266
+ <ComposerPrimitive.Send asChild>
1267
+ <Button size="sm">Update</Button>
1268
+ </ComposerPrimitive.Send>
1269
+ </div>
1270
+ </ComposerPrimitive.Root>
1271
+ </MessagePrimitive.Root>
1272
+ );
1273
+ };
1274
+
944
1275
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
945
1276
  className,
946
1277
  ...rest
@@ -949,7 +1280,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
949
1280
  <BranchPickerPrimitive.Root
950
1281
  hideWhenSingleBranch
951
1282
  className={cn(
952
- "text-muted-foreground inline-flex items-center text-xs",
1283
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
953
1284
  className,
954
1285
  )}
955
1286
  {...rest}
@@ -959,7 +1290,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
959
1290
  <ChevronLeftIcon />
960
1291
  </TooltipIconButton>
961
1292
  </BranchPickerPrimitive.Previous>
962
- <span className="font-medium">
1293
+ <span className="aui-branch-picker-state font-medium">
963
1294
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
964
1295
  </span>
965
1296
  <BranchPickerPrimitive.Next asChild>
@@ -971,17 +1302,102 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
971
1302
  );
972
1303
  };
973
1304
 
974
- const CircleStopIcon = () => {
1305
+ ```
1306
+
1307
+ ## components/assistant-ui/tool-fallback.tsx
1308
+
1309
+ ```tsx
1310
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1311
+ import {
1312
+ CheckIcon,
1313
+ ChevronDownIcon,
1314
+ ChevronUpIcon,
1315
+ XCircleIcon,
1316
+ } from "lucide-react";
1317
+ import { useState } from "react";
1318
+ import { Button } from "@/components/ui/button";
1319
+ import { cn } from "@/lib/utils";
1320
+
1321
+ export const ToolFallback: ToolCallMessagePartComponent = ({
1322
+ toolName,
1323
+ argsText,
1324
+ result,
1325
+ status,
1326
+ }) => {
1327
+ const [isCollapsed, setIsCollapsed] = useState(true);
1328
+
1329
+ const isCancelled =
1330
+ status?.type === "incomplete" && status.reason === "cancelled";
1331
+ const cancelledReason =
1332
+ isCancelled && status.error
1333
+ ? typeof status.error === "string"
1334
+ ? status.error
1335
+ : JSON.stringify(status.error)
1336
+ : null;
1337
+
975
1338
  return (
976
- <svg
977
- xmlns="http://www.w3.org/2000/svg"
978
- viewBox="0 0 16 16"
979
- fill="currentColor"
980
- width="16"
981
- height="16"
1339
+ <div
1340
+ className={cn(
1341
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1342
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1343
+ )}
982
1344
  >
983
- <rect width="10" height="10" x="3" y="3" rx="2" />
984
- </svg>
1345
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1346
+ {isCancelled ? (
1347
+ <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1348
+ ) : (
1349
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1350
+ )}
1351
+ <p
1352
+ className={cn(
1353
+ "aui-tool-fallback-title grow",
1354
+ isCancelled && "text-muted-foreground line-through",
1355
+ )}
1356
+ >
1357
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1358
+ <b>{toolName}</b>
1359
+ </p>
1360
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1361
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1362
+ </Button>
1363
+ </div>
1364
+ {!isCollapsed && (
1365
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1366
+ {cancelledReason && (
1367
+ <div className="aui-tool-fallback-cancelled-root px-4">
1368
+ <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1369
+ Cancelled reason:
1370
+ </p>
1371
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1372
+ {cancelledReason}
1373
+ </p>
1374
+ </div>
1375
+ )}
1376
+ <div
1377
+ className={cn(
1378
+ "aui-tool-fallback-args-root px-4",
1379
+ isCancelled && "opacity-60",
1380
+ )}
1381
+ >
1382
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1383
+ {argsText}
1384
+ </pre>
1385
+ </div>
1386
+ {!isCancelled && result !== undefined && (
1387
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1388
+ <p className="aui-tool-fallback-result-header font-semibold">
1389
+ Result:
1390
+ </p>
1391
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1392
+ {typeof result === "string"
1393
+ ? result
1394
+ : JSON.stringify(result, null, 2)}
1395
+ </pre>
1396
+ </div>
1397
+ )}
1398
+ </div>
1399
+ )}
1400
+ </div>
985
1401
  );
986
1402
  };
987
1403
 
@@ -992,7 +1408,7 @@ const CircleStopIcon = () => {
992
1408
  ```tsx
993
1409
  "use client";
994
1410
 
995
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1411
+ import { ComponentPropsWithRef, forwardRef } from "react";
996
1412
  import { Slottable } from "@radix-ui/react-slot";
997
1413
 
998
1414
  import {
@@ -1003,7 +1419,7 @@ import {
1003
1419
  import { Button } from "@/components/ui/button";
1004
1420
  import { cn } from "@/lib/utils";
1005
1421
 
1006
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1422
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1007
1423
  tooltip: string;
1008
1424
  side?: "top" | "bottom" | "left" | "right";
1009
1425
  };
@@ -1019,11 +1435,11 @@ export const TooltipIconButton = forwardRef<
1019
1435
  variant="ghost"
1020
1436
  size="icon"
1021
1437
  {...rest}
1022
- className={cn("size-6 p-1", className)}
1438
+ className={cn("aui-button-icon size-6 p-1", className)}
1023
1439
  ref={ref}
1024
1440
  >
1025
1441
  <Slottable>{children}</Slottable>
1026
- <span className="sr-only">{tooltip}</span>
1442
+ <span className="aui-sr-only sr-only">{tooltip}</span>
1027
1443
  </Button>
1028
1444
  </TooltipTrigger>
1029
1445
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -1035,6 +1451,62 @@ TooltipIconButton.displayName = "TooltipIconButton";
1035
1451
 
1036
1452
  ```
1037
1453
 
1454
+ ## components/ui/avatar.tsx
1455
+
1456
+ ```tsx
1457
+ "use client";
1458
+
1459
+ import * as React from "react";
1460
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1461
+
1462
+ import { cn } from "@/lib/utils";
1463
+
1464
+ const Avatar = React.forwardRef<
1465
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1466
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1467
+ >(({ className, ...props }, ref) => (
1468
+ <AvatarPrimitive.Root
1469
+ ref={ref}
1470
+ className={cn(
1471
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1472
+ className,
1473
+ )}
1474
+ {...props}
1475
+ />
1476
+ ));
1477
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1478
+
1479
+ const AvatarImage = React.forwardRef<
1480
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1481
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1482
+ >(({ className, ...props }, ref) => (
1483
+ <AvatarPrimitive.Image
1484
+ ref={ref}
1485
+ className={cn("aspect-square h-full w-full", className)}
1486
+ {...props}
1487
+ />
1488
+ ));
1489
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1490
+
1491
+ const AvatarFallback = React.forwardRef<
1492
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1493
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1494
+ >(({ className, ...props }, ref) => (
1495
+ <AvatarPrimitive.Fallback
1496
+ ref={ref}
1497
+ className={cn(
1498
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
1499
+ className,
1500
+ )}
1501
+ {...props}
1502
+ />
1503
+ ));
1504
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1505
+
1506
+ export { Avatar, AvatarImage, AvatarFallback };
1507
+
1508
+ ```
1509
+
1038
1510
  ## components/ui/button.tsx
1039
1511
 
1040
1512
  ```tsx
@@ -1047,16 +1519,16 @@ import { cva, type VariantProps } from "class-variance-authority";
1047
1519
  import { cn } from "@/lib/utils";
1048
1520
 
1049
1521
  const buttonVariants = cva(
1050
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
1522
+ "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",
1051
1523
  {
1052
1524
  variants: {
1053
1525
  variant: {
1054
1526
  default:
1055
1527
  "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
1056
1528
  destructive:
1057
- "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1529
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1058
1530
  outline:
1059
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
1531
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1060
1532
  secondary:
1061
1533
  "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
1062
1534
  ghost:
@@ -1065,7 +1537,7 @@ const buttonVariants = cva(
1065
1537
  },
1066
1538
  size: {
1067
1539
  default: "h-9 px-4 py-2 has-[>svg]:px-3",
1068
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
1540
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1069
1541
  lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1070
1542
  icon: "size-9",
1071
1543
  },
@@ -1102,6 +1574,147 @@ export { Button, buttonVariants };
1102
1574
 
1103
1575
  ```
1104
1576
 
1577
+ ## components/ui/dialog.tsx
1578
+
1579
+ ```tsx
1580
+ "use client";
1581
+
1582
+ import * as React from "react";
1583
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1584
+ import { XIcon } from "lucide-react";
1585
+
1586
+ import { cn } from "@/lib/utils";
1587
+
1588
+ function Dialog({
1589
+ ...props
1590
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1591
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1592
+ }
1593
+
1594
+ function DialogTrigger({
1595
+ ...props
1596
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1597
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1598
+ }
1599
+
1600
+ function DialogPortal({
1601
+ ...props
1602
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1603
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1604
+ }
1605
+
1606
+ function DialogClose({
1607
+ ...props
1608
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1609
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1610
+ }
1611
+
1612
+ function DialogOverlay({
1613
+ className,
1614
+ ...props
1615
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1616
+ return (
1617
+ <DialogPrimitive.Overlay
1618
+ data-slot="dialog-overlay"
1619
+ className={cn(
1620
+ "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",
1621
+ className,
1622
+ )}
1623
+ {...props}
1624
+ />
1625
+ );
1626
+ }
1627
+
1628
+ function DialogContent({
1629
+ className,
1630
+ children,
1631
+ ...props
1632
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
1633
+ return (
1634
+ <DialogPortal data-slot="dialog-portal">
1635
+ <DialogOverlay />
1636
+ <DialogPrimitive.Content
1637
+ data-slot="dialog-content"
1638
+ className={cn(
1639
+ "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",
1640
+ className,
1641
+ )}
1642
+ {...props}
1643
+ >
1644
+ {children}
1645
+ <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">
1646
+ <XIcon />
1647
+ <span className="sr-only">Close</span>
1648
+ </DialogPrimitive.Close>
1649
+ </DialogPrimitive.Content>
1650
+ </DialogPortal>
1651
+ );
1652
+ }
1653
+
1654
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1655
+ return (
1656
+ <div
1657
+ data-slot="dialog-header"
1658
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1659
+ {...props}
1660
+ />
1661
+ );
1662
+ }
1663
+
1664
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1665
+ return (
1666
+ <div
1667
+ data-slot="dialog-footer"
1668
+ className={cn(
1669
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1670
+ className,
1671
+ )}
1672
+ {...props}
1673
+ />
1674
+ );
1675
+ }
1676
+
1677
+ function DialogTitle({
1678
+ className,
1679
+ ...props
1680
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1681
+ return (
1682
+ <DialogPrimitive.Title
1683
+ data-slot="dialog-title"
1684
+ className={cn("font-semibold text-lg leading-none", className)}
1685
+ {...props}
1686
+ />
1687
+ );
1688
+ }
1689
+
1690
+ function DialogDescription({
1691
+ className,
1692
+ ...props
1693
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1694
+ return (
1695
+ <DialogPrimitive.Description
1696
+ data-slot="dialog-description"
1697
+ className={cn("text-muted-foreground text-sm", className)}
1698
+ {...props}
1699
+ />
1700
+ );
1701
+ }
1702
+
1703
+ export {
1704
+ Dialog,
1705
+ DialogClose,
1706
+ DialogContent,
1707
+ DialogDescription,
1708
+ DialogFooter,
1709
+ DialogHeader,
1710
+ DialogOverlay,
1711
+ DialogPortal,
1712
+ DialogTitle,
1713
+ DialogTrigger,
1714
+ };
1715
+
1716
+ ```
1717
+
1105
1718
  ## components/ui/tooltip.tsx
1106
1719
 
1107
1720
  ```tsx
@@ -1153,13 +1766,13 @@ function TooltipContent({
1153
1766
  data-slot="tooltip-content"
1154
1767
  sideOffset={sideOffset}
1155
1768
  className={cn(
1156
- "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",
1769
+ "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",
1157
1770
  className,
1158
1771
  )}
1159
1772
  {...props}
1160
1773
  >
1161
1774
  {children}
1162
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1775
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
1163
1776
  </TooltipPrimitive.Content>
1164
1777
  </TooltipPrimitive.Portal>
1165
1778
  );
@@ -1181,18 +1794,6 @@ export function cn(...inputs: ClassValue[]) {
1181
1794
 
1182
1795
  ```
1183
1796
 
1184
- ## next-env.d.ts
1185
-
1186
- ```typescript
1187
- /// <reference types="next" />
1188
- /// <reference types="next/image-types/global" />
1189
- import "./.next/types/routes.d.ts";
1190
-
1191
- // NOTE: This file should not be edited
1192
- // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
1193
-
1194
- ```
1195
-
1196
1797
  ## next.config.ts
1197
1798
 
1198
1799
  ```typescript
@@ -1216,11 +1817,10 @@ export default nextConfig;
1216
1817
  "scripts": {
1217
1818
  "dev": "next dev --turbo",
1218
1819
  "build": "next build",
1219
- "start": "next start",
1220
- "lint": "eslint ."
1820
+ "start": "next start"
1221
1821
  },
1222
1822
  "dependencies": {
1223
- "@ai-sdk/openai": "^2.0.68",
1823
+ "@ai-sdk/openai": "^2.0.77",
1224
1824
  "@assistant-ui/react": "workspace:*",
1225
1825
  "@assistant-ui/react-ai-sdk": "workspace:*",
1226
1826
  "@assistant-ui/react-hook-form": "workspace:*",
@@ -1229,27 +1829,27 @@ export default nextConfig;
1229
1829
  "@ffmpeg/util": "^0.12.2",
1230
1830
  "@hookform/resolvers": "^5.2.2",
1231
1831
  "@radix-ui/react-avatar": "^1.1.11",
1832
+ "@radix-ui/react-dialog": "^1.1.15",
1232
1833
  "@radix-ui/react-icons": "^1.3.2",
1233
1834
  "@radix-ui/react-label": "^2.1.8",
1234
1835
  "@radix-ui/react-slot": "^1.2.4",
1235
1836
  "@radix-ui/react-tabs": "^1.1.13",
1236
1837
  "@radix-ui/react-tooltip": "^1.2.8",
1237
1838
  "@react-hook/media-query": "^1.1.1",
1238
- "ai": "^5.0.93",
1839
+ "ai": "^5.0.107",
1239
1840
  "class-variance-authority": "^0.7.1",
1240
1841
  "clsx": "^2.1.1",
1241
- "json-schema-to-zod": "^2.6.1",
1242
- "lucide-react": "^0.554.0",
1243
- "next": "16.0.3",
1244
- "react": "19.2.0",
1245
- "react-dom": "19.2.0",
1246
- "react-hook-form": "^7.66.1",
1842
+ "lucide-react": "^0.556.0",
1843
+ "next": "16.0.7",
1844
+ "react": "19.2.1",
1845
+ "react-dom": "19.2.1",
1846
+ "react-hook-form": "^7.68.0",
1247
1847
  "react-resizable-panels": "^3.0.6",
1248
1848
  "remark-gfm": "^4.0.1",
1249
1849
  "tailwind-merge": "^3.4.0",
1250
1850
  "tw-animate-css": "^1.4.0",
1251
- "zod": "^4.1.12",
1252
- "zustand": "^5.0.8"
1851
+ "zod": "^4.1.13",
1852
+ "zustand": "^5.0.9"
1253
1853
  },
1254
1854
  "devDependencies": {
1255
1855
  "@assistant-ui/x-buildutils": "workspace:*",