@assistant-ui/mcp-docs-server 0.1.18 → 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 (148) hide show
  1. package/.docs/organized/code-examples/with-ag-ui.md +69 -1336
  2. package/.docs/organized/code-examples/with-ai-sdk-v6.md +429 -0
  3. package/.docs/organized/code-examples/with-assistant-transport.md +41 -1454
  4. package/.docs/organized/code-examples/with-cloud.md +73 -1442
  5. package/.docs/organized/code-examples/with-custom-thread-list.md +95 -1424
  6. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +674 -0
  7. package/.docs/organized/code-examples/with-external-store.md +68 -1334
  8. package/.docs/organized/code-examples/with-ffmpeg.md +73 -1341
  9. package/.docs/organized/code-examples/with-langgraph.md +52 -1545
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +58 -1364
  11. package/.docs/organized/code-examples/with-react-hook-form.md +73 -1726
  12. package/.docs/organized/code-examples/with-react-router.md +915 -0
  13. package/.docs/organized/code-examples/with-store.md +31 -31
  14. package/.docs/organized/code-examples/with-tanstack.md +78 -862
  15. package/.docs/raw/blog/2025-01-31-changelog/index.mdx +0 -2
  16. package/.docs/raw/docs/{architecture.mdx → (docs)/architecture.mdx} +3 -2
  17. package/.docs/raw/docs/{cli.mdx → (docs)/cli.mdx} +66 -18
  18. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-readable.mdx +1 -0
  19. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-tool-ui.mdx +2 -1
  20. package/.docs/raw/docs/{copilots → (docs)/copilots}/make-assistant-tool.mdx +2 -1
  21. package/.docs/raw/docs/{copilots → (docs)/copilots}/model-context.mdx +5 -4
  22. package/.docs/raw/docs/{copilots → (docs)/copilots}/motivation.mdx +4 -3
  23. package/.docs/raw/docs/{copilots → (docs)/copilots}/use-assistant-instructions.mdx +1 -0
  24. package/.docs/raw/docs/{devtools.mdx → (docs)/devtools.mdx} +4 -4
  25. package/.docs/raw/docs/{guides/Attachments.mdx → (docs)/guides/attachments.mdx} +6 -7
  26. package/.docs/raw/docs/{guides/Branching.mdx → (docs)/guides/branching.mdx} +2 -1
  27. package/.docs/raw/docs/{guides → (docs)/guides}/context-api.mdx +118 -117
  28. package/.docs/raw/docs/(docs)/guides/dictation.mdx +370 -0
  29. package/.docs/raw/docs/{guides/Editing.mdx → (docs)/guides/editing.mdx} +1 -0
  30. package/.docs/raw/docs/{guides/Latex.mdx → (docs)/guides/latex.mdx} +1 -2
  31. package/.docs/raw/docs/{guides/Speech.mdx → (docs)/guides/speech.mdx} +9 -10
  32. package/.docs/raw/docs/(docs)/guides/suggestions.mdx +296 -0
  33. package/.docs/raw/docs/{guides/ToolUI.mdx → (docs)/guides/tool-ui.mdx} +15 -14
  34. package/.docs/raw/docs/(docs)/guides/tools.mdx +564 -0
  35. package/.docs/raw/docs/(docs)/index.mdx +74 -0
  36. package/.docs/raw/docs/{getting-started.mdx → (docs)/installation.mdx} +18 -23
  37. package/.docs/raw/docs/(docs)/llm.mdx +209 -0
  38. package/.docs/raw/docs/{api-reference/context-providers/AssistantRuntimeProvider.mdx → (reference)/api-reference/context-providers/assistant-runtime-provider.mdx} +2 -1
  39. package/.docs/raw/docs/{api-reference/context-providers/TextMessagePartProvider.mdx → (reference)/api-reference/context-providers/text-message-part-provider.mdx} +2 -1
  40. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/react-data-stream.mdx +50 -3
  41. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/react-hook-form.mdx +2 -1
  42. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/integrations/vercel-ai-sdk.mdx +2 -2
  43. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/overview.mdx +10 -4
  44. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +327 -0
  45. package/.docs/raw/docs/{api-reference/primitives/ActionBar.mdx → (reference)/api-reference/primitives/action-bar.mdx} +7 -5
  46. package/.docs/raw/docs/{api-reference/primitives/AssistantIf.mdx → (reference)/api-reference/primitives/assistant-if.mdx} +51 -51
  47. package/.docs/raw/docs/{api-reference/primitives/AssistantModal.mdx → (reference)/api-reference/primitives/assistant-modal.mdx} +3 -1
  48. package/.docs/raw/docs/{api-reference/primitives/Attachment.mdx → (reference)/api-reference/primitives/attachment.mdx} +3 -2
  49. package/.docs/raw/docs/{api-reference/primitives/BranchPicker.mdx → (reference)/api-reference/primitives/branch-picker.mdx} +2 -1
  50. package/.docs/raw/docs/{api-reference/primitives/Composer.mdx → (reference)/api-reference/primitives/composer.mdx} +101 -2
  51. package/.docs/raw/docs/{api-reference → (reference)/api-reference}/primitives/composition.mdx +1 -0
  52. package/.docs/raw/docs/{api-reference/primitives/Error.mdx → (reference)/api-reference/primitives/error.mdx} +2 -1
  53. package/.docs/raw/docs/{api-reference/primitives/MessagePart.mdx → (reference)/api-reference/primitives/message-part.mdx} +2 -2
  54. package/.docs/raw/docs/{api-reference/primitives/Message.mdx → (reference)/api-reference/primitives/message.mdx} +2 -1
  55. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +153 -0
  56. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +221 -0
  57. package/.docs/raw/docs/{api-reference/primitives/ThreadListItem.mdx → (reference)/api-reference/primitives/thread-list-item.mdx} +2 -1
  58. package/.docs/raw/docs/{api-reference/primitives/ThreadList.mdx → (reference)/api-reference/primitives/thread-list.mdx} +2 -1
  59. package/.docs/raw/docs/{api-reference/primitives/Thread.mdx → (reference)/api-reference/primitives/thread.mdx} +30 -40
  60. package/.docs/raw/docs/{api-reference/runtimes/AssistantRuntime.mdx → (reference)/api-reference/runtimes/assistant-runtime.mdx} +2 -1
  61. package/.docs/raw/docs/{api-reference/runtimes/AttachmentRuntime.mdx → (reference)/api-reference/runtimes/attachment-runtime.mdx} +3 -2
  62. package/.docs/raw/docs/{api-reference/runtimes/ComposerRuntime.mdx → (reference)/api-reference/runtimes/composer-runtime.mdx} +2 -1
  63. package/.docs/raw/docs/{api-reference/runtimes/MessagePartRuntime.mdx → (reference)/api-reference/runtimes/message-part-runtime.mdx} +3 -2
  64. package/.docs/raw/docs/{api-reference/runtimes/MessageRuntime.mdx → (reference)/api-reference/runtimes/message-runtime.mdx} +3 -2
  65. package/.docs/raw/docs/{api-reference/runtimes/ThreadListItemRuntime.mdx → (reference)/api-reference/runtimes/thread-list-item-runtime.mdx} +2 -1
  66. package/.docs/raw/docs/{api-reference/runtimes/ThreadListRuntime.mdx → (reference)/api-reference/runtimes/thread-list-runtime.mdx} +2 -1
  67. package/.docs/raw/docs/{api-reference/runtimes/ThreadRuntime.mdx → (reference)/api-reference/runtimes/thread-runtime.mdx} +3 -5
  68. package/.docs/raw/docs/{legacy/styled/AssistantModal.mdx → (reference)/legacy/styled/assistant-modal.mdx} +2 -3
  69. package/.docs/raw/docs/{legacy/styled/Decomposition.mdx → (reference)/legacy/styled/decomposition.mdx} +6 -5
  70. package/.docs/raw/docs/{legacy/styled/Markdown.mdx → (reference)/legacy/styled/markdown.mdx} +2 -4
  71. package/.docs/raw/docs/{legacy/styled/Scrollbar.mdx → (reference)/legacy/styled/scrollbar.mdx} +2 -1
  72. package/.docs/raw/docs/{legacy/styled/ThreadWidth.mdx → (reference)/legacy/styled/thread-width.mdx} +1 -0
  73. package/.docs/raw/docs/{legacy/styled/Thread.mdx → (reference)/legacy/styled/thread.mdx} +2 -3
  74. package/.docs/raw/docs/{migrations → (reference)/migrations}/deprecation-policy.mdx +1 -0
  75. package/.docs/raw/docs/{migrations → (reference)/migrations}/react-langgraph-v0-7.mdx +1 -2
  76. package/.docs/raw/docs/{migrations → (reference)/migrations}/v0-11.mdx +1 -0
  77. package/.docs/raw/docs/(reference)/migrations/v0-12.mdx +300 -0
  78. package/.docs/raw/docs/{react-compatibility.mdx → (reference)/react-compatibility.mdx} +2 -3
  79. package/.docs/raw/docs/cloud/authorization.mdx +1 -0
  80. package/.docs/raw/docs/cloud/overview.mdx +1 -0
  81. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +2 -3
  82. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +5 -7
  83. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +9 -8
  84. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +2 -3
  85. package/.docs/raw/docs/runtimes/assistant-transport.mdx +10 -9
  86. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +7 -8
  87. package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -8
  88. package/.docs/raw/docs/runtimes/custom/local.mdx +55 -42
  89. package/.docs/raw/docs/runtimes/data-stream.mdx +67 -6
  90. package/.docs/raw/docs/runtimes/helicone.mdx +1 -0
  91. package/.docs/raw/docs/runtimes/langgraph/index.mdx +3 -3
  92. package/.docs/raw/docs/runtimes/langgraph/tutorial/index.mdx +1 -0
  93. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +1 -0
  94. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +1 -0
  95. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -0
  96. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +2 -1
  97. package/.docs/raw/docs/runtimes/langserve.mdx +2 -2
  98. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +4 -5
  99. package/.docs/raw/docs/runtimes/mastra/overview.mdx +1 -0
  100. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +3 -4
  101. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +2 -4
  102. package/.docs/raw/docs/ui/accordion.mdx +261 -0
  103. package/.docs/raw/docs/ui/assistant-modal.mdx +163 -0
  104. package/.docs/raw/docs/ui/assistant-sidebar.mdx +90 -0
  105. package/.docs/raw/docs/ui/attachment.mdx +227 -0
  106. package/.docs/raw/docs/ui/badge.mdx +140 -0
  107. package/.docs/raw/docs/ui/file.mdx +153 -0
  108. package/.docs/raw/docs/ui/image.mdx +101 -0
  109. package/.docs/raw/docs/ui/{Markdown.mdx → markdown.mdx} +11 -6
  110. package/.docs/raw/docs/ui/{Mermaid.mdx → mermaid.mdx} +12 -5
  111. package/.docs/raw/docs/ui/model-selector.mdx +226 -0
  112. package/.docs/raw/docs/ui/{PartGrouping.mdx → part-grouping.mdx} +6 -8
  113. package/.docs/raw/docs/ui/reasoning.mdx +150 -0
  114. package/.docs/raw/docs/ui/{Scrollbar.mdx → scrollbar.mdx} +9 -1
  115. package/.docs/raw/docs/ui/select.mdx +247 -0
  116. package/.docs/raw/docs/ui/sources.mdx +89 -0
  117. package/.docs/raw/docs/ui/streamdown.mdx +348 -0
  118. package/.docs/raw/docs/ui/{SyntaxHighlighting.mdx → syntax-highlighting.mdx} +9 -5
  119. package/.docs/raw/docs/ui/tabs.mdx +261 -0
  120. package/.docs/raw/docs/ui/thread-list.mdx +275 -0
  121. package/.docs/raw/docs/ui/{Thread.mdx → thread.mdx} +61 -76
  122. package/.docs/raw/docs/ui/tool-fallback.mdx +112 -0
  123. package/.docs/raw/docs/ui/tool-group.mdx +214 -0
  124. package/README.md +3 -3
  125. package/dist/tools/docs.js +1 -1
  126. package/dist/tools/examples.js +1 -1
  127. package/dist/tools/examples.js.map +1 -1
  128. package/package.json +5 -5
  129. package/src/tools/docs.ts +1 -1
  130. package/src/tools/examples.ts +1 -1
  131. package/src/tools/tests/docs.test.ts +18 -16
  132. package/src/tools/tests/examples.test.ts +6 -6
  133. package/src/tools/tests/path-traversal.test.ts +3 -3
  134. package/src/utils/tests/security.test.ts +3 -3
  135. package/.docs/organized/code-examples/with-ai-sdk-v5.md +0 -1735
  136. package/.docs/raw/docs/about-assistantui.mdx +0 -53
  137. package/.docs/raw/docs/guides/Tools.mdx +0 -738
  138. package/.docs/raw/docs/index.mdx +0 -7
  139. package/.docs/raw/docs/mcp-docs-server.mdx +0 -322
  140. package/.docs/raw/docs/migrations/v0-12.mdx +0 -125
  141. package/.docs/raw/docs/ui/AssistantModal.mdx +0 -45
  142. package/.docs/raw/docs/ui/AssistantSidebar.mdx +0 -41
  143. package/.docs/raw/docs/ui/Attachment.mdx +0 -84
  144. package/.docs/raw/docs/ui/Reasoning.mdx +0 -152
  145. package/.docs/raw/docs/ui/ThreadList.mdx +0 -90
  146. package/.docs/raw/docs/ui/ToolFallback.mdx +0 -63
  147. package/.docs/raw/docs/ui/ToolGroup.mdx +0 -96
  148. /package/.docs/raw/docs/{copilots → (docs)/copilots}/assistant-frame.mdx +0 -0
