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" }}
>
);
}
/**
* 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)}
),
)}
);
}