MediaPart
A picture reaches the transcript from both sides, and both sides work without you: UserMessage draws the file parts of a question as thumbnails in the bubble, and the list draws the ones that come with an answer the same way — an image that opens fullscreen, anything else a file chip. That holds for a human who writes beside the agent as well: give the message of an operator or a teammate the assistant role and what they attached renders like any other answer. These renderers are the fuller treatment on top of that: a larger image, audio and video in their native players, and onOpenFile for a file your app opens itself instead of downloading. createMediaPartRenderers() returns partRenderers for file parts of an answer; create it once and spread it next to your own renderers. The part is shown by its mediaType: an image opens a fullscreen ImageLightbox, audio and video play in the native <audio> and <video> without autoplay (a captions WebVTT address adds a track), and any other file is a FileAttachment chip that calls onOpenFile or downloads a safe url. Only http(s) addresses and data: images, audio and video reach the page; javascript:, blob: and data:text/html never do. labels covers openImage, imageAlt, audio, video, the names of the chip that opens or downloads (openFile, download) and attachment for the chip itself. MediaPart is the same component for a custom layout.
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-react