import { isSupportAttachmentImage, type SupportAttachmentDto, } from "@edr/types"; import { Box, Group, Image, Loader, Paper, Stack, Text } from "@mantine/core"; import { FileText, ImageOff } from "lucide-react"; import { useAttachmentObjectUrl } from "./useAttachmentObjectUrl"; /** Human-readable size — kept coarse; nobody needs bytes in a chat bubble. */ export function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } /** Cap the bubble: a tall screenshot would push the conversation off-screen. */ const THUMB = { maxHeight: 220, maxWidth: 260 } as const; /** * One image attachment. Its own component because the bytes are fetched through * the authenticated client (see {@link useAttachmentObjectUrl}) and a hook can't * be called from inside a `.map()`. */ function ImageAttachment({ a, onView, }: { a: SupportAttachmentDto; onView: (a: SupportAttachmentDto, src: string) => void; }) { const { src, failed } = useAttachmentObjectUrl(a.url); if (failed) { return ( Couldn't load {a.name} ); } if (!src) { return ( ); } return ( onView(a, src)} style={{ cursor: "zoom-in", borderRadius: 8, overflow: "hidden" }} > {a.name} ); } /** * Attachments inside a message bubble: images as thumbnails, everything else as * a labelled file row. Non-images are not fetched until opened — pulling every * document in a thread just to draw a filename would be wasteful. */ export function MessageAttachments({ attachments, mine, onView, onOpenFile, }: { attachments: SupportAttachmentDto[]; mine: boolean; onView: (a: SupportAttachmentDto, src: string) => void; onOpenFile: (a: SupportAttachmentDto) => void; }) { if (attachments.length === 0) return null; return ( {attachments.map((a) => isSupportAttachmentImage(a.mimeType) ? ( ) : ( onOpenFile(a)} px="sm" py={6} radius="md" style={{ cursor: "pointer", background: mine ? "rgba(255,255,255,0.16)" : "white", border: mine ? "none" : "1px solid var(--mantine-color-gray-3)", }} > {a.name} {formatBytes(a.size)} ), )} ); }