user-message
agent-elements/user-messageFreeComponent
agent-elements publishes no description for this item.
Live preview
live · rendered by the registry
Rendered by agent-elements on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://agent-elements.21st.dev/r/user-message.jsonSource
1import { memo, useState } from "react";2import type { UIMessage } from "ai";3import { cn } from "./utils/cn";4import { FileAttachment } from "./input/file-attachment";5import { ImageLightbox } from "./image-lightbox";67export type UserMessageProps = {8 message: UIMessage;9 className?: string;10 /**11 * When true (default) clicking an attached image opens a fullscreen12 * lightbox preview. Set to false to render images as plain thumbnails.13 */14 enableImagePreview?: boolean;15};1617type MessagePart = UIMessage["parts"][number];1819function isRecord(value: unknown): value is Record<string, unknown> {20 return typeof value === "object" && value !== null;21}2223function isTextPart(part: MessagePart): part is { type: "text"; text: string } {24 return (25 part.type === "text" &&26 typeof (part as { text?: unknown }).text === "string"27 );28}2930function getImageUrlFromPart(part: unknown): string | null {31 if (!isRecord(part)) return null;32 const type = part.type;33 if (typeof type !== "string") return null;3435 if (type === "image") {36 const imagePart = part as { url?: string; image?: string };37 return imagePart.url ?? imagePart.image ?? null;38 }3940 if (type === "data-image") {41 const dataPart = part as { data?: { url?: string } };42 return dataPart.data?.url ?? null;43 }4445 if (type === "file") {46 const filePart = part as { mimeType?: string; url?: string; data?: string };47 if (filePart.mimeType?.startsWith("image/")) {48 if (filePart.url) return filePart.url;49 if (filePart.data) {50 return `data:${filePart.mimeType};base64,${filePart.data}`;51 }52 }53 }5455 return null;56}5758type FilePart = {59 type: "file";60 filename?: string;61 name?: string;62 fileName?: string;63 size?: number;64 mimeType?: string;65 url?: string;66};6768function getFileFromPart(part: unknown) {69 if (!isRecord(part)) return null;70 if (part.type !== "file") return null;71 const filePart = part as FilePart;72 const filename =73 filePart.filename || filePart.name || filePart.fileName || "Attachment";74 const isImage = filePart.mimeType?.startsWith("image/") ?? false;75 if (isImage) return null;76 return {77 filename,78 size: filePart.size,79 };80}8182export const UserMessage = memo(function UserMessage({83 message,84 className,85 enableImagePreview = true,86}: UserMessageProps) {87 const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);88 const textParts = message.parts?.filter(isTextPart) ?? [];89// … truncated
More from agent-elements
All 25 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| agent-chatagent-chat | agent-elements | Components | Free | no deps | |
| attachment-buttonattachment-button | agent-elements | Components | Free | no deps | |
| bash-toolbash-tool | agent-elements | Components | Free | no deps | |
| edit-tooledit-tool | agent-elements | Components | Free | no deps | |
| error-messageerror-message | agent-elements | Components | Free | no deps | |
| file-attachmentfile-attachment | agent-elements | Components | Free | no deps | |
| generic-toolgeneric-tool | agent-elements | Components | Free | no deps | |
| input-barinput-bar | agent-elements | Components | Free | no deps |
