@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
@@ -244,6 +244,247 @@ export default function Home() {
244
244
 
245
245
  ```
246
246
 
247
+ ## components/assistant-ui/attachment.tsx
248
+
249
+ ```tsx
250
+ "use client";
251
+
252
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
253
+ import Image from "next/image";
254
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
255
+ import {
256
+ AttachmentPrimitive,
257
+ ComposerPrimitive,
258
+ MessagePrimitive,
259
+ useAssistantState,
260
+ useAssistantApi,
261
+ } from "@assistant-ui/react";
262
+ import { useShallow } from "zustand/shallow";
263
+ import {
264
+ Tooltip,
265
+ TooltipContent,
266
+ TooltipTrigger,
267
+ } from "@/components/ui/tooltip";
268
+ import {
269
+ Dialog,
270
+ DialogTitle,
271
+ DialogContent,
272
+ DialogTrigger,
273
+ } from "@/components/ui/dialog";
274
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
275
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
276
+ import { cn } from "@/lib/utils";
277
+
278
+ const useFileSrc = (file: File | undefined) => {
279
+ const [src, setSrc] = useState<string | undefined>(undefined);
280
+
281
+ useEffect(() => {
282
+ if (!file) {
283
+ setSrc(undefined);
284
+ return;
285
+ }
286
+
287
+ const objectUrl = URL.createObjectURL(file);
288
+ setSrc(objectUrl);
289
+
290
+ return () => {
291
+ URL.revokeObjectURL(objectUrl);
292
+ };
293
+ }, [file]);
294
+
295
+ return src;
296
+ };
297
+
298
+ const useAttachmentSrc = () => {
299
+ const { file, src } = useAssistantState(
300
+ useShallow(({ attachment }): { file?: File; src?: string } => {
301
+ if (attachment.type !== "image") return {};
302
+ if (attachment.file) return { file: attachment.file };
303
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
304
+ ?.image;
305
+ if (!src) return {};
306
+ return { src };
307
+ }),
308
+ );
309
+
310
+ return useFileSrc(file) ?? src;
311
+ };
312
+
313
+ type AttachmentPreviewProps = {
314
+ src: string;
315
+ };
316
+
317
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
318
+ const [isLoaded, setIsLoaded] = useState(false);
319
+ return (
320
+ <Image
321
+ src={src}
322
+ alt="Image Preview"
323
+ width={1}
324
+ height={1}
325
+ className={
326
+ isLoaded
327
+ ? "aui-attachment-preview-image-loaded block h-auto max-h-[80vh] w-auto max-w-full object-contain"
328
+ : "aui-attachment-preview-image-loading hidden"
329
+ }
330
+ onLoadingComplete={() => setIsLoaded(true)}
331
+ priority={false}
332
+ />
333
+ );
334
+ };
335
+
336
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
337
+ const src = useAttachmentSrc();
338
+
339
+ if (!src) return children;
340
+
341
+ return (
342
+ <Dialog>
343
+ <DialogTrigger
344
+ className="aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50"
345
+ asChild
346
+ >
347
+ {children}
348
+ </DialogTrigger>
349
+ <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">
350
+ <DialogTitle className="aui-sr-only sr-only">
351
+ Image Attachment Preview
352
+ </DialogTitle>
353
+ <div className="aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background">
354
+ <AttachmentPreview src={src} />
355
+ </div>
356
+ </DialogContent>
357
+ </Dialog>
358
+ );
359
+ };
360
+
361
+ const AttachmentThumb: FC = () => {
362
+ const isImage = useAssistantState(
363
+ ({ attachment }) => attachment.type === "image",
364
+ );
365
+ const src = useAttachmentSrc();
366
+
367
+ return (
368
+ <Avatar className="aui-attachment-tile-avatar h-full w-full rounded-none">
369
+ <AvatarImage
370
+ src={src}
371
+ alt="Attachment preview"
372
+ className="aui-attachment-tile-image object-cover"
373
+ />
374
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
375
+ <FileText className="aui-attachment-tile-fallback-icon size-8 text-muted-foreground" />
376
+ </AvatarFallback>
377
+ </Avatar>
378
+ );
379
+ };
380
+
381
+ const AttachmentUI: FC = () => {
382
+ const api = useAssistantApi();
383
+ const isComposer = api.attachment.source === "composer";
384
+
385
+ const isImage = useAssistantState(
386
+ ({ attachment }) => attachment.type === "image",
387
+ );
388
+ const typeLabel = useAssistantState(({ attachment }) => {
389
+ const type = attachment.type;
390
+ switch (type) {
391
+ case "image":
392
+ return "Image";
393
+ case "document":
394
+ return "Document";
395
+ case "file":
396
+ return "File";
397
+ default:
398
+ const _exhaustiveCheck: never = type;
399
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
400
+ }
401
+ });
402
+
403
+ return (
404
+ <Tooltip>
405
+ <AttachmentPrimitive.Root
406
+ className={cn(
407
+ "aui-attachment-root relative",
408
+ isImage &&
409
+ "aui-attachment-root-composer only:[&>#attachment-tile]:size-24",
410
+ )}
411
+ >
412
+ <AttachmentPreviewDialog>
413
+ <TooltipTrigger asChild>
414
+ <div
415
+ className={cn(
416
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
417
+ isComposer &&
418
+ "aui-attachment-tile-composer border-foreground/20",
419
+ )}
420
+ role="button"
421
+ id="attachment-tile"
422
+ aria-label={`${typeLabel} attachment`}
423
+ >
424
+ <AttachmentThumb />
425
+ </div>
426
+ </TooltipTrigger>
427
+ </AttachmentPreviewDialog>
428
+ {isComposer && <AttachmentRemove />}
429
+ </AttachmentPrimitive.Root>
430
+ <TooltipContent side="top">
431
+ <AttachmentPrimitive.Name />
432
+ </TooltipContent>
433
+ </Tooltip>
434
+ );
435
+ };
436
+
437
+ const AttachmentRemove: FC = () => {
438
+ return (
439
+ <AttachmentPrimitive.Remove asChild>
440
+ <TooltipIconButton
441
+ tooltip="Remove file"
442
+ 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"
443
+ side="top"
444
+ >
445
+ <XIcon className="aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" />
446
+ </TooltipIconButton>
447
+ </AttachmentPrimitive.Remove>
448
+ );
449
+ };
450
+
451
+ export const UserMessageAttachments: FC = () => {
452
+ return (
453
+ <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">
454
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
455
+ </div>
456
+ );
457
+ };
458
+
459
+ export const ComposerAttachments: FC = () => {
460
+ return (
461
+ <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">
462
+ <ComposerPrimitive.Attachments
463
+ components={{ Attachment: AttachmentUI }}
464
+ />
465
+ </div>
466
+ );
467
+ };
468
+
469
+ export const ComposerAddAttachment: FC = () => {
470
+ return (
471
+ <ComposerPrimitive.AddAttachment asChild>
472
+ <TooltipIconButton
473
+ tooltip="Add Attachment"
474
+ side="bottom"
475
+ variant="ghost"
476
+ size="icon"
477
+ 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"
478
+ aria-label="Add Attachment"
479
+ >
480
+ <PlusIcon className="aui-attachment-add-icon size-5 stroke-[1.5px]" />
481
+ </TooltipIconButton>
482
+ </ComposerPrimitive.AddAttachment>
483
+ );
484
+ };
485
+
486
+ ```
487
+
247
488
  ## components/assistant-ui/markdown-text.tsx
248
489
 
249
490
  ```tsx
