File size: 5,369 Bytes
aea470f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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;