import React, { useEffect, useState } from "react"; import { Image as ImageIcon, Copy, Check, Download } from "lucide-react"; import { toast } from "sonner"; import { fetchImageB64DataUrl, loadRenderableImageSrc, mergeImageFields, } from "../lib/imagePreview"; import { copyImageFromSrc, downloadImageFromSrc } from "../lib/imageExport"; /** Aperçu image générée — blob URL, URL directe ou /b64 fallback. */ export default function GeneratedImagePreview({ sources = [], title = "Image générée", className = "", showActions = true, testId = "generated-image-preview", }) { const fields = mergeImageFields(...sources); const [src, setSrc] = useState(null); const [failed, setFailed] = useState(false); const [copied, setCopied] = useState(false); const [busy, setBusy] = useState(false); useEffect(() => { let revokeSrc = null; let cancelled = false; setSrc(null); setFailed(false); if (!fields.image_base64 && !fields.image_url && !fields.has_image) { setFailed(true); return undefined; } (async () => { const hit = await loadRenderableImageSrc(fields); if (cancelled) { if (hit?.revoke && hit.src) URL.revokeObjectURL(hit.src); return; } if (hit?.src) { setSrc(hit.src); if (hit.revoke) revokeSrc = hit.src; } else { setFailed(true); } })(); return () => { cancelled = true; if (revokeSrc) URL.revokeObjectURL(revokeSrc); }; }, [fields.image_base64, fields.image_url, fields.mime, fields.has_image]); const handleImgError = async () => { if (fields.image_url) { const fallback = await fetchImageB64DataUrl(fields.image_url, fields.mime); if (fallback) { try { const resp = await fetch(fallback); const blob = await resp.blob(); if (blob.size > 500) { setSrc(URL.createObjectURL(blob)); setFailed(false); return; } } catch { setSrc(fallback); setFailed(false); return; } } } setFailed(true); }; const handleCopy = async () => { if (!src || busy) return; setBusy(true); try { await copyImageFromSrc(src); setCopied(true); toast.success("Image copiée"); setTimeout(() => setCopied(false), 1500); } catch { toast.error("Copie impossible"); } finally { setBusy(false); } }; const handleSave = async () => { if (!src || busy) return; setBusy(true); try { await downloadImageFromSrc(src); toast.success("Image enregistrée"); } catch { toast.error("Enregistrement impossible"); } finally { setBusy(false); } }; return (
{!src && !failed && (
)} {failed && (
Image indisponible
)} {src && !failed && ( <> {title {showActions && (
)} )}
{title && (

{title}

)}
); }