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

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