@assistant-ui/mcp-docs-server 0.1.9 → 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 (35) hide show
  1. package/.docs/organized/code-examples/with-ai-sdk-v5.md +26 -26
  2. package/.docs/organized/code-examples/with-assistant-transport.md +29 -29
  3. package/.docs/organized/code-examples/with-cloud.md +21 -21
  4. package/.docs/organized/code-examples/with-external-store.md +18 -18
  5. package/.docs/organized/code-examples/with-ffmpeg.md +22 -22
  6. package/.docs/organized/code-examples/with-langgraph.md +35 -120
  7. package/.docs/organized/code-examples/with-parent-id-grouping.md +18 -18
  8. package/.docs/organized/code-examples/with-react-hook-form.md +27 -27
  9. package/.docs/raw/docs/api-reference/primitives/Thread.mdx +40 -8
  10. package/.docs/raw/docs/cloud/persistence/langgraph.mdx +64 -68
  11. package/.docs/raw/docs/getting-started.mdx +541 -152
  12. package/.docs/raw/docs/guides/Attachments.mdx +21 -0
  13. package/.docs/raw/docs/guides/ToolUI.mdx +112 -37
  14. package/.docs/raw/docs/guides/Tools.mdx +170 -6
  15. package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +324 -0
  16. package/.docs/raw/docs/runtimes/ai-sdk/use-chat.mdx +2 -2
  17. package/.docs/raw/docs/runtimes/custom/custom-thread-list.mdx +218 -0
  18. package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -24
  19. package/.docs/raw/docs/runtimes/langgraph/index.mdx +55 -20
  20. package/.docs/raw/docs/runtimes/mastra/separate-server-integration.mdx +8 -3
  21. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +1 -1
  22. package/.docs/raw/docs/ui/AssistantModal.mdx +21 -0
  23. package/.docs/raw/docs/ui/AssistantSidebar.mdx +21 -0
  24. package/.docs/raw/docs/ui/Attachment.mdx +21 -0
  25. package/.docs/raw/docs/ui/Markdown.mdx +22 -1
  26. package/.docs/raw/docs/ui/Mermaid.mdx +22 -1
  27. package/.docs/raw/docs/ui/SyntaxHighlighting.mdx +43 -2
  28. package/.docs/raw/docs/ui/Thread.mdx +374 -5
  29. package/.docs/raw/docs/ui/ThreadList.mdx +48 -2
  30. package/.docs/raw/docs/ui/ToolFallback.mdx +21 -0
  31. package/package.json +7 -7
  32. package/.docs/raw/docs/migrations/v0-7.mdx +0 -188
  33. package/.docs/raw/docs/migrations/v0-8.mdx +0 -160
  34. package/.docs/raw/docs/migrations/v0-9.mdx +0 -75
  35. package/.docs/raw/docs/ui/primitives/Thread.mdx +0 -197
@@ -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,146 +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
- method="replace"
287
- autoSend
288
- >
289
- <span className="aui-thread-welcome-suggestion-text">
290
- What is the weather in Tokyo?
291
- </span>
292
- </ThreadPrimitive.Suggestion>
293
- <ThreadPrimitive.Suggestion
294
- className="aui-thread-welcome-suggestion"
295
- prompt="What is assistant-ui?"
296
- method="replace"
297
- autoSend
298
- >
299
- <span className="aui-thread-welcome-suggestion-text">
300
- What is assistant-ui?
301
- </span>
302
- </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
+ ))}
303
367
  </div>
304
368
  );
305
369
  };
306
370
 
307
371
  const Composer: FC = () => {
308
372
  return (
309
- <ComposerPrimitive.Root className="aui-composer-root">
310
- <ComposerPrimitive.Input
311
- rows={1}
312
- autoFocus
313
- placeholder="Write a message..."
314
- className="aui-composer-input"
315
- />
316
- <ComposerAction />
317
- </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>
318
387
  );
319
388
  };
320
389
 
321
390
  const ComposerAction: FC = () => {
322
391
  return (
323
- <>
392
+ <div className="aui-composer-action-wrapper">
393
+ <ComposerAddAttachment />
394
+
324
395
  <ThreadPrimitive.If running={false}>
325
396
  <ComposerPrimitive.Send asChild>
326
397
  <TooltipIconButton
327
- tooltip="Send"
398
+ tooltip="Send message"
399
+ side="bottom"
400
+ type="submit"
328
401
  variant="default"
402
+ size="icon"
329
403
  className="aui-composer-send"
404
+ aria-label="Send message"
330
405
  >
331
- <SendHorizontalIcon />
406
+ <ArrowUpIcon className="aui-composer-send-icon" />
332
407
  </TooltipIconButton>
333
408
  </ComposerPrimitive.Send>
334
409
  </ThreadPrimitive.If>
410
+
335
411
  <ThreadPrimitive.If running>
336
412
  <ComposerPrimitive.Cancel asChild>
337
- <TooltipIconButton
338
- tooltip="Cancel"
413
+ <Button
414
+ type="button"
339
415
  variant="default"
416
+ size="icon"
340
417
  className="aui-composer-cancel"
418
+ aria-label="Stop generating"
341
419
  >
342
- <CircleStopIcon />
343
- </TooltipIconButton>
420
+ <Square className="aui-composer-cancel-icon" />
421
+ </Button>
344
422
  </ComposerPrimitive.Cancel>
345
423
  </ThreadPrimitive.If>
346
- </>
347
- );
348
- };
349
-
350
- const UserMessage: FC = () => {
351
- return (
352
- <MessagePrimitive.Root className="aui-user-message-root">
353
- <UserActionBar />
354
-
355
- <div className="aui-user-message-content">
356
- <MessagePrimitive.Parts />
357
- </div>
358
-
359
- <BranchPicker className="aui-user-branch-picker" />
360
- </MessagePrimitive.Root>
361
- );
362
- };
363
-
364
- const UserActionBar: FC = () => {
365
- return (
366
- <ActionBarPrimitive.Root
367
- hideWhenRunning
368
- autohide="not-last"
369
- className="aui-user-action-bar-root"
370
- >
371
- <ActionBarPrimitive.Edit asChild>
372
- <TooltipIconButton tooltip="Edit">
373
- <PencilIcon />
374
- </TooltipIconButton>
375
- </ActionBarPrimitive.Edit>
376
- </ActionBarPrimitive.Root>
424
+ </div>
377
425
  );
378
426
  };
