Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140#!/usr/bin/env node// gen-jam-headers.mjs — generate N variations of the "field jam" keymaps header// using gpt-image-1 IMAGE EDITS with REAL reference images, so Jeffrey, the// whistlegraph butterfly, and the notepat.com interface are accurate (not// invented). Base prompt: papers/figures/header-prompt-jam-field.txt, plus a// hard correction block fixing the laptop-screen issues (interface legible on// FRONT-facing lids; the BACK of a lid is a logo-only shell).//// node papers/bin/gen-jam-headers.mjs # 6 variations// node papers/bin/gen-jam-headers.mjs 4//// Writes: papers/figures/jam-header-v1.png … vN.png (1536x1024)
import { readFileSync, writeFileSync, existsSync } from "node:fs";import { resolve, dirname, join, basename } from "node:path";import { fileURLToPath } from "node:url";
const HERE = dirname(fileURLToPath(import.meta.url));const PAPERS = resolve(HERE, "..");const REPO = resolve(PAPERS, "..");const FIG = join(PAPERS, "figures");const N = Math.max(1, Math.min(8, parseInt(process.argv[2] || "6", 10)));
// Real reference images (paths relative to repo root).const REFS = [ // Jeffrey's actual face — real studio shoot photos (identity grounding, the // way marketing/lib/jeffrey-refs.mjs locks likeness for gpt-image-2 edits). "papers/figures/refs/jeffrey-01-2k.jpg", "papers/figures/refs/jeffery-av--04-2k.jpg", "papers/figures/refs/jeffery-av--07-2k.jpg", // Canonical research-platter colored-pencil portrait of Jeffrey seated, WITH // the faceless single-stroke whistlegraph butterfly emblem (style + emblem). "papers/arxiv-whistlegraph/figures/cover.png", // The actual notepat.com interface, upright: colored key grid + waveform // tabs + FX sliders. "marketing/campaigns/serpentine-video/01_title/refs/notepat-screen.png",].map((p) => join(REPO, p));
function loadOpenAIKey() { if (process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY; const vault = `${REPO}/aesthetic-computer-vault/.devcontainer/envs/devcontainer.env`; if (existsSync(vault)) { for (const line of readFileSync(vault, "utf8").split("\n")) { if (line.startsWith("OPENAI_API_KEY=")) { return line.slice("OPENAI_API_KEY=".length).trim().replace(/^['"]|['"]$/g, ""); } } } throw new Error("OPENAI_API_KEY not found");}
const BASE = readFileSync(join(FIG, "header-prompt-jam-field.txt"), "utf8").trim();
const REF_BLOCK = `
USING THE REFERENCE IMAGES (critical — these are real, not decoration):- The PHOTOGRAPH is the real JEFFREY: match THIS man's actual face, hair, brow, nose, and build precisely for the single Jeffrey figure in the scene (a contemporary man in his late thirties, medium-length light-brown hair, warm eyes), rendered in the warm colored-pencil idiom. He appears ONCE, NOT centered, at peer eye-level.- The colored-pencil PORTRAIT reference shows Jeffrey's seated pose and his WHISTLEGRAPH BUTTERFLY EMBLEM (the small blue single-stroke butterfly floating beside him) — draw that emblem EXACTLY on the back of Jeffrey's chartreuse laptop lid: four soft petal-lobes around a pinched waist, a slim body, two curving antennae, pure single-color outline, NO face, NO fill, NO veins.- The SCREENSHOT reference is the ACTUAL notepat.com interface. EVERY front-facing laptop screen must show THIS interface, hand-rendered in colored pencil: the grid of soft-colored note-pad keys (each a flat pastel square with a tiny letter + note label), the slim toolbar of waveform tabs (sine / tri / saw / square / pno / harp / whist / sample), and the thin FX sliders along the top. Each laptop lights up DIFFERENT keys. Do NOT invent a different music app or a generic piano.`;
const FIX = `
CRITICAL LAPTOP-SCREEN RULES — these OVERRIDE anything that conflicts, and are the single most important thing to get right:1. A glowing screen showing the notepat interface appears ONLY on the INNER face of an open lid angled TOWARD the viewer. Render every front-facing screen CLEARLY and LEGIBLY as the notepat interface from the screenshot reference.2. The OUTER BACK of a laptop lid — the shell facing the viewer when a laptop is seen from behind — shows ONLY the solid matte shell color and a SINGLE small brand logo (or, on Jeffrey's chartreuse Neo, the whistlegraph butterfly emblem). NEVER render a screen, app interface, keys, visualizer, pixels, glyphs, text, glow, or reflection on the BACK of a lid. The back of a laptop is an opaque solid shell. This is the most common mistake — do not make it.3. No laptop screen shows the meadow, sky, faces, or any reflection of the scene. No floating UI detached from a screen.4. Make at least FOUR or FIVE laptops face the viewer clearly enough to read the notepat interface; the rest are seen from behind as clean LOGO-ONLY shells.5. Realistic, consistent laptop bezels, hinges, and proportions; no impossible double-screens, no screen on both sides of a lid.`;
const PROMPT = BASE + REF_BLOCK + FIX;
const mimeOf = (p) => (p.toLowerCase().endsWith(".jpg") || p.toLowerCase().endsWith(".jpeg") ? "image/jpeg" : "image/png");
async function gen(i, key) { const t0 = Date.now(); const form = new FormData(); form.append("model", "gpt-image-2"); form.append("prompt", PROMPT); form.append("size", "1536x1024"); form.append("quality", "high"); form.append("n", "1"); for (const ref of REFS) { const buf = readFileSync(ref); form.append("image[]", new Blob([buf], { type: mimeOf(ref) }), basename(ref)); } // gpt-image-2 can drop the socket mid-upload of a multi-ref bundle and the // server-side render is slow — raise the per-request timeout to 10 minutes. const ac = new AbortController(); const timer = setTimeout(() => ac.abort(), 10 * 60 * 1000); let res; try { res = await fetch("https://api.openai.com/v1/images/edits", { method: "POST", headers: { Authorization: `Bearer ${key}` }, body: form, signal: ac.signal, }); } finally { clearTimeout(timer); } if (!res.ok) throw new Error(`v${i}: OpenAI ${res.status}: ${(await res.text()).slice(0, 300)}`); const json = await res.json(); const b64 = json.data?.[0]?.b64_json; if (!b64) throw new Error(`v${i}: no image returned`); const out = join(FIG, `jam-header-v${i}.png`); writeFileSync(out, Buffer.from(b64, "base64")); console.log(` ✓ v${i} (${((Date.now() - t0) / 1000).toFixed(1)}s) -> ${out}`); return out;}
async function genWithRetry(i, key, tries = 5) { let last; for (let t = 1; t <= tries; t++) { try { return await gen(i, key); } catch (e) { last = e; const cause = e?.cause?.code || e?.code || e?.name || ""; console.error(` · v${i} try ${t} failed: ${e.message}${cause ? ` (${cause})` : ""}`); if (t < tries) { const backoff = Math.min(60, 3 * 2 ** (t - 1)); // 3,6,12,24,48s await new Promise((r) => setTimeout(r, backoff * 1000)); } } } throw last;}
for (const ref of REFS) if (!existsSync(ref)) throw new Error(`missing reference: ${ref}`);const key = loadOpenAIKey();console.log(`▸ ${N} jam-header variations via gpt-image-2 EDITS, refs:`);REFS.forEach((r) => console.log(` - ${r}`));const START = Math.max(1, parseInt(process.argv[3] || "1", 10));const ok = [];for (let k = START; k < START + N; k++) { try { ok.push(await genWithRetry(k, key)); } catch (e) { console.error(` ✗ ${e.message}`); }}console.log(`done: ${ok.length}/${N} succeeded`);if (ok.length) console.log(ok.join("\n"));