@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
@@ -335,6 +335,247 @@ export default function Home() {
335
335
 
336
336
  ```
337
337
 
338
+ ## components/assistant-ui/attachment.tsx
339
+
340
+ ```tsx
341
+ "use client";
342
+
343
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
344
+ import Image from "next/image";
345
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
346
+ import {
347
+ AttachmentPrimitive,
348
+ ComposerPrimitive,
349
+ MessagePrimitive,
350
+ useAssistantState,
351
+ useAssistantApi,
352
+ } from "@assistant-ui/react";
353
+ import { useShallow } from "zustand/shallow";
354
+ import {
355
+ Tooltip,
356
+ TooltipContent,
357
+ TooltipTrigger,
358
+ } from "@/components/ui/tooltip";
359
+ import {
360
+ Dialog,
361
+ DialogTitle,
362
+ DialogContent,
363
+ DialogTrigger,
364
+ } from "@/components/ui/dialog";
365
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
366
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
367
+ import { cn } from "@/lib/utils";
368
+
369
+ const useFileSrc = (file: File | undefined) => {
370
+ const [src, setSrc] = useState<string | undefined>(undefined);
371
+
372
+ useEffect(() => {
373
+ if (!file) {
374
+ setSrc(undefined);
375
+ return;
376
+ }
377
+
378
+ const objectUrl = URL.createObjectURL(file);
379
+ setSrc(objectUrl);
380
+
381
+ return () => {
382
+ URL.revokeObjectURL(objectUrl);
383
+ };
384
+ }, [file]);
385
+
386
+ return src;
387
+ };
388
+
389
+ const useAttachmentSrc = () => {
390
+ const { file, src } = useAssistantState(
391
+ useShallow(({ attachment }): { file?: File; src?: string } => {
392
+ if (attachment.type !== "image") return {};
393
+ if (attachment.file) return { file: attachment.file };
394
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
395
+ ?.image;
396
+ if (!src) return {};
397
+ return { src };
398
+ }),
399
+ );
400
+
401
+ return useFileSrc(file) ?? src;
402
+ };
403
+
404
+ type AttachmentPreviewProps = {
405
+ src: string;
406
+ };
407
+
408
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
409
+ const [isLoaded, setIsLoaded] = useState(false);
410
+ return (
411
+ <Image
412
+ src={src}
413
+ alt="Image Preview"
414
+ width={1}
415
+ height={1}
416
+ className={
417
+ isLoaded
418
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
419
+ : "aui-attachment-preview-image-loading hidden"
420
+ }
421
+ onLoadingComplete={() => setIsLoaded(true)}
422
+ priority={false}
423
+ />
424
+ );
425
+ };
426
+
427
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
428
+ const src = useAttachmentSrc();
429
+
430
+ if (!src) return children;
431
+
432
+ return (
433
+ <Dialog>
434
+ <DialogTrigger
435
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
436
+ asChild
437
+ >
438
+ {children}
439
+ </DialogTrigger>
440
+ <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">
441
+ <DialogTitle className="aui-sr-only sr-only">
442
+ Image Attachment Preview
443
+ </DialogTitle>
444
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
445
+ <AttachmentPreview src={src} />
446
+ </div>
447
+ </DialogContent>
448
+ </Dialog>
449
+ );
450
+ };
451
+
452
+ const AttachmentThumb: FC = () => {
453
+ const isImage = useAssistantState(
454
+ ({ attachment }) => attachment.type === "image",
455
+ );
456
+ const src = useAttachmentSrc();
457
+
458
+ return (
459
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
460
+ <AvatarImage
461
+ src={src}
462
+ alt="Attachment preview"
463
+ className="aui-attachment-tile-image object-cover"
464
+ />
465
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
466
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
467
+ </AvatarFallback>
468
+ </Avatar>
469
+ );
470
+ };
471
+
472
+ const AttachmentUI: FC = () => {
473
+ const api = useAssistantApi();
474
+ const isComposer = api.attachment.source === "composer";
475
+
476
+ const isImage = useAssistantState(
477
+ ({ attachment }) => attachment.type === "image",
478
+ );
479
+ const typeLabel = useAssistantState(({ attachment }) => {
480
+ const type = attachment.type;
481
+ switch (type) {
482
+ case "image":
483
+ return "Image";
484
+ case "document":
485
+ return "Document";
486
+ case "file":
487
+ return "File";
488
+ default:
489
+ const _exhaustiveCheck: never = type;
490
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
491
+ }
492
+ });
493
+
494
+ return (
495
+ <Tooltip>
496
+ <AttachmentPrimitive.Root
497
+ className={cn(
498
+ "aui-attachment-root relative",
499
+ isImage &&
500
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
501
+ )}
502
+ >
503
+ <AttachmentPreviewDialog>
504
+ <TooltipTrigger asChild>
505
+ <div
506
+ className={cn(
507
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
508
+ isComposer &&
509
+ "aui-attachment-tile-composer border-foreground/20",
510
+ )}
511
+ role="button"
512
+ id="attachment-tile"
513
+ aria-label={`${typeLabel} attachment`}
514
+ >
515
+ <AttachmentThumb />
516
+ </div>
517
+ </TooltipTrigger>
518
+ </AttachmentPreviewDialog>
519
+ {isComposer && <AttachmentRemove />}
520
+ </AttachmentPrimitive.Root>
521
+ <TooltipContent side="top">
522
+ <AttachmentPrimitive.Name />
523
+ </TooltipContent>
524
+ </Tooltip>
525
+ );
526
+ };
527
+
528
+ const AttachmentRemove: FC = () => {
529
+ return (
530
+ <AttachmentPrimitive.Remove asChild>
531
+ <TooltipIconButton
532
+ tooltip="Remove file"
533
+ 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"
534
+ side="top"
535
+ >
536
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
537
+ </TooltipIconButton>
538
+ </AttachmentPrimitive.Remove>
539
+ );
540
+ };
541
+
542
+ export const UserMessageAttachments: FC = () => {
543
+ return (
544
+ <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">
545
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
546
+ </div>
547
+ );
548
+ };
549
+
550
+ export const ComposerAttachments: FC = () => {
551
+ return (
552
+ <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">
553
+ <ComposerPrimitive.Attachments
554
+ components={{ Attachment: AttachmentUI }}
555
+ />
556
+ </div>
557
+ );
558
+ };
559
+
560
+ export const ComposerAddAttachment: FC = () => {
561
+ return (
562
+ <ComposerPrimitive.AddAttachment asChild>
563
+ <TooltipIconButton
564
+ tooltip="Add Attachment"
565
+ side="bottom"
566
+ variant="ghost"
567
+ size="icon"
568
+ 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"
569
+ aria-label="Add Attachment"
570
+ >
571
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
572
+ </TooltipIconButton>
573
+ </ComposerPrimitive.AddAttachment>
574
+ );
575
+ };
576
+
577
+ ```
578
+
338
579
  ## components/assistant-ui/markdown-text.tsx
339
580
 
340
581
  ```tsx
