// Generate one vision image via nano-gpt Image API. Usage: KEY env var set, node gen1.js "" // Prints one JSON line: {slug, ok, cost, bal, bytes, type, err} const [slug, prompt] = process.argv.slice(2); const KEY = process.env.RETIRE_NANO_KEY; async function main() { const out = { slug, ok: false }; try { const res = await fetch("https://nano-gpt.com/api/v1/images/generations", { method: "POST", headers: { "Authorization": "Bearer " + KEY, "Content-Type": "application/json" }, body: JSON.stringify({ model: "z-image-turbo", prompt, resolution: "1024*1024", n: 1 }), signal: AbortSignal.timeout(180000), }); const j = await res.json().catch(() => ({})); out.cost = j.cost ?? (j.data && j.data[0] && j.data[0].cost) ?? null; out.bal = j.remainingBalance ?? null; if (!res.ok) { out.err = "http " + res.status + " " + JSON.stringify(j).slice(0, 300); return out; } const d = j.data && j.data[0]; if (!d) { out.err = "no data: " + JSON.stringify(j).slice(0, 300); return out; } // dump raw response shape (strip image payloads) for evidence try { const fs0 = require("fs"); const copy = JSON.parse(JSON.stringify(j)); if (copy.data) copy.data = copy.data.map(x => ({ ...x, b64_json: x.b64_json ? "<" + x.b64_json.length + "b>" : undefined, url: x.url ? x.url.slice(0, 80) : undefined })); fs0.writeFileSync(".fetch/resp_" + slug + ".json", JSON.stringify(copy).slice(0, 4000)); } catch (e) {} let buf; if (d.b64_json) { buf = Buffer.from(d.b64_json, "base64"); } else if (d.url) { const img = await fetch(d.url, { signal: AbortSignal.timeout(120000) }); if (!img.ok) { out.err = "img fetch http " + img.status; return out; } buf = Buffer.from(await img.arrayBuffer()); } else { out.err = "no image in response"; return out; } // magic bytes let type = "bin"; if (buf.length > 12 && buf[0] === 0x89 && buf[1] === 0x50) type = "png"; else if (buf.slice(0, 4).toString("latin1") === "RIFF" && buf.slice(8, 12).toString("latin1") === "WEBP") type = "webp"; else if (buf[0] === 0xff && buf[1] === 0xd8) type = "jpeg"; out.type = type; out.bytes = buf.length; const fs = require("fs"); fs.mkdirSync("site/vision", { recursive: true }); // browsers sniff magic bytes; keep .png extension for the UI contract, save true extension alongside fs.writeFileSync("site/vision/" + slug + "." + (type === "png" ? "png" : type), buf); if (type !== "png") fs.copyFileSync("site/vision/" + slug + "." + type, "site/vision/" + slug + ".png"); out.ok = true; } catch (e) { out.err = String(e).slice(0, 300); } return out; } main().then(o => console.log(JSON.stringify(o)));