@assistant-ui/mcp-docs-server 0.1.10 → 0.1.11

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 (26) hide show
  1. package/.docs/organized/code-examples/with-ai-sdk-v5.md +21 -21
  2. package/.docs/organized/code-examples/with-assistant-transport.md +24 -24
  3. package/.docs/organized/code-examples/with-cloud.md +18 -18
  4. package/.docs/organized/code-examples/with-external-store.md +15 -15
  5. package/.docs/organized/code-examples/with-ffmpeg.md +18 -18
  6. package/.docs/organized/code-examples/with-langgraph.md +18 -18
  7. package/.docs/organized/code-examples/with-parent-id-grouping.md +15 -15
  8. package/.docs/organized/code-examples/with-react-hook-form.md +23 -23
  9. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +22 -2
  10. package/.docs/raw/docs/getting-started.mdx +541 -150
  11. package/.docs/raw/docs/guides/Attachments.mdx +21 -0
  12. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +2 -2
  13. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +218 -0
  14. package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -24
  15. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +8 -3
  16. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +1 -1
  17. package/.docs/raw/docs/ui/AssistantModal.mdx +21 -0
  18. package/.docs/raw/docs/ui/AssistantSidebar.mdx +21 -0
  19. package/.docs/raw/docs/ui/Attachment.mdx +21 -0
  20. package/.docs/raw/docs/ui/Markdown.mdx +22 -1
  21. package/.docs/raw/docs/ui/Mermaid.mdx +22 -1
  22. package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +43 -2
  23. package/.docs/raw/docs/ui/Thread.mdx +9 -3
  24. package/.docs/raw/docs/ui/ThreadList.mdx +48 -2
  25. package/.docs/raw/docs/ui/ToolFallback.mdx +21 -0
  26. package/package.json +5 -5
@@ -96,12 +96,16 @@ npm install \
96
96
  @assistant-ui/react \
97
97
  @assistant-ui/react-markdown \
98
98
  @assistant-ui/styles \
99
- @radix-ui/react-tooltip \
99
+ @radix-ui/react-avatar \
100
+ @radix-ui/react-dialog \
100
101
  @radix-ui/react-slot \
102
+ @radix-ui/react-tooltip \
103
+ class-variance-authority \
104
+ clsx \
101
105
  lucide-react \
106
+ motion \
102
107
  remark-gfm \
103
- class-variance-authority \
104
- clsx
108
+ zustand
105
109
  ```
106
110
 
107
111
  </Step>
@@ -192,59 +196,75 @@ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
192
196
  ```
193
197
 