@@ -343,13 +584,13 @@ export default function Home() {
343
584
  import "@assistant-ui/react-markdown/styles/dot.css";
344
585
 
345
586
  import {
346
- CodeHeaderProps,
587
+ type CodeHeaderProps,
347
588
  MarkdownTextPrimitive,
348
589
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
349
590
  useIsMarkdownCodeBlock,
350
591
  } from "@assistant-ui/react-markdown";
351
592
  import remarkGfm from "remark-gfm";
352
- import { FC, memo, useState } from "react";
593
+ import { type FC, memo, useState } from "react";
353
594
  import { CheckIcon, CopyIcon } from "lucide-react";
354
595
 
355
596
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -375,8 +616,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
375
616
  };
376
617
 
377
618
  return (
378
- <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">
379
- <span className="lowercase [&>span]:text-xs">{language}</span>
619
+ <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">
620
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
621
+ {language}
622
+ </span>
380
623
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
381
624
  {!isCopied && <CopyIcon />}
382
625
  {isCopied && <CheckIcon />}
@@ -408,7 +651,7 @@ const defaultComponents = memoizeMarkdownComponents({
408
651
  h1: ({ className, ...props }) => (
409
652
  <h1
410
653
  className={cn(
411
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
654
+ "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
412
655
  className,
413
656
  )}
414
657
  {...props}
@@ -417,7 +660,7 @@ const defaultComponents = memoizeMarkdownComponents({
417
660
  h2: ({ className, ...props }) => (
418
661
  <h2
419
662
  className={cn(
420
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
663
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
421
664
  className,
422
665
  )}
423
666
  {...props}
@@ -426,7 +669,7 @@ const defaultComponents = memoizeMarkdownComponents({
426
669
  h3: ({ className, ...props }) => (
427
670
  <h3
428
671
  className={cn(
429
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
672
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
430
673
  className,
431
674
  )}
432
675
  {...props}
@@ -435,7 +678,7 @@ const defaultComponents = memoizeMarkdownComponents({
435
678
  h4: ({ className, ...props }) => (
436
679
  <h4
437
680
  className={cn(
438
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
681
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
439
682
  className,
440
683
  )}
441
684
  {...props}
@@ -444,7 +687,7 @@ const defaultComponents = memoizeMarkdownComponents({
444
687
  h5: ({ className, ...props }) => (
445
688
  <h5
446
689
  className={cn(
447
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
690
+ "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
448
691
  className,
449
692
  )}
450
693
  {...props}
@@ -452,20 +695,26 @@ const defaultComponents = memoizeMarkdownComponents({
452
695
  ),
453
696
  h6: ({ className, ...props }) => (
454
697
  <h6
455
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
698
+ className={cn(
699
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
700
+ className,
701
+ )}
456
702
  {...props}
457
703
  />
458
704
  ),
459
705
  p: ({ className, ...props }) => (
460
706
  <p
461
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
707
+ className={cn(
708
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
709
+ className,
710
+ )}
462
711
  {...props}
463
712
  />
464
713
  ),
465
714
  a: ({ className, ...props }) => (
466
715
  <a
467
716
  className={cn(
468
- "text-primary font-medium underline underline-offset-4",
717
+ "aui-md-a font-medium text-primary underline underline-offset-4",
469
718
  className,
470
719
  )}
471
720
  {...props}
@@ -473,29 +722,29 @@ const defaultComponents = memoizeMarkdownComponents({
473
722
  ),
474
723
  blockquote: ({ className, ...props }) => (
475
724
  <blockquote
476
- className={cn("border-l-2 pl-6 italic", className)}
725
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
477
726
  {...props}
478
727
  />
479
728
  ),
480
729
  ul: ({ className, ...props }) => (
481
730
  <ul
482
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
731
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
483
732
  {...props}
484
733
  />
485
734
  ),
486
735
  ol: ({ className, ...props }) => (
487
736
  <ol
488
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
737
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
489
738
  {...props}
490
739
  />
491
740
  ),
492
741
  hr: ({ className, ...props }) => (
493
- <hr className={cn("my-5 border-b", className)} {...props} />
742
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
494
743
  ),
495
744
  table: ({ className, ...props }) => (
496
745
  <table
497
746
  className={cn(
498
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
747
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
499
748
  className,
500
749
  )}
501
750
  {...props}
@@ -504,7 +753,7 @@ const defaultComponents = memoizeMarkdownComponents({
504
753
  th: ({ className, ...props }) => (
505
754
  <th
506
755
  className={cn(
507
- "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",
756
+ "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",
508
757
  className,
509
758
  )}
510
759
  {...props}
@@ -513,7 +762,7 @@ const defaultComponents = memoizeMarkdownComponents({
513
762
  td: ({ className, ...props }) => (
514
763
  <td
515
764
  className={cn(
516
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
765
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
517
766
  className,
518
767
  )}
519
768
  {...props}
@@ -522,7 +771,7 @@ const defaultComponents = memoizeMarkdownComponents({
522
771
  tr: ({ className, ...props }) => (
523
772
  <tr
524
773
  className={cn(
525
- "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",
774
+ "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",
526
775
  className,
527
776
  )}
528
777
  {...props}
@@ -530,14 +779,14 @@ const defaultComponents = memoizeMarkdownComponents({
530
779
  ),
531
780
  sup: ({ className, ...props }) => (
532
781
  <sup
533
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
782
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
534
783
  {...props}
535
784
  />
536
785
  ),
537
786
  pre: ({ className, ...props }) => (
538
787
  <pre
539
788
  className={cn(
540
- "overflow-x-auto rounded-b-lg bg-black p-4 text-white",
789
+ "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
541
790
  className,
542
791
  )}
543
792
  {...props}
@@ -548,7 +797,8 @@ const defaultComponents = memoizeMarkdownComponents({
548
797
  return (
549
798
  <code
550
799
  className={cn(
551
- !isCodeBlock && "bg-muted rounded border font-semibold",
800
+ !isCodeBlock &&
801
+ "aui-md-inline-code rounded border bg-muted font-semibold",
552
802
  className,
553
803
  )}
554
804
  {...props}
@@ -563,21 +813,27 @@ const defaultComponents = memoizeMarkdownComponents({
563
813
  ## components/assistant-ui/thread-list.tsx
564
814
 
565
815
  ```tsx
566
- import type { FC } from "react";
816
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
817
+ import { Button } from "@/components/ui/button";
818
+ import { Skeleton } from "@/components/ui/skeleton";
567
819
  import {
820
+ AssistantIf,
568
821
  ThreadListItemPrimitive,
569
822
  ThreadListPrimitive,
570
823
  } from "@assistant-ui/react";
571
824
  import { ArchiveIcon, PlusIcon } from "lucide-react";
572
-
573
- import { Button } from "@/components/ui/button";
574
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
825
+ import type { FC } from "react";
575
826
 
576
827
  export const ThreadList: FC = () => {
577
828
  return (
578
- <ThreadListPrimitive.Root className="flex flex-col items-stretch gap-1.5">
829
+ <ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col gap-1">
579
830
  <ThreadListNew />
580
- <ThreadListItems />
831
+ <AssistantIf condition={({ threads }) => threads.isLoading}>
832
+ <ThreadListSkeleton />
833
+ </AssistantIf>
834
+ <AssistantIf condition={({ threads }) => !threads.isLoading}>
835
+ <ThreadListPrimitive.Items components={{ ThreadListItem }} />
836
+ </AssistantIf>
581
837
  </ThreadListPrimitive.Root>
582
838
  );
583
839
  };
@@ -586,48 +842,53 @@ const ThreadListNew: FC = () => {
586
842
  return (
587
843
  <ThreadListPrimitive.New asChild>
588
844
  <Button
589
- className="data-[active]:bg-muted hover:bg-muted flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start"
590
- variant="ghost"
845
+ variant="outline"
846
+ className="aui-thread-list-new h-9 justify-start gap-2 rounded-lg px-3 text-sm hover:bg-muted data-active:bg-muted"
591
847
  >
592
- <PlusIcon />
848
+ <PlusIcon className="size-4" />
593
849
  New Thread
594
850
  </Button>
595
851
  </ThreadListPrimitive.New>
596
852
  );
597
853
  };
598
854
 
599
- const ThreadListItems: FC = () => {
600
- return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;
855
+ const ThreadListSkeleton: FC = () => {
856
+ return (
857
+ <div className="flex flex-col gap-1">
858
+ {Array.from({ length: 5 }, (_, i) => (
859
+ <div
860
+ key={i}
861
+ role="status"
862
+ aria-label="Loading threads"
863
+ className="aui-thread-list-skeleton-wrapper flex h-9 items-center px-3"
864
+ >
865
+ <Skeleton className="aui-thread-list-skeleton h-4 w-full" />
866
+ </div>
867
+ ))}
868
+ </div>
869
+ );
601
870
  };
602
871
 
603
872
  const ThreadListItem: FC = () => {
604
873
  return (
605
- <ThreadListItemPrimitive.Root className="data-[active]:bg-muted hover:bg-muted focus-visible:bg-muted focus-visible:ring-ring flex items-center gap-2 rounded-lg transition-all focus-visible:ring-2 focus-visible:outline-none">
606
- <ThreadListItemPrimitive.Trigger className="flex-grow px-3 py-2 text-start">
607
- <ThreadListItemTitle />
874
+ <ThreadListItemPrimitive.Root className="aui-thread-list-item group flex h-9 items-center rounded-lg transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none data-active:bg-muted">
875
+ <ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger flex h-full flex-1 items-center truncate px-3 text-start text-sm">
876
+ <ThreadListItemPrimitive.Title fallback="New Chat" />
608
877
  </ThreadListItemPrimitive.Trigger>
609
878
  <ThreadListItemArchive />
610
879
  </ThreadListItemPrimitive.Root>
611
880
  );
612
881
  };
613
882
 
614
- const ThreadListItemTitle: FC = () => {
615
- return (
616
- <p className="text-sm">
617
- <ThreadListItemPrimitive.Title fallback="New Chat" />
618
- </p>
619
- );
620
- };
621
-
622
883
  const ThreadListItemArchive: FC = () => {
623
884
  return (
624
885
  <ThreadListItemPrimitive.Archive asChild>
625
886
  <TooltipIconButton
626
- className="hover:text-primary text-foreground mr-3 ml-auto size-4 p-0"
627
887
  variant="ghost"
628
888
  tooltip="Archive thread"
889
+ className="aui-thread-list-item-archive mr-2 size-7 p-0 opacity-0 transition-opacity group-hover:opacity-100"
629
890
  >
630
- <ArchiveIcon />
891
+ <ArchiveIcon className="size-4" />
631
892
  </TooltipIconButton>
632
893
  </ThreadListItemPrimitive.Archive>
633
894
  );
@@ -638,57 +899,67 @@ const ThreadListItemArchive: FC = () => {
638
899
  ## components/assistant-ui/thread.tsx
639
900
 
640
901
  ```tsx
902
+ import {
903
+ ComposerAddAttachment,
904
+ ComposerAttachments,
905
+ UserMessageAttachments,
906
+ } from "@/components/assistant-ui/attachment";
907
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
908
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
909
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
910
+ import { Button } from "@/components/ui/button";
911
+ import { cn } from "@/lib/utils";
641
912
  import {
642
913
  ActionBarPrimitive,
914
+ AssistantIf,
643
915
  BranchPickerPrimitive,
644
916
  ComposerPrimitive,
917
+ ErrorPrimitive,
645
918
  MessagePrimitive,
646
919
  ThreadPrimitive,
647
920
  } from "@assistant-ui/react";
648
- import type { FC } from "react";
649
921
  import {
650
922
  ArrowDownIcon,
923
+ ArrowUpIcon,
651
924
  CheckIcon,
652
925
  ChevronLeftIcon,
653
926
  ChevronRightIcon,
654
927
  CopyIcon,
928
+ DownloadIcon,
655
929
  PencilIcon,
656
930
  RefreshCwIcon,
657
- SendHorizontalIcon,
931
+ SquareIcon,
658
932
  } from "lucide-react";
659
- import { cn } from "@/lib/utils";
660
-
661
- import { Button } from "@/components/ui/button";
662
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
663
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
933
+ import type { FC } from "react";
664
934
 
665
935
  export const Thread: FC = () => {
666
936
  return (
667
937
  <ThreadPrimitive.Root
668
- className="bg-background box-border flex h-full flex-col overflow-hidden"
938
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
669
939
  style={{
670
- ["--thread-max-width" as string]: "42rem",
940
+ ["--thread-max-width" as string]: "44rem",
671
941
  }}
672
942
  >
673
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
674
- <ThreadWelcome />
943
+ <ThreadPrimitive.Viewport
944
+ turnAnchor="top"
945
+ className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
946
+ >
947
+ <AssistantIf condition={({ thread }) => thread.isEmpty}>
948
+ <ThreadWelcome />
949
+ </AssistantIf>
675
950
 
676
951
  <ThreadPrimitive.Messages
677
952
  components={{
678
- UserMessage: UserMessage,
679
- EditComposer: EditComposer,
680
- AssistantMessage: AssistantMessage,
953
+ UserMessage,
954
+ EditComposer,
955
+ AssistantMessage,
681
956
  }}
682
957
  />
683
958
 
684
- <ThreadPrimitive.If empty={false}>
685
- <div className="min-h-8 flex-grow" />
686
- </ThreadPrimitive.If>
687
-
688
- <div className="sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit pb-4">
959
+ <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
689
960
  <ThreadScrollToBottom />
690
961
  <Composer />
691
- </div>
962
+ </ThreadPrimitive.ViewportFooter>
692
963
  </ThreadPrimitive.Viewport>
693
964
  </ThreadPrimitive.Root>
694
965
  );
@@ -700,7 +971,7 @@ const ThreadScrollToBottom: FC = () => {
700
971
  <TooltipIconButton
701
972
  tooltip="Scroll to bottom"
702
973
  variant="outline"
703
- className="absolute -top-8 rounded-full disabled:invisible"
974
+ className="aui-thread-scroll-to-bottom -top-12 absolute z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
704
975
  >
705
976
  <ArrowDownIcon />
706
977
  </TooltipIconButton>
@@ -710,175 +981,247 @@ const ThreadScrollToBottom: FC = () => {
710
981
 
711
982
  const ThreadWelcome: FC = () => {
712
983
  return (
713
- <ThreadPrimitive.Empty>
714
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
715
- <div className="flex w-full flex-grow flex-col items-center justify-center">
716
- <p className="mt-4 font-medium">How can I help you today?</p>
984
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
985
+ <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
986
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
987
+ <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
988
+ Hello there!
989
+ </h1>
990
+ <p className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in text-muted-foreground text-xl delay-75 duration-200">
991
+ How can I help you today?
992
+ </p>
717
993
  </div>
718
- <ThreadWelcomeSuggestions />
719
994
  </div>
720
- </ThreadPrimitive.Empty>
995
+ <ThreadSuggestions />
996
+ </div>
721
997
  );
722
998
  };
723
999
 
724
- const ThreadWelcomeSuggestions: FC = () => {
1000
+ const SUGGESTIONS = [
1001
+ {
1002
+ title: "What's the weather",
1003
+ label: "in San Francisco?",
1004
+ prompt: "What's the weather in San Francisco?",
1005
+ },
1006
+ {
1007
+ title: "Explain React hooks",
1008
+ label: "like useState and useEffect",
1009
+ prompt: "Explain React hooks like useState and useEffect",
1010
+ },
1011
+ ] as const;
1012
+
1013
+ const ThreadSuggestions: FC = () => {
725
1014
  return (
726
- <div className="mt-3 flex w-full items-stretch justify-center gap-4">
727
- <ThreadPrimitive.Suggestion
728
- 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"
729
- prompt="What is the weather in Tokyo?"
730
- method="replace"
731
- autoSend
732
- >
733
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
734
- What is the weather in Tokyo?
735
- </span>
736
- </ThreadPrimitive.Suggestion>
737
- <ThreadPrimitive.Suggestion
738
- 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"
739
- prompt="What is assistant-ui?"
740
- method="replace"
741
- autoSend
742
- >
743
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
744
- What is assistant-ui?
745
- </span>
746
- </ThreadPrimitive.Suggestion>
1015
+ <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
1016
+ {SUGGESTIONS.map((suggestion, index) => (
1017
+ <div
1018
+ key={suggestion.prompt}
1019
+ className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200"
1020
+ style={{ animationDelay: `${100 + index * 50}ms` }}
1021
+ >
1022
+ <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
1023
+ <Button
1024
+ variant="ghost"
1025
+ className="aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted"
1026
+ aria-label={suggestion.prompt}
1027
+ >
1028
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
1029
+ {suggestion.title}
1030
+ </span>
1031
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
1032
+ {suggestion.label}
1033
+ </span>
1034
+ </Button>
1035
+ </ThreadPrimitive.Suggestion>
1036
+ </div>
1037
+ ))}
747
1038
  </div>
748
1039
  );
749
1040
  };
750
1041
 
751
1042
  const Composer: FC = () => {
752
1043
  return (
753
- <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">
754
- <ComposerPrimitive.Input
755
- rows={1}
756
- autoFocus
757
- placeholder="Write a message..."
758
- 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"
759
- />
760
- <ComposerAction />
1044
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
1045
+ <ComposerPrimitive.AttachmentDropzone className="aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50">
1046
+ <ComposerAttachments />
1047
+ <ComposerPrimitive.Input
1048
+ placeholder="Send a message..."
1049
+ className="aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0"
1050
+ rows={1}
1051
+ autoFocus
1052
+ aria-label="Message input"
1053
+ />
1054
+ <ComposerAction />
1055
+ </ComposerPrimitive.AttachmentDropzone>
761
1056
  </ComposerPrimitive.Root>
762
1057
  );
763
1058
  };
764
1059
 
765
1060
  const ComposerAction: FC = () => {
766
1061
  return (
767
- <>
768
- <ThreadPrimitive.If running={false}>
1062
+ <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
1063
+ <ComposerAddAttachment />
1064
+
1065
+ <AssistantIf condition={({ thread }) => !thread.isRunning}>
769
1066
  <ComposerPrimitive.Send asChild>
770
1067
  <TooltipIconButton
771
- tooltip="Send"
1068
+ tooltip="Send message"
1069
+ side="bottom"
1070
+ type="submit"
772
1071
  variant="default"
773
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
1072
+ size="icon"
1073
+ className="aui-composer-send size-8 rounded-full"
1074
+ aria-label="Send message"
774
1075
  >
775
- <SendHorizontalIcon />
1076
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
776
1077
  </TooltipIconButton>
777
1078
  </ComposerPrimitive.Send>
778
- </ThreadPrimitive.If>
779
- <ThreadPrimitive.If running>
1079
+ </AssistantIf>
1080
+
1081
+ <AssistantIf condition={({ thread }) => thread.isRunning}>
780
1082
  <ComposerPrimitive.Cancel asChild>
781
- <TooltipIconButton
782
- tooltip="Cancel"
1083
+ <Button
1084
+ type="button"
783
1085
  variant="default"
784
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
1086
+ size="icon"
1087
+ className="aui-composer-cancel size-8 rounded-full"
1088
+ aria-label="Stop generating"
785
1089
  >
786
- <CircleStopIcon />
787
- </TooltipIconButton>
1090
+ <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1091
+ </Button>
788
1092
  </ComposerPrimitive.Cancel>
789
- </ThreadPrimitive.If>
790
- </>
1093
+ </AssistantIf>
1094
+ </div>
791
1095
  );
792
1096
  };
793
1097
 
794
- const UserMessage: FC = () => {
1098
+ const MessageError: FC = () => {
795
1099
  return (
796
- <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">
797
- <UserActionBar />
1100
+ <MessagePrimitive.Error>
1101
+ <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200">
1102
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
1103
+ </ErrorPrimitive.Root>
1104
+ </MessagePrimitive.Error>
1105
+ );
1106
+ };
798
1107
 
799
- <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">
800
- <MessagePrimitive.Parts />
1108
+ const AssistantMessage: FC = () => {
1109
+ return (
1110
+ <MessagePrimitive.Root
1111
+ className="aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150"
1112
+ data-role="assistant"
1113
+ >
1114
+ <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
1115
+ <MessagePrimitive.Parts
1116
+ components={{
1117
+ Text: MarkdownText,
1118
+ tools: { Fallback: ToolFallback },
1119
+ }}
1120
+ />
1121
+ <MessageError />
801
1122
  </div>
802
1123
 
803
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1124
+ <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1125
+ <BranchPicker />
1126
+ <AssistantActionBar />
1127
+ </div>
804
1128
  </MessagePrimitive.Root>
805
1129
  );
806
1130
  };
807
1131
 
808
- const UserActionBar: FC = () => {
1132
+ const AssistantActionBar: FC = () => {
809
1133
  return (
810
1134
  <ActionBarPrimitive.Root
811
1135
  hideWhenRunning
812
1136
  autohide="not-last"
813
- className="col-start-1 row-start-2 mt-2.5 mr-3 flex flex-col items-end"
1137
+ autohideFloat="single-branch"
1138
+ className="aui-assistant-action-bar-root -ml-1 col-start-3 row-start-2 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
814
1139
  >
815
- <ActionBarPrimitive.Edit asChild>
816
- <TooltipIconButton tooltip="Edit">
817
- <PencilIcon />
1140
+ <ActionBarPrimitive.Copy asChild>
1141
+ <TooltipIconButton tooltip="Copy">
1142
+ <AssistantIf condition={({ message }) => message.isCopied}>
1143
+ <CheckIcon />
1144
+ </AssistantIf>
1145
+ <AssistantIf condition={({ message }) => !message.isCopied}>
1146
+ <CopyIcon />
1147
+ </AssistantIf>
818
1148
  </TooltipIconButton>
819
- </ActionBarPrimitive.Edit>
1149
+ </ActionBarPrimitive.Copy>
1150
+ <ActionBarPrimitive.ExportMarkdown asChild>
1151
+ <TooltipIconButton tooltip="Export as Markdown">
1152
+ <DownloadIcon />
1153
+ </TooltipIconButton>
1154
+ </ActionBarPrimitive.ExportMarkdown>
1155
+ <ActionBarPrimitive.Reload asChild>
1156
+ <TooltipIconButton tooltip="Refresh">
1157
+ <RefreshCwIcon />
1158
+ </TooltipIconButton>
1159
+ </ActionBarPrimitive.Reload>
820
1160
  </ActionBarPrimitive.Root>
821
1161
  );
822
1162
  };
823
1163
 
824
- const EditComposer: FC = () => {
1164
+ const UserMessage: FC = () => {
825
1165
  return (
826
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
827
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
828
-
829
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
830
- <ComposerPrimitive.Cancel asChild>
831
- <Button variant="ghost">Cancel</Button>
832
- </ComposerPrimitive.Cancel>
833
- <ComposerPrimitive.Send asChild>
834
- <Button>Send</Button>
835
- </ComposerPrimitive.Send>
836
- </div>
837
- </ComposerPrimitive.Root>
838
- );
839
- };
1166
+ <MessagePrimitive.Root
1167
+ className="aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-3 duration-150 [&:where(>*)]:col-start-2"
1168
+ data-role="user"
1169
+ >
1170
+ <UserMessageAttachments />
840
1171
 
841
- const AssistantMessage: FC = () => {
842
- return (
843
- <MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
844
- <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">
845
- <MessagePrimitive.Parts components={{ Text: MarkdownText }} />
1172
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1173
+ <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1174
+ <MessagePrimitive.Parts />
1175
+ </div>
1176
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
1177
+ <UserActionBar />
1178
+ </div>
846
1179
  </div>
847
1180
 
848
- <AssistantActionBar />
849
-
850
- <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
1181
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
851
1182
  </MessagePrimitive.Root>
852
1183
  );
853
1184
  };
854
1185
 
855
- const AssistantActionBar: FC = () => {
1186
+ const UserActionBar: FC = () => {
856
1187
  return (
857
1188
  <ActionBarPrimitive.Root
858
1189
  hideWhenRunning
859
1190
  autohide="not-last"
860
- autohideFloat="single-branch"
861
- 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"
1191
+ className="aui-user-action-bar-root flex flex-col items-end"
862
1192
  >
863
- <ActionBarPrimitive.Copy asChild>
864
- <TooltipIconButton tooltip="Copy">
865
- <MessagePrimitive.If copied>
866
- <CheckIcon />
867
- </MessagePrimitive.If>
868
- <MessagePrimitive.If copied={false}>
869
- <CopyIcon />
870
- </MessagePrimitive.If>
871
- </TooltipIconButton>
872
- </ActionBarPrimitive.Copy>
873
- <ActionBarPrimitive.Reload asChild>
874
- <TooltipIconButton tooltip="Refresh">
875
- <RefreshCwIcon />
1193
+ <ActionBarPrimitive.Edit asChild>
1194
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1195
+ <PencilIcon />
876
1196
  </TooltipIconButton>
877
- </ActionBarPrimitive.Reload>
1197
+ </ActionBarPrimitive.Edit>
878
1198
  </ActionBarPrimitive.Root>
879
1199
  );
880
1200
  };
881
1201
 
1202
+ const EditComposer: FC = () => {
1203
+ return (
1204
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1205
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1206
+ <ComposerPrimitive.Input
1207
+ className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1208
+ autoFocus
1209
+ />
1210
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1211
+ <ComposerPrimitive.Cancel asChild>
1212
+ <Button variant="ghost" size="sm">
1213
+ Cancel
1214
+ </Button>
1215
+ </ComposerPrimitive.Cancel>
1216
+ <ComposerPrimitive.Send asChild>
1217
+ <Button size="sm">Update</Button>
1218
+ </ComposerPrimitive.Send>
1219
+ </div>
1220
+ </ComposerPrimitive.Root>
1221
+ </MessagePrimitive.Root>
1222
+ );
1223
+ };
1224
+
882
1225
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
883
1226
  className,
884
1227
  ...rest
@@ -887,7 +1230,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
887
1230
  <BranchPickerPrimitive.Root
888
1231
  hideWhenSingleBranch
889
1232
  className={cn(
890
- "text-muted-foreground inline-flex items-center text-xs",
1233
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
891
1234
  className,
892
1235
  )}
893
1236
  {...rest}
@@ -897,7 +1240,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
897
1240
  <ChevronLeftIcon />
898
1241
  </TooltipIconButton>
899
1242
  </BranchPickerPrimitive.Previous>
900
- <span className="font-medium">
1243
+ <span className="aui-branch-picker-state font-medium">
901
1244
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
902
1245
  </span>
903
1246
  <BranchPickerPrimitive.Next asChild>
@@ -909,17 +1252,102 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
909
1252
  );
910
1253
  };
911
1254
 
912
- const CircleStopIcon = () => {
1255
+ ```
1256
+
1257
+ ## components/assistant-ui/tool-fallback.tsx
1258
+
1259
+ ```tsx
1260
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1261
+ import {
1262
+ CheckIcon,
1263
+ ChevronDownIcon,
1264
+ ChevronUpIcon,
1265
+ XCircleIcon,
1266
+ } from "lucide-react";
1267
+ import { useState } from "react";
1268
+ import { Button } from "@/components/ui/button";
1269
+ import { cn } from "@/lib/utils";
1270
+
1271
+ export const ToolFallback: ToolCallMessagePartComponent = ({
1272
+ toolName,
1273
+ argsText,
1274
+ result,
1275
+ status,
1276
+ }) => {
1277
+ const [isCollapsed, setIsCollapsed] = useState(true);
1278
+
1279
+ const isCancelled =
1280
+ status?.type === "incomplete" && status.reason === "cancelled";
1281
+ const cancelledReason =
1282
+ isCancelled && status.error
1283
+ ? typeof status.error === "string"
1284
+ ? status.error
1285
+ : JSON.stringify(status.error)
1286
+ : null;
1287
+
913
1288
  return (
914
- <svg
915
- xmlns="http://www.w3.org/2000/svg"
916
- viewBox="0 0 16 16"
917
- fill="currentColor"
918
- width="16"
919
- height="16"
1289
+ <div
1290
+ className={cn(
1291
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1292
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1293
+ )}
920
1294
  >
921
- <rect width="10" height="10" x="3" y="3" rx="2" />
922
- </svg>
1295
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1296
+ {isCancelled ? (
1297
+ <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1298
+ ) : (
1299
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1300
+ )}
1301
+ <p
1302
+ className={cn(
1303
+ "aui-tool-fallback-title grow",
1304
+ isCancelled && "text-muted-foreground line-through",
1305
+ )}
1306
+ >
1307
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1308
+ <b>{toolName}</b>
1309
+ </p>
1310
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1311
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1312
+ </Button>
1313
+ </div>
1314
+ {!isCollapsed && (
1315
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1316
+ {cancelledReason && (
1317
+ <div className="aui-tool-fallback-cancelled-root px-4">
1318
+ <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1319
+ Cancelled reason:
1320
+ </p>
1321
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1322
+ {cancelledReason}
1323
+ </p>
1324
+ </div>
1325
+ )}
1326
+ <div
1327
+ className={cn(
1328
+ "aui-tool-fallback-args-root px-4",
1329
+ isCancelled && "opacity-60",
1330
+ )}
1331
+ >
1332
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1333
+ {argsText}
1334
+ </pre>
1335
+ </div>
1336
+ {!isCancelled && result !== undefined && (
1337
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1338
+ <p className="aui-tool-fallback-result-header font-semibold">
1339
+ Result:
1340
+ </p>
1341
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1342
+ {typeof result === "string"
1343
+ ? result
1344
+ : JSON.stringify(result, null, 2)}
1345
+ </pre>
1346
+ </div>
1347
+ )}
1348
+ </div>
1349
+ )}
1350
+ </div>
923
1351
  );
924
1352
  };
925
1353
 
@@ -930,7 +1358,7 @@ const CircleStopIcon = () => {
930
1358
  ```tsx
931
1359
  "use client";
932
1360
 
933
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1361
+ import { ComponentPropsWithRef, forwardRef } from "react";
934
1362
  import { Slottable } from "@radix-ui/react-slot";
935
1363
 
936
1364
  import {
@@ -941,7 +1369,7 @@ import {
941
1369
  import { Button } from "@/components/ui/button";
942
1370
  import { cn } from "@/lib/utils";
943
1371
 
944
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1372
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
945
1373
  tooltip: string;
946
1374
  side?: "top" | "bottom" | "left" | "right";
947
1375
  };
@@ -957,11 +1385,11 @@ export const TooltipIconButton = forwardRef<
957
1385
  variant="ghost"
958
1386
  size="icon"
959
1387
  {...rest}
960
- className={cn("size-6 p-1", className)}
1388
+ className={cn("aui-button-icon size-6 p-1", className)}
961
1389
  ref={ref}
962
1390
  >
963
1391
  <Slottable>{children}</Slottable>
964
- <span className="sr-only">{tooltip}</span>
1392
+ <span className="aui-sr-only sr-only">{tooltip}</span>
965
1393
  </Button>
966
1394
  </TooltipTrigger>
967
1395
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -1007,17 +1435,17 @@ export function PriceSnapshot({
1007
1435
  return (
1008
1436
  <Card className="mx-auto w-full max-w-md">
1009
1437
  <CardHeader>
1010
- <CardTitle className="text-2xl font-bold">{ticker}</CardTitle>
1438
+ <CardTitle className="font-bold text-2xl">{ticker}</CardTitle>
1011
1439
  </CardHeader>
1012
1440
  <CardContent>
1013
1441
  <div className="grid grid-cols-2 gap-4">
1014
1442
  <div className="col-span-2">
1015
- <p className="text-3xl font-semibold">${price?.toFixed(2)}</p>
1443
+ <p className="font-semibold text-3xl">${price?.toFixed(2)}</p>
1016
1444
  </div>
1017
1445
  <div>
1018
1446
  <p className="text-muted-foreground text-sm">Day Change</p>
1019
1447
  <p
1020
- className={`flex items-center text-lg font-medium ${changeColor}`}
1448
+ className={`flex items-center font-medium text-lg ${changeColor}`}
1021
1449
  >
1022
1450
  <ArrowIcon className="mr-1 h-4 w-4" />$
1023
1451
  {Math.abs(day_change)?.toFixed(2)} (
@@ -1026,7 +1454,7 @@ export function PriceSnapshot({
1026
1454
  </div>
1027
1455
  <div>
1028
1456
  <p className="text-muted-foreground text-sm">Last Updated</p>
1029
- <p className="text-lg font-medium">
1457
+ <p className="font-medium text-lg">
1030
1458
  {new Date(time).toLocaleTimeString()}
1031
1459
  </p>
1032
1460
  </div>
@@ -1155,13 +1583,13 @@ export function TransactionConfirmationFinal(props: TransactionConfirmation) {
1155
1583
  <Card className="mx-auto w-full max-w-md">
1156
1584
  <CardHeader className="text-center">
1157
1585
  <CheckCircle className="mx-auto mb-4 h-16 w-16 text-green-500" />
1158
- <CardTitle className="text-2xl font-bold text-green-700">
1586
+ <CardTitle className="font-bold text-2xl text-green-700">
1159
1587
  Transaction Confirmed
1160
1588
  </CardTitle>
1161
1589
  </CardHeader>
1162
1590
  <CardContent className="space-y-4">
1163
1591
  <div className="rounded-md border border-green-200 bg-green-50 p-4">
1164
- <h3 className="mb-2 text-lg font-semibold text-green-800">
1592
+ <h3 className="mb-2 font-semibold text-green-800 text-lg">
1165
1593
  Purchase Summary
1166
1594
  </h3>
1167
1595
  <div className="grid grid-cols-2 gap-2 text-sm">
@@ -1176,12 +1604,12 @@ export function TransactionConfirmationFinal(props: TransactionConfirmation) {
1176
1604
  </div>
1177
1605
  </div>
1178
1606
  <div className="rounded-md border border-green-300 bg-green-100 p-4">
1179
- <p className="text-lg font-semibold text-green-800">Total Cost:</p>
1180
- <p className="text-2xl font-bold text-green-900">
1607
+ <p className="font-semibold text-green-800 text-lg">Total Cost:</p>
1608
+ <p className="font-bold text-2xl text-green-900">
1181
1609
  ${(quantity * maxPurchasePrice)?.toFixed(2)}
1182
1610
  </p>
1183
1611
  </div>
1184
- <p className="text-center text-sm text-green-600">
1612
+ <p className="text-center text-green-600 text-sm">
1185
1613
  Your purchase of {quantity} shares of {companyName} ({ticker}) has
1186
1614
  been successfully processed.
1187
1615
  </p>
@@ -1222,26 +1650,26 @@ export function TransactionConfirmationPending(props: TransactionConfirmation) {
1222
1650
  return (
1223
1651
  <Card className="mx-auto w-full max-w-md">
1224
1652
  <CardHeader>
1225
- <CardTitle className="text-2xl font-bold">
1653
+ <CardTitle className="font-bold text-2xl">
1226
1654
  Confirm Transaction
1227
1655
  </CardTitle>
1228
1656
  </CardHeader>
1229
1657
  <CardContent className="space-y-4">
1230
1658
  <div className="grid grid-cols-2 gap-2">
1231
- <p className="text-muted-foreground text-sm font-medium">Ticker:</p>
1232
- <p className="text-sm font-bold">{ticker}</p>
1233
- <p className="text-muted-foreground text-sm font-medium">Company:</p>
1659
+ <p className="font-medium text-muted-foreground text-sm">Ticker:</p>
1660
+ <p className="font-bold text-sm">{ticker}</p>
1661
+ <p className="font-medium text-muted-foreground text-sm">Company:</p>
1234
1662
  <p className="text-sm">{companyName}</p>
1235
- <p className="text-muted-foreground text-sm font-medium">Quantity:</p>
1663
+ <p className="font-medium text-muted-foreground text-sm">Quantity:</p>
1236
1664
  <p className="text-sm">{quantity} shares</p>
1237
- <p className="text-muted-foreground text-sm font-medium">
1665
+ <p className="font-medium text-muted-foreground text-sm">
1238
1666
  Max Purchase Price:
1239
1667
  </p>
1240
1668
  <p className="text-sm">${maxPurchasePrice?.toFixed(2)}</p>
1241
1669
  </div>
1242
- <div className="bg-muted rounded-md p-3">
1243
- <p className="text-sm font-medium">Total Maximum Cost:</p>
1244
- <p className="text-lg font-bold">
1670
+ <div className="rounded-md bg-muted p-3">
1671
+ <p className="font-medium text-sm">Total Maximum Cost:</p>
1672
+ <p className="font-bold text-lg">
1245
1673
  ${(quantity * maxPurchasePrice)?.toFixed(2)}
1246
1674
  </p>
1247
1675
  </div>
@@ -1268,7 +1696,7 @@ export function TransactionConfirmationPending(props: TransactionConfirmation) {
1268
1696
  import { ToolCallMessagePartComponent } from "@assistant-ui/react";
1269
1697
  import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
1270
1698
  import { useState } from "react";
1271
- import { Button } from "../ui/button";
1699
+ import { Button } from "@/components/ui/button";
1272
1700
 
1273
1701
  export const ToolFallback: ToolCallMessagePartComponent = ({
1274
1702
  toolName,
@@ -1311,6 +1739,62 @@ export const ToolFallback: ToolCallMessagePartComponent = ({
1311
1739
 
1312
1740
  ```
1313
1741
 
1742
+ ## components/ui/avatar.tsx
1743
+
1744
+ ```tsx
1745
+ "use client";
1746
+
1747
+ import * as React from "react";
1748
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1749
+
1750
+ import { cn } from "@/lib/utils";
1751
+
1752
+ const Avatar = React.forwardRef<
1753
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1754
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1755
+ >(({ className, ...props }, ref) => (
1756
+ <AvatarPrimitive.Root
1757
+ ref={ref}
1758
+ className={cn(
1759
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1760
+ className,
1761
+ )}
1762
+ {...props}
1763
+ />
1764
+ ));
1765
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1766
+
1767
+ const AvatarImage = React.forwardRef<
1768
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1769
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1770
+ >(({ className, ...props }, ref) => (
1771
+ <AvatarPrimitive.Image
1772
+ ref={ref}
1773
+ className={cn("aspect-square h-full w-full", className)}
1774
+ {...props}
1775
+ />
1776
+ ));
1777
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1778
+
1779
+ const AvatarFallback = React.forwardRef<
1780
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1781
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1782
+ >(({ className, ...props }, ref) => (
1783
+ <AvatarPrimitive.Fallback
1784
+ ref={ref}
1785
+ className={cn(
1786
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
1787
+ className,
1788
+ )}
1789
+ {...props}
1790
+ />
1791
+ ));
1792
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1793
+
1794
+ export { Avatar, AvatarImage, AvatarFallback };
1795
+
1796
+ ```
1797
+
1314
1798
  ## components/ui/button.tsx
1315
1799
 
1316
1800
  ```tsx
@@ -1321,7 +1805,7 @@ import { cva, type VariantProps } from "class-variance-authority";
1321
1805
  import { cn } from "@/lib/utils";
1322
1806
 
1323
1807
  const buttonVariants = cva(
1324
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
1808
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
1325
1809
  {
1326
1810
  variants: {
1327
1811
  variant: {
@@ -1388,7 +1872,7 @@ const Card = React.forwardRef<
1388
1872
  <div
1389
1873
  ref={ref}
1390
1874
  className={cn(
1391
- "bg-card text-card-foreground rounded-xl border shadow",
1875
+ "rounded-xl border bg-card text-card-foreground shadow",
1392
1876
  className,
1393
1877
  )}
1394
1878
  {...props}
@@ -1414,7 +1898,7 @@ const CardTitle = React.forwardRef<
1414
1898
  >(({ className, ...props }, ref) => (
1415
1899
  <div
1416
1900
  ref={ref}
1417
- className={cn("leading-none font-semibold tracking-tight", className)}
1901
+ className={cn("font-semibold leading-none tracking-tight", className)}
1418
1902
  {...props}
1419
1903
  />
1420
1904
  ));
@@ -1463,6 +1947,166 @@ export {
1463
1947
 
1464
1948
  ```
1465
1949
 
1950
+ ## components/ui/dialog.tsx
1951
+
1952
+ ```tsx
1953
+ "use client";
1954
+
1955
+ import * as React from "react";
1956
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1957
+ import { XIcon } from "lucide-react";
1958
+
1959
+ import { cn } from "@/lib/utils";
1960
+
1961
+ function Dialog({
1962
+ ...props
1963
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1964
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1965
+ }
1966
+
1967
+ function DialogTrigger({
1968
+ ...props
1969
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1970
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1971
+ }
1972
+
1973
+ function DialogPortal({
1974
+ ...props
1975
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1976
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1977
+ }
1978
+
1979
+ function DialogClose({
1980
+ ...props
1981
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1982
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1983
+ }
1984
+
1985
+ function DialogOverlay({
1986
+ className,
1987
+ ...props
1988
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1989
+ return (
1990
+ <DialogPrimitive.Overlay
1991
+ data-slot="dialog-overlay"
1992
+ className={cn(
1993
+ "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",
1994
+ className,
1995
+ )}
1996
+ {...props}
1997
+ />
1998
+ );
1999
+ }
2000
+
2001
+ function DialogContent({
2002
+ className,
2003
+ children,
2004
+ ...props
2005
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
2006
+ return (
2007
+ <DialogPortal data-slot="dialog-portal">
2008
+ <DialogOverlay />
2009
+ <DialogPrimitive.Content
2010
+ data-slot="dialog-content"
2011
+ className={cn(
2012
+ "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",
2013
+ className,
2014
+ )}
2015
+ {...props}
2016
+ >
2017
+ {children}
2018
+ <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">
2019
+ <XIcon />
2020
+ <span className="sr-only">Close</span>
2021
+ </DialogPrimitive.Close>
2022
+ </DialogPrimitive.Content>
2023
+ </DialogPortal>
2024
+ );
2025
+ }
2026
+
2027
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
2028
+ return (
2029
+ <div
2030
+ data-slot="dialog-header"
2031
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
2032
+ {...props}
2033
+ />
2034
+ );
2035
+ }
2036
+
2037
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
2038
+ return (
2039
+ <div
2040
+ data-slot="dialog-footer"
2041
+ className={cn(
2042
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
2043
+ className,
2044
+ )}
2045
+ {...props}
2046
+ />
2047
+ );
2048
+ }
2049
+
2050
+ function DialogTitle({
2051
+ className,
2052
+ ...props
2053
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
2054
+ return (
2055
+ <DialogPrimitive.Title
2056
+ data-slot="dialog-title"
2057
+ className={cn("font-semibold text-lg leading-none", className)}
2058
+ {...props}
2059
+ />
2060
+ );
2061
+ }
2062
+
2063
+ function DialogDescription({
2064
+ className,
2065
+ ...props
2066
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
2067
+ return (
2068
+ <DialogPrimitive.Description
2069
+ data-slot="dialog-description"
2070
+ className={cn("text-muted-foreground text-sm", className)}
2071
+ {...props}
2072
+ />
2073
+ );
2074
+ }
2075
+
2076
+ export {
2077
+ Dialog,
2078
+ DialogClose,
2079
+ DialogContent,
2080
+ DialogDescription,
2081
+ DialogFooter,
2082
+ DialogHeader,
2083
+ DialogOverlay,
2084
+ DialogPortal,
2085
+ DialogTitle,
2086
+ DialogTrigger,
2087
+ };
2088
+
2089
+ ```
2090
+
2091
+ ## components/ui/skeleton.tsx
2092
+
2093
+ ```tsx
2094
+ import { cn } from "@/lib/utils";
2095
+
2096
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
2097
+ return (
2098
+ <div
2099
+ data-slot="skeleton"
2100
+ className={cn("animate-pulse rounded-md bg-accent", className)}
2101
+ {...props}
2102
+ />
2103
+ );
2104
+ }
2105
+
2106
+ export { Skeleton };
2107
+
2108
+ ```
2109
+
1466
2110
  ## components/ui/tooltip.tsx
1467
2111
 
1468
2112
  ```tsx
@@ -1514,13 +2158,13 @@ function TooltipContent({
1514
2158
  data-slot="tooltip-content"
1515
2159
  sideOffset={sideOffset}
1516
2160
  className={cn(
1517
- "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",
2161
+ "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",
1518
2162
  className,
1519
2163
  )}
1520
2164
  {...props}
1521
2165
  >
1522
2166
  {children}
1523
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
2167
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
1524
2168
  </TooltipPrimitive.Content>
1525
2169
  </TooltipPrimitive.Portal>
1526
2170
  );
@@ -1619,18 +2263,6 @@ export function cn(...inputs: ClassValue[]) {
1619
2263
 
1620
2264
  ```
1621
2265
 
1622
- ## next-env.d.ts
1623
-
1624
- ```typescript
1625
- /// <reference types="next" />
1626
- /// <reference types="next/image-types/global" />
1627
- import "./.next/types/routes.d.ts";
1628
-
1629
- // NOTE: This file should not be edited
1630
- // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
1631
-
1632
- ```
1633
-
1634
2266
  ## next.config.ts
1635
2267
 
1636
2268
  ```typescript
@@ -1661,28 +2293,30 @@ export default nextConfig;
1661
2293
  "scripts": {
1662
2294
  "dev": "next dev --turbo",
1663
2295
  "build": "next build",
1664
- "start": "next start",
1665
- "lint": "eslint ."
2296
+ "start": "next start"
1666
2297
  },
1667
2298
  "dependencies": {
1668
2299
  "@assistant-ui/react": "workspace:*",
1669
2300
  "@assistant-ui/react-langgraph": "workspace:*",
1670
2301
  "@assistant-ui/react-markdown": "workspace:*",
1671
- "@langchain/langgraph-sdk": "^1.0.0",
2302
+ "@langchain/langgraph-sdk": "^1.2.0",
2303
+ "@radix-ui/react-avatar": "^1.1.11",
2304
+ "@radix-ui/react-dialog": "^1.1.15",
1672
2305
  "@radix-ui/react-slot": "^1.2.4",
1673
2306
  "@radix-ui/react-tooltip": "^1.2.8",
1674
2307
  "class-variance-authority": "^0.7.1",
1675
2308
  "clsx": "^2.1.1",
1676
2309
  "js-cookie": "^3.0.5",
1677
- "jsonwebtoken": "^9.0.2",
1678
- "lucide-react": "^0.554.0",
2310
+ "jsonwebtoken": "^9.0.3",
2311
+ "lucide-react": "^0.556.0",
1679
2312
  "nanoid": "5.1.6",
1680
- "next": "16.0.3",
1681
- "react": "19.2.0",
1682
- "react-dom": "19.2.0",
2313
+ "next": "16.0.7",
2314
+ "react": "19.2.1",
2315
+ "react-dom": "19.2.1",
1683
2316
  "remark-gfm": "^4.0.1",
1684
2317
  "tailwind-merge": "^3.4.0",
1685
- "tw-animate-css": "^1.4.0"
2318
+ "tw-animate-css": "^1.4.0",
2319
+ "zustand": "^5.0.9"
1686
2320
  },
1687
2321
  "devDependencies": {
1688
2322
  "@assistant-ui/x-buildutils": "workspace:*",