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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.docs/organized/code-examples/store-example.md +628 -0
  2. package/.docs/organized/code-examples/with-ag-ui.md +792 -178
  3. package/.docs/organized/code-examples/with-ai-sdk-v5.md +762 -209
  4. package/.docs/organized/code-examples/with-assistant-transport.md +707 -254
  5. package/.docs/organized/code-examples/with-cloud.md +848 -202
  6. package/.docs/organized/code-examples/with-custom-thread-list.md +1855 -0
  7. package/.docs/organized/code-examples/with-external-store.md +788 -172
  8. package/.docs/organized/code-examples/with-ffmpeg.md +796 -196
  9. package/.docs/organized/code-examples/with-langgraph.md +864 -230
  10. package/.docs/organized/code-examples/with-parent-id-grouping.md +785 -255
  11. package/.docs/organized/code-examples/with-react-hook-form.md +804 -226
  12. package/.docs/organized/code-examples/with-tanstack.md +1574 -0
  13. package/.docs/raw/blog/2024-07-29-hello/index.mdx +2 -3
  14. package/.docs/raw/docs/api-reference/overview.mdx +6 -6
  15. package/.docs/raw/docs/api-reference/primitives/ActionBar.mdx +85 -4
  16. package/.docs/raw/docs/api-reference/primitives/AssistantIf.mdx +200 -0
  17. package/.docs/raw/docs/api-reference/primitives/Composer.mdx +0 -20
  18. package/.docs/raw/docs/api-reference/primitives/Message.mdx +0 -45
  19. package/.docs/raw/docs/api-reference/primitives/Thread.mdx +0 -50
  20. package/.docs/raw/docs/cli.mdx +396 -0
  21. package/.docs/raw/docs/cloud/persistence/ai-sdk.mdx +2 -3
  22. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +2 -3
  23. package/.docs/raw/docs/devtools.mdx +2 -3
  24. package/.docs/raw/docs/getting-started.mdx +37 -1109
  25. package/.docs/raw/docs/guides/Attachments.mdx +3 -25
  26. package/.docs/raw/docs/guides/Branching.mdx +1 -1
  27. package/.docs/raw/docs/guides/Speech.mdx +1 -1
  28. package/.docs/raw/docs/guides/ToolUI.mdx +1 -1
  29. package/.docs/raw/docs/legacy/styled/AssistantModal.mdx +2 -3
  30. package/.docs/raw/docs/legacy/styled/Decomposition.mdx +6 -5
  31. package/.docs/raw/docs/legacy/styled/Markdown.mdx +2 -3
  32. package/.docs/raw/docs/legacy/styled/Thread.mdx +2 -3
  33. package/.docs/raw/docs/react-compatibility.mdx +2 -5
  34. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +3 -4
  35. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +3 -6
  36. package/.docs/raw/docs/runtimes/assistant-transport.mdx +891 -0
  37. package/.docs/raw/docs/runtimes/custom/external-store.mdx +2 -3
  38. package/.docs/raw/docs/runtimes/custom/local.mdx +11 -41
  39. package/.docs/raw/docs/runtimes/data-stream.mdx +15 -11
  40. package/.docs/raw/docs/runtimes/langgraph/index.mdx +4 -4
  41. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +1 -1
  42. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +2 -3
  43. package/.docs/raw/docs/runtimes/langserve.mdx +2 -3
  44. package/.docs/raw/docs/runtimes/mastra/full-stack-integration.mdx +2 -3
  45. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +2 -3
  46. package/.docs/raw/docs/ui/AssistantModal.mdx +3 -25
  47. package/.docs/raw/docs/ui/AssistantSidebar.mdx +2 -24
  48. package/.docs/raw/docs/ui/Attachment.mdx +3 -25
  49. package/.docs/raw/docs/ui/Markdown.mdx +2 -24
  50. package/.docs/raw/docs/ui/Mermaid.mdx +2 -24
  51. package/.docs/raw/docs/ui/Reasoning.mdx +2 -24
  52. package/.docs/raw/docs/ui/Scrollbar.mdx +4 -6
  53. package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +3 -47
  54. package/.docs/raw/docs/ui/Thread.mdx +38 -53
  55. package/.docs/raw/docs/ui/ThreadList.mdx +4 -47
  56. package/.docs/raw/docs/ui/ToolFallback.mdx +2 -24
  57. package/package.json +15 -8
@@ -231,15 +231,15 @@ const BrowserAlertTool = () => {
231
231
  },