194
198
  ```tsx title="components/assistant-ui/thread.tsx"
195
- import {
196
- ActionBarPrimitive,
197
- BranchPickerPrimitive,
198
- ComposerPrimitive,
199
- MessagePrimitive,
200
- ThreadPrimitive,
201
- } from "@assistant-ui/react";
202
- import type { FC } from "react";
203
199
  import {
204
200
  ArrowDownIcon,
201
+ ArrowUpIcon,
205
202
  CheckIcon,
206
203
  ChevronLeftIcon,
207
204
  ChevronRightIcon,
208
205
  CopyIcon,
209
206
  PencilIcon,
210
207
  RefreshCwIcon,
211
- SendHorizontalIcon,
208
+ Square,
212
209
  } from "lucide-react";
213
- import { cn } from "@/lib/utils";
210
+
211
+ import {
212
+ ActionBarPrimitive,
213
+ BranchPickerPrimitive,
214
+ ComposerPrimitive,
215
+ ErrorPrimitive,
216
+ MessagePrimitive,
217
+ ThreadPrimitive,
218
+ } from "@assistant-ui/react";
219
+
220
+ import type { FC } from "react";
221
+ import { LazyMotion, MotionConfig, domAnimation } from "motion/react";
222
+ import * as m from "motion/react-m";
214
223
 
215
224
  import { Button } from "@/components/ui/button";
216
225
  import { MarkdownText } from "@/components/assistant-ui/markdown-text";
226
+ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
217
227
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
228
+ import {
229
+ ComposerAddAttachment,
230
+ ComposerAttachments,
231
+ UserMessageAttachments,
232
+ } from "@/components/assistant-ui/attachment";
233
+
234
+ import { cn } from "@/lib/utils";
218
235
 
219
236
  export const Thread: FC = () => {
220
237
  return (
221
- <ThreadPrimitive.Root
222
- className="aui-root aui-thread-root"
223
- style={{
224
- ["--thread-max-width" as string]: "42rem",
225
- }}
226
- >
227
- <ThreadPrimitive.Viewport className="aui-thread-viewport">
228
- <ThreadWelcome />
229
-
230
- <ThreadPrimitive.Messages
231
- components={{
232
- UserMessage: UserMessage,
233
- EditComposer: EditComposer,
234
- AssistantMessage: AssistantMessage,
238
+ <LazyMotion features={domAnimation}>
239
+ <MotionConfig reducedMotion="user">
240
+ <ThreadPrimitive.Root
241
+ className="aui-root aui-thread-root"
242
+ style={{
243
+ ["--thread-max-width" as string]: "44rem",
235
244
  }}
236
- />
237
-
238
- <ThreadPrimitive.If empty={false}>
239
- <div className="aui-thread-viewport-spacer" />
240
- </ThreadPrimitive.If>
241
-
242
- <div className="aui-thread-viewport-footer">
243
- <ThreadScrollToBottom />
244
- <Composer />
245
- </div>
246
- </ThreadPrimitive.Viewport>
247
- </ThreadPrimitive.Root>
245
+ >
246
+ <ThreadPrimitive.Viewport className="aui-thread-viewport">
247
+ <ThreadPrimitive.If empty>
248
+ <ThreadWelcome />
249
+ </ThreadPrimitive.If>
250
+
251
+ <ThreadPrimitive.Messages
252
+ components={{
253
+ UserMessage,
254
+ EditComposer,
255
+ AssistantMessage,
256
+ }}
257
+ />
258
+
259
+ <ThreadPrimitive.If empty={false}>
260
+ <div className="aui-thread-viewport-spacer" />
261
+ </ThreadPrimitive.If>
262
+
263
+ <Composer />
264
+ </ThreadPrimitive.Viewport>
265
+ </ThreadPrimitive.Root>
266
+ </MotionConfig>
267
+ </LazyMotion>
248
268
  );
249
269
  };
250
270
 
@@ -264,144 +284,179 @@ const ThreadScrollToBottom: FC = () => {
264
284
 
265
285
  const ThreadWelcome: FC = () => {
266
286
  return (
267
- <ThreadPrimitive.Empty>
268
- <div className="aui-thread-welcome-root">
269
- <div className="aui-thread-welcome-center">
270
- <p className="aui-thread-welcome-message">
287
+ <div className="aui-thread-welcome-root">
288
+ <div className="aui-thread-welcome-center">
289
+ <div className="aui-thread-welcome-message">
290
+ <m.div
291
+ initial={{ opacity: 0, y: 10 }}
292
+ animate={{ opacity: 1, y: 0 }}
293
+ exit={{ opacity: 0, y: 10 }}
294
+ className="aui-thread-welcome-message-motion-1"
295
+ >
296
+ Hello there!
297
+ </m.div>
298
+ <m.div
299
+ initial={{ opacity: 0, y: 10 }}
300
+ animate={{ opacity: 1, y: 0 }}
301
+ exit={{ opacity: 0, y: 10 }}
302
+ transition={{ delay: 0.1 }}
303
+ className="aui-thread-welcome-message-motion-2"
304
+ >
271
305
  How can I help you today?
272
- </p>
306
+ </m.div>
273
307
  </div>
274
- <ThreadWelcomeSuggestions />
275
308
  </div>
276
- </ThreadPrimitive.Empty>
309
+ <ThreadSuggestions />
310
+ </div>
277
311
  );
278
312
  };
279
313
 
280
- const ThreadWelcomeSuggestions: FC = () => {
314
+ const ThreadSuggestions: FC = () => {
281
315
  return (
282
316
  <div className="aui-thread-welcome-suggestions">
283
- <ThreadPrimitive.Suggestion
284
- className="aui-thread-welcome-suggestion"
285
- prompt="What is the weather in Tokyo?"
286
- send
287
- >
288
- <span className="aui-thread-welcome-suggestion-text">
289
- What is the weather in Tokyo?
290
- </span>
291
- </ThreadPrimitive.Suggestion>
292
- <ThreadPrimitive.Suggestion
293
- className="aui-thread-welcome-suggestion"
294
- prompt="What is assistant-ui?"
295
- send
296
- >
297
- <span className="aui-thread-welcome-suggestion-text">
298
- What is assistant-ui?
299
- </span>
300
- </ThreadPrimitive.Suggestion>
317
+ {[
318
+ {
319
+ title: "What's the weather",
320
+ label: "in San Francisco?",
321
+ action: "What's the weather in San Francisco?",
322
+ },
323
+ {
324
+ title: "Explain React hooks",
325
+ label: "like useState and useEffect",
326
+ action: "Explain React hooks like useState and useEffect",
327
+ },
328
+ {
329
+ title: "Write a SQL query",
330
+ label: "to find top customers",
331
+ action: "Write a SQL query to find top customers",
332
+ },
333
+ {
334
+ title: "Create a meal plan",
335
+ label: "for healthy weight loss",
336
+ action: "Create a meal plan for healthy weight loss",
337
+ },
338
+ ].map((suggestedAction, index) => (
339
+ <m.div
340
+ initial={{ opacity: 0, y: 20 }}
341
+ animate={{ opacity: 1, y: 0 }}
342
+ exit={{ opacity: 0, y: 20 }}
343
+ transition={{ delay: 0.05 * index }}
344
+ key={`suggested-action-${suggestedAction.title}-${index}`}
345
+ className="aui-thread-welcome-suggestion-display"
346
+ >
347
+ <ThreadPrimitive.Suggestion
348
+ prompt={suggestedAction.action}
349
+ send
350
+ asChild
351
+ >
352
+ <Button
353
+ variant="ghost"
354
+ className="aui-thread-welcome-suggestion"
355
+ aria-label={suggestedAction.action}
356
+ >
357
+ <span className="aui-thread-welcome-suggestion-text-1">
358
+ {suggestedAction.title}
359
+ </span>
360
+ <span className="aui-thread-welcome-suggestion-text-2">
361
+ {suggestedAction.label}
362
+ </span>
363
+ </Button>
364
+ </ThreadPrimitive.Suggestion>
365
+ </m.div>
366
+ ))}
301
367
  </div>
302
368
  );
303
369
  };
304
370
 
305
371
  const Composer: FC = () => {
306
372
  return (
307
- <ComposerPrimitive.Root className="aui-composer-root">
308
- <ComposerPrimitive.Input
309
- rows={1}
310
- autoFocus
311
- placeholder="Write a message..."
312
- className="aui-composer-input"
313
- />
314
- <ComposerAction />
315
- </ComposerPrimitive.Root>
373
+ <div className="aui-composer-wrapper">
374
+ <ThreadScrollToBottom />
375
+ <ComposerPrimitive.Root className="aui-composer-root">
376
+ <ComposerAttachments />
377
+ <ComposerPrimitive.Input
378
+ placeholder="Send a message..."
379
+ className="aui-composer-input"
380
+ rows={1}
381
+ autoFocus
382
+ aria-label="Message input"
383
+ />
384
+ <ComposerAction />
385
+ </ComposerPrimitive.Root>
386
+ </div>
316
387
  );
317
388
  };
318
389
 
319
390
  const ComposerAction: FC = () => {
320
391
  return (
321
- <>
392
+ <div className="aui-composer-action-wrapper">
393
+ <ComposerAddAttachment />
394
+
322
395
  <ThreadPrimitive.If running={false}>
323
396
  <ComposerPrimitive.Send asChild>
324
397
  <TooltipIconButton
325
- tooltip="Send"
398
+ tooltip="Send message"
399
+ side="bottom"
400
+ type="submit"
326
401
  variant="default"
402
+ size="icon"
327
403
  className="aui-composer-send"
404
+ aria-label="Send message"
328
405
  >
329
- <SendHorizontalIcon />
406
+ <ArrowUpIcon className="aui-composer-send-icon" />
330
407
  </TooltipIconButton>
331
408
  </ComposerPrimitive.Send>
332
409
  </ThreadPrimitive.If>
410
+
333
411
  <ThreadPrimitive.If running>
334
412
  <ComposerPrimitive.Cancel asChild>
335
- <TooltipIconButton
336
- tooltip="Cancel"
413
+ <Button
414
+ type="button"
337
415
  variant="default"
416
+ size="icon"
338
417
  className="aui-composer-cancel"
418
+ aria-label="Stop generating"
339
419
  >
340
- <CircleStopIcon />
341
- </TooltipIconButton>
420
+ <Square className="aui-composer-cancel-icon" />
421
+ </Button>
342
422
  </ComposerPrimitive.Cancel>
343
423
  </ThreadPrimitive.If>
344
- </>
345
- );
346
- };
347
-
348
- const UserMessage: FC = () => {
349
- return (
350
- <MessagePrimitive.Root className="aui-user-message-root">
351
- <UserActionBar />
352
-
353
- <div className="aui-user-message-content">
354
- <MessagePrimitive.Parts />
355
- </div>
356
-
357
- <BranchPicker className="aui-user-branch-picker" />
358
- </MessagePrimitive.Root>
359
- );
360
- };
361
-
362
- const UserActionBar: FC = () => {
363
- return (
364
- <ActionBarPrimitive.Root
365
- hideWhenRunning
366
- autohide="not-last"
367
- className="aui-user-action-bar-root"
368
- >
369
- <ActionBarPrimitive.Edit asChild>
370
- <TooltipIconButton tooltip="Edit">
371
- <PencilIcon />
372
- </TooltipIconButton>
373
- </ActionBarPrimitive.Edit>
374
- </ActionBarPrimitive.Root>
424
+ </div>
375
425
  );
376
426
  };
377
427
 
378
- const EditComposer: FC = () => {
428
+ const MessageError: FC = () => {
379
429
  return (
380
- <ComposerPrimitive.Root className="aui-edit-composer-root">
381
- <ComposerPrimitive.Input className="aui-edit-composer-input" />
382
-
383
- <div className="aui-edit-composer-footer">
384
- <ComposerPrimitive.Cancel asChild>
385
- <Button variant="ghost">Cancel</Button>
386
- </ComposerPrimitive.Cancel>
387
- <ComposerPrimitive.Send asChild>
388
- <Button>Send</Button>
389
- </ComposerPrimitive.Send>
390
- </div>
391
- </ComposerPrimitive.Root>
430
+ <MessagePrimitive.Error>
431
+ <ErrorPrimitive.Root className="aui-message-error-root">
432
+ <ErrorPrimitive.Message className="aui-message-error-message" />
433
+ </ErrorPrimitive.Root>
434
+ </MessagePrimitive.Error>
392
435
  );
393
436
  };
394
437
 
395
438
  const AssistantMessage: FC = () => {
396
439
  return (
397
- <MessagePrimitive.Root className="aui-assistant-message-root">
398
- <div className="aui-assistant-message-content">
399
- <MessagePrimitive.Parts components={{ Text: MarkdownText }} />
400
- </div>
401
-
402
- <AssistantActionBar />
440
+ <MessagePrimitive.Root asChild>
441
+ <div
442
+ className="aui-assistant-message-root"
443
+ data-role="assistant"
444
+ >
445
+ <div className="aui-assistant-message-content">
446
+ <MessagePrimitive.Parts
447
+ components={{
448
+ Text: MarkdownText,
449
+ tools: { Fallback: ToolFallback },
450
+ }}
451
+ />
452
+ <MessageError />
453
+ </div>
403
454
 
404
- <BranchPicker className="aui-assistant-branch-picker" />
455
+ <div className="aui-assistant-message-footer">
456
+ <BranchPicker />
457
+ <AssistantActionBar />
458
+ </div>
459
+ </div>
405
460
  </MessagePrimitive.Root>
406
461
  );
407
462
  };
@@ -433,6 +488,72 @@ const AssistantActionBar: FC = () => {
433
488
  );
434
489
  };
435
490
 
491
+ const UserMessage: FC = () => {
492
+ return (
493
+ <MessagePrimitive.Root asChild>
494
+ <div
495
+ className="aui-user-message-root"
496
+ data-role="user"
497
+ >
498
+ <UserMessageAttachments />
499
+
500
+ <div className="aui-user-message-content-wrapper">
501
+ <div className="aui-user-message-content">
502
+ <MessagePrimitive.Parts />
503
+ </div>
504
+ <div className="aui-user-action-bar-wrapper">
505
+ <UserActionBar />
506
+ </div>
507
+ </div>
508
+
509
+ <BranchPicker className="aui-user-branch-picker" />
510
+ </div>
511
+ </MessagePrimitive.Root>
512
+ );
513
+ };
514
+
515
+ const UserActionBar: FC = () => {
516
+ return (
517
+ <ActionBarPrimitive.Root
518
+ hideWhenRunning
519
+ autohide="not-last"
520
+ className="aui-user-action-bar-root"
521
+ >
522
+ <ActionBarPrimitive.Edit asChild>
523
+ <TooltipIconButton tooltip="Edit" className="aui-user-action-edit">
524
+ <PencilIcon />
525
+ </TooltipIconButton>
526
+ </ActionBarPrimitive.Edit>
527
+ </ActionBarPrimitive.Root>
528
+ );
529
+ };
530
+
531
+ const EditComposer: FC = () => {
532
+ return (
533
+ <div className="aui-edit-composer-wrapper">
534
+ <ComposerPrimitive.Root className="aui-edit-composer-root">
535
+ <ComposerPrimitive.Input
536
+ className="aui-edit-composer-input"
537
+ autoFocus
538
+ />
539
+
540
+ <div className="aui-edit-composer-footer">
541
+ <ComposerPrimitive.Cancel asChild>
542
+ <Button variant="ghost" size="sm" aria-label="Cancel edit">
543
+ Cancel
544
+ </Button>
545
+ </ComposerPrimitive.Cancel>
546
+ <ComposerPrimitive.Send asChild>
547
+ <Button size="sm" aria-label="Update message">
548
+ Update
549
+ </Button>
550
+ </ComposerPrimitive.Send>
551
+ </div>
552
+ </ComposerPrimitive.Root>
553
+ </div>
554
+ );
555
+ };
556
+
436
557
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
437
558
  className,
438
559
  ...rest
@@ -459,20 +580,290 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
459
580
  </BranchPickerPrimitive.Root>
460
581
  );
461
582
  };
583
+ ```
584
+
585
+ ```tsx title="components/assistant-ui/attachment.tsx"
586
+ "use client";
587
+
588
+ import { PropsWithChildren, useEffect, useState, type FC } from "react";
589
+ import Image from "next/image";
590
+ import { XIcon, PlusIcon, FileText } from "lucide-react";
591
+ import {
592
+ AttachmentPrimitive,
593
+ ComposerPrimitive,
594
+ MessagePrimitive,
595
+ useAssistantState,
596
+ useAssistantApi,
597
+ } from "@assistant-ui/react";
598
+ import { useShallow } from "zustand/shallow";
599
+ import {
600
+ Tooltip,
601
+ TooltipContent,
602
+ TooltipTrigger,
603
+ } from "@/components/ui/tooltip";
604
+ import {
605
+ Dialog,
606
+ DialogTitle,
607
+ DialogContent,
608
+ DialogTrigger,
609
+ } from "@/components/ui/dialog";
610
+ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
611
+ import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
612
+ import { cn } from "@/lib/utils";
613
+
614
+ const useFileSrc = (file: File | undefined) => {
615
+ const [src, setSrc] = useState<string | undefined>(undefined);
616
+
617
+ useEffect(() => {
618
+ if (!file) {
619
+ setSrc(undefined);
620
+ return;
621
+ }
622
+
623
+ const objectUrl = URL.createObjectURL(file);
624
+ setSrc(objectUrl);
625
+
626
+ return () => {
627
+ URL.revokeObjectURL(objectUrl);
628
+ };
629
+ }, [file]);
630
+
631
+ return src;
632
+ };
633
+
634
+ const useAttachmentSrc = () => {
635
+ const { file, src } = useAssistantState(
636
+ useShallow(({ attachment }): { file?: File; src?: string } => {
637
+ if (attachment.type !== "image") return {};
638
+ if (attachment.file) return { file: attachment.file };
639
+ const src = attachment.content?.filter((c) => c.type === "image")[0]
640
+ ?.image;
641
+ if (!src) return {};
642
+ return { src };
643
+ }),
644
+ );
645
+
646
+ return useFileSrc(file) ?? src;
647
+ };
648
+
649
+ type AttachmentPreviewProps = {
650
+ src: string;
651
+ };
462
652
 
463
- const CircleStopIcon = () => {
653
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
654
+ const [isLoaded, setIsLoaded] = useState(false);
464
655
  return (
465
- <svg
466
- xmlns="http://www.w3.org/2000/svg"
467
- viewBox="0 0 16 16"
468
- fill="currentColor"
469
- width="16"
470
- height="16"
471
- >
472
- <rect width="10" height="10" x="3" y="3" rx="2" />
473
- </svg>
656
+ <Image
657
+ src={src}
658
+ alt="Image Preview"
659
+ width={1}
660
+ height={1}
661
+ className={
662
+ isLoaded
663
+ ? "aui-attachment-preview-image-loaded"
664
+ : "aui-attachment-preview-image-loading"
665
+ }
666
+ onLoadingComplete={() => setIsLoaded(true)}
667
+ priority={false}
668
+ />
669
+ );
670
+ };
671
+
672
+ const AttachmentPreviewDialog: FC<PropsWithChildren> = ({ children }) => {
673
+ const src = useAttachmentSrc();
674
+
675
+ if (!src) return children;
676
+
677
+ return (
678
+ <Dialog>
679
+ <DialogTrigger className="aui-attachment-preview-trigger" asChild>
680
+ {children}
681
+ </DialogTrigger>
682
+ <DialogContent className="aui-attachment-preview-dialog-content">
683
+ <DialogTitle className="aui-sr-only">
684
+ Image Attachment Preview
685
+ </DialogTitle>
686
+ <div className="aui-attachment-preview">
687
+ <AttachmentPreview src={src} />
688
+ </div>
689
+ </DialogContent>
690
+ </Dialog>
691
+ );
692
+ };
693
+
694
+ const AttachmentThumb: FC = () => {
695
+ const isImage = useAssistantState(
696
+ ({ attachment }) => attachment.type === "image",
697
+ );
698
+ const src = useAttachmentSrc();
699
+
700
+ return (
701
+ <Avatar className="aui-attachment-tile-avatar">
702
+ <AvatarImage
703
+ src={src}
704
+ alt="Attachment preview"
705
+ className="aui-attachment-tile-image"
706
+ />
707
+ <AvatarFallback delayMs={isImage ? 200 : 0}>
708
+ <FileText className="aui-attachment-tile-fallback-icon" />
709
+ </AvatarFallback>
710
+ </Avatar>
474
711
  );
475
712
  };
713
+
714
+ const AttachmentUI: FC = () => {
715
+ const api = useAssistantApi();
716
+ const isComposer = api.attachment.source === "composer";
717
+
718
+ const isImage = useAssistantState(
719
+ ({ attachment }) => attachment.type === "image",
720
+ );
721
+ const typeLabel = useAssistantState(({ attachment }) => {
722
+ const type = attachment.type;
723
+ switch (type) {
724
+ case "image":
725
+ return "Image";
726
+ case "document":
727
+ return "Document";
728
+ case "file":
729
+ return "File";
730
+ default:
731
+ const _exhaustiveCheck: never = type;
732
+ throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`);
733
+ }
734
+ });
735
+
736
+ return (
737
+ <Tooltip>
738
+ <AttachmentPrimitive.Root
739
+ className={cn(
740
+ "aui-attachment-root",
741
+ isImage && "aui-attachment-root-composer",
742
+ )}
743
+ >
744
+ <AttachmentPreviewDialog>
745
+ <TooltipTrigger asChild>
746
+ <div
747
+ className={cn(
748
+ "aui-attachment-tile",
749
+ isComposer && "aui-attachment-tile-composer",
750
+ )}
751
+ role="button"
752
+ id="attachment-tile"
753
+ aria-label={`${typeLabel} attachment`}
754
+ >
755
+ <AttachmentThumb />
756
+ </div>
757
+ </TooltipTrigger>
758
+ </AttachmentPreviewDialog>
759
+ {isComposer && <AttachmentRemove />}
760
+ </AttachmentPrimitive.Root>
761
+ <TooltipContent side="top">
762
+ <AttachmentPrimitive.Name />
763
+ </TooltipContent>
764
+ </Tooltip>
765
+ );
766
+ };
767
+
768
+ const AttachmentRemove: FC = () => {
769
+ return (
770
+ <AttachmentPrimitive.Remove asChild>
771
+ <TooltipIconButton
772
+ tooltip="Remove file"
773
+ className="aui-attachment-tile-remove"
774
+ side="top"
775
+ >
776
+ <XIcon className="aui-attachment-remove-icon" />
777
+ </TooltipIconButton>
778
+ </AttachmentPrimitive.Remove>
779
+ );
780
+ };
781
+
782
+ export const UserMessageAttachments: FC = () => {
783
+ return (
784
+ <div className="aui-user-message-attachments-end">
785
+ <MessagePrimitive.Attachments components={{ Attachment: AttachmentUI }} />
786
+ </div>
787
+ );
788
+ };
789
+
790
+ export const ComposerAttachments: FC = () => {
791
+ return (
792
+ <div className="aui-composer-attachments">
793
+ <ComposerPrimitive.Attachments
794
+ components={{ Attachment: AttachmentUI }}
795
+ />
796
+ </div>
797
+ );
798
+ };
799
+
800
+ export const ComposerAddAttachment: FC = () => {
801
+ return (
802
+ <ComposerPrimitive.AddAttachment asChild>
803
+ <TooltipIconButton
804
+ tooltip="Add Attachment"
805
+ side="bottom"
806
+ variant="ghost"
807
+ size="icon"
808
+ className="aui-composer-add-attachment"
809
+ aria-label="Add Attachment"
810
+ >
811
+ <PlusIcon className="aui-attachment-add-icon" />
812
+ </TooltipIconButton>
813
+ </ComposerPrimitive.AddAttachment>
814
+ );
815
+ };
816
+ ```
817
+
818
+ ```tsx title="components/assistant-ui/tool-fallback.tsx"
819
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
820
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
821
+ import { useState } from "react";
822
+ import { Button } from "@/components/ui/button";
823
+
824
+ export const ToolFallback: ToolCallMessagePartComponent = ({
825
+ toolName,
826
+ argsText,
827
+ result,
828
+ }) => {
829
+ const [isCollapsed, setIsCollapsed] = useState(true);
830
+ return (
831
+ <div className="aui-tool-fallback-root">
832
+ <div className="aui-tool-fallback-header">
833
+ <CheckIcon className="aui-tool-fallback-icon" />
834
+ <p className="aui-tool-fallback-title">
835
+ Used tool: <b>{toolName}</b>
836
+ </p>
837
+ <Button onClick={() => setIsCollapsed(!isCollapsed)}>
838
+ {isCollapsed ? <ChevronUpIcon /> : <ChevronDownIcon />}
839
+ </Button>
840
+ </div>
841
+ {!isCollapsed && (
842
+ <div className="aui-tool-fallback-content">
843
+ <div className="aui-tool-fallback-args-root">
844
+ <pre className="aui-tool-fallback-args-value">
845
+ {argsText}
846
+ </pre>
847
+ </div>
848
+ {result !== undefined && (
849
+ <div className="aui-tool-fallback-result-root">
850
+ <p className="aui-tool-fallback-result-header">
851
+ Result:
852
+ </p>
853
+ <pre className="aui-tool-fallback-result-content">
854
+ {typeof result === "string"
855
+ ? result
856
+ : JSON.stringify(result, null, 2)}
857
+ </pre>
858
+ </div>
859
+ )}
860
+ </div>
861
+ )}
862
+ </div>
863
+ );
864
+ };
865
+ ```
866
+
476
867
  ```
