import { i18n } from "@lingui/core"; import { t } from "@lingui/core/macro"; import { Trans, useLingui } from "@lingui/react/macro"; import { ChatMarkdown } from "@rakazo/chat-ui/web"; import { Button, Dialog, DialogClose, DialogContent, DialogTitle } from "@rakazo/ui-web"; import { Download, FileText, X } from "lucide-react"; import { type RefObject, useEffect, useRef, useState } from "react"; import { type ArtifactTarget, downloadArtifact, downloadArtifactBytes, fetchArtifactBytes, } from "../lib/artifact-open"; type ArtifactFileCardProps = { target: ArtifactTarget; artifactId: string; name: string; mimeType: string; size: number; }; export function ArtifactFileCard(props: ArtifactFileCardProps) { const { t } = useLingui(); const markdown = props.mimeType === "text/markdown"; const previewButton = useRef(null); const closeButton = useRef(null); const [previewOpen, setPreviewOpen] = useState(false); const [downloadError, setDownloadError] = useState(null); async function startDownload() { setDownloadError(null); try { await downloadArtifact(props.target, props.artifactId, props.name, props.mimeType); } catch { setDownloadError(t`Could not download ${props.name}. Try again.`); } } if (!markdown) { return (
{downloadError ? : null}
); } return ( <>
{downloadError ? : null}
); } function MarkdownPreview({ target, artifactId, name, mimeType, closeButtonRef, }: ArtifactFileCardProps & { closeButtonRef: RefObject }) { const { t } = useLingui(); const [downloadError, setDownloadError] = useState(null); const [state, setState] = useState< | { status: "loading" } | { status: "ready"; bytes: Uint8Array; markdown: string } | { status: "error"; message: string } >({ status: "loading" }); const targetBotId = "botId" in target ? target.botId : undefined; const targetGroupId = "groupId" in target ? target.groupId : undefined; useEffect(() => { let cancelled = false; const artifactTarget: ArtifactTarget = targetBotId !== undefined ? { botId: targetBotId } : { groupId: targetGroupId! }; void fetchArtifactBytes(artifactTarget, artifactId) .then((bytes) => { if (cancelled) return; try { const markdown = new TextDecoder("utf-8", { fatal: true }).decode(bytes); setState({ status: "ready", bytes, markdown }); } catch { setState({ status: "error", message: t`This file is not valid UTF-8 Markdown.` }); } }) .catch((error) => { if (cancelled) return; setState({ status: "error", message: error instanceof Error ? error.message : t`Could not load this file.`, }); }); return () => { cancelled = true; }; }, [artifactId, targetBotId, targetGroupId, t]); return ( <>
{name} } >
{downloadError ? (
) : null}
{state.status === "loading" ? (
Loading preview…
) : state.status === "error" ? (
{state.message}
) : ( {state.markdown} )}
); } function DownloadError({ message }: { message: string }) { return (
{message}
); } function formatBytes(size: number) { const locale = i18n.locale || "en"; if (size < 1024) return t`${size} B`; const format = (value: number) => new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1, }).format(value); if (size < 1024 * 1024) return t`${format(size / 1024)} KB`; return t`${format(size / (1024 * 1024))} MB`; }