@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
@@ -151,8 +151,8 @@ export default function RootLayout({
151
151
  <html lang="en">
152
152
  <body className="font-sans antialiased">
153
153
  <div className="flex h-screen flex-col">
154
- <header className="bg-background border-b px-4 py-2">
155
- <h1 className="text-lg font-semibold">
154
+ <header className="border-b bg-background px-4 py-2">
155
+ <h1 className="font-semibold text-lg">
156
156
  Assistant Transport Example
157
157
  </h1>
158
158
  </header>
@@ -181,7 +181,7 @@ import {
181
181
  convertLangChainMessages,
182
182
  LangChainMessage,
183
183
  } from "@assistant-ui/react-langgraph";
184
- import React, { ReactNode } from "react";
184
+ import { ReactNode } from "react";
185
185
  import { z } from "zod";
186
186
 
187
187
  // Frontend tool with execute function
@@ -265,7 +265,6 @@ const converter = (
265
265
  );
266
266
 
267
267
  const messages = [...state.messages, ...optimisticStateMessages.flat()];
268
- console.log({ state, messages });
269
268
  return {
270
269
  messages: LangChainMessageConverter.toThreadMessages(messages),
271
270
  isRunning: connectionMetadata.isSending || false,
@@ -359,6 +358,247 @@ export default function Home() {
359
358
 
360
359
  ```
361
360
 
361
+ ## components/assistant-ui/attachment.tsx
362
+
363
+ ```tsx
364
+ "use client";
365
+
366
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
367
+ import Image from "next/image";
368
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
369
+ import {
370
+ AttachmentPrimitive,
371
+ ComposerPrimitive,
372
+ MessagePrimitive,
373
+ useAssistantState,
374
+ useAssistantApi,
375
+ } from "@assistant-ui/react";
376
+ import { useShallow } from "zustand/shallow";
377
+ import {
378
+ Tooltip,
379
+ TooltipContent,
380
+ TooltipTrigger,
381
+ } from "@/components/ui/tooltip";
382
+ import {
383
+ Dialog,
384
+ DialogTitle,
385
+ DialogContent,
386
+ DialogTrigger,
387
+ } from "@/components/ui/dialog";
388
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
389
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
390
+ import { cn } from "@/lib/utils";
391
+
392
+ const useFileSrc = (file: File | undefined) => {
393
+ const [src, setSrc] = useState<string | undefined>(undefined);
394
+
395
+ useEffect(() => {
396
+ if (!file) {
397
+ setSrc(undefined);
398
+ return;
399
+ }
400
+
401
+ const objectUrl = URL.createObjectURL(file);
402
+ setSrc(objectUrl);
403
+
404
+ return () => {
405
+ URL.revokeObjectURL(objectUrl);
406
+ };
407
+ }, [file]);
408
+
409
+ return src;
410
+ };
411
+
412
+ const useAttachmentSrc = () => {
413
+ const { file, src } = useAssistantState(
414
+ useShallow(({ attachment }): { file?: File; src?: string } => {
415
+ if (attachment.type !== "image") return {};
416
+ if (attachment.file) return { file: attachment.file };
417
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
418
+ ?.image;
419
+ if (!src) return {};
420
+ return { src };
421
+ }),
422
+ );
423
+
424
+ return useFileSrc(file) ?? src;
425
+ };
426
+
427
+ type AttachmentPreviewProps = {
428
+ src: string;
429
+ };
430
+
431
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
432
+ const [isLoaded, setIsLoaded] = useState(false);
433
+ return (
434
+ <Image
435
+ src={src}
436
+ alt="Image Preview"
437
+ width={1}
438
+ height={1}
439
+ className={
440
+ isLoaded
441
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
442
+ : "aui-attachment-preview-image-loading hidden"
443
+ }
444
+ onLoadingComplete={() => setIsLoaded(true)}
445
+ priority={false}
446
+ />
447
+ );
448
+ };
449
+
450
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
451
+ const src = useAttachmentSrc();
452
+
453
+ if (!src) return children;
454
+
455
+ return (
456
+ <Dialog>
457
+ <DialogTrigger
458
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
459
+ asChild
460
+ >
461
+ {children}
462
+ </DialogTrigger>
463
+ <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">
464
+ <DialogTitle className="aui-sr-only sr-only">
465
+ Image Attachment Preview
466
+ </DialogTitle>
467
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
468
+ <AttachmentPreview src={src} />
469
+ </div>
470
+ </DialogContent>
471
+ </Dialog>
472
+ );
473
+ };
474
+
475
+ const AttachmentThumb: FC = () => {
476
+ const isImage = useAssistantState(
477
+ ({ attachment }) => attachment.type === "image",
478
+ );
479
+ const src = useAttachmentSrc();
480
+
481
+ return (
482
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
483
+ <AvatarImage
484
+ src={src}
485
+ alt="Attachment preview"
486
+ className="aui-attachment-tile-image object-cover"
487
+ />
488
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
489
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
490
+ </AvatarFallback>
491
+ </Avatar>
492
+ );
493
+ };
494
+
495
+ const AttachmentUI: FC = () => {
496
+ const api = useAssistantApi();
497
+ const isComposer = api.attachment.source === "composer";
498
+
499
+ const isImage = useAssistantState(
500
+ ({ attachment }) => attachment.type === "image",
501
+ );
502
+ const typeLabel = useAssistantState(({ attachment }) => {
503
+ const type = attachment.type;
504
+ switch (type) {
505
+ case "image":
506
+ return "Image";
507
+ case "document":
508
+ return "Document";
509
+ case "file":
510
+ return "File";
511
+ default:
512
+ const _exhaustiveCheck: never = type;
513
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
514
+ }
515
+ });
516
+
517
+ return (
518
+ <Tooltip>
519
+ <AttachmentPrimitive.Root
520
+ className={cn(
521
+ "aui-attachment-root relative",
522
+ isImage &&
523
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
524
+ )}
525
+ >
526
+ <AttachmentPreviewDialog>
527
+ <TooltipTrigger asChild>
528
+ <div
529
+ className={cn(
530
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
531
+ isComposer &&
532
+ "aui-attachment-tile-composer border-foreground/20",
533
+ )}
534
+ role="button"
535
+ id="attachment-tile"
536
+ aria-label={`${typeLabel} attachment`}
537
+ >
538
+ <AttachmentThumb />
539
+ </div>
540
+ </TooltipTrigger>
541
+ </AttachmentPreviewDialog>
542
+ {isComposer && <AttachmentRemove />}
543
+ </AttachmentPrimitive.Root>
544
+ <TooltipContent side="top">
545
+ <AttachmentPrimitive.Name />
546
+ </TooltipContent>
547
+ </Tooltip>
548
+ );
549
+ };
550
+
551
+ const AttachmentRemove: FC = () => {
552
+ return (
553
+ <AttachmentPrimitive.Remove asChild>
554
+ <TooltipIconButton
555
+ tooltip="Remove file"
556
+ 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"
557
+ side="top"
558
+ >
559
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
560
+ </TooltipIconButton>
561
+ </AttachmentPrimitive.Remove>
562
+ );
563
+ };
564
+
565
+ export const UserMessageAttachments: FC = () => {
566
+ return (
567
+ <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">
568
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
569
+ </div>
570
+ );
571
+ };
572
+
573
+ export const ComposerAttachments: FC = () => {
574
+ return (
575
+ <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">
576
+ <ComposerPrimitive.Attachments
577
+ components={{ Attachment: AttachmentUI }}
578
+ />
579
+ </div>
580
+ );
581
+ };
582
+
583
+ export const ComposerAddAttachment: FC = () => {
584
+ return (
585
+ <ComposerPrimitive.AddAttachment asChild>
586
+ <TooltipIconButton
587
+ tooltip="Add Attachment"
588
+ side="bottom"
589
+ variant="ghost"
590
+ size="icon"
591
+ 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"
592
+ aria-label="Add Attachment"
593
+ >
594
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
595
+ </TooltipIconButton>
596
+ </ComposerPrimitive.AddAttachment>
597
+ );
598
+ };
599
+
600
+ ```
601
+
362
602
  ## components/assistant-ui/markdown-text.tsx
363
603
 
364
604
  ```tsx
@@ -399,8 +639,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
399
639
  };
400
640
 
401
641
  return (
402
- <div className="mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
403
- <span className="lowercase [&>span]:text-xs">{language}</span>
642
+ <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">
643
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
644
+ {language}
645
+ </span>
404
646
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
405
647
  {!isCopied && <CopyIcon />}
406
648
  {isCopied && <CheckIcon />}
@@ -432,7 +674,7 @@ const defaultComponents = memoizeMarkdownComponents({
432
674
  h1: ({ className, ...props }) => (
433
675
  <h1
434
676
  className={cn(
435
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
677
+ "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
436
678
  className,
437
679
  )}
438
680
  {...props}
@@ -441,7 +683,7 @@ const defaultComponents = memoizeMarkdownComponents({
441
683
  h2: ({ className, ...props }) => (
442
684
  <h2
443
685
  className={cn(
444
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
686
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
445
687
  className,
446
688
  )}
447
689
  {...props}
@@ -450,7 +692,7 @@ const defaultComponents = memoizeMarkdownComponents({
450
692
  h3: ({ className, ...props }) => (
451
693
  <h3
452
694
  className={cn(
453
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
695
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
454
696
  className,
455
697
  )}
456
698
  {...props}
@@ -459,7 +701,7 @@ const defaultComponents = memoizeMarkdownComponents({
459
701
  h4: ({ className, ...props }) => (
460
702
  <h4
461
703
  className={cn(
462
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
704
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
463
705
  className,
464
706
  )}
465
707
  {...props}
@@ -468,7 +710,7 @@ const defaultComponents = memoizeMarkdownComponents({
468
710
  h5: ({ className, ...props }) => (
469
711
  <h5
470
712
  className={cn(
471
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
713
+ "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
472
714
  className,
473
715
  )}
474
716
  {...props}
@@ -476,20 +718,26 @@ const defaultComponents = memoizeMarkdownComponents({
476
718
  ),
477
719
  h6: ({ className, ...props }) => (
478
720
  <h6
479
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
721
+ className={cn(
722
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
723
+ className,
724
+ )}
480
725
  {...props}
481
726
  />
482
727
  ),
483
728
  p: ({ className, ...props }) => (
484
729
  <p
485
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
730
+ className={cn(
731
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
732
+ className,
733
+ )}
486
734
  {...props}
487
735
  />
488
736
  ),
489
737
  a: ({ className, ...props }) => (
490
738
  <a
491
739
  className={cn(
492
- "text-primary font-medium underline underline-offset-4",
740
+ "aui-md-a font-medium text-primary underline underline-offset-4",
493
741
  className,
494
742
  )}
495
743
  {...props}
@@ -497,29 +745,29 @@ const defaultComponents = memoizeMarkdownComponents({
497
745
  ),
498
746
  blockquote: ({ className, ...props }) => (
499
747
  <blockquote
500
- className={cn("border-l-2 pl-6 italic", className)}
748
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
501
749
  {...props}
502
750
  />
503
751
  ),
504
752
  ul: ({ className, ...props }) => (
505
753
  <ul
506
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
754
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
507
755
  {...props}
508
756
  />
509
757
  ),
510
758
  ol: ({ className, ...props }) => (
511
759
  <ol
512
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
760
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
513
761
  {...props}
514
762
  />
515
763
  ),
516
764
  hr: ({ className, ...props }) => (
517
- <hr className={cn("my-5 border-b", className)} {...props} />
765
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
518
766
  ),
519
767
  table: ({ className, ...props }) => (
520
768
  <table
521
769
  className={cn(
522
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
770
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
523
771
  className,
524
772
  )}
525
773
  {...props}
@@ -528,7 +776,7 @@ const defaultComponents = memoizeMarkdownComponents({
528
776
  th: ({ className, ...props }) => (
529
777
  <th
530
778
  className={cn(
531
- "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",
779
+ "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",
532
780
  className,
533
781
  )}
534
782
  {...props}
@@ -537,7 +785,7 @@ const defaultComponents = memoizeMarkdownComponents({
537
785
  td: ({ className, ...props }) => (
538
786
  <td
539
787
  className={cn(
540
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
788
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
541
789
  className,
542
790
  )}
543
791
  {...props}
@@ -546,7 +794,7 @@ const defaultComponents = memoizeMarkdownComponents({
546
794
  tr: ({ className, ...props }) => (
547
795
  <tr
548
796
  className={cn(
549
- "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",
797
+ "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",
550
798
  className,
551
799
  )}
552
800
  {...props}
@@ -554,14 +802,14 @@ const defaultComponents = memoizeMarkdownComponents({
554
802
  ),
555
803
  sup: ({ className, ...props }) => (
556
804
  <sup
557
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
805
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
558
806
  {...props}
559
807
  />
560
808
  ),
561
809
  pre: ({ className, ...props }) => (
562
810
  <pre
563
811
  className={cn(
564
- "overflow-x-auto !rounded-t-none rounded-b-lg bg-black p-4 text-white",
812
+ "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
565
813
  className,
566
814
  )}
567
815
  {...props}
@@ -572,7 +820,8 @@ const defaultComponents = memoizeMarkdownComponents({
572
820
  return (
573
821
  <code
574
822
  className={cn(
575
- !isCodeBlock && "bg-muted rounded border font-semibold",
823
+ !isCodeBlock &&
824
+ "aui-md-inline-code rounded border bg-muted font-semibold",
576
825
  className,
577
826
  )}
578
827
  {...props}
@@ -588,45 +837,53 @@ const defaultComponents = memoizeMarkdownComponents({
588
837
 
589
838
  ```tsx
590
839
  import {
591
- ThreadPrimitive,
592
- ComposerPrimitive,
593
- MessagePrimitive,
840
+ ComposerAddAttachment,
841
+ ComposerAttachments,
842
+ UserMessageAttachments,
843
+ } from "@/components/assistant-ui/attachment";
844
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
845
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
846
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
847
+ import { Button } from "@/components/ui/button";
848
+ import { cn } from "@/lib/utils";
849
+ import {
594
850
  ActionBarPrimitive,
851
+ AssistantIf,
595
852
  BranchPickerPrimitive,
853
+ ComposerPrimitive,
596
854
  ErrorPrimitive,
855
+ MessagePrimitive,
856
+ ThreadPrimitive,
597
857
  } from "@assistant-ui/react";
598
- import type { FC } from "react";
599
858
  import {
600
859
  ArrowDownIcon,
601
860
  ArrowUpIcon,
602
- PlusIcon,
603
- CopyIcon,
604
861
  CheckIcon,
605
- PencilIcon,
606
- RefreshCwIcon,
607
862
  ChevronLeftIcon,
608
863
  ChevronRightIcon,
609
- Square,
864
+ CopyIcon,
865
+ DownloadIcon,
866
+ PencilIcon,
867
+ RefreshCwIcon,
868
+ SquareIcon,
610
869
  } from "lucide-react";
611
-
612
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
613
- import { motion } from "framer-motion";
614
- import { Button } from "@/components/ui/button";
615
- import { cn } from "@/lib/utils";
616
- import { MarkdownText } from "./markdown-text";
617
- import { ToolFallback } from "./tool-fallback";
870
+ import type { FC } from "react";
618
871
 
619
872
  export const Thread: FC = () => {
620
873
  return (
621
874
  <ThreadPrimitive.Root
622
- className="bg-background flex h-full flex-col"
875
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
623
876
  style={{
624
- ["--thread-max-width" as string]: "48rem",
625
- ["--thread-padding-x" as string]: "1rem",
877
+ ["--thread-max-width" as string]: "44rem",
626
878
  }}
627
879
  >
628
- <ThreadPrimitive.Viewport className="relative flex min-w-0 flex-1 flex-col gap-6 overflow-y-scroll">
629
- <ThreadWelcome />
880
+ <ThreadPrimitive.Viewport
881
+ turnAnchor="top"
882
+ className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
883
+ >
884
+ <AssistantIf condition={({ thread }) => thread.isEmpty}>
885
+ <ThreadWelcome />
886
+ </AssistantIf>
630
887
 
631
888
  <ThreadPrimitive.Messages
632
889
  components={{
@@ -636,12 +893,11 @@ export const Thread: FC = () => {
636
893
  }}
637
894
  />
638
895
 
639
- <ThreadPrimitive.If empty={false}>
640
- <motion.div className="min-h-6 min-w-6 shrink-0" />
641
- </ThreadPrimitive.If>
896
+ <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">
897
+ <ThreadScrollToBottom />
898
+ <Composer />
899
+ </ThreadPrimitive.ViewportFooter>
642
900
  </ThreadPrimitive.Viewport>
643
-
644
- <Composer />
645
901
  </ThreadPrimitive.Root>
646
902
  );
647
903
  };
@@ -652,7 +908,7 @@ const ThreadScrollToBottom: FC = () => {
652
908
  <TooltipIconButton
653
909
  tooltip="Scroll to bottom"
654
910
  variant="outline"
655
- className="dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
911
+ 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"
656
912
  >
657
913
  <ArrowDownIcon />
658
914
  </TooltipIconButton>
@@ -662,84 +918,59 @@ const ThreadScrollToBottom: FC = () => {
662
918
 
663
919
  const ThreadWelcome: FC = () => {
664
920
  return (
665
- <ThreadPrimitive.Empty>
666
- <div className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col px-[var(--thread-padding-x)]">
667
- <div className="flex w-full flex-grow flex-col items-center justify-center">
668
- <div className="flex size-full flex-col justify-center px-8 md:mt-20">
669
- <motion.div
670
- initial={{ opacity: 0, y: 10 }}
671
- animate={{ opacity: 1, y: 0 }}
672
- exit={{ opacity: 0, y: 10 }}
673
- transition={{ delay: 0.5 }}
674
- className="text-2xl font-semibold"
675
- >
676
- Hello there!
677
- </motion.div>
678
- <motion.div
679
- initial={{ opacity: 0, y: 10 }}
680
- animate={{ opacity: 1, y: 0 }}
681
- exit={{ opacity: 0, y: 10 }}
682
- transition={{ delay: 0.6 }}
683
- className="text-muted-foreground/65 text-2xl"
684
- >
685
- How can I help you today?
686
- </motion.div>
687
- </div>
921
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
922
+ <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
923
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
924
+ <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
925
+ Hello there!
926
+ </h1>
927
+ <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">
928
+ How can I help you today?
929
+ </p>
688
930
  </div>
689
931
  </div>
690
- </ThreadPrimitive.Empty>
932
+ <ThreadSuggestions />
933
+ </div>
691
934
  );
692
935
  };
693
936
 
694
- const ThreadWelcomeSuggestions: FC = () => {
937
+ const SUGGESTIONS = [
938
+ {
939
+ title: "What's the weather",
940
+ label: "in San Francisco?",
941
+ prompt: "What's the weather in San Francisco?",
942
+ },
943
+ {
944
+ title: "Explain React hooks",
945
+ label: "like useState and useEffect",
946
+ prompt: "Explain React hooks like useState and useEffect",
947
+ },
948
+ ] as const;
949
+
950
+ const ThreadSuggestions: FC = () => {
695
951
  return (
696
- <div className="grid w-full gap-2 sm:grid-cols-2">
697
- {[
698
- {
699
- title: "What are the advantages",
700
- label: "of using Assistant Cloud?",
701
- action: "What are the advantages of using Assistant Cloud?",
702
- },
703
- {
704
- title: "Write code to",
705
- label: `demonstrate topological sorting`,
706
- action: `Write code to demonstrate topological sorting`,
707
- },
708
- {
709
- title: "Help me write an essay",
710
- label: `about AI chat applications`,
711
- action: `Help me write an essay about AI chat applications`,
712
- },
713
- {
714
- title: "What is the weather",
715
- label: "in San Francisco?",
716
- action: "What is the weather in San Francisco?",
717
- },
718
- ].map((suggestedAction, index) => (
719
- <motion.div
720
- initial={{ opacity: 0, y: 20 }}
721
- animate={{ opacity: 1, y: 0 }}
722
- exit={{ opacity: 0, y: 20 }}
723
- transition={{ delay: 0.05 * index }}
724
- key={`suggested-action-${suggestedAction.title}-${index}`}
725
- className="[&:nth-child(n+3)]:hidden sm:[&:nth-child(n+3)]:block"
952
+ <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
953
+ {SUGGESTIONS.map((suggestion, index) => (
954
+ <div
955
+ key={suggestion.prompt}
956
+ 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"
957
+ style={{ animationDelay: `${100 + index * 50}ms` }}
726
958
  >
727
- <ThreadPrimitive.Suggestion
728
- prompt={suggestedAction.action}
729
- method="replace"
730
- autoSend
731
- asChild
732
- >
959
+ <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
733
960
  <Button
734
961
  variant="ghost"
735
- className="dark:hover:bg-accent/60 h-auto w-full flex-1 flex-wrap items-start justify-start gap-1 rounded-xl border px-4 py-3.5 text-left text-sm sm:flex-col"
736
- aria-label={suggestedAction.action}
962
+ 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"
963
+ aria-label={suggestion.prompt}
737
964
  >
738
- <span className="font-medium">{suggestedAction.title}</span>
739
- <p className="text-muted-foreground">{suggestedAction.label}</p>
965
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
966
+ {suggestion.title}
967
+ </span>
968
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
969
+ {suggestion.label}
970
+ </span>
740
971
  </Button>
741
972
  </ThreadPrimitive.Suggestion>
742
- </motion.div>
973
+ </div>
743
974
  ))}
744
975
  </div>
745
976
  );
@@ -747,64 +978,56 @@ const ThreadWelcomeSuggestions: FC = () => {
747
978
 
748
979
  const Composer: FC = () => {
749
980
  return (
750
- <div className="bg-background relative mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-[var(--thread-padding-x)] pb-4 md:pb-6">
751
- <ThreadScrollToBottom />
752
- <ThreadPrimitive.Empty>
753
- <ThreadWelcomeSuggestions />
754
- </ThreadPrimitive.Empty>
755
- <ComposerPrimitive.Root className="relative flex w-full flex-col rounded-2xl focus-within:ring-2 focus-within:ring-black focus-within:ring-offset-2 dark:focus-within:ring-white">
981
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
982
+ <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">
983
+ <ComposerAttachments />
756
984
  <ComposerPrimitive.Input
757
985
  placeholder="Send a message..."
758
- className="bg-muted border-border dark:border-muted-foreground/15 focus:outline-primary placeholder:text-muted-foreground max-h-[calc(50dvh)] min-h-16 w-full resize-none rounded-t-2xl border-x border-t px-4 pt-2 pb-3 text-base outline-none"
986
+ 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"
759
987
  rows={1}
760
988
  autoFocus
761
989
  aria-label="Message input"
762
990
  />
763
991
  <ComposerAction />
764
- </ComposerPrimitive.Root>
765
- </div>
992
+ </ComposerPrimitive.AttachmentDropzone>
993
+ </ComposerPrimitive.Root>
766
994
  );
767
995
  };
768
996
 
769
997
  const ComposerAction: FC = () => {
770
998
  return (
771
- <div className="bg-muted border-border dark:border-muted-foreground/15 relative flex items-center justify-between rounded-b-2xl border-x border-b p-2">
772
- <TooltipIconButton
773
- tooltip="Attach file"
774
- variant="ghost"
775
- className="hover:bg-foreground/15 dark:hover:bg-background/50 scale-115 p-3.5"
776
- onClick={() => {
777
- console.log("Attachment clicked - not implemented");
778
- }}
779
- >
780
- <PlusIcon />
781
- </TooltipIconButton>
999
+ <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
1000
+ <ComposerAddAttachment />
782
1001
 
783
- <ThreadPrimitive.If running={false}>
1002
+ <AssistantIf condition={({ thread }) => !thread.isRunning}>
784
1003
  <ComposerPrimitive.Send asChild>
785
- <Button
1004
+ <TooltipIconButton
1005
+ tooltip="Send message"
1006
+ side="bottom"
786
1007
  type="submit"
787
1008
  variant="default"
788
- className="dark:border-muted-foreground/90 border-muted-foreground/60 hover:bg-primary/75 size-8 rounded-full border"
1009
+ size="icon"
1010
+ className="aui-composer-send size-8 rounded-full"
789
1011
  aria-label="Send message"
790
1012
  >
791
- <ArrowUpIcon className="size-5" />
792
- </Button>
1013
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
1014
+ </TooltipIconButton>
793
1015
  </ComposerPrimitive.Send>
794
- </ThreadPrimitive.If>
1016
+ </AssistantIf>
795
1017
 
796
- <ThreadPrimitive.If running>
1018
+ <AssistantIf condition={({ thread }) => thread.isRunning}>
797
1019
  <ComposerPrimitive.Cancel asChild>
798
1020
  <Button
799
1021
  type="button"
800
1022
  variant="default"
801
- className="dark:border-muted-foreground/90 border-muted-foreground/60 hover:bg-primary/75 size-8 rounded-full border"
1023
+ size="icon"
1024
+ className="aui-composer-cancel size-8 rounded-full"
802
1025
  aria-label="Stop generating"
803
1026
  >
804
- <Square className="size-3.5 fill-white dark:size-4 dark:fill-black" />
1027
+ <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
805
1028
  </Button>
806
1029
  </ComposerPrimitive.Cancel>
807
- </ThreadPrimitive.If>
1030
+ </AssistantIf>
808
1031
  </div>
809
1032
  );
810
1033
  };
@@ -812,8 +1035,8 @@ const ComposerAction: FC = () => {
812
1035
  const MessageError: FC = () => {
813
1036
  return (
814
1037
  <MessagePrimitive.Error>
815
- <ErrorPrimitive.Root className="border-destructive bg-destructive/10 dark:bg-destructive/5 text-destructive mt-2 rounded-md border p-3 text-sm dark:text-red-200">
816
- <ErrorPrimitive.Message className="line-clamp-2" />
1038
+ <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">
1039
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
817
1040
  </ErrorPrimitive.Root>
818
1041
  </MessagePrimitive.Error>
819
1042
  );
@@ -821,31 +1044,24 @@ const MessageError: FC = () => {
821
1044
 
822
1045
  const AssistantMessage: FC = () => {
823
1046
  return (
824
- <MessagePrimitive.Root asChild>
825
- <motion.div
826
- className="relative mx-auto grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] px-[var(--thread-padding-x)] py-4"
827
- initial={{ y: 5, opacity: 0 }}
828
- animate={{ y: 0, opacity: 1 }}
829
- data-role="assistant"
830
- >
831
- <div className="ring-border bg-background col-start-1 row-start-1 flex size-8 shrink-0 items-center justify-center rounded-full ring-1">
832
- <StarIcon size={14} />
833
- </div>
834
-
835
- <div className="text-foreground col-span-2 col-start-2 row-start-1 ml-4 leading-7 break-words">
836
- <MessagePrimitive.Content
837
- components={{
838
- Text: MarkdownText,
839
- tools: { Fallback: ToolFallback },
840
- }}
841
- />
842
- <MessageError />
843
- </div>
1047
+ <MessagePrimitive.Root
1048
+ 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"
1049
+ data-role="assistant"
1050
+ >
1051
+ <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
1052
+ <MessagePrimitive.Parts
1053
+ components={{
1054
+ Text: MarkdownText,
1055
+ tools: { Fallback: ToolFallback },
1056
+ }}
1057
+ />
1058
+ <MessageError />
1059
+ </div>
844
1060
 
1061
+ <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1062
+ <BranchPicker />
845
1063
  <AssistantActionBar />
846
-
847
- <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
848
- </motion.div>
1064
+ </div>
849
1065
  </MessagePrimitive.Root>
850
1066
  );
851
1067
  };
@@ -856,18 +1072,23 @@ const AssistantActionBar: FC = () => {
856
1072
  hideWhenRunning
857
1073
  autohide="not-last"
858
1074
  autohideFloat="single-branch"
859
- className="text-muted-foreground data-floating:bg-background col-start-3 row-start-2 mt-3 ml-3 flex gap-1 data-floating:absolute data-floating:mt-2 data-floating:rounded-md data-floating:border data-floating:p-1 data-floating:shadow-sm"
1075
+ 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"
860
1076
  >
861
1077
  <ActionBarPrimitive.Copy asChild>
862
1078
  <TooltipIconButton tooltip="Copy">
863
- <MessagePrimitive.If copied>
1079
+ <AssistantIf condition={({ message }) => message.isCopied}>
864
1080
  <CheckIcon />
865
- </MessagePrimitive.If>
866
- <MessagePrimitive.If copied={false}>
1081
+ </AssistantIf>
1082
+ <AssistantIf condition={({ message }) => !message.isCopied}>
867
1083
  <CopyIcon />
868
- </MessagePrimitive.If>
1084
+ </AssistantIf>
869
1085
  </TooltipIconButton>
870
1086
  </ActionBarPrimitive.Copy>
1087
+ <ActionBarPrimitive.ExportMarkdown asChild>
1088
+ <TooltipIconButton tooltip="Export as Markdown">
1089
+ <DownloadIcon />
1090
+ </TooltipIconButton>
1091
+ </ActionBarPrimitive.ExportMarkdown>
871
1092
  <ActionBarPrimitive.Reload asChild>
872
1093
  <TooltipIconButton tooltip="Refresh">
873
1094
  <RefreshCwIcon />
@@ -879,21 +1100,22 @@ const AssistantActionBar: FC = () => {
879
1100
 
880
1101
  const UserMessage: FC = () => {
881
1102
  return (
882
- <MessagePrimitive.Root asChild>
883
- <motion.div
884
- className="mx-auto grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-1 px-[var(--thread-padding-x)] py-4 [&:where(>*)]:col-start-2"
885
- initial={{ y: 5, opacity: 0 }}
886
- animate={{ y: 0, opacity: 1 }}
887
- data-role="user"
888
- >
889
- <UserActionBar />
1103
+ <MessagePrimitive.Root
1104
+ 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"
1105
+ data-role="user"
1106
+ >
1107
+ <UserMessageAttachments />
890
1108
 
891
- <div className="bg-muted text-foreground col-start-2 rounded-3xl px-5 py-2.5 break-words">
892
- <MessagePrimitive.Content components={{ Text: MarkdownText }} />
1109
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1110
+ <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1111
+ <MessagePrimitive.Parts />
893
1112
  </div>
1113
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
1114
+ <UserActionBar />
1115
+ </div>
1116
+ </div>
894
1117
 
895
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
896
- </motion.div>
1118
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
897
1119
  </MessagePrimitive.Root>
898
1120
  );
899
1121
  };
@@ -903,10 +1125,10 @@ const UserActionBar: FC = () => {
903
1125
  <ActionBarPrimitive.Root
904
1126
  hideWhenRunning
905
1127
  autohide="not-last"
906
- className="col-start-1 mt-2.5 mr-3 flex flex-col items-end"
1128
+ className="aui-user-action-bar-root flex flex-col items-end"
907
1129
  >
908
1130
  <ActionBarPrimitive.Edit asChild>
909
- <TooltipIconButton tooltip="Edit">
1131
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
910
1132
  <PencilIcon />
911
1133
  </TooltipIconButton>
912
1134
  </ActionBarPrimitive.Edit>
@@ -916,27 +1138,24 @@ const UserActionBar: FC = () => {
916
1138
 
917
1139
  const EditComposer: FC = () => {
918
1140
  return (
919
- <div className="mx-auto flex w-full max-w-[var(--thread-max-width)] flex-col gap-4 px-[var(--thread-padding-x)]">
920
- <ComposerPrimitive.Root className="bg-muted ml-auto flex w-full max-w-7/8 flex-col rounded-xl">
1141
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1142
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
921
1143
  <ComposerPrimitive.Input
922
- className="text-foreground flex min-h-[60px] w-full resize-none bg-transparent p-4 outline-none"
1144
+ className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
923
1145
  autoFocus
924
1146
  />
925
-
926
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
1147
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
927
1148
  <ComposerPrimitive.Cancel asChild>
928
- <Button variant="ghost" size="sm" aria-label="Cancel edit">
1149
+ <Button variant="ghost" size="sm">
929
1150
  Cancel
930
1151
  </Button>
931
1152
  </ComposerPrimitive.Cancel>
932
1153
  <ComposerPrimitive.Send asChild>
933
- <Button size="sm" aria-label="Update message">
934
- Update
935
- </Button>
1154
+ <Button size="sm">Update</Button>
936
1155
  </ComposerPrimitive.Send>
937
1156
  </div>
938
1157
  </ComposerPrimitive.Root>
939
- </div>
1158
+ </MessagePrimitive.Root>
940
1159
  );
941
1160
  };
942
1161
 
@@ -948,7 +1167,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
948
1167
  <BranchPickerPrimitive.Root
949
1168
  hideWhenSingleBranch
950
1169
  className={cn(
951
- "text-muted-foreground inline-flex items-center text-xs",
1170
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
952
1171
  className,
953
1172
  )}
954
1173
  {...rest}
@@ -958,7 +1177,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
958
1177
  <ChevronLeftIcon />
959
1178
  </TooltipIconButton>
960
1179
  </BranchPickerPrimitive.Previous>
961
- <span className="font-medium">
1180
+ <span className="aui-branch-picker-state font-medium">
962
1181
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
963
1182
  </span>
964
1183
  <BranchPickerPrimitive.Next asChild>
@@ -970,57 +1189,93 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
970
1189
  );
971
1190
  };
972
1191
 
973
- const StarIcon = ({ size = 14 }: { size?: number }) => (
974
- <svg
975
- width={size}
976
- height={size}
977
- viewBox="0 0 16 16"
978
- fill="none"
979
- xmlns="http://www.w3.org/2000/svg"
980
- >
981
- <path
982
- d="M8 0L9.79611 6.20389L16 8L9.79611 9.79611L8 16L6.20389 9.79611L0 8L6.20389 6.20389L8 0Z"
983
- fill="currentColor"
984
- />
985
- </svg>
986
- );
987
-
988
1192
  ```
989
1193
 
990
1194
  ## components/assistant-ui/tool-fallback.tsx
991
1195
 
992
1196
  ```tsx
993
- import { ToolCallMessagePartComponent } from "@assistant-ui/react";
994
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
1197
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1198
+ import {
1199
+ CheckIcon,
1200
+ ChevronDownIcon,
1201
+ ChevronUpIcon,
1202
+ XCircleIcon,
1203
+ } from "lucide-react";
995
1204
  import { useState } from "react";
996
- import { Button } from "../ui/button";
1205
+ import { Button } from "@/components/ui/button";
1206
+ import { cn } from "@/lib/utils";
997
1207
 
998
1208
  export const ToolFallback: ToolCallMessagePartComponent = ({
999
1209
  toolName,
1000
1210
  argsText,
1001
1211
  result,
1212
+ status,
1002
1213
  }) => {
1003
1214
  const [isCollapsed, setIsCollapsed] = useState(true);
1215
+
1216
+ const isCancelled =
1217
+ status?.type === "incomplete" && status.reason === "cancelled";
1218
+ const cancelledReason =
1219
+ isCancelled && status.error
1220
+ ? typeof status.error === "string"
1221
+ ? status.error
1222
+ : JSON.stringify(status.error)
1223
+ : null;
1224
+
1004
1225
  return (
1005
- <div className="mb-4 flex w-full flex-col gap-3 rounded-lg border py-3">
1006
- <div className="flex items-center gap-2 px-4">
1007
- <CheckIcon className="size-4" />
1008
- <p className="flex-grow">
1009
- Used tool: <b>{toolName}</b>
1226
+ <div
1227
+ className={cn(
1228
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1229
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1230
+ )}
1231
+ >
1232
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1233
+ {isCancelled ? (
1234
+ <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1235
+ ) : (
1236
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1237
+ )}
1238
+ <p
1239
+ className={cn(
1240
+ "aui-tool-fallback-title grow",
1241
+ isCancelled && "text-muted-foreground line-through",
1242
+ )}
1243
+ >
1244
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1245
+ <b>{toolName}</b>
1010
1246
  </p>
1011
1247
  <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1012
1248
  {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1013
1249
  </Button>
1014
1250
  </div>
1015
1251
  {!isCollapsed && (
1016
- <div className="flex flex-col gap-2 border-t pt-2">
1017
- <div className="px-4">
1018
- <pre className="whitespace-pre-wrap">{argsText}</pre>
1252
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1253
+ {cancelledReason && (
1254
+ <div className="aui-tool-fallback-cancelled-root px-4">
1255
+ <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1256
+ Cancelled reason:
1257
+ </p>
1258
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1259
+ {cancelledReason}
1260
+ </p>
1261
+ </div>
1262
+ )}
1263
+ <div
1264
+ className={cn(
1265
+ "aui-tool-fallback-args-root px-4",
1266
+ isCancelled && "opacity-60",
1267
+ )}
1268
+ >
1269
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1270
+ {argsText}
1271
+ </pre>
1019
1272
  </div>
1020
- {result !== undefined && (
1021
- <div className="border-t border-dashed px-4 pt-2">
1022
- <p className="font-semibold">Result:</p>
1023
- <pre className="whitespace-pre-wrap">
1273
+ {!isCancelled && result !== undefined && (
1274
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1275
+ <p className="aui-tool-fallback-result-header font-semibold">
1276
+ Result:
1277
+ </p>
1278
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1024
1279
  {typeof result === "string"
1025
1280
  ? result
1026
1281
  : JSON.stringify(result, null, 2)}
@@ -1040,7 +1295,7 @@ export const ToolFallback: ToolCallMessagePartComponent = ({
1040
1295
  ```tsx
1041
1296
  "use client";
1042
1297
 
1043
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1298
+ import { ComponentPropsWithRef, forwardRef } from "react";
1044
1299
  import { Slottable } from "@radix-ui/react-slot";
1045
1300
 
1046
1301
  import {
@@ -1051,7 +1306,7 @@ import {
1051
1306
  import { Button } from "@/components/ui/button";
1052
1307
  import { cn } from "@/lib/utils";
1053
1308
 
1054
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1309
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1055
1310
  tooltip: string;
1056
1311
  side?: "top" | "bottom" | "left" | "right";
1057
1312
  };
@@ -1067,11 +1322,11 @@ export const TooltipIconButton = forwardRef<
1067
1322
  variant="ghost"
1068
1323
  size="icon"
1069
1324
  {...rest}
1070
- className={cn("size-6 p-1", className)}
1325
+ className={cn("aui-button-icon size-6 p-1", className)}
1071
1326
  ref={ref}
1072
1327
  >
1073
1328
  <Slottable>{children}</Slottable>
1074
- <span className="sr-only">{tooltip}</span>
1329
+ <span className="aui-sr-only sr-only">{tooltip}</span>
1075
1330
  </Button>
1076
1331
  </TooltipTrigger>
1077
1332
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -1083,6 +1338,62 @@ TooltipIconButton.displayName = "TooltipIconButton";
1083
1338
 
1084
1339
  ```
1085
1340
 
1341
+ ## components/ui/avatar.tsx
1342
+
1343
+ ```tsx
1344
+ "use client";
1345
+
1346
+ import * as React from "react";
1347
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1348
+
1349
+ import { cn } from "@/lib/utils";
1350
+
1351
+ const Avatar = React.forwardRef<
1352
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1353
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1354
+ >(({ className, ...props }, ref) => (
1355
+ <AvatarPrimitive.Root
1356
+ ref={ref}
1357
+ className={cn(
1358
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1359
+ className,
1360
+ )}
1361
+ {...props}
1362
+ />
1363
+ ));
1364
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1365
+
1366
+ const AvatarImage = React.forwardRef<
1367
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1368
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1369
+ >(({ className, ...props }, ref) => (
1370
+ <AvatarPrimitive.Image
1371
+ ref={ref}
1372
+ className={cn("aspect-square h-full w-full", className)}
1373
+ {...props}
1374
+ />
1375
+ ));
1376
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1377
+
1378
+ const AvatarFallback = React.forwardRef<
1379
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1380
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1381
+ >(({ className, ...props }, ref) => (
1382
+ <AvatarPrimitive.Fallback
1383
+ ref={ref}
1384
+ className={cn(
1385
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
1386
+ className,
1387
+ )}
1388
+ {...props}
1389
+ />
1390
+ ));
1391
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1392
+
1393
+ export { Avatar, AvatarImage, AvatarFallback };
1394
+
1395
+ ```
1396
+
1086
1397
  ## components/ui/button.tsx
1087
1398
 
1088
1399
  ```tsx
@@ -1093,16 +1404,16 @@ import { cva, type VariantProps } from "class-variance-authority";
1093
1404
  import { cn } from "@/lib/utils";
1094
1405
 
1095
1406
  const buttonVariants = cva(
1096
- "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",
1407
+ "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",
1097
1408
  {
1098
1409
  variants: {
1099
1410
  variant: {
1100
1411
  default:
1101
1412
  "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
1102
1413
  destructive:
1103
- "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",
1414
+ "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",
1104
1415
  outline:
1105
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
1416
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1106
1417
  secondary:
1107
1418
  "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
1108
1419
  ghost:
@@ -1111,7 +1422,7 @@ const buttonVariants = cva(
1111
1422
  },
1112
1423
  size: {
1113
1424
  default: "h-9 px-4 py-2 has-[>svg]:px-3",
1114
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
1425
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1115
1426
  lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1116
1427
  icon: "size-9",
1117
1428
  },
@@ -1148,6 +1459,147 @@ export { Button, buttonVariants };
1148
1459
 
1149
1460
  ```
1150
1461
 
1462
+ ## components/ui/dialog.tsx
1463
+
1464
+ ```tsx
1465
+ "use client";
1466
+
1467
+ import * as React from "react";
1468
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1469
+ import { XIcon } from "lucide-react";
1470
+
1471
+ import { cn } from "@/lib/utils";
1472
+
1473
+ function Dialog({
1474
+ ...props
1475
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1476
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1477
+ }
1478
+
1479
+ function DialogTrigger({
1480
+ ...props
1481
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1482
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1483
+ }
1484
+
1485
+ function DialogPortal({
1486
+ ...props
1487
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1488
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1489
+ }
1490
+
1491
+ function DialogClose({
1492
+ ...props
1493
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1494
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1495
+ }
1496
+
1497
+ function DialogOverlay({
1498
+ className,
1499
+ ...props
1500
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1501
+ return (
1502
+ <DialogPrimitive.Overlay
1503
+ data-slot="dialog-overlay"
1504
+ className={cn(
1505
+ "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",
1506
+ className,
1507
+ )}
1508
+ {...props}
1509
+ />
1510
+ );
1511
+ }
1512
+
1513
+ function DialogContent({
1514
+ className,
1515
+ children,
1516
+ ...props
1517
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
1518
+ return (
1519
+ <DialogPortal data-slot="dialog-portal">
1520
+ <DialogOverlay />
1521
+ <DialogPrimitive.Content
1522
+ data-slot="dialog-content"
1523
+ className={cn(
1524
+ "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",
1525
+ className,
1526
+ )}
1527
+ {...props}
1528
+ >
1529
+ {children}
1530
+ <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">
1531
+ <XIcon />
1532
+ <span className="sr-only">Close</span>
1533
+ </DialogPrimitive.Close>
1534
+ </DialogPrimitive.Content>
1535
+ </DialogPortal>
1536
+ );
1537
+ }
1538
+
1539
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1540
+ return (
1541
+ <div
1542
+ data-slot="dialog-header"
1543
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1544
+ {...props}
1545
+ />
1546
+ );
1547
+ }
1548
+
1549
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1550
+ return (
1551
+ <div
1552
+ data-slot="dialog-footer"
1553
+ className={cn(
1554
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1555
+ className,
1556
+ )}
1557
+ {...props}
1558
+ />
1559
+ );
1560
+ }
1561
+
1562
+ function DialogTitle({
1563
+ className,
1564
+ ...props
1565
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1566
+ return (
1567
+ <DialogPrimitive.Title
1568
+ data-slot="dialog-title"
1569
+ className={cn("font-semibold text-lg leading-none", className)}
1570
+ {...props}
1571
+ />
1572
+ );
1573
+ }
1574
+
1575
+ function DialogDescription({
1576
+ className,
1577
+ ...props
1578
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1579
+ return (
1580
+ <DialogPrimitive.Description
1581
+ data-slot="dialog-description"
1582
+ className={cn("text-muted-foreground text-sm", className)}
1583
+ {...props}
1584
+ />
1585
+ );
1586
+ }
1587
+
1588
+ export {
1589
+ Dialog,
1590
+ DialogClose,
1591
+ DialogContent,
1592
+ DialogDescription,
1593
+ DialogFooter,
1594
+ DialogHeader,
1595
+ DialogOverlay,
1596
+ DialogPortal,
1597
+ DialogTitle,
1598
+ DialogTrigger,
1599
+ };
1600
+
1601
+ ```
1602
+
1151
1603
  ## components/ui/tooltip.tsx
1152
1604
 
1153
1605
  ```tsx
@@ -1199,13 +1651,13 @@ function TooltipContent({
1199
1651
  data-slot="tooltip-content"
1200
1652
  sideOffset={sideOffset}
1201
1653
  className={cn(
1202
- "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",
1654
+ "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",
1203
1655
  className,
1204
1656
  )}
1205
1657
  {...props}
1206
1658
  >
1207
1659
  {children}
1208
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1660
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
1209
1661
  </TooltipPrimitive.Content>
1210
1662
  </TooltipPrimitive.Portal>
1211
1663
  );
@@ -1261,28 +1713,30 @@ export default nextConfig;
1261
1713
  "@assistant-ui/react": "workspace:^",
1262
1714
  "@assistant-ui/react-langgraph": "workspace:^",
1263
1715
  "@assistant-ui/react-markdown": "workspace:^",
1716
+ "@radix-ui/react-avatar": "^1.1.11",
1717
+ "@radix-ui/react-dialog": "^1.1.15",
1264
1718
  "@radix-ui/react-slot": "^1.2.4",
1265
1719
  "@radix-ui/react-tooltip": "^1.2.8",
1266
1720
  "@tailwindcss/postcss": "^4.1.17",
1267
- "assistant-stream": "^0.2.41",
1721
+ "assistant-stream": "^0.2.44",
1268
1722
  "class-variance-authority": "^0.7.1",
1269
1723
  "clsx": "^2.1.1",
1270
- "framer-motion": "^12.23.24",
1271
- "lucide-react": "^0.554.0",
1272
- "next": "16.0.3",
1724
+ "lucide-react": "^0.556.0",
1725
+ "next": "16.0.7",
1273
1726
  "postcss": "^8.5.6",
1274
- "react": "19.2.0",
1275
- "react-dom": "19.2.0",
1727
+ "react": "19.2.1",
1728
+ "react-dom": "19.2.1",
1276
1729
  "remark-gfm": "^4.0.1",
1277
1730
  "tailwind-merge": "^3.4.0",
1278
1731
  "tailwindcss": "^4.1.17",
1279
1732
  "tailwindcss-animate": "^1.0.7",
1280
- "zod": "^4.1.12"
1733
+ "zod": "^4.1.13",
1734
+ "zustand": "^5.0.9"
1281
1735
  },
1282
1736
  "devDependencies": {
1283
1737
  "@assistant-ui/x-buildutils": "workspace:*",
1284
1738
  "@types/node": "^24.10.1",
1285
- "@types/react": "^19.2.5",
1739
+ "@types/react": "^19.2.7",
1286
1740
  "@types/react-dom": "^19.2.3",
1287
1741
  "tw-animate-css": "^1.4.0",
1288
1742
  "typescript": "^5.9.3"
@@ -1290,8 +1744,7 @@ export default nextConfig;
1290
1744
  "scripts": {
1291
1745
  "dev": "next dev",
1292
1746
  "build": "next build",
1293
- "start": "next start",
1294
- "lint": "eslint ."
1747
+ "start": "next start"
1295
1748
  }
1296
1749
  }
1297
1750