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