@@ -284,8 +525,10 @@ const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
284
525
  };
285
526
 
286
527
  return (
287
- <div className="mt-4 flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white">
288
- <span className="lowercase [&>span]:text-xs">{language}</span>
528
+ <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">
529
+ <span className="aui-code-header-language lowercase [&>span]:text-xs">
530
+ {language}
531
+ </span>
289
532
  <TooltipIconButton tooltip="Copy" onClick={onCopy}>
290
533
  {!isCopied && <CopyIcon />}
291
534
  {isCopied && <CheckIcon />}
@@ -317,7 +560,7 @@ const defaultComponents = memoizeMarkdownComponents({
317
560
  h1: ({ className, ...props }) => (
318
561
  <h1
319
562
  className={cn(
320
- "mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
563
+ "aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
321
564
  className,
322
565
  )}
323
566
  {...props}
@@ -326,7 +569,7 @@ const defaultComponents = memoizeMarkdownComponents({
326
569
  h2: ({ className, ...props }) => (
327
570
  <h2
328
571
  className={cn(
329
- "mt-8 mb-4 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
572
+ "aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
330
573
  className,
331
574
  )}
332
575
  {...props}
@@ -335,7 +578,7 @@ const defaultComponents = memoizeMarkdownComponents({
335
578
  h3: ({ className, ...props }) => (
336
579
  <h3
337
580
  className={cn(
338
- "mt-6 mb-4 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
581
+ "aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
339
582
  className,
340
583
  )}
341
584
  {...props}
@@ -344,7 +587,7 @@ const defaultComponents = memoizeMarkdownComponents({
344
587
  h4: ({ className, ...props }) => (
345
588
  <h4
346
589
  className={cn(
347
- "mt-6 mb-4 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
590
+ "aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
348
591
  className,
349
592
  )}
350
593
  {...props}
@@ -353,7 +596,7 @@ const defaultComponents = memoizeMarkdownComponents({
353
596
  h5: ({ className, ...props }) => (
354
597
  <h5
355
598
  className={cn(
356
- "my-4 text-lg font-semibold first:mt-0 last:mb-0",
599
+ "aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0",
357
600
  className,
358
601
  )}
359
602
  {...props}
@@ -361,20 +604,26 @@ const defaultComponents = memoizeMarkdownComponents({
361
604
  ),
362
605
  h6: ({ className, ...props }) => (
363
606
  <h6
364
- className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
607
+ className={cn(
608
+ "aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0",
609
+ className,
610
+ )}
365
611
  {...props}
366
612
  />
367
613
  ),
368
614
  p: ({ className, ...props }) => (
369
615
  <p
370
- className={cn("mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
616
+ className={cn(
617
+ "aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0",
618
+ className,
619
+ )}
371
620
  {...props}
372
621
  />
373
622
  ),
374
623
  a: ({ className, ...props }) => (
375
624
  <a
376
625
  className={cn(
377
- "text-primary font-medium underline underline-offset-4",
626
+ "aui-md-a font-medium text-primary underline underline-offset-4",
378
627
  className,
379
628
  )}
380
629
  {...props}
@@ -382,29 +631,29 @@ const defaultComponents = memoizeMarkdownComponents({
382
631
  ),
383
632
  blockquote: ({ className, ...props }) => (
384
633
  <blockquote
385
- className={cn("border-l-2 pl-6 italic", className)}
634
+ className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
386
635
  {...props}
387
636
  />
388
637
  ),
389
638
  ul: ({ className, ...props }) => (
390
639
  <ul
391
- className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
640
+ className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)}
392
641
  {...props}
393
642
  />
394
643
  ),
395
644
  ol: ({ className, ...props }) => (
396
645
  <ol
397
- className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
646
+ className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)}
398
647
  {...props}
399
648
  />
400
649
  ),
401
650
  hr: ({ className, ...props }) => (
402
- <hr className={cn("my-5 border-b", className)} {...props} />
651
+ <hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
403
652
  ),
404
653
  table: ({ className, ...props }) => (
405
654
  <table
406
655
  className={cn(
407
- "my-5 w-full border-separate border-spacing-0 overflow-y-auto",
656
+ "aui-md-table my-5 w-full border-separate border-spacing-0 overflow-y-auto",
408
657
  className,
409
658
  )}
410
659
  {...props}
@@ -413,7 +662,7 @@ const defaultComponents = memoizeMarkdownComponents({
413
662
  th: ({ className, ...props }) => (
414
663
  <th
415
664
  className={cn(
416
- "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",
665
+ "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",
417
666
  className,
418
667
  )}
419
668
  {...props}
@@ -422,7 +671,7 @@ const defaultComponents = memoizeMarkdownComponents({
422
671
  td: ({ className, ...props }) => (
423
672
  <td
424
673
  className={cn(
425
- "border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
674
+ "aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
426
675
  className,
427
676
  )}
428
677
  {...props}
@@ -431,7 +680,7 @@ const defaultComponents = memoizeMarkdownComponents({
431
680
  tr: ({ className, ...props }) => (
432
681
  <tr
433
682
  className={cn(
434
- "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",
683
+ "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",
435
684
  className,
436
685
  )}
437
686
  {...props}
@@ -439,14 +688,14 @@ const defaultComponents = memoizeMarkdownComponents({
439
688
  ),
440
689
  sup: ({ className, ...props }) => (
441
690
  <sup
442
- className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
691
+ className={cn("aui-md-sup [&>a]:text-xs [&>a]:no-underline", className)}
443
692
  {...props}
444
693
  />
445
694
  ),
446
695
  pre: ({ className, ...props }) => (
447
696
  <pre
448
697
  className={cn(
449
- "overflow-x-auto !rounded-t-none rounded-b-lg bg-black p-4 text-white",
698
+ "aui-md-pre overflow-x-auto rounded-t-none! rounded-b-lg bg-black p-4 text-white",
450
699
  className,
451
700
  )}
452
701
  {...props}
@@ -457,7 +706,8 @@ const defaultComponents = memoizeMarkdownComponents({
457
706
  return (
458
707
  <code
459
708
  className={cn(
460
- !isCodeBlock && "bg-muted rounded border font-semibold",
709
+ !isCodeBlock &&
710
+ "aui-md-inline-code rounded border bg-muted font-semibold",
461
711
  className,
462
712
  )}
463
713
  {...props}
@@ -472,59 +722,67 @@ const defaultComponents = memoizeMarkdownComponents({
472
722
  ## components/assistant-ui/thread.tsx
473
723
 
474
724
  ```tsx
725
+ import {
726
+ ComposerAddAttachment,
727
+ ComposerAttachments,
728
+ UserMessageAttachments,
729
+ } from "@/components/assistant-ui/attachment";
730
+ import { MarkdownText } from "@/components/assistant-ui/markdown-text";
731
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
732
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
733
+ import { Button } from "@/components/ui/button";
734
+ import { cn } from "@/lib/utils";
475
735
  import {
476
736
  ActionBarPrimitive,
737
+ AssistantIf,
477
738
  BranchPickerPrimitive,
478
739
  ComposerPrimitive,
479
740
  ErrorPrimitive,
480
741
  MessagePrimitive,
481
742
  ThreadPrimitive,
482
743
  } from "@assistant-ui/react";
483
- import type { FC } from "react";
484
744
  import {
485
745
  ArrowDownIcon,
746
+ ArrowUpIcon,
486
747
  CheckIcon,
487
748
  ChevronLeftIcon,
488
749
  ChevronRightIcon,
489
750
  CopyIcon,
751
+ DownloadIcon,
490
752
  PencilIcon,
491
753
  RefreshCwIcon,
492
- SendHorizontalIcon,
754
+ SquareIcon,
493
755
  } from "lucide-react";
494
- import { cn } from "@/lib/utils";
495
-
496
- import { Button } from "@/components/ui/button";
497
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
498
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
499
- import { ToolFallback } from "./tool-fallback";
756
+ import type { FC } from "react";
500
757
 
501
758
  export const Thread: FC = () => {
502
759
  return (
503
760
  <ThreadPrimitive.Root
504
- className="text-foreground bg-background box-border flex h-full flex-col overflow-hidden"
761
+ className="aui-root aui-thread-root @container flex h-full flex-col bg-background"
505
762
  style={{
506
- ["--thread-max-width" as string]: "42rem",
763
+ ["--thread-max-width" as string]: "44rem",
507
764
  }}
508
765
  >
509
- <ThreadPrimitive.Viewport className="flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8">
510
- <ThreadWelcome />
766
+ <ThreadPrimitive.Viewport
767
+ turnAnchor="top"
768
+ className="aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth px-4 pt-4"
769
+ >
770
+ <AssistantIf condition={({ thread }) => thread.isEmpty}>
771
+ <ThreadWelcome />
772
+ </AssistantIf>
511
773
 
512
774
  <ThreadPrimitive.Messages
513
775
  components={{
514
- UserMessage: UserMessage,
515
- EditComposer: EditComposer,
516
- AssistantMessage: AssistantMessage,
776
+ UserMessage,
777
+ EditComposer,
778
+ AssistantMessage,
517
779
  }}
518
780
  />
519
781
 
520
- <ThreadPrimitive.If empty={false}>
521
- <div className="min-h-8 flex-grow" />
522
- </ThreadPrimitive.If>
523
-
524
- <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">
782
+ <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">
525
783
  <ThreadScrollToBottom />
526
784
  <Composer />
527
- </div>
785
+ </ThreadPrimitive.ViewportFooter>
528
786
  </ThreadPrimitive.Viewport>
529
787
  </ThreadPrimitive.Root>
530
788
  );
@@ -536,7 +794,7 @@ const ThreadScrollToBottom: FC = () => {
536
794
  <TooltipIconButton
537
795
  tooltip="Scroll to bottom"
538
796
  variant="outline"
539
- className="absolute -top-8 rounded-full disabled:invisible"
797
+ 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"
540
798
  >
541
799
  <ArrowDownIcon />
542
800
  </TooltipIconButton>
@@ -546,179 +804,177 @@ const ThreadScrollToBottom: FC = () => {
546
804
 
547
805
  const ThreadWelcome: FC = () => {
548
806
  return (
549
- <ThreadPrimitive.Empty>
550
- <div className="flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col">
551
- <div className="flex w-full flex-grow flex-col items-center justify-center">
552
- <p className="mt-4 font-medium">How can I help you today?</p>
807
+ <div className="aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col">
808
+ <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
809
+ <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
810
+ <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in font-semibold text-2xl duration-200">
811
+ Hello there!
812
+ </h1>
813
+ <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">
814
+ How can I help you today?
815
+ </p>
553
816
  </div>
554
- <ThreadWelcomeSuggestions />
555
817
  </div>
556
- </ThreadPrimitive.Empty>
818
+ <ThreadSuggestions />
819
+ </div>
557
820
  );
558
821
  };
559
822
 
560
- const ThreadWelcomeSuggestions: FC = () => {
823
+ const SUGGESTIONS = [
824
+ {
825
+ title: "What's the weather",
826
+ label: "in San Francisco?",
827
+ prompt: "What's the weather in San Francisco?",
828
+ },
829
+ {
830
+ title: "Explain React hooks",
831
+ label: "like useState and useEffect",
832
+ prompt: "Explain React hooks like useState and useEffect",
833
+ },
834
+ ] as const;
835
+
836
+ const ThreadSuggestions: FC = () => {
561
837
  return (
562
- <div className="mt-3 flex w-full items-stretch justify-center gap-4">
563
- <ThreadPrimitive.Suggestion
564
- 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"
565
- prompt="What is the weather in Tokyo?"
566
- method="replace"
567
- autoSend
568
- >
569
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
570
- What is the weather in Tokyo?
571
- </span>
572
- </ThreadPrimitive.Suggestion>
573
- <ThreadPrimitive.Suggestion
574
- 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"
575
- prompt="What is assistant-ui?"
576
- method="replace"
577
- autoSend
578
- >
579
- <span className="line-clamp-2 text-sm font-semibold text-ellipsis">
580
- What is assistant-ui?
581
- </span>
582
- </ThreadPrimitive.Suggestion>
838
+ <div className="aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4">
839
+ {SUGGESTIONS.map((suggestion, index) => (
840
+ <div
841
+ key={suggestion.prompt}
842
+ 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"
843
+ style={{ animationDelay: `${100 + index * 50}ms` }}
844
+ >
845
+ <ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
846
+ <Button
847
+ variant="ghost"
848
+ 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"
849
+ aria-label={suggestion.prompt}
850
+ >
851
+ <span className="aui-thread-welcome-suggestion-text-1 font-medium">
852
+ {suggestion.title}
853
+ </span>
854
+ <span className="aui-thread-welcome-suggestion-text-2 text-muted-foreground">
855
+ {suggestion.label}
856
+ </span>
857
+ </Button>
858
+ </ThreadPrimitive.Suggestion>
859
+ </div>
860
+ ))}
583
861
  </div>
584
862
  );
585
863
  };
586
864
 
587
865
  const Composer: FC = () => {
588
866
  return (
589
- <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">
590
- <ComposerPrimitive.Input
591
- rows={1}
592
- autoFocus
593
- placeholder="Write a message..."
594
- 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"
595
- />
596
- <ComposerAction />
867
+ <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
868
+ <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">
869
+ <ComposerAttachments />
870
+ <ComposerPrimitive.Input
871
+ placeholder="Send a message..."
872
+ 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"
873
+ rows={1}
874
+ autoFocus
875
+ aria-label="Message input"
876
+ />
877
+ <ComposerAction />
878
+ </ComposerPrimitive.AttachmentDropzone>
597
879
  </ComposerPrimitive.Root>
598
880
  );
599
881
  };
600
882
 
601
883
  const ComposerAction: FC = () => {
602
884
  return (
603
- <>
604
- <ThreadPrimitive.If running={false}>
885
+ <div className="aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-between">
886
+ <ComposerAddAttachment />
887
+
888
+ <AssistantIf condition={({ thread }) => !thread.isRunning}>
605
889
  <ComposerPrimitive.Send asChild>
606
890
  <TooltipIconButton
607
- tooltip="Send"
891
+ tooltip="Send message"
892
+ side="bottom"
893
+ type="submit"
608
894
  variant="default"
609
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
895
+ size="icon"
896
+ className="aui-composer-send size-8 rounded-full"
897
+ aria-label="Send message"
610
898
  >
611
- <SendHorizontalIcon />
899
+ <ArrowUpIcon className="aui-composer-send-icon size-4" />
612
900
  </TooltipIconButton>
613
901
  </ComposerPrimitive.Send>
614
- </ThreadPrimitive.If>
615
- <ThreadPrimitive.If running>
902
+ </AssistantIf>
903
+
904
+ <AssistantIf condition={({ thread }) => thread.isRunning}>
616
905
  <ComposerPrimitive.Cancel asChild>
617
- <TooltipIconButton
618
- tooltip="Cancel"
906
+ <Button
907
+ type="button"
619
908
  variant="default"
620
- className="my-2.5 size-8 p-2 transition-opacity ease-in"
909
+ size="icon"
910
+ className="aui-composer-cancel size-8 rounded-full"
911
+ aria-label="Stop generating"
621
912
  >
622
- <CircleStopIcon />
623
- </TooltipIconButton>
913
+ <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
914
+ </Button>
624
915
  </ComposerPrimitive.Cancel>
625
- </ThreadPrimitive.If>
626
- </>
627
- );
628
- };
629
-
630
- const UserMessage: FC = () => {
631
- return (
632
- <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">
633
- <UserActionBar />
634
-
635
- <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">
636
- <MessagePrimitive.Parts />
637
- </div>
638
-
639
- <BranchPicker className="col-span-full col-start-1 row-start-3 -mr-1 justify-end" />
640
- </MessagePrimitive.Root>
641
- );
642
- };
643
-
644
- const UserActionBar: FC = () => {
645
- return (
646
- <ActionBarPrimitive.Root
647
- hideWhenRunning
648
- autohide="not-last"
649
- className="col-start-1 row-start-2 mt-2.5 mr-3 flex flex-col items-end"
650
- >
651
- <ActionBarPrimitive.Edit asChild>
652
- <TooltipIconButton tooltip="Edit">
653
- <PencilIcon />
654
- </TooltipIconButton>
655
- </ActionBarPrimitive.Edit>
656
- </ActionBarPrimitive.Root>
916
+ </AssistantIf>
917
+ </div>
657
918
  );
658
919
  };
659
920
 
660
- const EditComposer: FC = () => {
921
+ const MessageError: FC = () => {
661
922
  return (
662
- <ComposerPrimitive.Root className="bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl">
663
- <ComposerPrimitive.Input className="text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none" />
664
-
665
- <div className="mx-3 mb-3 flex items-center justify-center gap-2 self-end">
666
- <ComposerPrimitive.Cancel asChild>
667
- <Button variant="ghost">Cancel</Button>
668
- </ComposerPrimitive.Cancel>
669
- <ComposerPrimitive.Send asChild>
670
- <Button>Send</Button>
671
- </ComposerPrimitive.Send>
672
- </div>
673
- </ComposerPrimitive.Root>
923
+ <MessagePrimitive.Error>
924
+ <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">
925
+ <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
926
+ </ErrorPrimitive.Root>
927
+ </MessagePrimitive.Error>
674
928
  );
675
929
  };
676
930
 
677
931
  const AssistantMessage: FC = () => {
678
932
  return (
679
- <MessagePrimitive.Root className="relative grid w-full max-w-[var(--thread-max-width)] grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] py-4">
680
- <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">
933
+ <MessagePrimitive.Root
934
+ 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"
935
+ data-role="assistant"
936
+ >
937
+ <div className="aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed">
681
938
  <MessagePrimitive.Parts
682
- components={{ Text: MarkdownText, tools: { Fallback: ToolFallback } }}
939
+ components={{
940
+ Text: MarkdownText,
941
+ tools: { Fallback: ToolFallback },
942
+ }}
683
943
  />
684
944
  <MessageError />
685
945
  </div>
686
946
 
687
- <AssistantActionBar />
688
-
689
- <BranchPicker className="col-start-2 row-start-2 mr-2 -ml-2" />
947
+ <div className="aui-assistant-message-footer mt-1 ml-2 flex">
948
+ <BranchPicker />
949
+ <AssistantActionBar />
950
+ </div>
690
951
  </MessagePrimitive.Root>
691
952
  );
692
953
  };
693
954
 
694
- const MessageError: FC = () => {
695
- return (
696
- <MessagePrimitive.Error>
697
- <ErrorPrimitive.Root className="border-destructive bg-destructive/10 dark:bg-destructive/5 text-destructive mt-2 rounded-md border p-3 text-sm dark:text-red-200">
698
- <ErrorPrimitive.Message className="line-clamp-2" />
699
- </ErrorPrimitive.Root>
700
- </MessagePrimitive.Error>
701
- );
702
- };
703
-
704
955
  const AssistantActionBar: FC = () => {
705
956
  return (
706
957
  <ActionBarPrimitive.Root
707
958
  hideWhenRunning
708
959
  autohide="not-last"
709
960
  autohideFloat="single-branch"
710
- 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"
961
+ 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"
711
962
  >
712
963
  <ActionBarPrimitive.Copy asChild>
713
964
  <TooltipIconButton tooltip="Copy">
714
- <MessagePrimitive.If copied>
965
+ <AssistantIf condition={({ message }) => message.isCopied}>
715
966
  <CheckIcon />
716
- </MessagePrimitive.If>
717
- <MessagePrimitive.If copied={false}>
967
+ </AssistantIf>
968
+ <AssistantIf condition={({ message }) => !message.isCopied}>
718
969
  <CopyIcon />
719
- </MessagePrimitive.If>
970
+ </AssistantIf>
720
971
  </TooltipIconButton>
721
972
  </ActionBarPrimitive.Copy>
973
+ <ActionBarPrimitive.ExportMarkdown asChild>
974
+ <TooltipIconButton tooltip="Export as Markdown">
975
+ <DownloadIcon />
976
+ </TooltipIconButton>
977
+ </ActionBarPrimitive.ExportMarkdown>
722
978
  <ActionBarPrimitive.Reload asChild>
723
979
  <TooltipIconButton tooltip="Refresh">
724
980
  <RefreshCwIcon />
@@ -728,6 +984,67 @@ const AssistantActionBar: FC = () => {
728
984
  );
729
985
  };
730
986
 
987
+ const UserMessage: FC = () => {
988
+ return (
989
+ <MessagePrimitive.Root
990
+ 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"
991
+ data-role="user"
992
+ >
993
+ <UserMessageAttachments />
994
+
995
+ <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
996
+ <div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
997
+ <MessagePrimitive.Parts />
998
+ </div>
999
+ <div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-1/2 absolute top-1/2 left-0 pr-2">
1000
+ <UserActionBar />
1001
+ </div>
1002
+ </div>
1003
+
1004
+ <BranchPicker className="aui-user-branch-picker -mr-1 col-span-full col-start-1 row-start-3 justify-end" />
1005
+ </MessagePrimitive.Root>
1006
+ );
1007
+ };
1008
+
1009
+ const UserActionBar: FC = () => {
1010
+ return (
1011
+ <ActionBarPrimitive.Root
1012
+ hideWhenRunning
1013
+ autohide="not-last"
1014
+ className="aui-user-action-bar-root flex flex-col items-end"
1015
+ >
1016
+ <ActionBarPrimitive.Edit asChild>
1017
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1018
+ <PencilIcon />
1019
+ </TooltipIconButton>
1020
+ </ActionBarPrimitive.Edit>
1021
+ </ActionBarPrimitive.Root>
1022
+ );
1023
+ };
1024
+
1025
+ const EditComposer: FC = () => {
1026
+ return (
1027
+ <MessagePrimitive.Root className="aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3">
1028
+ <ComposerPrimitive.Root className="aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted">
1029
+ <ComposerPrimitive.Input
1030
+ className="aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none"
1031
+ autoFocus
1032
+ />
1033
+ <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1034
+ <ComposerPrimitive.Cancel asChild>
1035
+ <Button variant="ghost" size="sm">
1036
+ Cancel
1037
+ </Button>
1038
+ </ComposerPrimitive.Cancel>
1039
+ <ComposerPrimitive.Send asChild>
1040
+ <Button size="sm">Update</Button>
1041
+ </ComposerPrimitive.Send>
1042
+ </div>
1043
+ </ComposerPrimitive.Root>
1044
+ </MessagePrimitive.Root>
1045
+ );
1046
+ };
1047
+
731
1048
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
732
1049
  className,
733
1050
  ...rest
@@ -736,7 +1053,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
736
1053
  <BranchPickerPrimitive.Root
737
1054
  hideWhenSingleBranch
738
1055
  className={cn(
739
- "text-muted-foreground inline-flex items-center text-xs",
1056
+ "aui-branch-picker-root -ml-2 mr-2 inline-flex items-center text-muted-foreground text-xs",
740
1057
  className,
741
1058
  )}
742
1059
  {...rest}
@@ -746,7 +1063,7 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
746
1063
  <ChevronLeftIcon />
747
1064
  </TooltipIconButton>
748
1065
  </BranchPickerPrimitive.Previous>
749
- <span className="font-medium">
1066
+ <span className="aui-branch-picker-state font-medium">
750
1067
  <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
751
1068
  </span>
752
1069
  <BranchPickerPrimitive.Next asChild>
@@ -758,56 +1075,93 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
758
1075
  );
759
1076
  };
760
1077
 
761
- const CircleStopIcon = () => {
762
- return (
763
- <svg
764
- xmlns="http://www.w3.org/2000/svg"
765
- viewBox="0 0 16 16"
766
- fill="currentColor"
767
- width="16"
768
- height="16"
769
- >
770
- <rect width="10" height="10" x="3" y="3" rx="2" />
771
- </svg>
772
- );
773
- };
774
-
775
1078
  ```
776
1079
 
777
1080
  ## components/assistant-ui/tool-fallback.tsx
778
1081
 
779
1082
  ```tsx
780
- import { ToolCallMessagePartComponent } from "@assistant-ui/react";
781
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
1083
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1084
+ import {
1085
+ CheckIcon,
1086
+ ChevronDownIcon,
1087
+ ChevronUpIcon,
1088
+ XCircleIcon,
1089
+ } from "lucide-react";
782
1090
  import { useState } from "react";
783
- import { Button } from "../ui/button";
1091
+ import { Button } from "@/components/ui/button";
1092
+ import { cn } from "@/lib/utils";
784
1093
 
785
1094
  export const ToolFallback: ToolCallMessagePartComponent = ({
786
1095
  toolName,
787
1096
  argsText,
788
1097
  result,
1098
+ status,
789
1099
  }) => {
790
1100
  const [isCollapsed, setIsCollapsed] = useState(true);
1101
+
1102
+ const isCancelled =
1103
+ status?.type === "incomplete" && status.reason === "cancelled";
1104
+ const cancelledReason =
1105
+ isCancelled && status.error
1106
+ ? typeof status.error === "string"
1107
+ ? status.error
1108
+ : JSON.stringify(status.error)
1109
+ : null;
1110
+
791
1111
  return (
792
- <div className="mb-4 flex w-full flex-col gap-3 rounded-lg border py-3">
793
- <div className="flex items-center gap-2 px-4">
794
- <CheckIcon className="size-4" />
795
- <p className="flex-grow">
796
- Used tool: <b>{toolName}</b>
1112
+ <div
1113
+ className={cn(
1114
+ "aui-tool-fallback-root mb-4 flex w-full flex-col gap-3 rounded-lg border py-3",
1115
+ isCancelled && "border-muted-foreground/30 bg-muted/30",
1116
+ )}
1117
+ >
1118
+ <div className="aui-tool-fallback-header flex items-center gap-2 px-4">
1119
+ {isCancelled ? (
1120
+ <XCircleIcon className="aui-tool-fallback-icon size-4 text-muted-foreground" />
1121
+ ) : (
1122
+ <CheckIcon className="aui-tool-fallback-icon size-4" />
1123
+ )}
1124
+ <p
1125
+ className={cn(
1126
+ "aui-tool-fallback-title grow",
1127
+ isCancelled && "text-muted-foreground line-through",
1128
+ )}
1129
+ >
1130
+ {isCancelled ? "Cancelled tool: " : "Used tool: "}
1131
+ <b>{toolName}</b>
797
1132
  </p>
798
1133
  <Button onClick={() => setIsCollapsed(!isCollapsed)}>
799
1134
  {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
800
1135
  </Button>
801
1136
  </div>
802
1137
  {!isCollapsed && (
803
- <div className="flex flex-col gap-2 border-t pt-2">
804
- <div className="px-4">
805
- <pre className="whitespace-pre-wrap">{argsText}</pre>
1138
+ <div className="aui-tool-fallback-content flex flex-col gap-2 border-t pt-2">
1139
+ {cancelledReason && (
1140
+ <div className="aui-tool-fallback-cancelled-root px-4">
1141
+ <p className="aui-tool-fallback-cancelled-header font-semibold text-muted-foreground">
1142
+ Cancelled reason:
1143
+ </p>
1144
+ <p className="aui-tool-fallback-cancelled-reason text-muted-foreground">
1145
+ {cancelledReason}
1146
+ </p>
1147
+ </div>
1148
+ )}
1149
+ <div
1150
+ className={cn(
1151
+ "aui-tool-fallback-args-root px-4",
1152
+ isCancelled && "opacity-60",
1153
+ )}
1154
+ >
1155
+ <pre className="aui-tool-fallback-args-value whitespace-pre-wrap">
1156
+ {argsText}
1157
+ </pre>
806
1158
  </div>
807
- {result !== undefined && (
808
- <div className="border-t border-dashed px-4 pt-2">
809
- <p className="font-semibold">Result:</p>
810
- <pre className="whitespace-pre-wrap">
1159
+ {!isCancelled && result !== undefined && (
1160
+ <div className="aui-tool-fallback-result-root border-t border-dashed px-4 pt-2">
1161
+ <p className="aui-tool-fallback-result-header font-semibold">
1162
+ Result:
1163
+ </p>
1164
+ <pre className="aui-tool-fallback-result-content whitespace-pre-wrap">
811
1165
  {typeof result === "string"
812
1166
  ? result
813
1167
  : JSON.stringify(result, null, 2)}
@@ -827,7 +1181,7 @@ export const ToolFallback: ToolCallMessagePartComponent = ({
827
1181
  ```tsx
828
1182
  "use client";
829
1183
 
830
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1184
+ import { ComponentPropsWithRef, forwardRef } from "react";
831
1185
  import { Slottable } from "@radix-ui/react-slot";
832
1186
 
833
1187
  import {
@@ -838,7 +1192,7 @@ import {
838
1192
  import { Button } from "@/components/ui/button";
839
1193
  import { cn } from "@/lib/utils";
840
1194
 
841
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1195
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
842
1196
  tooltip: string;
843
1197
  side?: "top" | "bottom" | "left" | "right";
844
1198
  };
@@ -854,11 +1208,11 @@ export const TooltipIconButton = forwardRef<
854
1208
  variant="ghost"
855
1209
  size="icon"
856
1210
  {...rest}
857
- className={cn("size-6 p-1", className)}
1211
+ className={cn("aui-button-icon size-6 p-1", className)}
858
1212
  ref={ref}
859
1213
  >
860
1214
  <Slottable>{children}</Slottable>
861
- <span className="sr-only">{tooltip}</span>
1215
+ <span className="aui-sr-only sr-only">{tooltip}</span>
862
1216
  </Button>
863
1217
  </TooltipTrigger>
864
1218
  <TooltipContent side={side}>{tooltip}</TooltipContent>
@@ -870,6 +1224,62 @@ TooltipIconButton.displayName = "TooltipIconButton";
870
1224
 
871
1225
  ```
872
1226
 
1227
+ ## components/ui/avatar.tsx
1228
+
1229
+ ```tsx
1230
+ "use client";
1231
+
1232
+ import * as React from "react";
1233
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
1234
+
1235
+ import { cn } from "@/lib/utils";
1236
+
1237
+ const Avatar = React.forwardRef<
1238
+ React.ElementRef<typeof AvatarPrimitive.Root>,
1239
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
1240
+ >(({ className, ...props }, ref) => (
1241
+ <AvatarPrimitive.Root
1242
+ ref={ref}
1243
+ className={cn(
1244
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
1245
+ className,
1246
+ )}
1247
+ {...props}
1248
+ />
1249
+ ));
1250
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
1251
+
1252
+ const AvatarImage = React.forwardRef<
1253
+ React.ElementRef<typeof AvatarPrimitive.Image>,
1254
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
1255
+ >(({ className, ...props }, ref) => (
1256
+ <AvatarPrimitive.Image
1257
+ ref={ref}
1258
+ className={cn("aspect-square h-full w-full", className)}
1259
+ {...props}
1260
+ />
1261
+ ));
1262
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
1263
+
1264
+ const AvatarFallback = React.forwardRef<
1265
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
1266
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
1267
+ >(({ className, ...props }, ref) => (
1268
+ <AvatarPrimitive.Fallback
1269
+ ref={ref}
1270
+ className={cn(
1271
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
1272
+ className,
1273
+ )}
1274
+ {...props}
1275
+ />
1276
+ ));
1277
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
1278
+
1279
+ export { Avatar, AvatarImage, AvatarFallback };
1280
+
1281
+ ```
1282
+
873
1283
  ## components/ui/button.tsx
874
1284
 
875
1285
  ```tsx
@@ -880,16 +1290,16 @@ import { cva, type VariantProps } from "class-variance-authority";
880
1290
  import { cn } from "@/lib/utils";
881
1291
 
882
1292
  const buttonVariants = cva(
883
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
1293
+ "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",
884
1294
  {
885
1295
  variants: {
886
1296
  variant: {
887
1297
  default:
888
1298
  "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
889
1299
  destructive:
890
- "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1300
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
891
1301
  outline:
892
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
1302
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
893
1303
  secondary:
894
1304
  "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
895
1305
  ghost:
@@ -898,7 +1308,7 @@ const buttonVariants = cva(
898
1308
  },
899
1309
  size: {
900
1310
  default: "h-9 px-4 py-2 has-[>svg]:px-3",
901
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
1311
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
902
1312
  lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
903
1313
  icon: "size-9",
904
1314
  },
@@ -935,6 +1345,147 @@ export { Button, buttonVariants };
935
1345
 
936
1346
  ```
937
1347
 
1348
+ ## components/ui/dialog.tsx
1349
+
1350
+ ```tsx
1351
+ "use client";
1352
+
1353
+ import * as React from "react";
1354
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
1355
+ import { XIcon } from "lucide-react";
1356
+
1357
+ import { cn } from "@/lib/utils";
1358
+
1359
+ function Dialog({
1360
+ ...props
1361
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
1362
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
1363
+ }
1364
+
1365
+ function DialogTrigger({
1366
+ ...props
1367
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
1368
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
1369
+ }
1370
+
1371
+ function DialogPortal({
1372
+ ...props
1373
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
1374
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
1375
+ }
1376
+
1377
+ function DialogClose({
1378
+ ...props
1379
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
1380
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
1381
+ }
1382
+
1383
+ function DialogOverlay({
1384
+ className,
1385
+ ...props
1386
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
1387
+ return (
1388
+ <DialogPrimitive.Overlay
1389
+ data-slot="dialog-overlay"
1390
+ className={cn(
1391
+ "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",
1392
+ className,
1393
+ )}
1394
+ {...props}
1395
+ />
1396
+ );
1397
+ }
1398
+
1399
+ function DialogContent({
1400
+ className,
1401
+ children,
1402
+ ...props
1403
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
1404
+ return (
1405
+ <DialogPortal data-slot="dialog-portal">
1406
+ <DialogOverlay />
1407
+ <DialogPrimitive.Content
1408
+ data-slot="dialog-content"
1409
+ className={cn(
1410
+ "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",
1411
+ className,
1412
+ )}
1413
+ {...props}
1414
+ >
1415
+ {children}
1416
+ <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">
1417
+ <XIcon />
1418
+ <span className="sr-only">Close</span>
1419
+ </DialogPrimitive.Close>
1420
+ </DialogPrimitive.Content>
1421
+ </DialogPortal>
1422
+ );
1423
+ }
1424
+
1425
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
1426
+ return (
1427
+ <div
1428
+ data-slot="dialog-header"
1429
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
1430
+ {...props}
1431
+ />
1432
+ );
1433
+ }
1434
+
1435
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
1436
+ return (
1437
+ <div
1438
+ data-slot="dialog-footer"
1439
+ className={cn(
1440
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1441
+ className,
1442
+ )}
1443
+ {...props}
1444
+ />
1445
+ );
1446
+ }
1447
+
1448
+ function DialogTitle({
1449
+ className,
1450
+ ...props
1451
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
1452
+ return (
1453
+ <DialogPrimitive.Title
1454
+ data-slot="dialog-title"
1455
+ className={cn("font-semibold text-lg leading-none", className)}
1456
+ {...props}
1457
+ />
1458
+ );
1459
+ }
1460
+
1461
+ function DialogDescription({
1462
+ className,
1463
+ ...props
1464
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
1465
+ return (
1466
+ <DialogPrimitive.Description
1467
+ data-slot="dialog-description"
1468
+ className={cn("text-muted-foreground text-sm", className)}
1469
+ {...props}
1470
+ />
1471
+ );
1472
+ }
1473
+
1474
+ export {
1475
+ Dialog,
1476
+ DialogClose,
1477
+ DialogContent,
1478
+ DialogDescription,
1479
+ DialogFooter,
1480
+ DialogHeader,
1481
+ DialogOverlay,
1482
+ DialogPortal,
1483
+ DialogTitle,
1484
+ DialogTrigger,
1485
+ };
1486
+
1487
+ ```
1488
+
938
1489
  ## components/ui/tooltip.tsx
939
1490
 
940
1491
  ```tsx
@@ -986,13 +1537,13 @@ function TooltipContent({
986
1537
  data-slot="tooltip-content"
987
1538
  sideOffset={sideOffset}
988
1539
  className={cn(
989
- "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",
1540
+ "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",
990
1541
  className,
991
1542
  )}
992
1543
  {...props}
993
1544
  >
994
1545
  {children}
995
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
1546
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
996
1547
  </TooltipPrimitive.Content>
997
1548
  </TooltipPrimitive.Portal>
998
1549
  );
@@ -1035,31 +1586,34 @@ export default nextConfig;
1035
1586
  "version": "0.0.0",
1036
1587
  "type": "module",
1037
1588
  "dependencies": {
1038
- "@ai-sdk/openai": "^2.0.68",
1039
- "@ai-sdk/react": "^2.0.93",
1589
+ "@ai-sdk/openai": "^2.0.77",
1590
+ "@ai-sdk/react": "^2.0.107",
1040
1591
  "@assistant-ui/react": "workspace:^",
1041
1592
  "@assistant-ui/react-ai-sdk": "workspace:*",
1042
1593
  "@assistant-ui/react-markdown": "workspace:^",
1594
+ "@radix-ui/react-avatar": "^1.1.11",
1595
+ "@radix-ui/react-dialog": "^1.1.15",
1043
1596
  "@radix-ui/react-slot": "^1.2.4",
1044
1597
  "@radix-ui/react-tooltip": "^1.2.8",
1045
1598
  "@tailwindcss/postcss": "^4.1.17",
1046
- "ai": "^5.0.93",
1599
+ "ai": "^5.0.107",
1047
1600
  "class-variance-authority": "^0.7.1",
1048
1601
  "clsx": "^2.1.1",
1049
- "lucide-react": "^0.554.0",
1050
- "next": "16.0.3",
1602
+ "lucide-react": "^0.556.0",
1603
+ "next": "16.0.7",
1051
1604
  "postcss": "^8.5.6",
1052
- "react": "19.2.0",
1053
- "react-dom": "19.2.0",
1605
+ "react": "19.2.1",
1606
+ "react-dom": "19.2.1",
1054
1607
  "remark-gfm": "^4.0.1",
1055
1608
  "tailwind-merge": "^3.4.0",
1056
1609
  "tailwindcss": "^4.1.17",
1057
- "zod": "^4.1.12"
1610
+ "zod": "^4.1.13",
1611
+ "zustand": "^5.0.9"
1058
1612
  },
1059
1613
  "devDependencies": {
1060
1614
  "@assistant-ui/x-buildutils": "workspace:*",
1061
1615
  "@types/node": "^24.10.1",
1062
- "@types/react": "^19.2.5",
1616
+ "@types/react": "^19.2.7",
1063
1617
  "@types/react-dom": "^19.2.3",
1064
1618
  "tw-animate-css": "^1.4.0",
1065
1619
  "typescript": "^5.9.3"
@@ -1067,8 +1621,7 @@ export default nextConfig;
1067
1621
  "scripts": {
1068
1622
  "dev": "next dev",
1069
1623
  "build": "next build",
1070
- "start": "next start",
1071
- "lint": "eslint ."
1624
+ "start": "next start"
1072
1625
  }
1073
1626
  }
1074
1627