232
232
  render: ({ args, result }) => (
233
233
  <div className="mt-3 w-full max-w-[var(--thread-max-width)] rounded-lg border px-4 py-3 text-sm">
234
- <p className="text-muted-foreground font-semibold">browser_alert</p>
234
+ <p className="font-semibold text-muted-foreground">browser_alert</p>
235
235
  <p className="mt-1">
236
236
  Requested alert with message:
237
- <span className="text-foreground ml-1 font-mono">
237
+ <span className="ml-1 font-mono text-foreground">
238
238
  {JSON.stringify(args.message)}
239
239
  </span>
240
240
  </p>
241
241
  {result?.status === "shown" && (
242
- <p className="text-foreground/70 mt-2 text-xs">
242
+ <p className="mt-2 text-foreground/70 text-xs">
243
243
  Alert displayed in this tab.
244
244
  </p>
245
245
  )}
@@ -288,6 +288,247 @@ export default function Home() {
288
288
 
289
289
  ```
290
290
 
291
+ ## components/assistant-ui/attachment.tsx
292
+
293
+ ```tsx
294
+ "use client";
295
+
296
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
297
+ import Image from "next/image";
298
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
299
+ import {
300
+ AttachmentPrimitive,
301
+ ComposerPrimitive,
302
+ MessagePrimitive,
303
+ useAssistantState,
304
+ useAssistantApi,
305
+ } from "@assistant-ui/react";
306
+ import { useShallow } from "zustand/shallow";
307
+ import {
308
+ Tooltip,
309
+ TooltipContent,
310
+ TooltipTrigger,
311
+ } from "@/components/ui/tooltip";
312
+ import {
313
+ Dialog,
314
+ DialogTitle,
315
+ DialogContent,
316
+ DialogTrigger,
317
+ } from "@/components/ui/dialog";
318
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
319
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
320
+ import { cn } from "@/lib/utils";
321
+
322
+ const useFileSrc = (file: File | undefined) => {
323
+ const [src, setSrc] = useState<string | undefined>(undefined);
324
+
325
+ useEffect(() => {
326
+ if (!file) {
327
+ setSrc(undefined);
328
+ return;
329
+ }
330
+
331
+ const objectUrl = URL.createObjectURL(file);
332
+ setSrc(objectUrl);
333
+
334
+ return () => {
335
+ URL.revokeObjectURL(objectUrl);
336
+ };
337
+ }, [file]);
338
+
339
+ return src;
340
+ };
341
+
342
+ const useAttachmentSrc = () => {
343
+ const { file, src } = useAssistantState(
344
+ useShallow(({ attachment }): { file?: File; src?: string } => {
345
+ if (attachment.type !== "image") return {};
346
+ if (attachment.file) return { file: attachment.file };
347
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
348
+ ?.image;
349
+ if (!src) return {};
350
+ return { src };
351
+ }),
352
+ );
353
+
354
+ return useFileSrc(file) ?? src;
355
+ };
356
+
357
+ type AttachmentPreviewProps = {
358
+ src: string;
359
+ };
360
+
361
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
362
+ const [isLoaded, setIsLoaded] = useState(false);
363
+ return (
364
+ <Image
365
+ src={src}
366
+ alt="Image Preview"
367
+ width={1}
368
+ height={1}
369
+ className={
370
+ isLoaded
371
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
372
+ : "aui-attachment-preview-image-loading hidden"
373
+ }
374
+ onLoadingComplete={() => setIsLoaded(true)}
375
+ priority={false}
376
+ />
377
+ );
378
+ };
379
+
380
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
381
+ const src = useAttachmentSrc();
382
+
383
+ if (!src) return children;
384
+
385
+ return (
386
+ <Dialog>
387
+ <DialogTrigger
388
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
389
+ asChild
390
+ >
391
+ {children}
392
+ </DialogTrigger>
393
+ <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">
394
+ <DialogTitle className="aui-sr-only sr-only">
395
+ Image Attachment Preview
396
+ </DialogTitle>
397
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
398
+ <AttachmentPreview src={src} />
399
+ </div>
400
+ </DialogContent>
401
+ </Dialog>
402
+ );
403
+ };
404
+
405
+ const AttachmentThumb: FC = () => {
406
+ const isImage = useAssistantState(
407
+ ({ attachment }) => attachment.type === "image",
408
+ );
409
+ const src = useAttachmentSrc();
410
+
411
+ return (
412
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
413
+ <AvatarImage
414
+ src={src}
415
+ alt="Attachment preview"
416
+ className="aui-attachment-tile-image object-cover"
417
+ />
418
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
419
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
420
+ </AvatarFallback>
421
+ </Avatar>
422
+ );
423
+ };
424
+
425
+ const AttachmentUI: FC = () => {
426
+ const api = useAssistantApi();
427
+ const isComposer = api.attachment.source === "composer";
428
+
429
+ const isImage = useAssistantState(
430
+ ({ attachment }) => attachment.type === "image",
431
+ );
432
+ const typeLabel = useAssistantState(({ attachment }) => {
433
+ const type = attachment.type;
434
+ switch (type) {
435
+ case "image":
436
+ return "Image";
437
+ case "document":
438
+ return "Document";
439
+ case "file":
440
+ return "File";
441
+ default:
442
+ const _exhaustiveCheck: never = type;
443
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
444
+ }
445
+ });
446
+
447
+ return (
448
+ <Tooltip>
449
+ <AttachmentPrimitive.Root
450
+ className={cn(
451
+ "aui-attachment-root relative",
452
+ isImage &&
453
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
454
+ )}
455
+ >
456
+ <AttachmentPreviewDialog>
457
+ <TooltipTrigger asChild>
458
+ <div
459
+ className={cn(
460
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
461
+ isComposer &&
462
+ "aui-attachment-tile-composer border-foreground/20",
463
+ )}
464
+ role="button"
465
+ id="attachment-tile"
466
+ aria-label={`${typeLabel} attachment`}
467
+ >
468
+ <AttachmentThumb />
469
+ </div>
470
+ </TooltipTrigger>
471
+ </AttachmentPreviewDialog>
472
+ {isComposer && <AttachmentRemove />}
473
+ </AttachmentPrimitive.Root>
474
+ <TooltipContent side="top">
475
+ <AttachmentPrimitive.Name />
476
+ </TooltipContent>
477
+ </Tooltip>
478
+ );
479
+ };
480
+
481
+ const AttachmentRemove: FC = () => {
482
+ return (
483
+ <AttachmentPrimitive.Remove asChild>
484
+ <TooltipIconButton
485
+ tooltip="Remove file"
486
+ 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"
487
+ side="top"
488
+ >
489
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
490
+ </TooltipIconButton>
491
+ </AttachmentPrimitive.Remove>
492
+ );
493
+ };
494
+
495
+ export const UserMessageAttachments: FC = () => {
496
+ return (
497
+ <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">
498
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
499
+ </div>
500
+ );
501
+ };
502
+
503
+ export const ComposerAttachments: FC = () => {
504
+ return (
505
+ <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">
506
+ <ComposerPrimitive.Attachments
507
+ components={{ Attachment: AttachmentUI }}
508
+ />
509
+ </div>
510
+ );
511
+ };
512
+
513
+ export const ComposerAddAttachment: FC = () => {
514
+ return (
515
+ <ComposerPrimitive.AddAttachment asChild>
516
+ <TooltipIconButton
517
+ tooltip="Add Attachment"
518
+ side="bottom"
519
+ variant="ghost"
520
+ size="icon"
521
+ 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"
522
+ aria-label="Add Attachment"
523
+ >
524
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
525
+ </TooltipIconButton>
526
+ </ComposerPrimitive.AddAttachment>
527
+ );
528
+ };
529
+
530
+ ```
531
+
291
532
  ## components/assistant-ui/markdown-text.tsx
292
533
 
293
534
  ```tsx