379
427
 
380
- const EditComposer: FC = () => {
428
+ const MessageError: FC = () => {
381
429
  return (
382
- <ComposerPrimitive.Root className="aui-edit-composer-root">
383
- <ComposerPrimitive.Input className="aui-edit-composer-input" />
384
-
385
- <div className="aui-edit-composer-footer">
386
- <ComposerPrimitive.Cancel asChild>
387
- <Button variant="ghost">Cancel</Button>
388
- </ComposerPrimitive.Cancel>
389
- <ComposerPrimitive.Send asChild>
390
- <Button>Send</Button>
391
- </ComposerPrimitive.Send>
392
- </div>
393
- </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>
394
435
  );
395
436
  };
396
437
 
397
438
  const AssistantMessage: FC = () => {
398
439
  return (
399
- <MessagePrimitive.Root className="aui-assistant-message-root">
400
- <div className="aui-assistant-message-content">
401
- <MessagePrimitive.Parts components={{ Text: MarkdownText }} />
402
- </div>
403
-
404
- <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>
405
454
 
406
- <BranchPicker className="aui-assistant-branch-picker" />
455
+ <div className="aui-assistant-message-footer">
456
+ <BranchPicker />
457
+ <AssistantActionBar />
458
+ </div>
459
+ </div>
407
460
  </MessagePrimitive.Root>
408
461
  );
409
462
  };
@@ -435,6 +488,72 @@ const AssistantActionBar: FC = () => {
435
488
  );
436
489
  };
437
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
+
438
557
  const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
439
558
  className,
440
559
  ...rest
@@ -461,20 +580,290 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
461
580
  </BranchPickerPrimitive.Root>
462
581
  );
463
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
+ };
464
652
 
465
- const CircleStopIcon = () => {
653
+ const AttachmentPreview: FC<AttachmentPreviewProps> = ({ src }) => {
654
+ const [isLoaded, setIsLoaded] = useState(false);
466
655
  return (
467
- <svg
468
- xmlns="http://www.w3.org/2000/svg"
469
- viewBox="0 0 16 16"
470
- fill="currentColor"
471
- width="16"
472
- height="16"
473
- >
474
- <rect width="10" height="10" x="3" y="3" rx="2" />
475
- </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>
476
711
  );
477
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
+
478
867
  ```
479
868
 
480
869
  ```tsx title="components/assistant-ui/thread-list.tsx"
@@ -525,9 +914,9 @@ const ThreadListItem: FC = () => {
525
914
 
526
915
  const ThreadListItemTitle: FC = () => {
527
916
  return (
528
- <p className="aui-thread-list-item-title">
917
+ <span className="aui-thread-list-item-title">
529
918
  <ThreadListItemPrimitive.Title fallback="New Chat" />
530
- </p>
919
+ </span>
531
920
  );
532
921
  };
533
922
 
@@ -552,13 +941,13 @@ const ThreadListItemArchive: FC = () => {
552
941
  import "@assistant-ui/react-markdown/styles/dot.css";
553
942
 
554
943
  import {
555
- CodeHeaderProps,
944
+ type CodeHeaderProps,
556
945
  MarkdownTextPrimitive,
557
946
  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
558
947
  useIsMarkdownCodeBlock,
559
948
  } from "@assistant-ui/react-markdown";
560
949
  import remarkGfm from "remark-gfm";
561
- import { FC, memo, useState } from "react";
950
+ import { type FC, memo, useState } from "react";
562
951
  import { CheckIcon, CopyIcon } from "lucide-react";
563
952
 
564
953
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
@@ -684,7 +1073,8 @@ const defaultComponents = memoizeMarkdownComponents({
684
1073
  ```tsx title="components/assistant-ui/tooltip-icon-button.tsx"
685
1074
  "use client";
686
1075
 
687
- import { ComponentPropsWithoutRef, forwardRef } from "react";
1076
+ import { ComponentPropsWithRef, forwardRef } from "react";
1077
+ import { Slottable } from "@radix-ui/react-slot";
688
1078
 
689
1079
  import {
690
1080
  Tooltip,
@@ -693,9 +1083,8 @@ import {
693
1083
  } from "@/components/ui/tooltip";
694
1084
  import { Button } from "@/components/ui/button";
695
1085
  import { cn } from "@/lib/utils";
696
- import { Slottable } from "@radix-ui/react-slot";
697
1086
 
698
- export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
1087
+ export type TooltipIconButtonProps = ComponentPropsWithRef<typeof Button> & {
699
1088
  tooltip: string;
700
1089
  side?: "top" | "bottom" | "left" | "right";
701
1090
  };
@@ -1075,7 +1464,7 @@ const MyApp = () => {
1075
1464
 
1076
1465
  return (
1077
1466
  <AssistantRuntimeProvider runtime={runtime}>
1078
- <div className="grid h-dvh grid-cols-[200px_1fr] gap-x-2 px-4 py-4">
1467
+ <div>
1079
1468
  <ThreadList />
1080
1469
  <Thread />
1081
1470
  </div>