@assistant-ui/mcp-docs-server 0.1.19 → 0.1.20

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 (54) hide show
  1. package/.docs/organized/code-examples/with-ag-ui.md +65 -1622
  2. package/.docs/organized/code-examples/with-ai-sdk-v6.md +41 -1639
  3. package/.docs/organized/code-examples/with-assistant-transport.md +39 -1742
  4. package/.docs/organized/code-examples/with-cloud.md +70 -1744
  5. package/.docs/organized/code-examples/with-custom-thread-list.md +86 -1722
  6. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +68 -1635
  7. package/.docs/organized/code-examples/with-external-store.md +66 -1623
  8. package/.docs/organized/code-examples/with-ffmpeg.md +70 -1628
  9. package/.docs/organized/code-examples/with-langgraph.md +94 -1892
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +56 -1653
  11. package/.docs/organized/code-examples/with-react-hook-form.md +219 -2162
  12. package/.docs/organized/code-examples/with-react-router.md +63 -1315
  13. package/.docs/organized/code-examples/with-store.md +30 -30
  14. package/.docs/organized/code-examples/with-tanstack.md +74 -858
  15. package/.docs/raw/docs/(docs)/cli.mdx +66 -0
  16. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +4 -4
  17. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +3 -3
  18. package/.docs/raw/docs/(docs)/guides/attachments.mdx +2 -2
  19. package/.docs/raw/docs/(docs)/guides/context-api.mdx +117 -117
  20. package/.docs/raw/docs/(docs)/guides/suggestions.mdx +296 -0
  21. package/.docs/raw/docs/(docs)/guides/tools.mdx +336 -513
  22. package/.docs/raw/docs/(docs)/index.mdx +33 -410
  23. package/.docs/raw/docs/(docs)/installation.mdx +451 -0
  24. package/.docs/raw/docs/(docs)/llm.mdx +209 -0
  25. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +48 -2
  26. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +9 -3
  27. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +7 -7
  28. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +4 -4
  29. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +49 -49
  30. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +153 -0
  31. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +28 -39
  32. package/.docs/raw/docs/(reference)/legacy/styled/decomposition.mdx +5 -5
  33. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +207 -33
  34. package/.docs/raw/docs/runtimes/assistant-transport.mdx +3 -3
  35. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +5 -5
  36. package/.docs/raw/docs/runtimes/custom/local.mdx +43 -34
  37. package/.docs/raw/docs/runtimes/data-stream.mdx +35 -2
  38. package/.docs/raw/docs/ui/accordion.mdx +261 -0
  39. package/.docs/raw/docs/ui/badge.mdx +140 -0
  40. package/.docs/raw/docs/ui/file.mdx +153 -0
  41. package/.docs/raw/docs/ui/image.mdx +101 -0
  42. package/.docs/raw/docs/ui/model-selector.mdx +226 -0
  43. package/.docs/raw/docs/ui/part-grouping.mdx +2 -2
  44. package/.docs/raw/docs/ui/reasoning.mdx +4 -2
  45. package/.docs/raw/docs/ui/select.mdx +247 -0
  46. package/.docs/raw/docs/ui/sources.mdx +6 -4
  47. package/.docs/raw/docs/ui/streamdown.mdx +348 -0
  48. package/.docs/raw/docs/ui/tabs.mdx +261 -0
  49. package/.docs/raw/docs/ui/thread.mdx +56 -70
  50. package/README.md +3 -3
  51. package/package.json +4 -4
  52. package/src/tools/tests/examples.test.ts +1 -1
  53. package/.docs/raw/docs/(docs)/about-assistantui.mdx +0 -54
  54. package/.docs/raw/docs/(docs)/mcp-docs-server.mdx +0 -321
@@ -6,6 +6,8 @@
6
6
  @import "tailwindcss";
7
7
  @import "tw-animate-css";
8
8
 
9
+ @source "../../../packages/ui/src";
10
+
9
11
  @custom-variant dark (&:is(.dark *));
10
12
 