@@ -296,13 +537,13 @@ export default function Home() {
296
537
  import "@assistant-ui/react-markdown/styles/dot.css";
297
538
 
298
539
  import {
299
- CodeHeaderProps,
540
+ type CodeHeaderProps,
300
541
  MarkdownTextPrimitive,
301
542
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
302
543
  useIsMarkdownCodeBlock,
303
544
  } from "@assistant-ui/react-markdown";
304
545
  import remarkGfm from "remark-gfm";
305
- import { FC, memo, useState } from "react";
546
+ import { type FC, memo, useState } from "react";
306
547
  import { CheckIcon, CopyIcon } from "lucide-react";
307
548
 
308
549
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -328,8 +569,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
328
569
  };
329
570
 
330
571
  return (
331
- <div className="flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
332
- <span className="lowercase [&>span]:text-xs">{language}</span>
572
+ <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">
573
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
574
+ {language}
575
+ </span>
333
576
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
334
577
  {!isCopied && <CopyIcon />}
335
578
  {isCopied && <CheckIcon />}
@@ -361,7 +604,7 @@ const defaultComponents = memoizeMarkdownComponents({
361
604
  h1: ({ className, ...props }) => (
362
605
  <h1
363
606
  className={cn(
364
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
607
+ "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
365
608
  className,
366
609
  )}
367
610
  {...props}
@@ -370,7 +613,7 @@ const defaultComponents = memoizeMarkdownComponents({
370
613
  h2: ({ className, ...props }) => (
371
614
  <h2
372
615
  className={cn(
373
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
616
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
374
617
  className,
375
618
  )}
376
619
  {...props}
@@ -379,7 +622,7 @@ const defaultComponents = memoizeMarkdownComponents({
379
622
  h3: ({ className, ...props }) => (
380
623
  <h3
381
624
  className={cn(
382
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
625
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
383
626
  className,
384
627
  )}
385
628
  {...props}
@@ -388,7 +631,7 @@ const defaultComponents = memoizeMarkdownComponents({
388
631
  h4: ({ className, ...props }) => (
389
632
  <h4
390
633
  className={cn(
391
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
634
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
392
635
  className,
393
636
  )}
394
637
  {...props}
@@ -397,7 +640,7 @@ const defaultComponents = memoizeMarkdownComponents({
397
640
  h5: ({ className, ...props }) => (
398
641
  <h5
399
642
  className={cn(
400
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
643
+ "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
401
644
  className,
402
645
  )}
403
646
  {...props}
@@ -405,20 +648,26 @@ const defaultComponents = memoizeMarkdownComponents({
405
648
  ),
406
649
  h6: ({ className, ...props }) => (
407
650
  <h6
408
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
651
+ className={cn(
652
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
653
+ className,
654
+ )}
409
655
  {...props}
410
656
  />
411
657
  ),
412
658
  p: ({ className, ...props }) => (
413
659
  <p
414
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
660
+ className={cn(
661
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
662
+ className,
663
+ )}
415
664
  {...props}
416
665
  />
417
666
  ),
418
667
  a: ({ className, ...props }) => (
419
668
  <a
420
669
  className={cn(
421
- "text-primary font-medium underline underline-offset-4",
670
+ "aui-md-a font-medium text-primary underline underline-offset-4",
422
671
  className,
423
672
  )}
424
673
  {...props}
@@ -426,29 +675,29 @@ const defaultComponents = memoizeMarkdownComponents({
426
675
  ),
427
676
  blockquote: ({ className, ...props }) => (
428
677
  <blockquote
429
- className={cn("border-l-2 pl-6 italic", className)}
678
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
430
679
  {...props}
431
680
  />
432
681
  ),
433
682
  ul: ({ className, ...props }) => (
434
683
  <ul
435
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
684
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
436
685
  {...props}
437
686
  />
438
687
  ),
439
688
  ol: ({ className, ...props }) => (
440
689
  <ol
441
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
690
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
442
691
  {...props}
443
692
  />
444
693
  ),
445
694
  hr: ({ className, ...props }) => (
446
- <hr className={cn("my-5 border-b", className)} {...props} />
695
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
447
696
  ),
448
697
  table: ({ className, ...props }) => (
449
698
  <table
450
699
  className={cn(
451
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
700
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
452
701
  className,
453
702
  )}
454
703
  {...props}
@@ -457,7 +706,7 @@ const defaultComponents = memoizeMarkdownComponents({
457
706
  th: ({ className, ...props }) => (
458
707
  <th
459
708
  className={cn(
460
- "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",
709
+ "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",
461
710
  className,
462
711
  )}
463
712
  {...props}
@@ -466,7 +715,7 @@ const defaultComponents = memoizeMarkdownComponents({
466
715
  td: ({ className, ...props }) => (
467
716
  <td
468
717
  className={cn(
469
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
718
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
470
719
  className,
471
720
  )}
472
721
  {...props}
@@ -475,7 +724,7 @@ const defaultComponents = memoizeMarkdownComponents({
475
724
  tr: ({ className, ...props }) => (
476
725
  <tr
477
726
  className={cn(
478
- "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",
727
+ "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",
479
728
  className,
480
729
  )}
481
730
  {...props}
@@ -483,14 +732,14 @@ const defaultComponents = memoizeMarkdownComponents({
483
732
  ),
484
733
  sup: ({ className, ...props }) => (
485
734
  <sup
486
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
735
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
487
736
  {...props}
488
737
  />
489
738
  ),
490
739
  pre: ({ className, ...props }) => (
491
740
  <pre
492
741
  className={cn(
493
- "overflow-x-auto rounded-b-lg bg-black p-4 text-white",
742
+ "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
494
743
  className,
495
744
  )}
496
745
  {...props}
@@ -501,7 +750,8 @@ const defaultComponents = memoizeMarkdownComponents({
501
750
  return (
502
751
  <code
503
752
  className={cn(
504
- !isCodeBlock && "bg-muted rounded border font-semibold",
753
+ !isCodeBlock &&
754
+ "aui-md-inline-code rounded border bg-muted font-semibold",
505
755
  className,
506
756
  )}
507
757
  {...props}
@@ -516,57 +766,67 @@ const defaultComponents = memoizeMarkdownComponents({
516
766
  ## components/assistant-ui/thread.tsx
517
767
 
518
768
  ```tsx
769
+ import {
770
+ ComposerAddAttachment,
771
+ ComposerAttachments,
772
+ UserMessageAttachments,
773
+ } from "@/components/assistant-ui/attachment";
774
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
775
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
776
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
777
+ import { Button } from "@/components/ui/button";
778
+ import { cn } from "@/lib/utils";
519
779
  import {
520
780
  ActionBarPrimitive,
781
+ AssistantIf,
521
782
  BranchPickerPrimitive,
522
783
  ComposerPrimitive,
784
+ ErrorPrimitive,
523
785
  MessagePrimitive,
524
786
  ThreadPrimitive,
525
787
  } from "@assistant-ui/react";
526
- import type { FC } from "react";
527
788
  import {
528
789
  ArrowDownIcon,
790
+ ArrowUpIcon,
529
791
  CheckIcon,
530
792
  ChevronLeftIcon,
531
793
  ChevronRightIcon,
532
794
  CopyIcon,
795
+ DownloadIcon,
533
796
  PencilIcon,
534
797
  RefreshCwIcon,
535
- SendHorizontalIcon,
798
+ SquareIcon,
536
799
  } from "lucide-react";
537
- import { cn } from "@/lib/utils";
538
-
539
- import { Button } from "@/components/ui/button";
540
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
541
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
800
+ import type { FC } from "react";
542
801
 
543
802
  export const Thread: FC = () => {
544
803
  return (
545
804
  <ThreadPrimitive.Root
546
- className="bg-background box-border flex h-full flex-col overflow-hidden"
805
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
547
806
  style={{
548
- ["--thread-max-width" as string]: "42rem",
807
+ ["--thread-max-width" as string]: "44rem",
549
808
  }}
550
809
  >
551
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
552
- <ThreadWelcome />
810
+ <ThreadPrimitive.Viewport
811
+ turnAnchor="top"
812
+ className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
813
+ >
814
+ <AssistantIf condition={({ thread }) => thread.isEmpty}>
815
+ <ThreadWelcome />
816
+ </AssistantIf>
553
817
 
554
818
  <ThreadPrimitive.Messages
555
819
  components={{
556
- UserMessage: UserMessage,
557
- EditComposer: EditComposer,
558
- AssistantMessage: AssistantMessage,
820
+ UserMessage,
821
+ EditComposer,
822
+ AssistantMessage,
559
823
  }}
560
824
  />
561
825
 
562
- <ThreadPrimitive.If empty={false}>
563
- <div className="min-h-8 flex-grow" />
564
- </ThreadPrimitive.If>
565
-
566
- <div className="sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit pb-4">
826
+ <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6">
567
827
  <ThreadScrollToBottom />
568
828
  <Composer />
569
- </div>
829
+ </ThreadPrimitive.ViewportFooter>
570
830
  </ThreadPrimitive.Viewport>
571
831
  </ThreadPrimitive.Root>
572
832
  );
@@ -578,7 +838,7 @@ const ThreadScrollToBottom: FC = () => {
578
838
  <TooltipIconButton
579
839
  tooltip="Scroll to bottom"
580
840
  variant="outline"
581
- className="absolute -top-8 rounded-full disabled:invisible"
841
+ className="aui-thread-scroll-to-bottom -top-12 absolute z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent"
582
842
  >
583
843
  <ArrowDownIcon />
584
844
  </TooltipIconButton>
@@ -588,175 +848,247 @@ const ThreadScrollToBottom: FC = () => {
588
848
 
589
849
  const ThreadWelcome: FC = () => {
590
850
  return (
591
- <ThreadPrimitive.Empty>
592
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
593
- <div className="flex w-full flex-grow flex-col items-center justify-center">
594
- <p className="mt-4 font-medium">How can I help you today?</p>
851
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
852
+ <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
853
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
854
+ <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
855
+ Hello there!
856
+ </h1>
857
+ <p className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in text-muted-foreground text-xl delay-75 duration-200">
858
+ How can I help you today?
859
+ </p>
595
860
  </div>
596
- <ThreadWelcomeSuggestions />
597
861
  </div>
598
- </ThreadPrimitive.Empty>
862
+ <ThreadSuggestions />
863
+ </div>
599
864
  );
600
865
  };
601
866
 
602
- const ThreadWelcomeSuggestions: FC = () => {
867
+ const SUGGESTIONS = [
868
+ {
869
+ title: "What's the weather",
870
+ label: "in San Francisco?",
871
+ prompt: "What's the weather in San Francisco?",
872
+ },
873
+ {
874
+ title: "Explain React hooks",
875
+ label: "like useState and useEffect",
876
+ prompt: "Explain React hooks like useState and useEffect",
877
+ },
878
+ ] as const;
879
+
880
+ const ThreadSuggestions: FC = () => {
603
881
  return (
604
- <div className="mt-3 flex w-full items-stretch justify-center gap-4">
605
- <ThreadPrimitive.Suggestion
606
- className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
607
- prompt="What is the weather in Tokyo?"
608
- method="replace"
609
- autoSend
610
- >
611
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
612
- What is the weather in Tokyo?
613
- </span>
614
- </ThreadPrimitive.Suggestion>
615
- <ThreadPrimitive.Suggestion
616
- className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
617
- prompt="What is assistant-ui?"
618
- method="replace"
619
- autoSend
620
- >
621
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
622
- What is assistant-ui?
623
- </span>
624
- </ThreadPrimitive.Suggestion>
882
+ <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
883
+ {SUGGESTIONS.map((suggestion, index) => (
884
+ <div
885
+ key={suggestion.prompt}
886
+ className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200"
887
+ style={{ animationDelay: `${100 + index * 50}ms` }}
888
+ >
889
+ <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
890
+ <Button
891
+ variant="ghost"
892
+ className="aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted"
893
+ aria-label={suggestion.prompt}
894
+ >
895
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
896
+ {suggestion.title}
897
+ </span>
898
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
899
+ {suggestion.label}
900
+ </span>
901
+ </Button>
902
+ </ThreadPrimitive.Suggestion>
903
+ </div>
904
+ ))}
625
905
  </div>
626
906
  );
627
907
  };
628
908
 
629
909
  const Composer: FC = () => {
630
910
  return (
631
- <ComposerPrimitive.Root className="focus-within:border-ring/20 flex w-full flex-wrap items-end rounded-lg border bg-inherit px-2.5 shadow-sm transition-colors ease-in">
632
- <ComposerPrimitive.Input
633
- rows={1}
634
- autoFocus
635
- placeholder="Write a message..."
636
- className="placeholder:text-muted-foreground max-h-40 flex-grow resize-none border-none bg-transparent px-2 py-4 text-sm outline-none focus:ring-0 disabled:cursor-not-allowed"
637
- />
638
- <ComposerAction />
911
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
912
+ <ComposerPrimitive.AttachmentDropzone className="aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50">
913
+ <ComposerAttachments />
914
+ <ComposerPrimitive.Input
915
+ placeholder="Send a message..."
916
+ className="aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0"
917
+ rows={1}
918
+ autoFocus
919
+ aria-label="Message input"
920
+ />
921
+ <ComposerAction />
922
+ </ComposerPrimitive.AttachmentDropzone>
639
923
  </ComposerPrimitive.Root>
640
924
  );
641
925
  };
642
926
 
643
927
  const ComposerAction: FC = () => {
644
928
  return (
645
- <>
646
- <ThreadPrimitive.If running={false}>
929
+ <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
930
+ <ComposerAddAttachment />
931
+
932
+ <AssistantIf condition={({ thread }) => !thread.isRunning}>
647
933
  <ComposerPrimitive.Send asChild>
648
934
  <TooltipIconButton
649
- tooltip="Send"
935
+ tooltip="Send message"
936
+ side="bottom"
937
+ type="submit"
650
938
  variant="default"
651
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
939
+ size="icon"
940
+ className="aui-composer-send size-8 rounded-full"
941
+ aria-label="Send message"
652
942
  >
653
- <SendHorizontalIcon />
943
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
654
944
  </TooltipIconButton>
655
945
  </ComposerPrimitive.Send>
656
- </ThreadPrimitive.If>
657
- <ThreadPrimitive.If running>
946
+ </AssistantIf>
947
+
948
+ <AssistantIf condition={({ thread }) => thread.isRunning}>
658
949
  <ComposerPrimitive.Cancel asChild>
659
- <TooltipIconButton
660
- tooltip="Cancel"
950
+ <Button
951
+ type="button"
661
952
  variant="default"
662
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
953
+ size="icon"
954
+ className="aui-composer-cancel size-8 rounded-full"
955
+ aria-label="Stop generating"
663
956
  >
664
- <CircleStopIcon />
665
- </TooltipIconButton>
957
+ <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
958
+ </Button>
666
959
  </ComposerPrimitive.Cancel>
667
- </ThreadPrimitive.If>
668
- </>
960
+ </AssistantIf>
961
+ </div>
669
962
  );
670
963
  };
671
964
 
672
- const UserMessage: FC = () => {
965
+ const MessageError: FC = () => {
673
966
  return (
674
- <MessagePrimitive.Root className="grid w-full max-w-[var(--thread-max-width)] auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 py-4 [&:where(>*)]:col-start-2">
675
- <UserActionBar />
967
+ <MessagePrimitive.Error>
968
+ <ErrorPrimitive.Root className="aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200">
969
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
970
+ </ErrorPrimitive.Root>
971
+ </MessagePrimitive.Error>
972
+ );
973
+ };
676
974
 
677
- <div className="bg-muted text-foreground col-start-2 row-start-2 max-w-[calc(var(--thread-max-width)*0.8)] rounded-3xl px-5 py-2.5 break-words">
678
- <MessagePrimitive.Parts />
975
+ const AssistantMessage: FC = () => {
976
+ return (
977
+ <MessagePrimitive.Root
978
+ className="aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150"
979
+ data-role="assistant"
980
+ >
981
+ <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
982
+ <MessagePrimitive.Parts
983
+ components={{
984
+ Text: MarkdownText,
985
+ tools: { Fallback: ToolFallback },
986
+ }}
987
+ />
988
+ <MessageError />
679
989
  </div>
680
990
 
681
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
991
+ <div className="aui-assistant-message-footer mt-1 ml-2 flex">
992
+ <BranchPicker />
993
+ <AssistantActionBar />
994
+ </div>
682
995
  </MessagePrimitive.Root>
683
996
  );
684
997
  };
685
998
 
686
- const UserActionBar: FC = () => {
999
+ const AssistantActionBar: FC = () => {
687
1000
  return (
688
1001
  <ActionBarPrimitive.Root
689
1002
  hideWhenRunning
690
1003
  autohide="not-last"
691
- className="col-start-1 row-start-2 mt-2.5 mr-3 flex flex-col items-end"
1004
+ autohideFloat="single-branch"
1005
+ className="aui-assistant-action-bar-root -ml-1 col-start-3 row-start-2 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
692
1006
  >
693
- <ActionBarPrimitive.Edit asChild>
694
- <TooltipIconButton tooltip="Edit">
695
- <PencilIcon />
1007
+ <ActionBarPrimitive.Copy asChild>
1008
+ <TooltipIconButton tooltip="Copy">
1009
+ <AssistantIf condition={({ message }) => message.isCopied}>
1010
+ <CheckIcon />
1011
+ </AssistantIf>
1012
+ <AssistantIf condition={({ message }) => !message.isCopied}>
1013
+ <CopyIcon />
1014
+ </AssistantIf>
696
1015
  </TooltipIconButton>
697
- </ActionBarPrimitive.Edit>
1016
+ </ActionBarPrimitive.Copy>
1017
+ <ActionBarPrimitive.ExportMarkdown asChild>
1018
+ <TooltipIconButton tooltip="Export as Markdown">
1019
+ <DownloadIcon />
1020
+ </TooltipIconButton>
1021
+ </ActionBarPrimitive.ExportMarkdown>
1022
+ <ActionBarPrimitive.Reload asChild>
1023
+ <TooltipIconButton tooltip="Refresh">
1024
+ <RefreshCwIcon />
1025
+ </TooltipIconButton>
1026
+ </ActionBarPrimitive.Reload>
698
1027
  </ActionBarPrimitive.Root>
699
1028
  );
700
1029
  };
701
1030
 
702
- const EditComposer: FC = () => {
1031
+ const UserMessage: FC = () => {
703
1032
  return (
704
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
705
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
706
-
707
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
708
- <ComposerPrimitive.Cancel asChild>
709
- <Button variant="ghost">Cancel</Button>
710
- </ComposerPrimitive.Cancel>
711
- <ComposerPrimitive.Send asChild>
712
- <Button>Send</Button>
713
- </ComposerPrimitive.Send>
714
- </div>
715
- </ComposerPrimitive.Root>
716
- );
717
- };
1033
+ <MessagePrimitive.Root
1034
+ 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"
1035
+ data-role="user"
1036
+ >
1037
+ <UserMessageAttachments />
718
1038
 
719
- const AssistantMessage: FC = () => {
720
- return (
721
- <MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
722
- <div className="text-foreground col-span-2 col-start-2 row-start-1 my-1.5 max-w-[calc(var(--thread-max-width)*0.8)] leading-7 break-words">
723
- <MessagePrimitive.Parts components={{ Text: MarkdownText }} />
1039
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1040
+ <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
1041
+ <MessagePrimitive.Parts />
1042
+ </div>
1043
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
1044
+ <UserActionBar />
1045
+ </div>
724
1046
  </div>
725
1047
 
726
- <AssistantActionBar />
727
-
728
- <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
1048
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
729
1049
  </MessagePrimitive.Root>
730
1050
  );
731
1051
  };
732
1052
 
733
- const AssistantActionBar: FC = () => {
1053
+ const UserActionBar: FC = () => {
734
1054
  return (
735
1055
  <ActionBarPrimitive.Root
736
1056
  hideWhenRunning
737
1057
  autohide="not-last"
738
- autohideFloat="single-branch"
739
- className="text-muted-foreground data-[floating]:bg-background col-start-3 row-start-2 -ml-1 flex gap-1 data-[floating]:absolute data-[floating]:rounded-md data-[floating]:border data-[floating]:p-1 data-[floating]:shadow-sm"
1058
+ className="aui-user-action-bar-root flex flex-col items-end"
740
1059
  >
741
- <ActionBarPrimitive.Copy asChild>
742
- <TooltipIconButton tooltip="Copy">
743
- <MessagePrimitive.If copied>
744
- <CheckIcon />
745
- </MessagePrimitive.If>
746
- <MessagePrimitive.If copied={false}>
747
- <CopyIcon />
748
- </MessagePrimitive.If>
749
- </TooltipIconButton>
750
- </ActionBarPrimitive.Copy>
751
- <ActionBarPrimitive.Reload asChild>
752
- <TooltipIconButton tooltip="Refresh">
753
- <RefreshCwIcon />
1060
+ <ActionBarPrimitive.Edit asChild>
1061
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1062
+ <PencilIcon />
754
1063
  </TooltipIconButton>
755
- </ActionBarPrimitive.Reload>
1064
+ </ActionBarPrimitive.Edit>
756
1065
  </ActionBarPrimitive.Root>
757
1066
  );
758
1067
  };
759
1068
 
1069
+ const EditComposer: FC = () => {
1070
+ return (
1071
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1072
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1073
+ <ComposerPrimitive.Input
1074
+ className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1075
+ autoFocus
1076
+ />
1077
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1078
+ <ComposerPrimitive.Cancel asChild>
1079
+ <Button variant="ghost" size="sm">
1080
+ Cancel
1081
+ </Button>
1082
+ </ComposerPrimitive.Cancel>
1083
+ <ComposerPrimitive.Send asChild>
1084
+ <Button size="sm">Update</Button>
1085
+ </ComposerPrimitive.Send>
1086
+ </div>
1087
+ </ComposerPrimitive.Root>
1088
+ </MessagePrimitive.Root>
1089
+ );
1090
+ };
1091
+
760
1092
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
761
1093
  className,
762
1094
  ...rest
@@ -765,7 +1097,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
765
1097
  <BranchPickerPrimitive.Root
766
1098
  hideWhenSingleBranch
767
1099
  className={cn(
768
- "text-muted-foreground inline-flex items-center text-xs",
1100
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
769
1101
  className,
770
1102
  )}
771
1103
  {...rest}
@@ -775,7 +1107,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
775
1107
  <ChevronLeftIcon />
776
1108
  </TooltipIconButton>
777
1109
  </BranchPickerPrimitive.Previous>
778
- <span className="font-medium">
1110
+ <span className="aui-branch-picker-state font-medium">
779
1111
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
780
1112
  </span>
781
1113
  <BranchPickerPrimitive.Next asChild>
@@ -787,17 +1119,102 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
787
1119
  );
788
1120
  };
789
1121
 
790
- const CircleStopIcon = () => {
1122
+ ```
1123
+
1124
+ ## components/assistant-ui/tool-fallback.tsx
1125
+
1126
+ ```tsx
1127
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1128
+ import {
1129
+ CheckIcon,
1130
+ ChevronDownIcon,
1131
+ ChevronUpIcon,
1132
+ XCircleIcon,
1133
+ } from "lucide-react";
1134
+ import { useState } from "react";
1135
+ import { Button } from "@/components/ui/button";
1136
+ import { cn } from "@/lib/utils";
1137
+
1138
+ export const ToolFallback: ToolCallMessagePartComponent = ({
1139
+ toolName,
1140
+ argsText,
1141
+ result,
1142
+ status,
1143
+ }) => {
1144
+ const [isCollapsed, setIsCollapsed] = useState(true);
1145
+
1146
+ const isCancelled =
1147
+ status?.type === "incomplete" && status.reason === "cancelled";
1148
+ const cancelledReason =
1149
+ isCancelled && status.error
1150
+ ? typeof status.error === "string"
1151
+ ? status.error
1152
+ : JSON.stringify(status.error)
1153
+ : null;
1154
+
791
1155
  return (
792
- <svg
793
- xmlns="http://www.w3.org/2000/svg"
794
- viewBox="0 0 16 16"
795
- fill="currentColor"
796
- width="16"
797
- height="16"
1156
+ <div
1157
+ className={cn(
1158
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1159
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1160
+ )}
798
1161
  >
799
- <rect width="10" height="10" x="3" y="3" rx="2" />
800
- </svg>
1162
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1163
+ {isCancelled ? (
1164
+ <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1165
+ ) : (
1166
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1167
+ )}
1168
+ <p
1169
+ className={cn(
1170
+ "aui-tool-fallback-title grow",
1171
+ isCancelled && "text-muted-foreground line-through",
1172
+ )}
1173
+ >
1174
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1175
+ <b>{toolName}</b>
1176
+ </p>
1177
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
1178
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
1179
+ </Button>
1180
+ </div>
1181
+ {!isCollapsed && (
1182
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1183
+ {cancelledReason && (
1184
+ <div className="aui-tool-fallback-cancelled-root px-4">
1185
+ <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1186
+ Cancelled reason:
1187
+ </p>
1188
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1189
+ {cancelledReason}
1190
+ </p>
1191
+ </div>
1192
+ )}
1193
+ <div
1194
+ className={cn(
1195
+ "aui-tool-fallback-args-root px-4",
1196
+ isCancelled && "opacity-60",
1197
+ )}
1198
+ >
1199
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1200
+ {argsText}
1201
+ </pre>
1202
+ </div>
1203
+ {!isCancelled && result !== undefined && (
1204
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1205
+ <p className="aui-tool-fallback-result-header font-semibold">
1206
+ Result:
1207
+ </p>
1208
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
1209
+ {typeof result === "string"
1210
+ ? result
1211
+ : JSON.stringify(result, null, 2)}
1212
+ </pre>
1213
+ </div>
1214
+ )}
1215
+ </div>
1216
+ )}
1217
+ </div>
801
1218
  );
802
1219
  };
803
1220
 
@@ -808,7 +1225,7 @@ const CircleStopIcon = () => {
808
1225
  ```tsx
809
1226
  "use client";
810
1227
 
811
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1228
+ import { ComponentPropsWithRef, forwardRef } from "react";
812
1229
  import { Slottable } from "@radix-ui/react-slot";
813
1230
 
814
1231
  import {
@@ -819,7 +1236,7 @@ import {
819
1236
  import { Button } from "@/components/ui/button";
820
1237
  import { cn } from "@/lib/utils";
821
1238
 
822
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1239
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
823
1240
  tooltip: string;
824
1241
  side?: "top" | "bottom" | "left" | "right";
825
1242
  };
@@ -835,11 +1252,11 @@ export const TooltipIconButton = forwardRef<
835
1252
  variant="ghost"
836
1253
  size="icon"
837
1254
  {...rest}
838
- className={cn("size-6 p-1", className)}
1255
+ className={cn("aui-button-icon size-6 p-1", className)}
839
1256
  ref={ref}
840
1257
  >
841
1258
  <Slottable>{children}</Slottable>
842
- <span className="sr-only">{tooltip}</span>
1259
+ <span className="aui-sr-only sr-only">{tooltip}</span>
843
1260
  </Button>
844
1261
  </TooltipTrigger>
845
1262
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -851,6 +1268,62 @@ TooltipIconButton.displayName = "TooltipIconButton";
851
1268
 
852
1269
  ```
853
1270
 
1271
+ ## components/ui/avatar.tsx
1272
+
1273
+ ```tsx
1274
+ "use client";
1275
+
1276
+ import * as React from "react";
1277
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1278
+
1279
+ import { cn } from "@/lib/utils";
1280
+
1281
+ const Avatar = React.forwardRef<
1282
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1283
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1284
+ >(({ className, ...props }, ref) => (
1285
+ <AvatarPrimitive.Root
1286
+ ref={ref}
1287
+ className={cn(
1288
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1289
+ className,
1290
+ )}
1291
+ {...props}
1292
+ />
1293
+ ));
1294
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1295
+
1296
+ const AvatarImage = React.forwardRef<
1297
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1298
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1299
+ >(({ className, ...props }, ref) => (
1300
+ <AvatarPrimitive.Image
1301
+ ref={ref}
1302
+ className={cn("aspect-square h-full w-full", className)}
1303
+ {...props}
1304
+ />
1305
+ ));
1306
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1307
+
1308
+ const AvatarFallback = React.forwardRef<
1309
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1310
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1311
+ >(({ className, ...props }, ref) => (
1312
+ <AvatarPrimitive.Fallback
1313
+ ref={ref}
1314
+ className={cn(
1315
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
1316
+ className,
1317
+ )}
1318
+ {...props}
1319
+ />
1320
+ ));
1321
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1322
+
1323
+ export { Avatar, AvatarImage, AvatarFallback };
1324
+
1325
+ ```
1326
+
854
1327
  ## components/ui/button.tsx
855
1328
 
856
1329
  ```tsx
@@ -863,7 +1336,7 @@ import { cva, type VariantProps } from "class-variance-authority";
863
1336
  import { cn } from "@/lib/utils";
864
1337
 
865
1338
  const buttonVariants = cva(
866
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
1339
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
867
1340
  {
868
1341
  variants: {
869
1342
  variant: {
@@ -916,6 +1389,147 @@ export { Button, buttonVariants };
916
1389
 
917
1390
  ```
918
1391
 
1392
+ ## components/ui/dialog.tsx
1393
+
1394
+ ```tsx
1395
+ "use client";
1396
+
1397
+ import * as React from "react";
1398
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1399
+ import { XIcon } from "lucide-react";
1400
+
1401
+ import { cn } from "@/lib/utils";
1402
+
1403
+ function Dialog({
1404
+ ...props
1405
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1406
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1407
+ }
1408
+
1409
+ function DialogTrigger({
1410
+ ...props
1411
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1412
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1413
+ }
1414
+
1415
+ function DialogPortal({
1416
+ ...props
1417
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1418
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1419
+ }
1420
+
1421
+ function DialogClose({
1422
+ ...props
1423
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1424
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1425
+ }
1426
+
1427
+ function DialogOverlay({
1428
+ className,
1429
+ ...props
1430
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1431
+ return (
1432
+ <DialogPrimitive.Overlay
1433
+ data-slot="dialog-overlay"
1434
+ className={cn(
1435
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
1436
+ className,
1437
+ )}
1438
+ {...props}
1439
+ />
1440
+ );
1441
+ }
1442
+
1443
+ function DialogContent({
1444
+ className,
1445
+ children,
1446
+ ...props
1447
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
1448
+ return (
1449
+ <DialogPortal data-slot="dialog-portal">
1450
+ <DialogOverlay />
1451
+ <DialogPrimitive.Content
1452
+ data-slot="dialog-content"
1453
+ className={cn(
1454
+ "data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg",
1455
+ className,
1456
+ )}
1457
+ {...props}
1458
+ >
1459
+ {children}
1460
+ <DialogPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0">
1461
+ <XIcon />
1462
+ <span className="sr-only">Close</span>
1463
+ </DialogPrimitive.Close>
1464
+ </DialogPrimitive.Content>
1465
+ </DialogPortal>
1466
+ );
1467
+ }
1468
+
1469
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1470
+ return (
1471
+ <div
1472
+ data-slot="dialog-header"
1473
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1474
+ {...props}
1475
+ />
1476
+ );
1477
+ }
1478
+
1479
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1480
+ return (
1481
+ <div
1482
+ data-slot="dialog-footer"
1483
+ className={cn(
1484
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1485
+ className,
1486
+ )}
1487
+ {...props}
1488
+ />
1489
+ );
1490
+ }
1491
+
1492
+ function DialogTitle({
1493
+ className,
1494
+ ...props
1495
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1496
+ return (
1497
+ <DialogPrimitive.Title
1498
+ data-slot="dialog-title"
1499
+ className={cn("font-semibold text-lg leading-none", className)}
1500
+ {...props}
1501
+ />
1502
+ );
1503
+ }
1504
+
1505
+ function DialogDescription({
1506
+ className,
1507
+ ...props
1508
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1509
+ return (
1510
+ <DialogPrimitive.Description
1511
+ data-slot="dialog-description"
1512
+ className={cn("text-muted-foreground text-sm", className)}
1513
+ {...props}
1514
+ />
1515
+ );
1516
+ }
1517
+
1518
+ export {
1519
+ Dialog,
1520
+ DialogClose,
1521
+ DialogContent,
1522
+ DialogDescription,
1523
+ DialogFooter,
1524
+ DialogHeader,
1525
+ DialogOverlay,
1526
+ DialogPortal,
1527
+ DialogTitle,
1528
+ DialogTrigger,
1529
+ };
1530
+
1531
+ ```
1532
+
919
1533
  ## components/ui/tooltip.tsx
920
1534
 
921
1535
  ```tsx
@@ -967,13 +1581,13 @@ function TooltipContent({
967
1581
  data-slot="tooltip-content"
968
1582
  sideOffset={sideOffset}
969
1583
  className={cn(
970
- "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-[--radix-tooltip-content-transform-origin] rounded-md px-3 py-1.5 text-xs text-balance",
1584
+ "fade-in-0 zoom-in-95 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-[--radix-tooltip-content-transform-origin] animate-in text-balance rounded-md bg-primary px-3 py-1.5 text-primary-foreground text-xs data-[state=closed]:animate-out",
971
1585
  className,
972
1586
  )}
973
1587
  {...props}
974
1588
  >
975
1589
  {children}
976
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1590
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
977
1591
  </TooltipPrimitive.Content>
978
1592
  </TooltipPrimitive.Portal>
979
1593
  );
@@ -1018,34 +1632,34 @@ export default nextConfig;
1018
1632
  "scripts": {
1019
1633
  "dev": "next dev --turbo",
1020
1634
  "build": "next build",
1021
- "start": "next start",
1022
- "lint": "eslint ."
1635
+ "start": "next start"
1023
1636
  },
1024
1637
  "dependencies": {
1025
- "@ai-sdk/openai": "^2.0.68",
1638
+ "@ai-sdk/openai": "^2.0.77",
1026
1639
  "@assistant-ui/react": "workspace:*",
1027
1640
  "@assistant-ui/react-markdown": "workspace:*",
1028
1641
  "@assistant-ui/react-ag-ui": "workspace:*",
1029
- "@ag-ui/client": "^0.0.41",
1642
+ "@ag-ui/client": "^0.0.42",
1643
+ "@radix-ui/react-avatar": "^1.1.11",
1644
+ "@radix-ui/react-dialog": "^1.1.15",
1030
1645
  "@radix-ui/react-slot": "^1.2.4",
1031
1646
  "@radix-ui/react-tooltip": "^1.2.8",
1032
1647
  "class-variance-authority": "^0.7.1",
1033
1648
  "clsx": "^2.1.1",
1034
- "lucide-react": "^0.554.0",
1035
- "next": "16.0.3",
1036
- "react": "19.2.0",
1037
- "react-dom": "19.2.0",
1649
+ "lucide-react": "^0.556.0",
1650
+ "next": "16.0.7",
1651
+ "react": "19.2.1",
1652
+ "react-dom": "19.2.1",
1038
1653
  "remark-gfm": "^4.0.1",
1039
1654
  "tailwind-merge": "^3.4.0",
1040
- "tw-animate-css": "^1.4.0"
1655
+ "tw-animate-css": "^1.4.0",
1656
+ "zustand": "^5.0.9"
1041
1657
  },
1042
1658
  "devDependencies": {
1043
1659
  "@assistant-ui/x-buildutils": "workspace:*",
1044
1660
  "@types/node": "^24",
1045
1661
  "@types/react": "^19",
1046
1662
  "@types/react-dom": "^19",
1047
- "eslint": "^9",
1048
- "eslint-config-next": "16.0.3",
1049
1663
  "postcss": "^8",
1050
1664
  "tailwindcss": "^4.1.17",
1051
1665
  "typescript": "^5"