@@ -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 {
@@ -414,1332 +416,12 @@ export default function Home() {
414
416
  "lib": "@/lib",
415
417
  "hooks": "@/hooks"
416
418
  },
417
- "iconLibrary": "lucide"
418
- }
419
-
420
- ```
421
-
422
- ## components/assistant-ui/attachment.tsx
423
-
424
- ```tsx
425
- "use client";
426
-
427
- import { PropsWithChildren, useEffect, useState, type FC } from "react";
428
- import Image from "next/image";
429
- import { XIcon, PlusIcon, FileText } from "lucide-react";
430
- import {
431
- AttachmentPrimitive,
432
- ComposerPrimitive,
433
- MessagePrimitive,
434
- useAssistantState,
435
- useAssistantApi,
436
- } from "@assistant-ui/react";
437
- import { useShallow } from "zustand/shallow";
438
- import {
439
- Tooltip,
440
- TooltipContent,
441
- TooltipTrigger,
442
- } from "@/components/ui/tooltip";
443
- import {
444
- Dialog,
445
- DialogTitle,
446
- DialogContent,
447
- DialogTrigger,
448
- } from "@/components/ui/dialog";
449
- import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
450
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
451
- import { cn } from "@/lib/utils";
452
-
453
- const useFileSrc = (file: File | undefined) => {
454
- const [src, setSrc] = useState<string | undefined>(undefined);
455
-
456
- useEffect(() => {
457
- if (!file) {
458
- setSrc(undefined);
459
- return;
460
- }
461
-
462
- const objectUrl = URL.createObjectURL(file);
463
- setSrc(objectUrl);
464
-
465
- return () => {
466
- URL.revokeObjectURL(objectUrl);
467
- };
468
- }, [file]);
469
-
470
- return src;
471
- };
472
-
473
- const useAttachmentSrc = () => {
474
- const { file, src } = useAssistantState(
475
- useShallow(({ attachment }): { file?: File; src?: string } => {
476
- if (attachment.type !== "image") return {};
477
- if (attachment.file) return { file: attachment.file };
478
- const src = attachment.content?.filter((c) => c.type === "image")[0]
479
- ?.image;
480
- if (!src) return {};
481
- return { src };
482
- }),
483
- );
484
-
485
- return useFileSrc(file) ?? src;
486
- };
487
-
488
- type AttachmentPreviewProps = {
489
- src: string;
490
- };
491
-
492
- const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
493
- const [isLoaded, setIsLoaded] = useState(false);
494
- return (
495
- <Image
496
- src={src}
497
- alt="Image Preview"
498
- width={1}
499
- height={1}
500
- className={
501
- isLoaded
502
- ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
503
- : "aui-attachment-preview-image-loading hidden"
504
- }
505
- onLoadingComplete={() => setIsLoaded(true)}
506
- priority={false}
507
- />
508
- );
509
- };
510
-
511
- const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
512
- const src = useAttachmentSrc();
513
-
514
- if (!src) return children;
515
-
516
- return (
517
- <Dialog>
518
- <DialogTrigger
519
- className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
520
- asChild
521
- >
522
- {children}
523
- </DialogTrigger>
524
- <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">
525
- <DialogTitle className="aui-sr-only sr-only">
526
- Image Attachment Preview
527
- </DialogTitle>
528
- <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
529
- <AttachmentPreview src={src} />
530
- </div>
531
- </DialogContent>
532
- </Dialog>
533
- );
534
- };
535
-
536
- const AttachmentThumb: FC = () => {
537
- const isImage = useAssistantState(
538
- ({ attachment }) => attachment.type === "image",
539
- );
540
- const src = useAttachmentSrc();
541
-
542
- return (
543
- <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
544
- <AvatarImage
545
- src={src}
546
- alt="Attachment preview"
547
- className="aui-attachment-tile-image object-cover"
548
- />
549
- <AvatarFallback delayMs={isImage ? 200 : 0}>
550
- <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
551
- </AvatarFallback>
552
- </Avatar>
553
- );
554
- };
555
-
556
- const AttachmentUI: FC = () => {
557
- const api = useAssistantApi();
558
- const isComposer = api.attachment.source === "composer";
559
-
560
- const isImage = useAssistantState(
561
- ({ attachment }) => attachment.type === "image",
562
- );
563
- const typeLabel = useAssistantState(({ attachment }) => {
564
- const type = attachment.type;
565
- switch (type) {
566
- case "image":
567
- return "Image";
568
- case "document":
569
- return "Document";
570
- case "file":
571
- return "File";
572
- default:
573
- const _exhaustiveCheck: never = type;
574
- throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
575
- }
576
- });
577
-
578
- return (
579
- <Tooltip>
580
- <AttachmentPrimitive.Root
581
- className={cn(
582
- "aui-attachment-root relative",
583
- isImage &&
584
- "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
585
- )}
586
- >
587
- <AttachmentPreviewDialog>
588
- <TooltipTrigger asChild>
589
- <div
590
- className={cn(
591
- "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
592
- isComposer &&
593
- "aui-attachment-tile-composer border-foreground/20",
594
- )}
595
- role="button"
596
- id="attachment-tile"
597
- aria-label={`${typeLabel} attachment`}
598
- >
599
- <AttachmentThumb />
600
- </div>
601
- </TooltipTrigger>
602
- </AttachmentPreviewDialog>
603
- {isComposer && <AttachmentRemove />}
604
- </AttachmentPrimitive.Root>
605
- <TooltipContent side="top">
606
- <AttachmentPrimitive.Name />
607
- </TooltipContent>
608
- </Tooltip>
609
- );
610
- };
611
-
612
- const AttachmentRemove: FC = () => {
613
- return (
614
- <AttachmentPrimitive.Remove asChild>
615
- <TooltipIconButton
616
- tooltip="Remove file"
617
- 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"
618
- side="top"
619
- >
620
- <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
621
- </TooltipIconButton>
622
- </AttachmentPrimitive.Remove>
623
- );
624
- };
625
-
626
- export const UserMessageAttachments: FC = () => {
627
- return (
628
- <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">
629
- <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
630
- </div>
631
- );
632
- };
633
-
634
- export const ComposerAttachments: FC = () => {
635
- return (
636
- <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">
637
- <ComposerPrimitive.Attachments
638
- components={{ Attachment: AttachmentUI }}
639
- />
640
- </div>
641
- );
642
- };
643
-
644
- export const ComposerAddAttachment: FC = () => {
645
- return (
646
- <ComposerPrimitive.AddAttachment asChild>
647
- <TooltipIconButton
648
- tooltip="Add Attachment"
649
- side="bottom"
650
- variant="ghost"
651
- size="icon"
652
- 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"
653
- aria-label="Add Attachment"
654
- >
655
- <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
656
- </TooltipIconButton>
657
- </ComposerPrimitive.AddAttachment>
658
- );
659
- };
660
-
661
- ```
662
-
663
- ## components/assistant-ui/markdown-text.tsx
664
-
665
- ```tsx
666
- "use client";
667
-
668
- import "@assistant-ui/react-markdown/styles/dot.css";
669
-
670
- import {
671
- type CodeHeaderProps,
672
- MarkdownTextPrimitive,
673
- unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
674
- useIsMarkdownCodeBlock,
675
- } from "@assistant-ui/react-markdown";
676
- import remarkGfm from "remark-gfm";
677
- import { type FC, memo, useState } from "react";
678
- import { CheckIcon, CopyIcon } from "lucide-react";
679
-
680
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
681
- import { cn } from "@/lib/utils";
682
-
683
- const MarkdownTextImpl = () => {
684
- return (
685
- <MarkdownTextPrimitive
686
- remarkPlugins={[remarkGfm]}
687
- className="aui-md"
688
- components={defaultComponents}
689
- />
690
- );
691
- };
692
-
693
- export const MarkdownText = memo(MarkdownTextImpl);
694
-
695
- const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
696
- const { isCopied, copyToClipboard } = useCopyToClipboard();
697
- const onCopy = () => {
698
- if (!code || isCopied) return;
699
- copyToClipboard(code);
700
- };
701
-
702
- return (
703
- <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">
704
- <span className="aui-code-header-language lowercase [&>span]:text-xs">
705
- {language}
706
- </span>
707
- <TooltipIconButton tooltip="Copy" onClick={onCopy}>
708
- {!isCopied && <CopyIcon />}
709
- {isCopied && <CheckIcon />}
710
- </TooltipIconButton>
711
- </div>
712
- );
713
- };
714
-
715
- const useCopyToClipboard = ({
716
- copiedDuration = 3000,
717
- }: {
718
- copiedDuration?: number;
719
- } = {}) => {
720
- const [isCopied, setIsCopied] = useState<boolean>(false);
721
-
722
- const copyToClipboard = (value: string) => {
723
- if (!value) return;
724
-
725
- navigator.clipboard.writeText(value).then(() => {
726
- setIsCopied(true);
727
- setTimeout(() => setIsCopied(false), copiedDuration);
728
- });
729
- };
730
-
731
- return { isCopied, copyToClipboard };
732
- };
733
-
734
- const defaultComponents = memoizeMarkdownComponents({
735
- h1: ({ className, ...props }) => (
736
- <h1
737
- className={cn(
738
- "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
739
- className,
740
- )}
741
- {...props}
742
- />
743
- ),
744
- h2: ({ className, ...props }) => (
745
- <h2
746
- className={cn(
747
- "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
748
- className,
749
- )}
750
- {...props}
751
- />
752
- ),
753
- h3: ({ className, ...props }) => (
754
- <h3
755
- className={cn(
756
- "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
757
- className,
758
- )}
759
- {...props}
760
- />
761
- ),
762
- h4: ({ className, ...props }) => (
763
- <h4
764
- className={cn(
765
- "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
766
- className,
767
- )}
768
- {...props}
769
- />
770
- ),
771
- h5: ({ className, ...props }) => (
772
- <h5
773
- className={cn(
774
- "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
775
- className,
776
- )}
777
- {...props}
778
- />
779
- ),
780
- h6: ({ className, ...props }) => (
781
- <h6
782
- className={cn(
783
- "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
784
- className,
785
- )}
786
- {...props}
787
- />
788
- ),
789
- p: ({ className, ...props }) => (
790
- <p
791
- className={cn(
792
- "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
793
- className,
794
- )}
795
- {...props}
796
- />
797
- ),
798
- a: ({ className, ...props }) => (
799
- <a
800
- className={cn(
801
- "aui-md-a font-medium text-primary underline underline-offset-4",
802
- className,
803
- )}
804
- {...props}
805
- />
806
- ),
807
- blockquote: ({ className, ...props }) => (
808
- <blockquote
809
- className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
810
- {...props}
811
- />
812
- ),
813
- ul: ({ className, ...props }) => (
814
- <ul
815
- className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
816
- {...props}
817
- />
818
- ),
819
- ol: ({ className, ...props }) => (
820
- <ol
821
- className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
822
- {...props}
823
- />
824
- ),
825
- hr: ({ className, ...props }) => (
826
- <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
827
- ),
828
- table: ({ className, ...props }) => (
829
- <table
830
- className={cn(
831
- "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
832
- className,
833
- )}
834
- {...props}
835
- />
836
- ),
837
- th: ({ className, ...props }) => (
838
- <th
839
- className={cn(
840
- "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",
841
- className,
842
- )}
843
- {...props}
844
- />
845
- ),
846
- td: ({ className, ...props }) => (
847
- <td
848
- className={cn(
849
- "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
850
- className,
851
- )}
852
- {...props}
853
- />
854
- ),
855
- tr: ({ className, ...props }) => (
856
- <tr
857
- className={cn(
858
- "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",
859
- className,
860
- )}
861
- {...props}
862
- />
863
- ),
864
- sup: ({ className, ...props }) => (
865
- <sup
866
- className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
867
- {...props}
868
- />
869
- ),
870
- pre: ({ className, ...props }) => (
871
- <pre
872
- className={cn(
873
- "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
874
- className,
875
- )}
876
- {...props}
877
- />
878
- ),
879
- code: function Code({ className, ...props }) {
880
- const isCodeBlock = useIsMarkdownCodeBlock();
881
- return (
882
- <code
883
- className={cn(
884
- !isCodeBlock &&
885
- "aui-md-inline-code rounded border bg-muted font-semibold",
886
- className,
887
- )}
888
- {...props}
889
- />
890
- );
891
- },
892
- CodeHeader,
893
- });
894
-
895
- ```
896
-
897
- ## components/assistant-ui/thread.tsx
898
-
899
- ```tsx
900
- import {
901
- ComposerAddAttachment,
902
- ComposerAttachments,
903
- UserMessageAttachments,
904
- } from "@/components/assistant-ui/attachment";
905
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
906
- import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
907
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
908
- import { Button } from "@/components/ui/button";
909
- import { cn } from "@/lib/utils";
910
- import {
911
- ActionBarPrimitive,
912
- AssistantIf,
913
- BranchPickerPrimitive,
914
- ComposerPrimitive,
915
- ErrorPrimitive,
916
- MessagePrimitive,
917
- ThreadPrimitive,
918
- } from "@assistant-ui/react";
919
- import {
920
- ArrowDownIcon,
921
- ArrowUpIcon,
922
- CheckIcon,
923
- ChevronLeftIcon,
924
- ChevronRightIcon,
925
- CopyIcon,
926
- DownloadIcon,
927
- PencilIcon,
928
- RefreshCwIcon,
929
- SquareIcon,
930
- } from "lucide-react";
931
- import type { FC } from "react";
932
-
933
- export const Thread: FC = () => {
934
- return (
935
- <ThreadPrimitive.Root
936
- className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
937
- style={{
938
- ["--thread-max-width" as string]: "44rem",
939
- }}
940
- >
941
- <ThreadPrimitive.Viewport
942
- turnAnchor="top"
943
- className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
944
- >
945
- <AssistantIf condition={({ thread }) => thread.isEmpty}>
946
- <ThreadWelcome />
947
- </AssistantIf>
948
-
949
- <ThreadPrimitive.Messages
950
- components={{
951
- UserMessage,
952
- EditComposer,
953
- AssistantMessage,
954
- }}
955
- />
956
-
957
- <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">
958
- <ThreadScrollToBottom />
959
- <Composer />
960
- </ThreadPrimitive.ViewportFooter>
961
- </ThreadPrimitive.Viewport>
962
- </ThreadPrimitive.Root>
963
- );
964
- };
965
-
966
- const ThreadScrollToBottom: FC = () => {
967
- return (
968
- <ThreadPrimitive.ScrollToBottom asChild>
969
- <TooltipIconButton
970
- tooltip="Scroll to bottom"
971
- variant="outline"
972
- 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"
973
- >
974
- <ArrowDownIcon />
975
- </TooltipIconButton>
976
- </ThreadPrimitive.ScrollToBottom>
977
- );
978
- };
979
-
980
- const ThreadWelcome: FC = () => {
981
- return (
982
- <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
983
- <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
984
- <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
985
- <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
986
- Hello there!
987
- </h1>
988
- <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">
989
- How can I help you today?
990
- </p>
991
- </div>
992
- </div>
993
- <ThreadSuggestions />
994
- </div>
995
- );
996
- };
997
-
998
- const SUGGESTIONS = [
999
- {
1000
- title: "What's the weather",
1001
- label: "in San Francisco?",
1002
- prompt: "What's the weather in San Francisco?",
1003
- },
1004
- {
1005
- title: "Explain React hooks",
1006
- label: "like useState and useEffect",
1007
- prompt: "Explain React hooks like useState and useEffect",
1008
- },
1009
- ] as const;
1010
-
1011
- const ThreadSuggestions: FC = () => {
1012
- return (
1013
- <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
1014
- {SUGGESTIONS.map((suggestion, index) => (
1015
- <div
1016
- key={suggestion.prompt}
1017
- 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"
1018
- style={{ animationDelay: `${100 + index * 50}ms` }}
1019
- >
1020
- <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
1021
- <Button
1022
- variant="ghost"
1023
- 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"
1024
- aria-label={suggestion.prompt}
1025
- >
1026
- <span className="aui-thread-welcome-suggestion-text-1 font-medium">
1027
- {suggestion.title}
1028
- </span>
1029
- <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
1030
- {suggestion.label}
1031
- </span>
1032
- </Button>
1033
- </ThreadPrimitive.Suggestion>
1034
- </div>
1035
- ))}
1036
- </div>
1037
- );
1038
- };
1039
-
1040
- const Composer: FC = () => {
1041
- return (
1042
- <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
1043
- <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">
1044
- <ComposerAttachments />
1045
- <ComposerPrimitive.Input
1046
- placeholder="Send a message..."
1047
- 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"
1048
- rows={1}
1049
- autoFocus
1050
- aria-label="Message input"
1051
- />
1052
- <ComposerAction />
1053
- </ComposerPrimitive.AttachmentDropzone>
1054
- </ComposerPrimitive.Root>
1055
- );
1056
- };
1057
-
1058
- const ComposerAction: FC = () => {
1059
- return (
1060
- <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
1061
- <ComposerAddAttachment />
1062
-
1063
- <AssistantIf condition={({ thread }) => !thread.isRunning}>
1064
- <ComposerPrimitive.Send asChild>
1065
- <TooltipIconButton
1066
- tooltip="Send message"
1067
- side="bottom"
1068
- type="submit"
1069
- variant="default"
1070
- size="icon"
1071
- className="aui-composer-send size-8 rounded-full"
1072
- aria-label="Send message"
1073
- >
1074
- <ArrowUpIcon className="aui-composer-send-icon size-4" />
1075
- </TooltipIconButton>
1076
- </ComposerPrimitive.Send>
1077
- </AssistantIf>
1078
-
1079
- <AssistantIf condition={({ thread }) => thread.isRunning}>
1080
- <ComposerPrimitive.Cancel asChild>
1081
- <Button
1082
- type="button"
1083
- variant="default"
1084
- size="icon"
1085
- className="aui-composer-cancel size-8 rounded-full"
1086
- aria-label="Stop generating"
1087
- >
1088
- <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1089
- </Button>
1090
- </ComposerPrimitive.Cancel>
1091
- </AssistantIf>
1092
- </div>
1093
- );
1094
- };
1095
-
1096
- const MessageError: FC = () => {
1097
- return (
1098
- <MessagePrimitive.Error>
1099
- <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">
1100
- <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
1101
- </ErrorPrimitive.Root>
1102
- </MessagePrimitive.Error>
1103
- );
1104
- };
1105
-
1106
- const AssistantMessage: FC = () => {
1107
- return (
1108
- <MessagePrimitive.Root
1109
- 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"
1110
- data-role="assistant"
1111
- >
1112
- <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
1113
- <MessagePrimitive.Parts
1114
- components={{
1115
- Text: MarkdownText,
1116
- tools: { Fallback: ToolFallback },
1117
- }}
1118
- />
1119
- <MessageError />
1120
- </div>
1121
-
1122
- <div className="aui-assistant-message-footer mt-1 ml-2 flex">
1123
- <BranchPicker />
1124
- <AssistantActionBar />
1125
- </div>
1126
- </MessagePrimitive.Root>
1127
- );
1128
- };
1129
-
1130
- const AssistantActionBar: FC = () => {
1131
- return (
1132
- <ActionBarPrimitive.Root
1133
- hideWhenRunning
1134
- autohide="not-last"
1135
- autohideFloat="single-branch"
1136
- 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"
1137
- >
1138
- <ActionBarPrimitive.Copy asChild>
1139
- <TooltipIconButton tooltip="Copy">
1140
- <AssistantIf condition={({ message }) => message.isCopied}>
1141
- <CheckIcon />
1142
- </AssistantIf>
1143
- <AssistantIf condition={({ message }) => !message.isCopied}>
1144
- <CopyIcon />
1145
- </AssistantIf>
1146
- </TooltipIconButton>
1147
- </ActionBarPrimitive.Copy>
1148
- <ActionBarPrimitive.ExportMarkdown asChild>
1149
- <TooltipIconButton tooltip="Export as Markdown">
1150
- <DownloadIcon />
1151
- </TooltipIconButton>
1152
- </ActionBarPrimitive.ExportMarkdown>
1153
- <ActionBarPrimitive.Reload asChild>
1154
- <TooltipIconButton tooltip="Refresh">
1155
- <RefreshCwIcon />
1156
- </TooltipIconButton>
1157
- </ActionBarPrimitive.Reload>
1158
- </ActionBarPrimitive.Root>
1159
- );
1160
- };
1161
-
1162
- const UserMessage: FC = () => {
1163
- return (
1164
- <MessagePrimitive.Root
1165
- 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"
1166
- data-role="user"
1167
- >
1168
- <UserMessageAttachments />
1169
-
1170
- <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1171
- <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1172
- <MessagePrimitive.Parts />
1173
- </div>
1174
- <div className="aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2">
1175
- <UserActionBar />
1176
- </div>
1177
- </div>
1178
-
1179
- <BranchPicker className="aui-user-branch-picker col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
1180
- </MessagePrimitive.Root>
1181
- );
1182
- };
1183
-
1184
- const UserActionBar: FC = () => {
1185
- return (
1186
- <ActionBarPrimitive.Root
1187
- hideWhenRunning
1188
- autohide="not-last"
1189
- className="aui-user-action-bar-root flex flex-col items-end"
1190
- >
1191
- <ActionBarPrimitive.Edit asChild>
1192
- <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1193
- <PencilIcon />
1194
- </TooltipIconButton>
1195
- </ActionBarPrimitive.Edit>
1196
- </ActionBarPrimitive.Root>
1197
- );
1198
- };
1199
-
1200
- const EditComposer: FC = () => {
1201
- return (
1202
- <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1203
- <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1204
- <ComposerPrimitive.Input
1205
- className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1206
- autoFocus
1207
- />
1208
- <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1209
- <ComposerPrimitive.Cancel asChild>
1210
- <Button variant="ghost" size="sm">
1211
- Cancel
1212
- </Button>
1213
- </ComposerPrimitive.Cancel>
1214
- <ComposerPrimitive.Send asChild>
1215
- <Button size="sm">Update</Button>
1216
- </ComposerPrimitive.Send>
1217
- </div>
1218
- </ComposerPrimitive.Root>
1219
- </MessagePrimitive.Root>
1220
- );
1221
- };
1222
-
1223
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1224
- className,
1225
- ...rest
1226
- }) => {
1227
- return (
1228
- <BranchPickerPrimitive.Root
1229
- hideWhenSingleBranch
1230
- className={cn(
1231
- "aui-branch-picker-root mr-2 -ml-2 inline-flex items-center text-muted-foreground text-xs",
1232
- className,
1233
- )}
1234
- {...rest}
1235
- >
1236
- <BranchPickerPrimitive.Previous asChild>
1237
- <TooltipIconButton tooltip="Previous">
1238
- <ChevronLeftIcon />
1239
- </TooltipIconButton>
1240
- </BranchPickerPrimitive.Previous>
1241
- <span className="aui-branch-picker-state font-medium">
1242
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
1243
- </span>
1244
- <BranchPickerPrimitive.Next asChild>
1245
- <TooltipIconButton tooltip="Next">
1246
- <ChevronRightIcon />
1247
- </TooltipIconButton>
1248
- </BranchPickerPrimitive.Next>
1249
- </BranchPickerPrimitive.Root>
1250
- );
1251
- };
1252
-
1253
- ```
1254
-
1255
- ## components/assistant-ui/tool-fallback.tsx
1256
-
1257
- ```tsx
1258
- import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1259
- import {
1260
- CheckIcon,
1261
- ChevronDownIcon,
1262
- ChevronUpIcon,
1263
- XCircleIcon,
1264
- } from "lucide-react";
1265
- import { useState } from "react";
1266
- import { Button } from "@/components/ui/button";
1267
- import { cn } from "@/lib/utils";
1268
-
1269
- export const ToolFallback: ToolCallMessagePartComponent = ({
1270
- toolName,
1271
- argsText,
1272
- result,
1273
- status,
1274
- }) => {
1275
- const [isCollapsed, setIsCollapsed] = useState(true);
1276
-
1277
- const isCancelled =
1278
- status?.type === "incomplete" && status.reason === "cancelled";
1279
- const cancelledReason =
1280
- isCancelled && status.error
1281
- ? typeof status.error === "string"
1282
- ? status.error
1283
- : JSON.stringify(status.error)
1284
- : null;
1285
-
1286
- return (
1287
- <div
1288
- className={cn(
1289
- "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1290
- isCancelled && "border-muted-foreground/30 bg-muted/30",
1291
- )}
1292
- >
1293
- <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1294
- {isCancelled ? (
1295
- <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1296
- ) : (
1297
- <CheckIcon className="aui-tool-fallback-icon size-4" />
1298
- )}
1299
- <p
1300
- className={cn(
1301
- "aui-tool-fallback-title grow",
1302
- isCancelled && "text-muted-foreground line-through",
1303
- )}
1304
- >
1305
- {isCancelled ? "Cancelled tool: " : "Used tool: "}
1306
- <b>{toolName}</b>
1307
- </p>
1308
- <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1309
- {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1310
- </Button>
1311
- </div>
1312
- {!isCollapsed && (
1313
- <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1314
- {cancelledReason && (
1315
- <div className="aui-tool-fallback-cancelled-root px-4">
1316
- <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1317
- Cancelled reason:
1318
- </p>
1319
- <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1320
- {cancelledReason}
1321
- </p>
1322
- </div>
1323
- )}
1324
- <div
1325
- className={cn(
1326
- "aui-tool-fallback-args-root px-4",
1327
- isCancelled && "opacity-60",
1328
- )}
1329
- >
1330
- <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1331
- {argsText}
1332
- </pre>
1333
- </div>
1334
- {!isCancelled && result !== undefined && (
1335
- <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1336
- <p className="aui-tool-fallback-result-header font-semibold">
1337
- Result:
1338
- </p>
1339
- <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1340
- {typeof result === "string"
1341
- ? result
1342
- : JSON.stringify(result, null, 2)}
1343
- </pre>
1344
- </div>
1345
- )}
1346
- </div>
1347
- )}
1348
- </div>
1349
- );
1350
- };
1351
-
1352
- ```
1353
-
1354
- ## components/assistant-ui/tooltip-icon-button.tsx
1355
-
1356
- ```tsx
1357
- "use client";
1358
-
1359
- import { ComponentPropsWithRef, forwardRef } from "react";
1360
- import { Slottable } from "@radix-ui/react-slot";
1361
-
1362
- import {
1363
- Tooltip,
1364
- TooltipContent,
1365
- TooltipTrigger,
1366
- } from "@/components/ui/tooltip";
1367
- import { Button } from "@/components/ui/button";
1368
- import { cn } from "@/lib/utils";
1369
-
1370
- export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
1371
- tooltip: string;
1372
- side?: "top" | "bottom" | "left" | "right";
1373
- };
1374
-
1375
- export const TooltipIconButton = forwardRef<
1376
- HTMLButtonElement,
1377
- TooltipIconButtonProps
1378
- >(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
1379
- return (
1380
- <Tooltip>
1381
- <TooltipTrigger asChild>
1382
- <Button
1383
- variant="ghost"
1384
- size="icon"
1385
- {...rest}
1386
- className={cn("aui-button-icon size-6 p-1", className)}
1387
- ref={ref}
1388
- >
1389
- <Slottable>{children}</Slottable>
1390
- <span className="aui-sr-only sr-only">{tooltip}</span>
1391
- </Button>
1392
- </TooltipTrigger>
1393
- <TooltipContent side={side}>{tooltip}</TooltipContent>
1394
- </Tooltip>
1395
- );
1396
- });
1397
-
1398
- TooltipIconButton.displayName = "TooltipIconButton";
1399
-
1400
- ```
1401
-
1402
- ## components/ui/avatar.tsx
1403
-
1404
- ```tsx
1405
- "use client";
1406
-
1407
- import * as React from "react";
1408
- import * as AvatarPrimitive from "@radix-ui/react-avatar";
1409
-
1410
- import { cn } from "@/lib/utils";
1411
-
1412
- function Avatar({
1413
- className,
1414
- ...props
1415
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
1416
- return (
1417
- <AvatarPrimitive.Root
1418
- data-slot="avatar"
1419
- className={cn(
1420
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
1421
- className,
1422
- )}
1423
- {...props}
1424
- />
1425
- );
1426
- }
1427
-
1428
- function AvatarImage({
1429
- className,
1430
- ...props
1431
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
1432
- return (
1433
- <AvatarPrimitive.Image
1434
- data-slot="avatar-image"
1435
- className={cn("aspect-square size-full", className)}
1436
- {...props}
1437
- />
1438
- );
1439
- }
1440
-
1441
- function AvatarFallback({
1442
- className,
1443
- ...props
1444
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
1445
- return (
1446
- <AvatarPrimitive.Fallback
1447
- data-slot="avatar-fallback"
1448
- className={cn(
1449
- "flex size-full items-center justify-center rounded-full bg-muted",
1450
- className,
1451
- )}
1452
- {...props}
1453
- />
1454
- );
1455
- }
1456
-
1457
- export { Avatar, AvatarImage, AvatarFallback };
1458
-
1459
- ```
1460
-
1461
- ## components/ui/button.tsx
1462
-
1463
- ```tsx
1464
- import * as React from "react";
1465
- import { Slot } from "@radix-ui/react-slot";
1466
- import { cva, type VariantProps } from "class-variance-authority";
1467
-
1468
- import { cn } from "@/lib/utils";
1469
-
1470
- const buttonVariants = cva(
1471
- "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",
1472
- {
1473
- variants: {
1474
- variant: {
1475
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
1476
- destructive:
1477
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1478
- outline:
1479
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1480
- secondary:
1481
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1482
- ghost:
1483
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1484
- link: "text-primary underline-offset-4 hover:underline",
1485
- },
1486
- size: {
1487
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
1488
- sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1489
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1490
- icon: "size-9",
1491
- "icon-sm": "size-8",
1492
- "icon-lg": "size-10",
1493
- },
1494
- },
1495
- defaultVariants: {
1496
- variant: "default",
1497
- size: "default",
1498
- },
1499
- },
1500
- );
1501
-
1502
- function Button({
1503
- className,
1504
- variant = "default",
1505
- size = "default",
1506
- asChild = false,
1507
- ...props
1508
- }: React.ComponentProps<"button"> &
1509
- VariantProps<typeof buttonVariants> & {
1510
- asChild?: boolean;
1511
- }) {
1512
- const Comp = asChild ? Slot : "button";
1513
-
1514
- return (
1515
- <Comp
1516
- data-slot="button"
1517
- data-variant={variant}
1518
- data-size={size}
1519
- className={cn(buttonVariants({ variant, size, className }))}
1520
- {...props}
1521
- />
1522
- );
1523
- }
1524
-
1525
- export { Button, buttonVariants };
1526
-
1527
- ```
1528
-
1529
- ## components/ui/dialog.tsx
1530
-
1531
- ```tsx
1532
- "use client";
1533
-
1534
- import * as React from "react";
1535
- import * as DialogPrimitive from "@radix-ui/react-dialog";
1536
- import { XIcon } from "lucide-react";
1537
-
1538
- import { cn } from "@/lib/utils";
1539
-
1540
- function Dialog({
1541
- ...props
1542
- }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1543
- return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1544
- }
1545
-
1546
- function DialogTrigger({
1547
- ...props
1548
- }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1549
- return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1550
- }
1551
-
1552
- function DialogPortal({
1553
- ...props
1554
- }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1555
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1556
- }
1557
-
1558
- function DialogClose({
1559
- ...props
1560
- }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1561
- return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1562
- }
1563
-
1564
- function DialogOverlay({
1565
- className,
1566
- ...props
1567
- }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1568
- return (
1569
- <DialogPrimitive.Overlay
1570
- data-slot="dialog-overlay"
1571
- className={cn(
1572
- "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",
1573
- className,
1574
- )}
1575
- {...props}
1576
- />
1577
- );
1578
- }
1579
-
1580
- function DialogContent({
1581
- className,
1582
- children,
1583
- showCloseButton = true,
1584
- ...props
1585
- }: React.ComponentProps<typeof DialogPrimitive.Content> & {
1586
- showCloseButton?: boolean;
1587
- }) {
1588
- return (
1589
- <DialogPortal data-slot="dialog-portal">
1590
- <DialogOverlay />
1591
- <DialogPrimitive.Content
1592
- data-slot="dialog-content"
1593
- className={cn(
1594
- "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",
1595
- className,
1596
- )}
1597
- {...props}
1598
- >
1599
- {children}
1600
- {showCloseButton && (
1601
- <DialogPrimitive.Close
1602
- data-slot="dialog-close"
1603
- 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"
1604
- >
1605
- <XIcon />
1606
- <span className="sr-only">Close</span>
1607
- </DialogPrimitive.Close>
1608
- )}
1609
- </DialogPrimitive.Content>
1610
- </DialogPortal>
1611
- );
1612
- }
1613
-
1614
- function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1615
- return (
1616
- <div
1617
- data-slot="dialog-header"
1618
- className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1619
- {...props}
1620
- />
1621
- );
1622
- }
1623
-
1624
- function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1625
- return (
1626
- <div
1627
- data-slot="dialog-footer"
1628
- className={cn(
1629
- "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1630
- className,
1631
- )}
1632
- {...props}
1633
- />
1634
- );
1635
- }
1636
-
1637
- function DialogTitle({
1638
- className,
1639
- ...props
1640
- }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1641
- return (
1642
- <DialogPrimitive.Title
1643
- data-slot="dialog-title"
1644
- className={cn("font-semibold text-lg leading-none", className)}
1645
- {...props}
1646
- />
1647
- );
1648
- }
1649
-
1650
- function DialogDescription({
1651
- className,
1652
- ...props
1653
- }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1654
- return (
1655
- <DialogPrimitive.Description
1656
- data-slot="dialog-description"
1657
- className={cn("text-muted-foreground text-sm", className)}
1658
- {...props}
1659
- />
1660
- );
1661
- }
1662
-
1663
- export {
1664
- Dialog,
1665
- DialogClose,
1666
- DialogContent,
1667
- DialogDescription,
1668
- DialogFooter,
1669
- DialogHeader,
1670
- DialogOverlay,
1671
- DialogPortal,
1672
- DialogTitle,
1673
- DialogTrigger,
1674
- };
1675
-
1676
- ```
1677
-
1678
- ## components/ui/tooltip.tsx
1679
-
1680
- ```tsx
1681
- "use client";
1682
-
1683
- import * as React from "react";
1684
- import * as TooltipPrimitive from "@radix-ui/react-tooltip";
1685
-
1686
- import { cn } from "@/lib/utils";
1687
-
1688
- function TooltipProvider({
1689
- delayDuration = 0,
1690
- ...props
1691
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
1692
- return (
1693
- <TooltipPrimitive.Provider
1694
- data-slot="tooltip-provider"
1695
- delayDuration={delayDuration}
1696
- {...props}
1697
- />
1698
- );
1699
- }
1700
-
1701
- function Tooltip({
1702
- ...props
1703
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
1704
- return (
1705
- <TooltipProvider>
1706
- <TooltipPrimitive.Root data-slot="tooltip" {...props} />
1707
- </TooltipProvider>
1708
- );
1709
- }
1710
-
1711
- function TooltipTrigger({
1712
- ...props
1713
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
1714
- return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
1715
- }
1716
-
1717
- function TooltipContent({
1718
- className,
1719
- sideOffset = 0,
1720
- children,
1721
- ...props
1722
- }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
1723
- return (
1724
- <TooltipPrimitive.Portal>
1725
- <TooltipPrimitive.Content
1726
- data-slot="tooltip-content"
1727
- sideOffset={sideOffset}
1728
- className={cn(
1729
- "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",
1730
- className,
1731
- )}
1732
- {...props}
1733
- >
1734
- {children}
1735
- <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
1736
- </TooltipPrimitive.Content>
1737
- </TooltipPrimitive.Portal>
1738
- );
419
+ "iconLibrary": "lucide",
420
+ "registries": {
421
+ "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
422
+ }
1739
423
  }
1740
424
 
1741
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
1742
-
1743
425
  ```
1744
426
 
1745
427
  ## lib/utils.ts
@@ -1781,27 +463,28 @@ export default nextConfig;
1781
463
  "start": "next start"
1782
464
  },
1783
465
  "dependencies": {
466
+ "@ai-sdk/openai": "^3.0.19",
1784
467
  "@assistant-ui/react": "workspace:*",
1785
468
  "@assistant-ui/react-markdown": "workspace:*",
469
+ "@assistant-ui/ui": "workspace:*",
1786
470
  "@radix-ui/react-avatar": "^1.1.11",
471
+ "@radix-ui/react-collapsible": "^1.1.12",
1787
472
  "@radix-ui/react-dialog": "^1.1.15",
1788
473
  "@radix-ui/react-slot": "^1.2.4",
1789
474
  "@radix-ui/react-tooltip": "^1.2.8",
1790
475
  "class-variance-authority": "^0.7.1",
1791
476
  "clsx": "^2.1.1",
1792
- "lucide-react": "^0.562.0",
1793
- "next": "16.1.0",
1794
- "react": "19.2.3",
1795
- "react-dom": "19.2.3",
1796
- "remark-gfm": "^4.0.1",
1797
- "tailwind-merge": "^3.4.0",
1798
- "zustand": "^5.0.9"
477
+ "lucide-react": "^0.563.0",
478
+ "next": "^16.1.5",
479
+ "react": "^19.2.4",
480
+ "react-dom": "^19.2.4",
481
+ "tailwind-merge": "^3.4.0"
1799
482
  },
1800
483
  "devDependencies": {
1801
484
  "@assistant-ui/x-buildutils": "workspace:*",
1802
485
  "@tailwindcss/postcss": "^4.1.18",
1803
- "@types/node": "^25.0.3",
1804
- "@types/react": "^19.2.7",
486
+ "@types/node": "^25.0.10",
487
+ "@types/react": "^19.2.9",
1805
488
  "@types/react-dom": "^19.2.3",
1806
489
  "postcss": "^8.5.6",
1807
490
  "tailwindcss": "^4.1.18",
@@ -1819,15 +502,37 @@ export default nextConfig;
1819
502
 
1820
503
  This example demonstrates how to use the parent ID feature in assistant-ui to group related message parts together.
1821
504
 
505
+ ## Quick Start
506
+
507
+ ### Using CLI (Recommended)
508
+
509
+ ```bash
510
+ npx assistant-ui@latest create my-app --example with-parent-id-grouping
511
+ cd my-app
512
+ ```
513
+
514
+ ### Environment Variables
515
+
516
+ Create `.env.local`:
517
+
518
+ ```
519
+ OPENAI_API_KEY=sk-...
520
+ ```
521
+
522
+ ### Run
523
+
524
+ ```bash
525
+ npm install
526
+ npm run dev
527
+ ```
528
+
529
+ Open [http://localhost:3000](http://localhost:3000) to see the example.
530
+
1822
531
  ## Features
1823
532
 
1824
533
  - **Parent ID Support**: Message parts can have a `parentId` field that groups them together
1825
534
  - **Visual Grouping**: Related parts are displayed in collapsible groups
1826
- - **Custom Group Component**: The example shows how to create a custom Group component that:
1827
- - Shows grouped parts in a bordered container
1828
- - Provides expand/collapse functionality
1829
- - Displays meaningful labels for each group
1830
- - Leaves ungrouped parts (without parentId) as-is
535
+ - **Custom Group Component**: Shows grouped parts in a bordered container with expand/collapse functionality
1831
536
 
1832
537
  ## How it works
1833
538
 
@@ -1841,32 +546,9 @@ This example demonstrates how to use the parent ID feature in assistant-ui to gr
1841
546
  }
1842
547
  ```
1843
548
 
1844
- 2. **Grouping Component**: Uses `MessagePrimitive.Unstable_PartsGroupedByParentId` which automatically:
1845
- - Groups parts by their `parentId`
1846
- - Maintains order based on first occurrence
1847
- - Places ungrouped parts after grouped ones
1848
-
1849
- 3. **Custom Rendering**: The `ParentIdGroup` component provides:
1850
- - Collapsible sections for each group
1851
- - Custom styling with borders and backgrounds
1852
- - Meaningful labels based on the parent ID
549
+ 2. **Grouping Component**: Uses `MessagePrimitive.Unstable_PartsGroupedByParentId` which automatically groups parts by their `parentId`
1853
550
 
1854
- ## Running the Example
1855
-
1856
- ```bash
1857
- # Install dependencies
1858
- npm install
1859
-
1860
- # Run the development server
1861
- npm run dev
1862
- ```
1863
-
1864
- Open [http://localhost:3000](http://localhost:3000) to see the example.
1865
-
1866
- ## Key Components
1867
-
1868
- - `MyRuntimeProvider.tsx`: Sets up the external store with dummy messages containing parent IDs
1869
- - `thread.tsx`: Contains the custom `ParentIdGroup` component and uses `Unstable_PartsGroupedByParentId`
551
+ 3. **Custom Rendering**: The `ParentIdGroup` component provides collapsible sections for each group
1870
552
 
1871
553
  ## Use Cases
1872
554
 
@@ -1877,6 +559,11 @@ This pattern is useful for:
1877
559
  - Creating hierarchical content structures
1878
560
  - Showing related content in collapsible sections
1879
561
 
562
+ ## Related Documentation
563
+
564
+ - [assistant-ui Documentation](https://www.assistant-ui.com/docs)
565
+ - [Message Structure Guide](https://www.assistant-ui.com/docs/concepts/messages)
566
+
1880
567
  ```
1881
568
 
1882
569
  ## tsconfig.json
@@ -1885,7 +572,14 @@ This pattern is useful for:
1885
572
  {
1886
573
  "extends": "@assistant-ui/x-buildutils/ts/next",
1887
574
  "compilerOptions": {
1888
- "paths": { "@/*": ["./*"] }
575
+ "paths": {
576
+ "@/*": ["./*"],
577
+ "@/components/assistant-ui/*": [
578
+ "../../packages/ui/src/components/assistant-ui/*"
579
+ ],
580
+ "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
581
+ "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
582
+ }
1889
583
  },
1890
584
  "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1891
585
  "exclude": ["node_modules"]