11
13
  @theme inline {
@@ -281,1621 +283,12 @@ export default function Home() {
281
283
  "lib": "@/lib",
282
284
  "hooks": "@/hooks"
283
285
  },
284
- "iconLibrary": "lucide"
285
- }
286
-
287
- ```
288
-
289
- ## components/assistant-ui/attachment.tsx
290
-
291
- ```tsx
292
- "use client";
293
-
294
- import { PropsWithChildren, useEffect, useState, type FC } from "react";
295
- import Image from "next/image";
296
- import { XIcon, PlusIcon, FileText } from "lucide-react";
297
- import {
298
- AttachmentPrimitive,
299
- ComposerPrimitive,
300
- MessagePrimitive,
301
- useAssistantState,
302
- useAssistantApi,
303
- } from "@assistant-ui/react";
304
- import { useShallow } from "zustand/shallow";
305
- import {
306
- Tooltip,
307
- TooltipContent,
308
- TooltipTrigger,
309
- } from "@/components/ui/tooltip";
310
- import {
311
- Dialog,
312
- DialogTitle,
313
- DialogContent,
314
- DialogTrigger,
315
- } from "@/components/ui/dialog";
316
- import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
317
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
318
- import { cn } from "@/lib/utils";
319
-
320
- const useFileSrc = (file: File | undefined) => {
321
- const [src, setSrc] = useState<string | undefined>(undefined);
322
-
323
- useEffect(() => {
324
- if (!file) {
325
- setSrc(undefined);
326
- return;
327
- }
328
-
329
- const objectUrl = URL.createObjectURL(file);
330
- setSrc(objectUrl);
331
-
332
- return () => {
333
- URL.revokeObjectURL(objectUrl);
334
- };
335
- }, [file]);
336
-
337
- return src;
338
- };
339
-
340
- const useAttachmentSrc = () => {
341
- const { file, src } = useAssistantState(
342
- useShallow(({ attachment }): { file?: File; src?: string } => {
343
- if (attachment.type !== "image") return {};
344
- if (attachment.file) return { file: attachment.file };
345
- const src = attachment.content?.filter((c) => c.type === "image")[0]
346
- ?.image;
347
- if (!src) return {};
348
- return { src };
349
- }),
350
- );
351
-
352
- return useFileSrc(file) ?? src;
353
- };
354
-
355
- type AttachmentPreviewProps = {
356
- src: string;
357
- };
358
-
359
- const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
360
- const [isLoaded, setIsLoaded] = useState(false);
361
- return (
362
- <Image
363
- src={src}
364
- alt="Image Preview"
365
- width={1}
366
- height={1}
367
- className={
368
- isLoaded
369
- ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
370
- : "aui-attachment-preview-image-loading hidden"
371
- }
372
- onLoadingComplete={() => setIsLoaded(true)}
373
- priority={false}
374
- />
375
- );
376
- };
377
-
378
- const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
379
- const src = useAttachmentSrc();
380
-
381
- if (!src) return children;
382
-
383
- return (
384
- <Dialog>
385
- <DialogTrigger
386
- className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
387
- asChild
388
- >
389
- {children}
390
- </DialogTrigger>
391
- <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">
392
- <DialogTitle className="aui-sr-only sr-only">
393
- Image Attachment Preview
394
- </DialogTitle>
395
- <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
396
- <AttachmentPreview src={src} />
397
- </div>
398
- </DialogContent>
399
- </Dialog>
400
- );
401
- };
402
-
403
- const AttachmentThumb: FC = () => {
404
- const isImage = useAssistantState(
405
- ({ attachment }) => attachment.type === "image",
406
- );
407
- const src = useAttachmentSrc();
408
-
409
- return (
410
- <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
411
- <AvatarImage
412
- src={src}
413
- alt="Attachment preview"
414
- className="aui-attachment-tile-image object-cover"
415
- />
416
- <AvatarFallback delayMs={isImage ? 200 : 0}>
417
- <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
418
- </AvatarFallback>
419
- </Avatar>
420
- );
421
- };
422
-
423
- const AttachmentUI: FC = () => {
424
- const api = useAssistantApi();
425
- const isComposer = api.attachment.source === "composer";
426
-
427
- const isImage = useAssistantState(
428
- ({ attachment }) => attachment.type === "image",
429
- );
430
- const typeLabel = useAssistantState(({ attachment }) => {
431
- const type = attachment.type;
432
- switch (type) {
433
- case "image":
434
- return "Image";
435
- case "document":
436
- return "Document";
437
- case "file":
438
- return "File";
439
- default:
440
- const _exhaustiveCheck: never = type;
441
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
442
- }
443
- });
444
-
445
- return (
446
- <Tooltip>
447
- <AttachmentPrimitive.Root
448
- className={cn(
449
- "aui-attachment-root relative",
450
- isImage &&
451
- "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
452
- )}
453
- >
454
- <AttachmentPreviewDialog>
455
- <TooltipTrigger asChild>
456
- <div
457
- className={cn(
458
- "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
459
- isComposer &&
460
- "aui-attachment-tile-composer border-foreground/20",
461
- )}
462
- role="button"
463
- id="attachment-tile"
464
- aria-label={`${typeLabel} attachment`}
465
- >
466
- <AttachmentThumb />
467
- </div>
468
- </TooltipTrigger>
469
- </AttachmentPreviewDialog>
470
- {isComposer && <AttachmentRemove />}
471
- </AttachmentPrimitive.Root>
472
- <TooltipContent side="top">
473
- <AttachmentPrimitive.Name />
474
- </TooltipContent>
475
- </Tooltip>
476
- );
477
- };
478
-
479
- const AttachmentRemove: FC = () => {
480
- return (
481
- <AttachmentPrimitive.Remove asChild>
482
- <TooltipIconButton
483
- tooltip="Remove file"
484
- 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"
485
- side="top"
486
- >
487
- <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
488
- </TooltipIconButton>
489
- </AttachmentPrimitive.Remove>
490
- );
491
- };
492
-
493
- export const UserMessageAttachments: FC = () => {
494
- return (
495
- <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">
496
- <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
497
- </div>
498
- );
499
- };
500
-
501
- export const ComposerAttachments: FC = () => {
502
- return (
503
- <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">
504
- <ComposerPrimitive.Attachments
505
- components={{ Attachment: AttachmentUI }}
506
- />
507
- </div>
508
- );
509
- };
510
-
511
- export const ComposerAddAttachment: FC = () => {
512
- return (
513
- <ComposerPrimitive.AddAttachment asChild>
514
- <TooltipIconButton
515
- tooltip="Add Attachment"
516
- side="bottom"
517
- variant="ghost"
518
- size="icon"
519
- className="aui-composer-add-attachment size-8.5 rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
520
- aria-label="Add Attachment"
521
- >
522
- <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
523
- </TooltipIconButton>
524
- </ComposerPrimitive.AddAttachment>
525
- );
526
- };
527
-
528
- ```
529
-
530
- ## components/assistant-ui/markdown-text.tsx
531
-
532
- ```tsx
533
- "use client";
534
-
535
- import "@assistant-ui/react-markdown/styles/dot.css";
536
-
537
- import {
538
- type CodeHeaderProps,
539
- MarkdownTextPrimitive,
540
- unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
541
- useIsMarkdownCodeBlock,
542
- } from "@assistant-ui/react-markdown";
543
- import remarkGfm from "remark-gfm";
544
- import { type FC, memo, useState } from "react";
545
- import { CheckIcon, CopyIcon } from "lucide-react";
546
-
547
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
548
- import { cn } from "@/lib/utils";
549
-
550
- const MarkdownTextImpl = () => {
551
- return (
552
- <MarkdownTextPrimitive
553
- remarkPlugins={[remarkGfm]}
554
- className="aui-md"
555
- components={defaultComponents}
556
- />
557
- );
558
- };
559
-
560
- export const MarkdownText = memo(MarkdownTextImpl);
561
-
562
- const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
563
- const { isCopied, copyToClipboard } = useCopyToClipboard();
564
- const onCopy = () => {
565
- if (!code || isCopied) return;
566
- copyToClipboard(code);
567
- };
568
-
569
- return (
570
- <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">
571
- <span className="aui-code-header-language lowercase [&>span]:text-xs">
572
- {language}
573
- </span>
574
- <TooltipIconButton tooltip="Copy" onClick={onCopy}>
575
- {!isCopied && <CopyIcon />}
576
- {isCopied && <CheckIcon />}
577
- </TooltipIconButton>
578
- </div>
579
- );
580
- };
581
-
582
- const useCopyToClipboard = ({
583
- copiedDuration = 3000,
584
- }: {
585
- copiedDuration?: number;
586
- } = {}) => {
587
- const [isCopied, setIsCopied] = useState<boolean>(false);
588
-
589
- const copyToClipboard = (value: string) => {
590
- if (!value) return;
591
-
592
- navigator.clipboard.writeText(value).then(() => {
593
- setIsCopied(true);
594
- setTimeout(() => setIsCopied(false), copiedDuration);
595
- });
596
- };
597
-
598
- return { isCopied, copyToClipboard };
599
- };
600
-
601
- const defaultComponents = memoizeMarkdownComponents({
602
- h1: ({ className, ...props }) => (
603
- <h1
604
- className={cn(
605
- "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
606
- className,
607
- )}
608
- {...props}
609
- />
610
- ),
611
- h2: ({ className, ...props }) => (
612
- <h2
613
- className={cn(
614
- "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
615
- className,
616
- )}
617
- {...props}
618
- />
619
- ),
620
- h3: ({ className, ...props }) => (
621
- <h3
622
- className={cn(
623
- "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
624
- className,
625
- )}
626
- {...props}
627
- />
628
- ),
629
- h4: ({ className, ...props }) => (
630
- <h4
631
- className={cn(
632
- "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
633
- className,
634
- )}
635
- {...props}
636
- />
637
- ),
638
- h5: ({ className, ...props }) => (
639
- <h5
640
- className={cn(
641
- "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
642
- className,
643
- )}
644
- {...props}
645
- />
646
- ),
647
- h6: ({ className, ...props }) => (
648
- <h6
649
- className={cn(
650
- "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
651
- className,
652
- )}
653
- {...props}
654
- />
655
- ),
656
- p: ({ className, ...props }) => (
657
- <p
658
- className={cn(
659
- "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
660
- className,
661
- )}
662
- {...props}
663
- />
664
- ),
665
- a: ({ className, ...props }) => (
666
- <a
667
- className={cn(
668
- "aui-md-a font-medium text-primary underline underline-offset-4",
669
- className,
670
- )}
671
- {...props}
672
- />
673
- ),
674
- blockquote: ({ className, ...props }) => (
675
- <blockquote
676
- className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
677
- {...props}
678
- />
679
- ),
680
- ul: ({ className, ...props }) => (
681
- <ul
682
- className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
683
- {...props}
684
- />
685
- ),
686
- ol: ({ className, ...props }) => (
687
- <ol
688
- className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
689
- {...props}
690
- />
691
- ),
692
- hr: ({ className, ...props }) => (
693
- <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
694
- ),
695
- table: ({ className, ...props }) => (
696
- <table
697
- className={cn(
698
- "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
699
- className,
700
- )}
701
- {...props}
702
- />
703
- ),
704
- th: ({ className, ...props }) => (
705
- <th
706
- className={cn(
707
- "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",
708
- className,
709
- )}
710
- {...props}
711
- />
712
- ),
713
- td: ({ className, ...props }) => (
714
- <td
715
- className={cn(
716
- "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
717
- className,
718
- )}
719
- {...props}
720
- />
721
- ),
722
- tr: ({ className, ...props }) => (
723
- <tr
724
- className={cn(
725
- "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",
726
- className,
727
- )}
728
- {...props}
729
- />
730
- ),
731
- sup: ({ className, ...props }) => (
732
- <sup
733
- className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
734
- {...props}
735
- />
736
- ),
737
- pre: ({ className, ...props }) => (
738
- <pre
739
- className={cn(
740
- "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
741
- className,
742
- )}
743
- {...props}
744
- />
745
- ),
746
- code: function Code({ className, ...props }) {
747
- const isCodeBlock = useIsMarkdownCodeBlock();
748
- return (
749
- <code
750
- className={cn(
751
- !isCodeBlock &&
752
- "aui-md-inline-code rounded border bg-muted font-semibold",
753
- className,
754
- )}
755
- {...props}
756
- />
757
- );
758
- },
759
- CodeHeader,
760
- });
761
-
762
- ```
763
-
764
- ## components/assistant-ui/thread.tsx
765
-
766
- ```tsx
767
- import {
768
- ComposerAddAttachment,
769
- ComposerAttachments,
770
- UserMessageAttachments,
771
- } from "@/components/assistant-ui/attachment";
772
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
773
- import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
774
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
775
- import { Button } from "@/components/ui/button";
776
- import { cn } from "@/lib/utils";
777
- import {
778
- ActionBarMorePrimitive,
779
- ActionBarPrimitive,
780
- AssistantIf,
781
- BranchPickerPrimitive,
782
- ComposerPrimitive,
783
- ErrorPrimitive,
784
- MessagePrimitive,
785
- ThreadPrimitive,
786
- } from "@assistant-ui/react";
787
- import {
788
- ArrowDownIcon,
789
- ArrowUpIcon,
790
- CheckIcon,
791
- ChevronLeftIcon,
792
- ChevronRightIcon,
793
- CopyIcon,
794
- DownloadIcon,
795
- MoreHorizontalIcon,
796
- PencilIcon,
797
- RefreshCwIcon,
798
- SquareIcon,
799
- } from "lucide-react";
800
- import type { FC } from "react";
801
-
802
- export const Thread: FC = () => {
803
- return (
804
- <ThreadPrimitive.Root
805
- className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
806
- style={{
807
- ["--thread-max-width" as string]: "44rem",
808
- }}
809
- >
810
- <ThreadPrimitive.Viewport
811
- turnAnchor="top"
812
- className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
813
- >
814
- <AssistantIf condition={({ thread }) => thread.isEmpty}>
815
- <ThreadWelcome />
816
- </AssistantIf>
817
-
818
- <ThreadPrimitive.Messages
819
- components={{
820
- UserMessage,
821
- EditComposer,
822
- AssistantMessage,
823
- }}
824
- />
825
-
826
- <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">
827
- <ThreadScrollToBottom />
828
- <Composer />
829
- </ThreadPrimitive.ViewportFooter>
830
- </ThreadPrimitive.Viewport>
831
- </ThreadPrimitive.Root>
832
- );
833
- };
834
-
835
- const ThreadScrollToBottom: FC = () => {
836
- return (
837
- <ThreadPrimitive.ScrollToBottom asChild>
838
- <TooltipIconButton
839
- tooltip="Scroll to bottom"
840
- variant="outline"
841
- className="aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
842
- >
843
- <ArrowDownIcon />
844
- </TooltipIconButton>
845
- </ThreadPrimitive.ScrollToBottom>
846
- );
847
- };
848
-
849
- const ThreadWelcome: FC = () => {
850
- return (
851
- <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
852
- <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
853
- <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
854
- <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
855
- Hello there!
856
- </h1>
857
- <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">
858
- How can I help you today?
859
- </p>
860
- </div>
861
- </div>
862
- <ThreadSuggestions />
863
- </div>
864
- );
865
- };
866
-
867
- const SUGGESTIONS = [
868
- {
869
- title: "What's the weather",
870
- label: "in San Francisco?",
871
- prompt: "What's the weather in San Francisco?",
872
- },
873
- {
874
- title: "Explain React hooks",
875
- label: "like useState and useEffect",
876
- prompt: "Explain React hooks like useState and useEffect",
877
- },
878
- ] as const;
879
-
880
- const ThreadSuggestions: FC = () => {
881
- return (
882
- <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
883
- {SUGGESTIONS.map((suggestion, index) => (
884
- <div
885
- key={suggestion.prompt}
886
- 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"
887
- style={{ animationDelay: `${100 + index * 50}ms` }}
888
- >
889
- <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
890
- <Button
891
- variant="ghost"
892
- 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"
893
- aria-label={suggestion.prompt}
894
- >
895
- <span className="aui-thread-welcome-suggestion-text-1 font-medium">
896
- {suggestion.title}
897
- </span>
898
- <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
899
- {suggestion.label}
900
- </span>
901
- </Button>
902
- </ThreadPrimitive.Suggestion>
903
- </div>
904
- ))}
905
- </div>
906
- );
907
- };
908
-
909
- const Composer: FC = () => {
910
- return (
911
- <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
912
- <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">
913
- <ComposerAttachments />
914
- <ComposerPrimitive.Input
915
- placeholder="Send a message..."
916
- 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"
917
- rows={1}
918
- autoFocus
919
- aria-label="Message input"
920
- />
921
- <ComposerAction />
922
- </ComposerPrimitive.AttachmentDropzone>
923
- </ComposerPrimitive.Root>
924
- );
925
- };
926
-
927
- const ComposerAction: FC = () => {
928
- return (
929
- <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
930
- <ComposerAddAttachment />
931
-
932
- <AssistantIf condition={({ thread }) => !thread.isRunning}>
933
- <ComposerPrimitive.Send asChild>
934
- <TooltipIconButton
935
- tooltip="Send message"
936
- side="bottom"
937
- type="submit"
938
- variant="default"
939
- size="icon"
940
- className="aui-composer-send size-8 rounded-full"
941
- aria-label="Send message"
942
- >
943
- <ArrowUpIcon className="aui-composer-send-icon size-4" />
944
- </TooltipIconButton>
945
- </ComposerPrimitive.Send>
946
- </AssistantIf>
947
-
948
- <AssistantIf condition={({ thread }) => thread.isRunning}>
949
- <ComposerPrimitive.Cancel asChild>
950
- <Button
951
- type="button"
952
- variant="default"
953
- size="icon"
954
- className="aui-composer-cancel size-8 rounded-full"
955
- aria-label="Stop generating"
956
- >
957
- <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
958
- </Button>
959
- </ComposerPrimitive.Cancel>
960
- </AssistantIf>
961
- </div>
962
- );
963
- };
964
-
965
- const MessageError: FC = () => {
966
- return (
967
- <MessagePrimitive.Error>
968
- <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">
969
- <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
970
- </ErrorPrimitive.Root>
971
- </MessagePrimitive.Error>
972
- );
973
- };
974
-
975
- const AssistantMessage: FC = () => {
976
- return (
977
- <MessagePrimitive.Root
978
- 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"
979
- data-role="assistant"
980
- >
981
- <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
982
- <MessagePrimitive.Parts
983
- components={{
984
- Text: MarkdownText,
985
- tools: { Fallback: ToolFallback },
986
- }}
987
- />
988
- <MessageError />
989
- </div>
990
-
991
- <div className="aui-assistant-message-footer mt-1 ml-2 flex">
992
- <BranchPicker />
993
- <AssistantActionBar />
994
- </div>
995
- </MessagePrimitive.Root>
996
- );
997
- };
998
-
999
- const AssistantActionBar: FC = () => {
1000
- return (
1001
- <ActionBarPrimitive.Root
1002
- hideWhenRunning
1003
- autohide="not-last"
1004
- autohideFloat="single-branch"
1005
- className="aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 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"
1006
- >
1007
- <ActionBarPrimitive.Copy asChild>
1008
- <TooltipIconButton tooltip="Copy">
1009
- <AssistantIf condition={({ message }) => message.isCopied}>
1010
- <CheckIcon />
1011
- </AssistantIf>
1012
- <AssistantIf condition={({ message }) => !message.isCopied}>
1013
- <CopyIcon />
1014
- </AssistantIf>
1015
- </TooltipIconButton>
1016
- </ActionBarPrimitive.Copy>
1017
- <ActionBarPrimitive.Reload asChild>
1018
- <TooltipIconButton tooltip="Refresh">
1019
- <RefreshCwIcon />
1020
- </TooltipIconButton>
1021
- </ActionBarPrimitive.Reload>
1022
- <ActionBarMorePrimitive.Root>
1023
- <ActionBarMorePrimitive.Trigger asChild>
1024
- <TooltipIconButton
1025
- tooltip="More"
1026
- className="data-[state=open]:bg-accent"
1027
- >
1028
- <MoreHorizontalIcon />
1029
- </TooltipIconButton>
1030
- </ActionBarMorePrimitive.Trigger>
1031
- <ActionBarMorePrimitive.Content
1032
- side="bottom"
1033
- align="start"
1034
- className="aui-action-bar-more-content z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
1035
- >
1036
- <ActionBarPrimitive.ExportMarkdown asChild>
1037
- <ActionBarMorePrimitive.Item className="aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground">
1038
- <DownloadIcon className="size-4" />
1039
- Export as Markdown
1040
- </ActionBarMorePrimitive.Item>
1041
- </ActionBarPrimitive.ExportMarkdown>
1042
- </ActionBarMorePrimitive.Content>
1043
- </ActionBarMorePrimitive.Root>
1044
- </ActionBarPrimitive.Root>
1045
- );
1046
- };
1047
-
1048
- const UserMessage: FC = () => {
1049
- return (
1050
- <MessagePrimitive.Root
1051
- 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"
1052
- data-role="user"
1053
- >
1054
- <UserMessageAttachments />
1055
-
1056
- <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1057
- <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1058
- <MessagePrimitive.Parts />
1059
- </div>
1060
- <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
1061
- <UserActionBar />
1062
- </div>
1063
- </div>
1064
-
1065
- <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1066
- </MessagePrimitive.Root>
1067
- );
1068
- };
1069
-
1070
- const UserActionBar: FC = () => {
1071
- return (
1072
- <ActionBarPrimitive.Root
1073
- hideWhenRunning
1074
- autohide="not-last"
1075
- className="aui-user-action-bar-root flex flex-col items-end"
1076
- >
1077
- <ActionBarPrimitive.Edit asChild>
1078
- <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1079
- <PencilIcon />
1080
- </TooltipIconButton>
1081
- </ActionBarPrimitive.Edit>
1082
- </ActionBarPrimitive.Root>
1083
- );
1084
- };
1085
-
1086
- const EditComposer: FC = () => {
1087
- return (
1088
- <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1089
- <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1090
- <ComposerPrimitive.Input
1091
- className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1092
- autoFocus
1093
- />
1094
- <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1095
- <ComposerPrimitive.Cancel asChild>
1096
- <Button variant="ghost" size="sm">
1097
- Cancel
1098
- </Button>
1099
- </ComposerPrimitive.Cancel>
1100
- <ComposerPrimitive.Send asChild>
1101
- <Button size="sm">Update</Button>
1102
- </ComposerPrimitive.Send>
1103
- </div>
1104
- </ComposerPrimitive.Root>
1105
- </MessagePrimitive.Root>
1106
- );
1107
- };
1108
-
1109
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1110
- className,
1111
- ...rest
1112
- }) => {
1113
- return (
1114
- <BranchPickerPrimitive.Root
1115
- hideWhenSingleBranch
1116
- className={cn(
1117
- "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-muted-foreground text-xs",
1118
- className,
1119
- )}
1120
- {...rest}
1121
- >
1122
- <BranchPickerPrimitive.Previous asChild>
1123
- <TooltipIconButton tooltip="Previous">
1124
- <ChevronLeftIcon />
1125
- </TooltipIconButton>
1126
- </BranchPickerPrimitive.Previous>
1127
- <span className="aui-branch-picker-state font-medium">
1128
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
1129
- </span>
1130
- <BranchPickerPrimitive.Next asChild>
1131
- <TooltipIconButton tooltip="Next">
1132
- <ChevronRightIcon />
1133
- </TooltipIconButton>
1134
- </BranchPickerPrimitive.Next>
1135
- </BranchPickerPrimitive.Root>
1136
- );
1137
- };
1138
-
1139
- ```
1140
-
1141
- ## components/assistant-ui/tool-fallback.tsx
1142
-
1143
- ```tsx
1144
- "use client";
1145
-
1146
- import { memo, useCallback, useRef, useState } from "react";
1147
- import {
1148
- AlertCircleIcon,
1149
- CheckIcon,
1150
- ChevronDownIcon,
1151
- LoaderIcon,
1152
- XCircleIcon,
1153
- } from "lucide-react";
1154
- import {
1155
- useScrollLock,
1156
- type ToolCallMessagePartStatus,
1157
- type ToolCallMessagePartComponent,
1158
- } from "@assistant-ui/react";
1159
- import {
1160
- Collapsible,
1161
- CollapsibleContent,
1162
- CollapsibleTrigger,
1163
- } from "@/components/ui/collapsible";
1164
- import { cn } from "@/lib/utils";
1165
-
1166
- const ANIMATION_DURATION = 200;
1167
-
1168
- export type ToolFallbackRootProps = Omit<
1169
- React.ComponentProps<typeof Collapsible>,
1170
- "open" | "onOpenChange"
1171
- > & {
1172
- open?: boolean;
1173
- onOpenChange?: (open: boolean) => void;
1174
- defaultOpen?: boolean;
1175
- };
1176
-
1177
- function ToolFallbackRoot({
1178
- className,
1179
- open: controlledOpen,
1180
- onOpenChange: controlledOnOpenChange,
1181
- defaultOpen = false,
1182
- children,
1183
- ...props
1184
- }: ToolFallbackRootProps) {
1185
- const collapsibleRef = useRef<HTMLDivElement>(null);
1186
- const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
1187
- const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION);
1188
-
1189
- const isControlled = controlledOpen !== undefined;
1190
- const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
1191
-
1192
- const handleOpenChange = useCallback(
1193
- (open: boolean) => {
1194
- if (!open) {
1195
- lockScroll();
1196
- }
1197
- if (!isControlled) {
1198
- setUncontrolledOpen(open);
1199
- }
1200
- controlledOnOpenChange?.(open);
1201
- },
1202
- [lockScroll, isControlled, controlledOnOpenChange],
1203
- );
1204
-
1205
- return (
1206
- <Collapsible
1207
- ref={collapsibleRef}
1208
- data-slot="tool-fallback-root"
1209
- open={isOpen}
1210
- onOpenChange={handleOpenChange}
1211
- className={cn(
1212
- "aui-tool-fallback-root group/tool-fallback-root w-full rounded-lg border py-3",
1213
- className,
1214
- )}
1215
- style={
1216
- {
1217
- "--animation-duration": `${ANIMATION_DURATION}ms`,
1218
- } as React.CSSProperties
1219
- }
1220
- {...props}
1221
- >
1222
- {children}
1223
- </Collapsible>
1224
- );
1225
- }
1226
-
1227
- type ToolStatus = ToolCallMessagePartStatus["type"];
1228
-
1229
- const statusIconMap: Record<ToolStatus, React.ElementType> = {
1230
- running: LoaderIcon,
1231
- complete: CheckIcon,
1232
- incomplete: XCircleIcon,
1233
- "requires-action": AlertCircleIcon,
1234
- };
1235
-
1236
- function ToolFallbackTrigger({
1237
- toolName,
1238
- status,
1239
- className,
1240
- ...props
1241
- }: React.ComponentProps<typeof CollapsibleTrigger> & {
1242
- toolName: string;
1243
- status?: ToolCallMessagePartStatus;
1244
- }) {
1245
- const statusType = status?.type ?? "complete";
1246
- const isRunning = statusType === "running";
1247
- const isCancelled =
1248
- status?.type === "incomplete" && status.reason === "cancelled";
1249
-
1250
- const Icon = statusIconMap[statusType];
1251
- const label = isCancelled ? "Cancelled tool" : "Used tool";
1252
-
1253
- return (
1254
- <CollapsibleTrigger
1255
- data-slot="tool-fallback-trigger"
1256
- className={cn(
1257
- "aui-tool-fallback-trigger group/trigger flex w-full items-center gap-2 px-4 text-sm transition-colors",
1258
- className,
1259
- )}
1260
- {...props}
1261
- >
1262
- <Icon
1263
- data-slot="tool-fallback-trigger-icon"
1264
- className={cn(
1265
- "aui-tool-fallback-trigger-icon size-4 shrink-0",
1266
- isCancelled && "text-muted-foreground",
1267
- isRunning && "animate-spin",
1268
- )}
1269
- />
1270
- <span
1271
- data-slot="tool-fallback-trigger-label"
1272
- className={cn(
1273
- "aui-tool-fallback-trigger-label-wrapper relative inline-block grow text-left leading-none",
1274
- isCancelled && "text-muted-foreground line-through",
1275
- )}
1276
- >
1277
- <span>
1278
- {label}: <b>{toolName}</b>
1279
- </span>
1280
- {isRunning && (
1281
- <span
1282
- aria-hidden
1283
- data-slot="tool-fallback-trigger-shimmer"
1284
- className="aui-tool-fallback-trigger-shimmer shimmer pointer-events-none absolute inset-0 motion-reduce:animate-none"
1285
- >
1286
- {label}: <b>{toolName}</b>
1287
- </span>
1288
- )}
1289
- </span>
1290
- <ChevronDownIcon
1291
- data-slot="tool-fallback-trigger-chevron"
1292
- className={cn(
1293
- "aui-tool-fallback-trigger-chevron size-4 shrink-0",
1294
- "transition-transform duration-(--animation-duration) ease-out",
1295
- "group-data-[state=closed]/trigger:-rotate-90",
1296
- "group-data-[state=open]/trigger:rotate-0",
1297
- )}
1298
- />
1299
- </CollapsibleTrigger>
1300
- );
1301
- }
1302
-
1303
- function ToolFallbackContent({
1304
- className,
1305
- children,
1306
- ...props
1307
- }: React.ComponentProps<typeof CollapsibleContent>) {
1308
- return (
1309
- <CollapsibleContent
1310
- data-slot="tool-fallback-content"
1311
- className={cn(
1312
- "aui-tool-fallback-content relative overflow-hidden text-sm outline-none",
1313
- "group/collapsible-content ease-out",
1314
- "data-[state=closed]:animate-collapsible-up",
1315
- "data-[state=open]:animate-collapsible-down",
1316
- "data-[state=closed]:fill-mode-forwards",
1317
- "data-[state=closed]:pointer-events-none",
1318
- "data-[state=open]:duration-(--animation-duration)",
1319
- "data-[state=closed]:duration-(--animation-duration)",
1320
- className,
1321
- )}
1322
- {...props}
1323
- >
1324
- <div className="mt-3 flex flex-col gap-2 border-t pt-2">{children}</div>
1325
- </CollapsibleContent>
1326
- );
1327
- }
1328
-
1329
- function ToolFallbackArgs({
1330
- argsText,
1331
- className,
1332
- ...props
1333
- }: React.ComponentProps<"div"> & {
1334
- argsText?: string;
1335
- }) {
1336
- if (!argsText) return null;
1337
-
1338
- return (
1339
- <div
1340
- data-slot="tool-fallback-args"
1341
- className={cn("aui-tool-fallback-args px-4", className)}
1342
- {...props}
1343
- >
1344
- <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1345
- {argsText}
1346
- </pre>
1347
- </div>
1348
- );
1349
- }
1350
-
1351
- function ToolFallbackResult({
1352
- result,
1353
- className,
1354
- ...props
1355
- }: React.ComponentProps<"div"> & {
1356
- result?: unknown;
1357
- }) {
1358
- if (result === undefined) return null;
1359
-
1360
- return (
1361
- <div
1362
- data-slot="tool-fallback-result"
1363
- className={cn(
1364
- "aui-tool-fallback-result border-t border-dashed px-4 pt-2",
1365
- className,
1366
- )}
1367
- {...props}
1368
- >
1369
- <p className="aui-tool-fallback-result-header font-semibold">Result:</p>
1370
- <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1371
- {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
1372
- </pre>
1373
- </div>
1374
- );
1375
- }
1376
-
1377
- function ToolFallbackError({
1378
- status,
1379
- className,
1380
- ...props
1381
- }: React.ComponentProps<"div"> & {
1382
- status?: ToolCallMessagePartStatus;
1383
- }) {
1384
- if (status?.type !== "incomplete") return null;
1385
-
1386
- const error = status.error;
1387
- const errorText = error
1388
- ? typeof error === "string"
1389
- ? error
1390
- : JSON.stringify(error)
1391
- : null;
1392
-
1393
- if (!errorText) return null;
1394
-
1395
- const isCancelled = status.reason === "cancelled";
1396
- const headerText = isCancelled ? "Cancelled reason:" : "Error:";
1397
-
1398
- return (
1399
- <div
1400
- data-slot="tool-fallback-error"
1401
- className={cn("aui-tool-fallback-error px-4", className)}
1402
- {...props}
1403
- >
1404
- <p className="aui-tool-fallback-error-header font-semibold text-muted-foreground">
1405
- {headerText}
1406
- </p>
1407
- <p className="aui-tool-fallback-error-reason text-muted-foreground">
1408
- {errorText}
1409
- </p>
1410
- </div>
1411
- );
1412
- }
1413
-
1414
- const ToolFallbackImpl: ToolCallMessagePartComponent = ({
1415
- toolName,
1416
- argsText,
1417
- result,
1418
- status,
1419
- }) => {
1420
- const isCancelled =
1421
- status?.type === "incomplete" && status.reason === "cancelled";
1422
-
1423
- return (
1424
- <ToolFallbackRoot
1425
- className={cn(isCancelled && "border-muted-foreground/30 bg-muted/30")}
1426
- >
1427
- <ToolFallbackTrigger toolName={toolName} status={status} />
1428
- <ToolFallbackContent>
1429
- <ToolFallbackError status={status} />
1430
- <ToolFallbackArgs
1431
- argsText={argsText}
1432
- className={cn(isCancelled && "opacity-60")}
1433
- />
1434
- {!isCancelled && <ToolFallbackResult result={result} />}
1435
- </ToolFallbackContent>
1436
- </ToolFallbackRoot>
1437
- );
1438
- };
1439
-
1440
- const ToolFallback = memo(
1441
- ToolFallbackImpl,
1442
- ) as unknown as ToolCallMessagePartComponent & {
1443
- Root: typeof ToolFallbackRoot;
1444
- Trigger: typeof ToolFallbackTrigger;
1445
- Content: typeof ToolFallbackContent;
1446
- Args: typeof ToolFallbackArgs;
1447
- Result: typeof ToolFallbackResult;
1448
- Error: typeof ToolFallbackError;
1449
- };
1450
-
1451
- ToolFallback.displayName = "ToolFallback";
1452
- ToolFallback.Root = ToolFallbackRoot;
1453
- ToolFallback.Trigger = ToolFallbackTrigger;
1454
- ToolFallback.Content = ToolFallbackContent;
1455
- ToolFallback.Args = ToolFallbackArgs;
1456
- ToolFallback.Result = ToolFallbackResult;
1457
- ToolFallback.Error = ToolFallbackError;
1458
-
1459
- export {
1460
- ToolFallback,
1461
- ToolFallbackRoot,
1462
- ToolFallbackTrigger,
1463
- ToolFallbackContent,
1464
- ToolFallbackArgs,
1465
- ToolFallbackResult,
1466
- ToolFallbackError,
1467
- };
1468
-
1469
- ```
1470
-
1471
- ## components/assistant-ui/tooltip-icon-button.tsx
1472
-
1473
- ```tsx
1474
- "use client";
1475
-
1476
- import { ComponentPropsWithRef, forwardRef } from "react";
1477
- import { Slottable } from "@radix-ui/react-slot";
1478
-
1479
- import {
1480
- Tooltip,
1481
- TooltipContent,
1482
- TooltipTrigger,
1483
- } from "@/components/ui/tooltip";
1484
- import { Button } from "@/components/ui/button";
1485
- import { cn } from "@/lib/utils";
1486
-
1487
- export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1488
- tooltip: string;
1489
- side?: "top" | "bottom" | "left" | "right";
1490
- };
1491
-
1492
- export const TooltipIconButton = forwardRef<
1493
- HTMLButtonElement,
1494
- TooltipIconButtonProps
1495
- >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
1496
- return (
1497
- <Tooltip>
1498
- <TooltipTrigger asChild>
1499
- <Button
1500
- variant="ghost"
1501
- size="icon"
1502
- {...rest}
1503
- className={cn("aui-button-icon size-6 p-1", className)}
1504
- ref={ref}
1505
- >
1506
- <Slottable>{children}</Slottable>
1507
- <span className="aui-sr-only sr-only">{tooltip}</span>
1508
- </Button>
1509
- </TooltipTrigger>
1510
- <TooltipContent side={side}>{tooltip}</TooltipContent>
1511
- </Tooltip>
1512
- );
1513
- });
1514
-
1515
- TooltipIconButton.displayName = "TooltipIconButton";
1516
-
1517
- ```
1518
-
1519
- ## components/ui/avatar.tsx
1520
-
1521
- ```tsx
1522
- "use client";
1523
-
1524
- import * as React from "react";
1525
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
1526
-
1527
- import { cn } from "@/lib/utils";
1528
-
1529
- function Avatar({
1530
- className,
1531
- ...props
1532
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
1533
- return (
1534
- <AvatarPrimitive.Root
1535
- data-slot="avatar"
1536
- className={cn(
1537
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
1538
- className,
1539
- )}
1540
- {...props}
1541
- />
1542
- );
1543
- }
1544
-
1545
- function AvatarImage({
1546
- className,
1547
- ...props
1548
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
1549
- return (
1550
- <AvatarPrimitive.Image
1551
- data-slot="avatar-image"
1552
- className={cn("aspect-square size-full", className)}
1553
- {...props}
1554
- />
1555
- );
1556
- }
1557
-
1558
- function AvatarFallback({
1559
- className,
1560
- ...props
1561
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
1562
- return (
1563
- <AvatarPrimitive.Fallback
1564
- data-slot="avatar-fallback"
1565
- className={cn(
1566
- "flex size-full items-center justify-center rounded-full bg-muted",
1567
- className,
1568
- )}
1569
- {...props}
1570
- />
1571
- );
1572
- }
1573
-
1574
- export { Avatar, AvatarImage, AvatarFallback };
1575
-
1576
- ```
1577
-
1578
- ## components/ui/button.tsx
1579
-
1580
- ```tsx
1581
- import * as React from "react";
1582
- import { Slot } from "@radix-ui/react-slot";
1583
- import { cva, type VariantProps } from "class-variance-authority";
1584
-
1585
- import { cn } from "@/lib/utils";
1586
-
1587
- const buttonVariants = cva(
1588
- "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
1589
- {
1590
- variants: {
1591
- variant: {
1592
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
1593
- destructive:
1594
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1595
- outline:
1596
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1597
- secondary:
1598
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1599
- ghost:
1600
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1601
- link: "text-primary underline-offset-4 hover:underline",
1602
- },
1603
- size: {
1604
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
1605
- sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1606
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1607
- icon: "size-9",
1608
- "icon-sm": "size-8",
1609
- "icon-lg": "size-10",
1610
- },
1611
- },
1612
- defaultVariants: {
1613
- variant: "default",
1614
- size: "default",
1615
- },
1616
- },
1617
- );
1618
-
1619
- function Button({
1620
- className,
1621
- variant = "default",
1622
- size = "default",
1623
- asChild = false,
1624
- ...props
1625
- }: React.ComponentProps<"button"> &
1626
- VariantProps<typeof buttonVariants> & {
1627
- asChild?: boolean;
1628
- }) {
1629
- const Comp = asChild ? Slot : "button";
1630
-
1631
- return (
1632
- <Comp
1633
- data-slot="button"
1634
- data-variant={variant}
1635
- data-size={size}
1636
- className={cn(buttonVariants({ variant, size, className }))}
1637
- {...props}
1638
- />
1639
- );
1640
- }
1641
-
1642
- export { Button, buttonVariants };
1643
-
1644
- ```
1645
-
1646
- ## components/ui/collapsible.tsx
1647
-
1648
- ```tsx
1649
- "use client";
1650
-
1651
- import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
1652
-
1653
- function Collapsible({
1654
- ...props
1655
- }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
1656
- return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
1657
- }
1658
-
1659
- function CollapsibleTrigger({
1660
- ...props
1661
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
1662
- return (
1663
- <CollapsiblePrimitive.CollapsibleTrigger
1664
- data-slot="collapsible-trigger"
1665
- {...props}
1666
- />
1667
- );
1668
- }
1669
-
1670
- function CollapsibleContent({
1671
- ...props
1672
- }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
1673
- return (
1674
- <CollapsiblePrimitive.CollapsibleContent
1675
- data-slot="collapsible-content"
1676
- {...props}
1677
- />
1678
- );
1679
- }
1680
-
1681
- export { Collapsible, CollapsibleTrigger, CollapsibleContent };
1682
-
1683
- ```
1684
-
1685
- ## components/ui/dialog.tsx
1686
-
1687
- ```tsx
1688
- "use client";
1689
-
1690
- import * as React from "react";
1691
- import * as DialogPrimitive from "@radix-ui/react-dialog";
1692
- import { XIcon } from "lucide-react";
1693
-
1694
- import { cn } from "@/lib/utils";
1695
-
1696
- function Dialog({
1697
- ...props
1698
- }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1699
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1700
- }
1701
-
1702
- function DialogTrigger({
1703
- ...props
1704
- }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1705
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1706
- }
1707
-
1708
- function DialogPortal({
1709
- ...props
1710
- }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1711
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1712
- }
1713
-
1714
- function DialogClose({
1715
- ...props
1716
- }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1717
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1718
- }
1719
-
1720
- function DialogOverlay({
1721
- className,
1722
- ...props
1723
- }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1724
- return (
1725
- <DialogPrimitive.Overlay
1726
- data-slot="dialog-overlay"
1727
- className={cn(
1728
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in",
1729
- className,
1730
- )}
1731
- {...props}
1732
- />
1733
- );
1734
- }
1735
-
1736
- function DialogContent({
1737
- className,
1738
- children,
1739
- showCloseButton = true,
1740
- ...props
1741
- }: React.ComponentProps<typeof DialogPrimitive.Content> & {
1742
- showCloseButton?: boolean;
1743
- }) {
1744
- return (
1745
- <DialogPortal data-slot="dialog-portal">
1746
- <DialogOverlay />
1747
- <DialogPrimitive.Content
1748
- data-slot="dialog-content"
1749
- className={cn(
1750
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 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 outline-none duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
1751
- className,
1752
- )}
1753
- {...props}
1754
- >
1755
- {children}
1756
- {showCloseButton && (
1757
- <DialogPrimitive.Close
1758
- data-slot="dialog-close"
1759
- 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"
1760
- >
1761
- <XIcon />
1762
- <span className="sr-only">Close</span>
1763
- </DialogPrimitive.Close>
1764
- )}
1765
- </DialogPrimitive.Content>
1766
- </DialogPortal>
1767
- );
1768
- }
1769
-
1770
- function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1771
- return (
1772
- <div
1773
- data-slot="dialog-header"
1774
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1775
- {...props}
1776
- />
1777
- );
1778
- }
1779
-
1780
- function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1781
- return (
1782
- <div
1783
- data-slot="dialog-footer"
1784
- className={cn(
1785
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1786
- className,
1787
- )}
1788
- {...props}
1789
- />
1790
- );
1791
- }
1792
-
1793
- function DialogTitle({
1794
- className,
1795
- ...props
1796
- }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1797
- return (
1798
- <DialogPrimitive.Title
1799
- data-slot="dialog-title"
1800
- className={cn("font-semibold text-lg leading-none", className)}
1801
- {...props}
1802
- />
1803
- );
1804
- }
1805
-
1806
- function DialogDescription({
1807
- className,
1808
- ...props
1809
- }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1810
- return (
1811
- <DialogPrimitive.Description
1812
- data-slot="dialog-description"
1813
- className={cn("text-muted-foreground text-sm", className)}
1814
- {...props}
1815
- />
1816
- );
1817
- }
1818
-
1819
- export {
1820
- Dialog,
1821
- DialogClose,
1822
- DialogContent,
1823
- DialogDescription,
1824
- DialogFooter,
1825
- DialogHeader,
1826
- DialogOverlay,
1827
- DialogPortal,
1828
- DialogTitle,
1829
- DialogTrigger,
1830
- };
1831
-
1832
- ```
1833
-
1834
- ## components/ui/tooltip.tsx
1835
-
1836
- ```tsx
1837
- "use client";
1838
-
1839
- import * as React from "react";
1840
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1841
-
1842
- import { cn } from "@/lib/utils";
1843
-
1844
- function TooltipProvider({
1845
- delayDuration = 0,
1846
- ...props
1847
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1848
- return (
1849
- <TooltipPrimitive.Provider
1850
- data-slot="tooltip-provider"
1851
- delayDuration={delayDuration}
1852
- {...props}
1853
- />
1854
- );
1855
- }
1856
-
1857
- function Tooltip({
1858
- ...props
1859
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1860
- return (
1861
- <TooltipProvider>
1862
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1863
- </TooltipProvider>
1864
- );
1865
- }
1866
-
1867
- function TooltipTrigger({
1868
- ...props
1869
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
1870
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
1871
- }
1872
-
1873
- function TooltipContent({
1874
- className,
1875
- sideOffset = 0,
1876
- children,
1877
- ...props
1878
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
1879
- return (
1880
- <TooltipPrimitive.Portal>
1881
- <TooltipPrimitive.Content
1882
- data-slot="tooltip-content"
1883
- sideOffset={sideOffset}
1884
- className={cn(
1885
- "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-foreground px-3 py-1.5 text-background text-xs data-[state=closed]:animate-out",
1886
- className,
1887
- )}
1888
- {...props}
1889
- >
1890
- {children}
1891
- <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
1892
- </TooltipPrimitive.Content>
1893
- </TooltipPrimitive.Portal>
1894
- );
286
+ "iconLibrary": "lucide",
287
+ "registries": {
288
+ "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
289
+ }
1895
290
  }
1896
291
 
1897
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1898
-
1899
292
  ```
1900
293
 
1901
294
  ## lib/utils.ts
@@ -1941,6 +334,7 @@ export default nextConfig;
1941
334
  "@assistant-ui/react": "workspace:*",
1942
335
  "@assistant-ui/react-ag-ui": "workspace:*",
1943
336
  "@assistant-ui/react-markdown": "workspace:*",
337
+ "@assistant-ui/ui": "workspace:*",
1944
338
  "@radix-ui/react-avatar": "^1.1.11",
1945
339
  "@radix-ui/react-collapsible": "^1.1.12",
1946
340
  "@radix-ui/react-dialog": "^1.1.15",
@@ -1948,18 +342,16 @@ export default nextConfig;
1948
342
  "@radix-ui/react-tooltip": "^1.2.8",
1949
343
  "class-variance-authority": "^0.7.1",
1950
344
  "clsx": "^2.1.1",
1951
- "lucide-react": "^0.562.0",
1952
- "next": "^16.1.4",
1953
- "react": "^19.2.3",
1954
- "react-dom": "^19.2.3",
1955
- "remark-gfm": "^4.0.1",
1956
- "tailwind-merge": "^3.4.0",
1957
- "zustand": "^5.0.10"
345
+ "lucide-react": "^0.563.0",
346
+ "next": "^16.1.5",
347
+ "react": "^19.2.4",
348
+ "react-dom": "^19.2.4",
349
+ "tailwind-merge": "^3.4.0"
1958
350
  },
1959
351
  "devDependencies": {
1960
352
  "@assistant-ui/x-buildutils": "workspace:*",
1961
353
  "@tailwindcss/postcss": "^4.1.18",
1962
- "@types/node": "^25.0.9",
354
+ "@types/node": "^25.0.10",
1963
355
  "@types/react": "^19.2.9",
1964
356
  "@types/react-dom": "^19.2.3",
1965
357
  "postcss": "^8.5.6",
@@ -1971,13 +363,64 @@ export default nextConfig;
1971
363
 
1972
364
  ```
1973
365
 
366
+ ## README.md
367
+
368
+ ```markdown
369
+ # AG-UI Protocol Integration
370
+
371
+ This example demonstrates how to integrate assistant-ui with the AG-UI protocol for connecting to AG-UI compatible agents.
372
+
373
+ ## Quick Start
374
+
375
+ ### Using CLI (Recommended)
376
+
377
+ ```bash
378
+ npx assistant-ui@latest create my-app --example with-ag-ui
379
+ cd my-app
380
+ ```
381
+
382
+ ### Environment Variables
383
+
384
+ Create `.env.local`:
385
+
386
+ ```
387
+ NEXT_PUBLIC_AGUI_AGENT_URL=http://localhost:8000/agent
388
+ ```
389
+
390
+ ### Run
391
+
392
+ ```bash
393
+ npm run dev
394
+ ```
395
+
396
+ ## Features
397
+
398
+ - AG-UI protocol integration via `@assistant-ui/react-ag-ui`
399
+ - Custom browser alert tool demonstration
400
+ - Client-side tool execution
401
+ - Tool result rendering
402
+
403
+ ## Related Documentation
404
+
405
+ - [assistant-ui Documentation](https://www.assistant-ui.com/docs)
406
+ - [AG-UI Protocol](https://docs.ag-ui.com)
407
+
408
+ ```
409
+
1974
410
  ## tsconfig.json
1975
411
 
1976
412
  ```json
1977
413
  {
1978
414
  "extends": "@assistant-ui/x-buildutils/ts/next",
1979
415
  "compilerOptions": {
1980
- "paths": { "@/*": ["./*"] }
416
+ "paths": {
417
+ "@/*": ["./*"],
418
+ "@/components/assistant-ui/*": [
419
+ "../../packages/ui/src/components/assistant-ui/*"
420
+ ],
421
+ "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
422
+ "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
423
+ }
1981
424
  },
1982
425
  "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1983
426
  "exclude": ["node_modules"]