54 lines
2.7 KiB
JavaScript
54 lines
2.7 KiB
JavaScript
// Generate one vision image via nano-gpt Image API. Usage: KEY env var set, node gen1.js <slug> "<prompt>"
|
|
// 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)));
|