AUDIT / src /components /ImageGenBlock.tsx
Arypulka98's picture
feat(audit): deploy full backend cluster node
aea470f verified
Raw
History Blame Contribute Delete
5.37 kB
import { memo, useState } from "react";
import { generateImage, IMAGE_GEN_MODELS, DEFAULT_IMAGE_MODEL } from "@/lib/imageGen";
import { getHuggingFaceToken as getToken } from "@/lib/apiKeys";
interface ImageGenBlockProps {
prompt: string;
}
const ImageGenBlock = memo(function ImageGenBlock({ prompt }: ImageGenBlockProps) {
const [status, setStatus] = useState<"idle" | "loading" | "done" | "error">("idle");
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [model, setModel] = useState(DEFAULT_IMAGE_MODEL.id);
// S725-IMG1: imageGen.ts ora restituisce data URL — nessuna revoca necessaria
const generate = async () => {
const token = getToken();
if (!token) { setError("Token HuggingFace mancante. Vai in Impostazioni."); setStatus("error"); return; }
setStatus("loading");
setError(null);
try {
const result = await generateImage(token, prompt.trim(), model);
setImageUrl(result.url);
setStatus("done");
// S725-IMG1: auto-save nel VFS — riutilizzabile dall'agente in step successivi
const _vfsPath = `images/generated_${Date.now()}.png`;
import("@/lib/vfsDb").then(({ vfsAsync }) =>
vfsAsync.write(_vfsPath, result.url, "image/png").catch(() => {})
).catch(() => {});
} catch (err) {
setError(err instanceof Error ? err.message : "Errore generazione");
setStatus("error");
}
};
const download = () => {
if (!imageUrl) return;
const _fn = `image-${Date.now()}.png`;
// S725-XLSX1: tenta a.click() (desktop) + agent:download-ready per iOS DownloadQueue
try {
const a = document.createElement("a");
a.href = imageUrl; a.download = _fn;
document.body.appendChild(a); a.click(); document.body.removeChild(a);
} catch { /* noop */ }
window.dispatchEvent(new CustomEvent("agent:download-ready", { detail: { url: imageUrl, filename: _fn } }));
};
return (
<div style={{
background: "#0d0d18", border: "1px solid #1e1e2e", borderRadius: 12,
marginBottom: "0.6em", overflow: "hidden",
}}>
<div style={{ padding: "0.55rem 0.85rem", background: "#0a0a14", borderBottom: "1px solid #1e1e2e", display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ fontSize: "0.7rem", fontWeight: 700, color: "#7c4dff" }}>IMG</span>
<span style={{ fontSize: "0.78rem", color: "#9090a8", fontWeight: 600 }}>Generazione immagine AI</span>
<span style={{ flex: 1 }} />
{status === "idle" && (
<select value={model} onChange={e => setModel(e.target.value)} style={{
background: "rgba(255,255,255,0.05)", border: "1px solid #2a2a40", borderRadius: 5,
color: "#9090a8", fontSize: "0.7rem", padding: "2px 6px", cursor: "pointer",
}}>
{IMAGE_GEN_MODELS.map(m => <option key={m.id} value={m.id}>{m.label}</option>)}
</select>
)}
</div>
<div style={{ padding: "0.75rem 0.85rem" }}>
<div style={{ fontSize: "0.78rem", color: "#6b6b7b", fontStyle: "italic", marginBottom: 10, lineHeight: 1.4 }}>
"{prompt.slice(0, 200)}{prompt.length > 200 ? "..." : ""}"
</div>
{status === "idle" && (
<button onClick={generate} style={{
all: "unset", cursor: "pointer", background: "rgba(124,77,255,0.12)",
border: "1px solid rgba(124,77,255,0.25)", color: "#a388f7",
padding: "0.42rem 1rem", borderRadius: 8, fontSize: "0.8rem", fontWeight: 600,
transition: "background 0.15s",
}}>Genera immagine</button>
)}
{status === "loading" && (
<div style={{ display: "flex", alignItems: "center", gap: 8, color: "#9090a8", fontSize: "0.8rem" }}>
<span className="spin"></span> Generazione in corso... (30-60s)
</div>
)}
{status === "error" && (
<div>
<div style={{ color: "#fca5a5", fontSize: "0.8rem", marginBottom: 8 }}>Err: {error}</div>
<button onClick={generate} style={{
all: "unset", cursor: "pointer", color: "#4f8ef7", fontSize: "0.78rem",
padding: "3px 8px", borderRadius: 5, background: "rgba(79,142,247,0.1)",
}}>Riprova</button>
</div>
)}
{status === "done" && imageUrl && (
<div>
<img src={imageUrl} alt={prompt} loading="lazy" decoding="async" style={{
width: "100%", maxWidth: 480, borderRadius: 8, display: "block",
border: "1px solid #2a2a3e",
}} />
<div style={{ display: "flex", gap: 8, marginTop: 8 }}>
<button onClick={download} style={{
all: "unset", cursor: "pointer", color: "#4f8ef7", fontSize: "0.75rem",
padding: "3px 8px", borderRadius: 5, background: "rgba(79,142,247,0.1)",
border: "1px solid rgba(79,142,247,0.2)",
}}>⬇ Scarica</button>
<button onClick={() => setStatus("idle")} style={{
all: "unset", cursor: "pointer", color: "#9090a8", fontSize: "0.75rem",
padding: "3px 8px",
}}>↺ Rigenera</button>
</div>
</div>
)}
</div>
</div>
);
}
);
export default ImageGenBlock;