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

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.
@@ -421,6 +421,247 @@ export default function Home() {
421
421
 
422
422
  ```
423
423
 
424
+ ## components/assistant-ui/attachment.tsx
425
+
426
+ ```tsx
427
+ "use client";
428
+
429
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
430
+ import Image from "next/image";
431
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
432
+ import {
433
+ AttachmentPrimitive,
434
+ ComposerPrimitive,
435
+ MessagePrimitive,
436
+ useAssistantState,
437
+ useAssistantApi,
438
+ } from "@assistant-ui/react";
439
+ import { useShallow } from "zustand/shallow";
440
+ import {
441
+ Tooltip,
442
+ TooltipContent,
443
+ TooltipTrigger,
444
+ } from "@/components/ui/tooltip";
445
+ import {
446
+ Dialog,
447
+ DialogTitle,
448
+ DialogContent,
449
+ DialogTrigger,
450
+ } from "@/components/ui/dialog";
451
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
452
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
453
+ import { cn } from "@/lib/utils";
454
+
455
+ const useFileSrc = (file: File | undefined) => {
456
+ const [src, setSrc] = useState<string | undefined>(undefined);
457
+
458
+ useEffect(() => {
459
+ if (!file) {
460
+ setSrc(undefined);
461
+ return;
462
+ }
463
+
464
+ const objectUrl = URL.createObjectURL(file);
465
+ setSrc(objectUrl);
466
+
467
+ return () => {
468
+ URL.revokeObjectURL(objectUrl);
469
+ };
470
+ }, [file]);
471
+
472
+ return src;
473
+ };
474
+
475
+ const useAttachmentSrc = () => {
476
+ const { file, src } = useAssistantState(
477
+ useShallow(({ attachment }): { file?: File; src?: string } => {
478
+ if (attachment.type !== "image") return {};
479
+ if (attachment.file) return { file: attachment.file };
480
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
481
+ ?.image;
482
+ if (!src) return {};
483
+ return { src };
484
+ }),
485
+ );
486
+
487
+ return useFileSrc(file) ?? src;
488
+ };
489
+
490
+ type AttachmentPreviewProps = {
491
+ src: string;
492
+ };
493
+
494
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
495
+ const [isLoaded, setIsLoaded] = useState(false);
496
+ return (
497
+ <Image
498
+ src={src}
499
+ alt="Image Preview"
500
+ width={1}
501
+ height={1}
502
+ className={
503
+ isLoaded
504
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
505
+ : "aui-attachment-preview-image-loading hidden"
506
+ }
507
+ onLoadingComplete={() => setIsLoaded(true)}
508
+ priority={false}
509
+ />
510
+ );
511
+ };
512
+
513
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
514
+ const src = useAttachmentSrc();
515
+
516
+ if (!src) return children;
517
+
518
+ return (
519
+ <Dialog>
520
+ <DialogTrigger
521
+ className="aui-attachment-preview-trigger hover:bg-accent/50 cursor-pointer transition-colors"
522
+ asChild
523
+ >
524
+ {children}
525
+ </DialogTrigger>
526
+ <DialogContent className="aui-attachment-preview-dialog-content [&_svg]:text-background [&>button]:bg-foreground/60 [&>button]:hover:[&_svg]:text-destructive p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:p-1 [&>button]:opacity-100 [&>button]:!ring-0">
527
+ <DialogTitle className="aui-sr-only sr-only">
528
+ Image Attachment Preview
529
+ </DialogTitle>
530
+ <div className="aui-attachment-preview bg-background relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden">
531
+ <AttachmentPreview src={src} />
532
+ </div>
533
+ </DialogContent>
534
+ </Dialog>
535
+ );
536
+ };
537
+
538
+ const AttachmentThumb: FC = () => {
539
+ const isImage = useAssistantState(
540
+ ({ attachment }) => attachment.type === "image",
541
+ );
542
+ const src = useAttachmentSrc();
543
+
544
+ return (
545
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
546
+ <AvatarImage
547
+ src={src}
548
+ alt="Attachment preview"
549
+ className="aui-attachment-tile-image object-cover"
550
+ />
551
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
552
+ <FileText className="aui-attachment-tile-fallback-icon text-muted-foreground size-8" />
553
+ </AvatarFallback>
554
+ </Avatar>
555
+ );
556
+ };
557
+
558
+ const AttachmentUI: FC = () => {
559
+ const api = useAssistantApi();
560
+ const isComposer = api.attachment.source === "composer";
561
+
562
+ const isImage = useAssistantState(
563
+ ({ attachment }) => attachment.type === "image",
564
+ );
565
+ const typeLabel = useAssistantState(({ attachment }) => {
566
+ const type = attachment.type;
567
+ switch (type) {
568
+ case "image":
569
+ return "Image";
570
+ case "document":
571
+ return "Document";
572
+ case "file":
573
+ return "File";
574
+ default:
575
+ const _exhaustiveCheck: never = type;
576
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
577
+ }
578
+ });
579
+
580
+ return (
581
+ <Tooltip>
582
+ <AttachmentPrimitive.Root
583
+ className={cn(
584
+ "aui-attachment-root relative",
585
+ isImage &&
586
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
587
+ )}
588
+ >
589
+ <AttachmentPreviewDialog>
590
+ <TooltipTrigger asChild>
591
+ <div
592
+ className={cn(
593
+ "aui-attachment-tile bg-muted size-14 cursor-pointer overflow-hidden rounded-[14px] border transition-opacity hover:opacity-75",
594
+ isComposer &&
595
+ "aui-attachment-tile-composer border-foreground/20",
596
+ )}
597
+ role="button"
598
+ id="attachment-tile"
599
+ aria-label={`${typeLabel} attachment`}
600
+ >
601
+ <AttachmentThumb />
602
+ </div>
603
+ </TooltipTrigger>
604
+ </AttachmentPreviewDialog>
605
+ {isComposer && <AttachmentRemove />}
606
+ </AttachmentPrimitive.Root>
607
+ <TooltipContent side="top">
608
+ <AttachmentPrimitive.Name />
609
+ </TooltipContent>
610
+ </Tooltip>
611
+ );
612
+ };
613
+
614
+ const AttachmentRemove: FC = () => {
615
+ return (
616
+ <AttachmentPrimitive.Remove asChild>
617
+ <TooltipIconButton
618
+ tooltip="Remove file"
619
+ className="aui-attachment-tile-remove text-muted-foreground hover:[&_svg]:text-destructive absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white opacity-100 shadow-sm hover:!bg-white [&_svg]:text-black"
620
+ side="top"
621
+ >
622
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
623
+ </TooltipIconButton>
624
+ </AttachmentPrimitive.Remove>
625
+ );
626
+ };
627
+
628
+ export const UserMessageAttachments: FC = () => {
629
+ return (
630
+ <div className="aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2">
631
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
632
+ </div>
633
+ );
634
+ };
635
+
636
+ export const ComposerAttachments: FC = () => {
637
+ return (
638
+ <div className="aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden">
639
+ <ComposerPrimitive.Attachments
640
+ components={{ Attachment: AttachmentUI }}
641
+ />
642
+ </div>
643
+ );
644
+ };
645
+
646
+ export const ComposerAddAttachment: FC = () => {
647
+ return (
648
+ <ComposerPrimitive.AddAttachment asChild>
649
+ <TooltipIconButton
650
+ tooltip="Add Attachment"
651
+ side="bottom"
652
+ variant="ghost"
653
+ size="icon"
654
+ className="aui-composer-add-attachment hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30 size-[34px] rounded-full p-1 text-xs font-semibold"
655
+ aria-label="Add Attachment"
656
+ >
657
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
658
+ </TooltipIconButton>
659
+ </ComposerPrimitive.AddAttachment>
660
+ );
661
+ };
662
+
663
+ ```
664
+
424
665
  ## components/assistant-ui/markdown-text.tsx
425
666
 
426
667
  ```tsx
