import { PdfPreviewSurface } from "../../lib/app-surfaces"; import { Link } from "../../lib/navigation"; import { Component, lazy, Suspense, useEffect, useMemo, useRef, useState, type ReactNode, type RefObject, } from "react"; import type { RetainedArtifactReference } from "@opengeni/sdk"; import { createWorkspaceRetainedArtifactLoader, useLightboxGalleryItem, type LightboxGalleryEntry, } from "../../../timeline"; import { isRetainedImageContentType } from "../../../artifacts"; import { useAppContext } from "../../app-context"; import { Button } from "../../../ui/button"; import { cn } from "../../lib/utils"; import { PRIVATE_FILE_PRESENTATION, isPrivateFileLoadError, } from "../../lib/retained-artifact-load-error"; import { InlineChatImage } from "./inline-chat-image"; import { DeferredChatMedia } from "../../../artifacts"; import { isRetainedTextPreview } from "./retained-text-preview-policy"; import { cachedRetainedArtifactLoader, cachedRetainedMetadata, retainedImageCache, retainedImageScope, } from "./retained-image-cache"; const RetainedTextPreview = lazy(() => import("./retained-text-preview")); export function retainedPreviewKind(contentType: string, filename?: string) { // Older sandbox publications predate media MIME classification. Use only the // saved filename, never chat labels, and leave the integrity receipt unchanged. if (contentType === "application/octet-stream" && filename) { const extension = filename.split(".").pop()?.toLowerCase(); if (["mp4", "webm", "ogv"].includes(extension ?? "")) return "video"; if (["mp3", "m4a", "ogg", "wav", "flac"].includes(extension ?? "")) return "audio"; } if (isRetainedImageContentType(contentType)) return "image"; if (["video/mp4", "video/webm", "video/ogg"].includes(contentType)) return "video"; if ( ["audio/mpeg", "audio/mp4", "audio/ogg", "audio/wav", "audio/webm", "audio/flac"].includes( contentType, ) ) return "audio"; if (contentType === "application/pdf") return "pdf"; return null; } /** The same authorized preview is used by the artifact page and chat. */ type PreviewProps = { workspaceId: string; artifact: RetainedArtifactReference; title: string; filename?: string | undefined; /** Explicit opt-in for the workbench; chat and other viewers stay unchanged. */ workbenchTextPreview?: boolean | undefined; /** Let artifact pages size images and video to the window instead of the chat slot. */ fullSize?: boolean | undefined; }; export function RetainedFilePreview(props: PreviewProps) { const { accessKeyVersion } = useAppContext(); if (!props.artifact.available) return

Artifact unavailable.

; if ( props.workbenchTextPreview && props.artifact.kind === "file" && isRetainedTextPreview(props.artifact.contentType, props.filename) ) return ( Loading preview…

}>
); return ( ); } function RetainedFilePreviewBody({ workspaceId, artifact: initialArtifact, title, filename, fullSize, }: PreviewProps) { // The parent remounts on receipt changes, not object allocation on a rerender. const [artifact] = useState(initialArtifact); const { client, accessKeyVersion } = useAppContext(); const kind = retainedPreviewKind(artifact.contentType, filename); const [retry, setRetry] = useState(0); const [failed, setFailed] = useState(false); const [source, setSource] = useState<{ key: string; client: typeof client; url: string; } | null>(null); const key = `${workspaceId}:${artifact.artifactId}:${artifact.sha256}:${accessKeyVersion}:${retry}`; useEffect(() => { setSource(null); setFailed(false); if (!kind || kind === "image") return; const controller = new AbortController(); let objectUrl: string | undefined; void (async () => { // PDFs need an inline Blob rather than a storage URL with download disposition. const url = kind === "pdf" ? URL.createObjectURL( new Blob( [ ( await client.downloadRetainedArtifact(workspaceId, artifact, { signal: controller.signal, }) ).bytes as BlobPart, ], { type: "application/pdf" }, ), ) : artifact.kind === "generated_video" ? ( await client.createVideoArtifactPlaybackSource(workspaceId, artifact.artifactId, { signal: controller.signal, }) ).url : ( await client.createRetainedArtifactDownloadUrl(workspaceId, artifact, { signal: controller.signal, }) ).url; if (kind === "pdf") objectUrl = url; if (controller.signal.aborted) { if (objectUrl) URL.revokeObjectURL(objectUrl); return; } setSource({ key, client, url }); })().catch(() => { if (!controller.signal.aborted) setFailed(true); }); return () => { controller.abort(); if (objectUrl) URL.revokeObjectURL(objectUrl); }; }, [client, key, kind, workspaceId, artifact]); if (kind === "image") return ( ); if (!kind) return (

Preview is not available for this file. Download it to open it.

); if (failed) return (
Preview could not be loaded. Use the artifact action to open the file.{" "}
); if (!source || source.key !== key || source.client !== client) return (

Loading preview…

); if (kind === "video") return (