477
868
 
478
869
  ```tsx title="components/assistant-ui/thread-list.tsx"
@@ -523,9 +914,9 @@ const ThreadListItem: FC = () => {
523
914
 
524
915
  const ThreadListItemTitle: FC = () => {
525
916
  return (
526
- <p className="aui-thread-list-item-title">
917
+ <span className="aui-thread-list-item-title">
527
918
  <ThreadListItemPrimitive.Title fallback="New Chat" />
528
- </p>
919
+ </span>
529
920
  );
530
921
  };
531
922
 
@@ -550,13 +941,13 @@ const ThreadListItemArchive: FC = () => {
550
941
  import "@assistant-ui/react-markdown/styles/dot.css";
551
942
 
552
943
  import {
553
- CodeHeaderProps,
944
+ type CodeHeaderProps,
554
945
  MarkdownTextPrimitive,
555
946
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
556
947
  useIsMarkdownCodeBlock,
557
948
  } from "@assistant-ui/react-markdown";
558
949
  import remarkGfm from "remark-gfm";
559
- import { FC, memo, useState } from "react";
950
+ import { type FC, memo, useState } from "react";
560
951
  import { CheckIcon, CopyIcon } from "lucide-react";
561
952
 
562
953
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -682,7 +1073,8 @@ const defaultComponents = memoizeMarkdownComponents({
682
1073
  ```tsx title="components/assistant-ui/tooltip-icon-button.tsx"
683
1074
  "use client";
684
1075
 
685
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1076
+ import { ComponentPropsWithRef, forwardRef } from "react";
1077
+ import { Slottable } from "@radix-ui/react-slot";
686
1078
 
687
1079
  import {
688
1080
  Tooltip,
@@ -691,9 +1083,8 @@ import {
691
1083
  } from "@/components/ui/tooltip";
692
1084
  import { Button } from "@/components/ui/button";
693
1085
  import { cn } from "@/lib/utils";
694
- import { Slottable } from "@radix-ui/react-slot";
695
1086
 
696
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1087
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
697
1088
  tooltip: string;
698
1089
  side?: "top" | "bottom" | "left" | "right";
699
1090
  };
@@ -1073,7 +1464,7 @@ const MyApp = () => {
1073
1464
 
1074
1465
  return (
1075
1466
  <AssistantRuntimeProvider runtime={runtime}>
1076
- <div className="grid h-dvh grid-cols-[200px_1fr] gap-x-2 px-4 py-4">
1467
+ <div>
1077
1468
  <ThreadList />
1078
1469
  <Thread />
1079
1470
  </div>