@@ -429,13 +670,13 @@ export default function Home() {
429
670
  import "@assistant-ui/react-markdown/styles/dot.css";
430
671
 
431
672
  import {
432
- CodeHeaderProps,
673
+ type CodeHeaderProps,
433
674
  MarkdownTextPrimitive,
434
675
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
435
676
  useIsMarkdownCodeBlock,
436
677
  } from "@assistant-ui/react-markdown";
437
678
  import remarkGfm from "remark-gfm";
438
- import { FC, memo, useState } from "react";
679
+ import { type FC, memo, useState } from "react";
439
680
  import { CheckIcon, CopyIcon } from "lucide-react";
440
681
 
441
682
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -461,8 +702,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
461
702
  };
462
703
 
463
704
  return (
464
- <div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
465
- <span className="lowercase [&>span]:text-xs">{language}</span>
705
+ <div className="aui-code-header-root bg-muted-foreground/15 text-foreground dark:bg-muted-foreground/20 mt-4 flex items-center justify-between gap-4 rounded-t-lg px-4 py-2 text-sm font-semibold">
706
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
707
+ {language}
708
+ </span>
466
709
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
467
710
  {!isCopied && <CopyIcon />}
468
711
  {isCopied && <CheckIcon />}
@@ -494,7 +737,7 @@ const defaultComponents = memoizeMarkdownComponents({
494
737
  h1: ({ className, ...props }) => (
495
738
  <h1
496
739
  className={cn(
497
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
740
+ "aui-md-h1 mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
498
741
  className,
499
742
  )}
500
743
  {...props}
@@ -503,7 +746,7 @@ const defaultComponents = memoizeMarkdownComponents({
503
746
  h2: ({ className, ...props }) => (
504
747
  <h2
505
748
  className={cn(
506
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
749
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
507
750
  className,
508
751
  )}
509
752
  {...props}
@@ -512,7 +755,7 @@ const defaultComponents = memoizeMarkdownComponents({
512
755
  h3: ({ className, ...props }) => (
513
756
  <h3
514
757
  className={cn(
515
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
758
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
516
759
  className,
517
760
  )}
518
761
  {...props}
@@ -521,7 +764,7 @@ const defaultComponents = memoizeMarkdownComponents({
521
764
  h4: ({ className, ...props }) => (
522
765
  <h4
523
766
  className={cn(
524
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
767
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
525
768
  className,
526
769
  )}
527
770
  {...props}
@@ -530,7 +773,7 @@ const defaultComponents = memoizeMarkdownComponents({
530
773
  h5: ({ className, ...props }) => (
531
774
  <h5
532
775
  className={cn(
533
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
776
+ "aui-md-h5 my-4 text-lg font-semibold first:mt-0 last:mb-0",
534
777
  className,
535
778
  )}
536
779
  {...props}
@@ -538,20 +781,26 @@ const defaultComponents = memoizeMarkdownComponents({
538
781
  ),
539
782
  h6: ({ className, ...props }) => (
540
783
  <h6
541
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
784
+ className={cn(
785
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
786
+ className,
787
+ )}
542
788
  {...props}
543
789
  />
544
790
  ),
545
791
  p: ({ className, ...props }) => (
546
792
  <p
547
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
793
+ className={cn(
794
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
795
+ className,
796
+ )}
548
797
  {...props}
549
798
  />
550
799
  ),
551
800
  a: ({ className, ...props }) => (
552
801
  <a
553
802
  className={cn(
554
- "text-primary font-medium underline underline-offset-4",
803
+ "aui-md-a text-primary font-medium underline underline-offset-4",
555
804
  className,
556
805
  )}
557
806
  {...props}
@@ -559,29 +808,29 @@ const defaultComponents = memoizeMarkdownComponents({
559
808
  ),
560
809
  blockquote: ({ className, ...props }) => (
561
810
  <blockquote
562
- className={cn("border-l-2 pl-6 italic", className)}
811
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
563
812
  {...props}
564
813
  />
565
814
  ),
566
815
  ul: ({ className, ...props }) => (
567
816
  <ul
568
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
817
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
569
818
  {...props}
570
819
  />
571
820
  ),
572
821
  ol: ({ className, ...props }) => (
573
822
  <ol
574
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
823
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
575
824
  {...props}
576
825
  />
577
826
  ),
578
827
  hr: ({ className, ...props }) => (
579
- <hr className={cn("my-5 border-b", className)} {...props} />
828
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
580
829
  ),
581
830
  table: ({ className, ...props }) => (
582
831
  <table
583
832
  className={cn(
584
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
833
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
585
834
  className,
586
835
  )}
587
836
  {...props}
@@ -590,7 +839,7 @@ const defaultComponents = memoizeMarkdownComponents({
590
839
  th: ({ className, ...props }) => (
591
840
  <th
592
841
  className={cn(
593
- "bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right",
842
+ "aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right",
594
843
  className,
595
844
  )}
596
845
  {...props}
@@ -599,7 +848,7 @@ const defaultComponents = memoizeMarkdownComponents({
599
848
  td: ({ className, ...props }) => (
600
849
  <td
601
850
  className={cn(
602
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
851
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
603
852
  className,
604
853
  )}
605
854
  {...props}
@@ -608,7 +857,7 @@ const defaultComponents = memoizeMarkdownComponents({
608
857
  tr: ({ className, ...props }) => (
609
858
  <tr
610
859
  className={cn(
611
- "m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
860
+ "aui-md-tr m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
612
861
  className,
613
862
  )}
614
863
  {...props}
@@ -616,14 +865,14 @@ const defaultComponents = memoizeMarkdownComponents({
616
865
  ),
617
866
  sup: ({ className, ...props }) => (
618
867
  <sup
619
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
868
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
620
869
  {...props}
621
870
  />
622
871
  ),
623
872
  pre: ({ className, ...props }) => (
624
873
  <pre
625
874
  className={cn(
626
- "overflow-x-auto rounded-b-lg bg-black p-4 text-white",
875
+ "aui-md-pre overflow-x-auto !rounded-t-none rounded-b-lg bg-black p-4 text-white",
627
876
  className,
628
877
  )}
629
878
  {...props}
@@ -634,7 +883,8 @@ const defaultComponents = memoizeMarkdownComponents({
634
883
  return (
635
884
  <code
636
885
  className={cn(
637
- !isCodeBlock && "bg-muted rounded border font-semibold",
886
+ !isCodeBlock &&
887
+ "aui-md-inline-code bg-muted rounded border font-semibold",
638
888
  className,
639
889
  )}
640
890
  {...props}
@@ -1022,12 +1272,111 @@ const CircleStopIcon = () => {
1022
1272
 
1023
1273
  ```
1024
1274
 
1275
+ ## components/assistant-ui/tool-fallback.tsx
1276
+
1277
+ ```tsx
1278
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1279
+ import {
1280
+ CheckIcon,
1281
+ ChevronDownIcon,
1282
+ ChevronUpIcon,
1283
+ XCircleIcon,
1284
+ } from "lucide-react";
1285
+ import { useState } from "react";
1286
+ import { Button } from "@/components/ui/button";
1287
+ import { cn } from "@/lib/utils";
1288
+
1289
+ export const ToolFallback: ToolCallMessagePartComponent = ({
1290
+ toolName,
1291
+ argsText,
1292
+ result,
1293
+ status,
1294
+ }) => {
1295
+ const [isCollapsed, setIsCollapsed] = useState(true);
1296
+
1297
+ const isCancelled =
1298
+ status?.type === "incomplete" && status.reason === "cancelled";
1299
+ const cancelledReason =
1300
+ isCancelled && status.error
1301
+ ? typeof status.error === "string"
1302
+ ? status.error
1303
+ : JSON.stringify(status.error)
1304
+ : null;
1305
+
1306
+ return (
1307
+ <div
1308
+ className={cn(
1309
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1310
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1311
+ )}
1312
+ >
1313
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1314
+ {isCancelled ? (
1315
+ <XCircleIcon className="aui-tool-fallback-icon text-muted-foreground size-4" />
1316
+ ) : (
1317
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1318
+ )}
1319
+ <p
1320
+ className={cn(
1321
+ "aui-tool-fallback-title grow",
1322
+ isCancelled && "text-muted-foreground line-through",
1323
+ )}
1324
+ >
1325
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1326
+ <b>{toolName}</b>
1327
+ </p>
1328
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1329
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1330
+ </Button>
1331
+ </div>
1332
+ {!isCollapsed && (
1333
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1334
+ {cancelledReason && (
1335
+ <div className="aui-tool-fallback-cancelled-root px-4">
1336
+ <p className="aui-tool-fallback-cancelled-header text-muted-foreground font-semibold">
1337
+ Cancelled reason:
1338
+ </p>
1339
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1340
+ {cancelledReason}
1341
+ </p>
1342
+ </div>
1343
+ )}
1344
+ <div
1345
+ className={cn(
1346
+ "aui-tool-fallback-args-root px-4",
1347
+ isCancelled && "opacity-60",
1348
+ )}
1349
+ >
1350
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1351
+ {argsText}
1352
+ </pre>
1353
+ </div>
1354
+ {!isCancelled && result !== undefined && (
1355
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1356
+ <p className="aui-tool-fallback-result-header font-semibold">
1357
+ Result:
1358
+ </p>
1359
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1360
+ {typeof result === "string"
1361
+ ? result
1362
+ : JSON.stringify(result, null, 2)}
1363
+ </pre>
1364
+ </div>
1365
+ )}
1366
+ </div>
1367
+ )}
1368
+ </div>
1369
+ );
1370
+ };
1371
+
1372
+ ```
1373
+
1025
1374
  ## components/assistant-ui/tooltip-icon-button.tsx
1026
1375
 
1027
1376
  ```tsx
1028
1377
  "use client";
1029
1378
 
1030
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1379
+ import { ComponentPropsWithRef, forwardRef } from "react";
1031
1380
  import { Slottable } from "@radix-ui/react-slot";
1032
1381
 
1033
1382
  import {
@@ -1038,7 +1387,7 @@ import {
1038
1387
  import { Button } from "@/components/ui/button";
1039
1388
  import { cn } from "@/lib/utils";
1040
1389
 
1041
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1390
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1042
1391
  tooltip: string;
1043
1392
  side?: "top" | "bottom" | "left" | "right";
1044
1393
  };
@@ -1054,11 +1403,11 @@ export const TooltipIconButton = forwardRef<
1054
1403
  variant="ghost"
1055
1404
  size="icon"
1056
1405
  {...rest}
1057
- className={cn("size-6 p-1", className)}
1406
+ className={cn("aui-button-icon size-6 p-1", className)}
1058
1407
  ref={ref}
1059
1408
  >
1060
1409
  <Slottable>{children}</Slottable>
1061
- <span className="sr-only">{tooltip}</span>
1410
+ <span className="aui-sr-only sr-only">{tooltip}</span>
1062
1411
  </Button>
1063
1412
  </TooltipTrigger>
1064
1413
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -1070,6 +1419,62 @@ TooltipIconButton.displayName = "TooltipIconButton";
1070
1419
 
1071
1420
  ```
1072
1421
 
1422
+ ## components/ui/avatar.tsx
1423
+
1424
+ ```tsx
1425
+ "use client";
1426
+
1427
+ import * as React from "react";
1428
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1429
+
1430
+ import { cn } from "@/lib/utils";
1431
+
1432
+ const Avatar = React.forwardRef<
1433
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1434
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1435
+ >(({ className, ...props }, ref) => (
1436
+ <AvatarPrimitive.Root
1437
+ ref={ref}
1438
+ className={cn(
1439
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1440
+ className,
1441
+ )}
1442
+ {...props}
1443
+ />
1444
+ ));
1445
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1446
+
1447
+ const AvatarImage = React.forwardRef<
1448
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1449
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1450
+ >(({ className, ...props }, ref) => (
1451
+ <AvatarPrimitive.Image
1452
+ ref={ref}
1453
+ className={cn("aspect-square h-full w-full", className)}
1454
+ {...props}
1455
+ />
1456
+ ));
1457
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1458
+
1459
+ const AvatarFallback = React.forwardRef<
1460
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1461
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1462
+ >(({ className, ...props }, ref) => (
1463
+ <AvatarPrimitive.Fallback
1464
+ ref={ref}
1465
+ className={cn(
1466
+ "bg-muted flex h-full w-full items-center justify-center rounded-full",
1467
+ className,
1468
+ )}
1469
+ {...props}
1470
+ />
1471
+ ));
1472
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1473
+
1474
+ export { Avatar, AvatarImage, AvatarFallback };
1475
+
1476
+ ```
1477
+
1073
1478
  ## components/ui/button.tsx
1074
1479
 
1075
1480
  ```tsx
@@ -1135,6 +1540,147 @@ export { Button, buttonVariants };
1135
1540
 
1136
1541
  ```
1137
1542
 
1543
+ ## components/ui/dialog.tsx
1544
+
1545
+ ```tsx
1546
+ "use client";
1547
+
1548
+ import * as React from "react";
1549
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1550
+ import { XIcon } from "lucide-react";
1551
+
1552
+ import { cn } from "@/lib/utils";
1553
+
1554
+ function Dialog({
1555
+ ...props
1556
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1557
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1558
+ }
1559
+
1560
+ function DialogTrigger({
1561
+ ...props
1562
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1563
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1564
+ }
1565
+
1566
+ function DialogPortal({
1567
+ ...props
1568
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1569
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1570
+ }
1571
+
1572
+ function DialogClose({
1573
+ ...props
1574
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1575
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1576
+ }
1577
+
1578
+ function DialogOverlay({
1579
+ className,
1580
+ ...props
1581
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1582
+ return (
1583
+ <DialogPrimitive.Overlay
1584
+ data-slot="dialog-overlay"
1585
+ className={cn(
1586
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
1587
+ className,
1588
+ )}
1589
+ {...props}
1590
+ />
1591
+ );
1592
+ }
1593
+
1594
+ function DialogContent({
1595
+ className,
1596
+ children,
1597
+ ...props
1598
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
1599
+ return (
1600
+ <DialogPortal data-slot="dialog-portal">
1601
+ <DialogOverlay />
1602
+ <DialogPrimitive.Content
1603
+ data-slot="dialog-content"
1604
+ className={cn(
1605
+ "bg-background data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in 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 p-6 shadow-lg duration-200 sm:max-w-lg",
1606
+ className,
1607
+ )}
1608
+ {...props}
1609
+ >
1610
+ {children}
1611
+ <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
1612
+ <XIcon />
1613
+ <span className="sr-only">Close</span>
1614
+ </DialogPrimitive.Close>
1615
+ </DialogPrimitive.Content>
1616
+ </DialogPortal>
1617
+ );
1618
+ }
1619
+
1620
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1621
+ return (
1622
+ <div
1623
+ data-slot="dialog-header"
1624
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1625
+ {...props}
1626
+ />
1627
+ );
1628
+ }
1629
+
1630
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1631
+ return (
1632
+ <div
1633
+ data-slot="dialog-footer"
1634
+ className={cn(
1635
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1636
+ className,
1637
+ )}
1638
+ {...props}
1639
+ />
1640
+ );
1641
+ }
1642
+
1643
+ function DialogTitle({
1644
+ className,
1645
+ ...props
1646
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1647
+ return (
1648
+ <DialogPrimitive.Title
1649
+ data-slot="dialog-title"
1650
+ className={cn("text-lg leading-none font-semibold", className)}
1651
+ {...props}
1652
+ />
1653
+ );
1654
+ }
1655
+
1656
+ function DialogDescription({
1657
+ className,
1658
+ ...props
1659
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1660
+ return (
1661
+ <DialogPrimitive.Description
1662
+ data-slot="dialog-description"
1663
+ className={cn("text-muted-foreground text-sm", className)}
1664
+ {...props}
1665
+ />
1666
+ );
1667
+ }
1668
+
1669
+ export {
1670
+ Dialog,
1671
+ DialogClose,
1672
+ DialogContent,
1673
+ DialogDescription,
1674
+ DialogFooter,
1675
+ DialogHeader,
1676
+ DialogOverlay,
1677
+ DialogPortal,
1678
+ DialogTitle,
1679
+ DialogTrigger,
1680
+ };
1681
+
1682
+ ```
1683
+
1138
1684
  ## components/ui/tooltip.tsx
1139
1685
 
1140
1686
  ```tsx
@@ -1241,20 +1787,24 @@ export default nextConfig;
1241
1787
  "lint": "eslint ."
1242
1788
  },
1243
1789
  "dependencies": {
1244
- "@ai-sdk/openai": "^2.0.68",
1790
+ "@ai-sdk/openai": "^2.0.73",
1245
1791
  "@assistant-ui/react": "workspace:*",
1246
1792
  "@assistant-ui/react-markdown": "workspace:*",
1793
+ "@radix-ui/react-avatar": "^1.1.4",
1794
+ "@radix-ui/react-dialog": "^1.1.7",
1247
1795
  "@radix-ui/react-slot": "^1.2.4",
1248
1796
  "@radix-ui/react-tooltip": "^1.2.8",
1249
1797
  "class-variance-authority": "^0.7.1",
1250
1798
  "clsx": "^2.1.1",
1251
- "lucide-react": "^0.554.0",
1252
- "next": "16.0.3",
1799
+ "lucide-react": "^0.555.0",
1800
+ "motion": "^11.18.2",
1801
+ "next": "16.0.4",
1253
1802
  "react": "19.2.0",
1254
1803
  "react-dom": "19.2.0",
1255
1804
  "remark-gfm": "^4.0.1",
1256
1805
  "tailwind-merge": "^3.4.0",
1257
- "tw-animate-css": "^1.4.0"
1806
+ "tw-animate-css": "^1.4.0",
1807
+ "zustand": "^5.0.8"
1258
1808
  },
1259
1809
  "devDependencies": {
1260
1810
  "@assistant-ui/x-buildutils": "workspace:*",