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