← back to Wallco Ai
LA Toile: Nano Banana generator (literal scenic toile, 30/30 settlement-clean)
07dab7dac5b6e717f69bf61f99f32cda4d9302df · 2026-06-03 07:53:06 -0700 · steve@designerwallcoverings.com
Files touched
A scripts/generate-la-toile-nb.js
Diff
commit 07dab7dac5b6e717f69bf61f99f32cda4d9302df
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Wed Jun 3 07:53:06 2026 -0700
LA Toile: Nano Banana generator (literal scenic toile, 30/30 settlement-clean)
---
scripts/generate-la-toile-nb.js | 115 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 115 insertions(+)
diff --git a/scripts/generate-la-toile-nb.js b/scripts/generate-la-toile-nb.js
new file mode 100644
index 0000000..b812baf
--- /dev/null
+++ b/scripts/generate-la-toile-nb.js
@@ -0,0 +1,115 @@
+// LA Toile de Jouy via Gemini Nano Banana (gemini-2.5-flash-image) — LITERAL,
+// recognizable engraved-toile scenes (the SDXL/ComfyUI lane produced abstract blobs;
+// Steve redirected to the Gemini path already wired in this codebase, 2026-06-03).
+// Generates 30 scenes to /tmp/nb-toile-NN.png with a settlement gate (no bird/
+// butterfly/banana/grape — Steve's legal rule). No DB writes (proof set for review).
+// Usage: node scripts/generate-la-toile-nb.js [startIdx] [count]
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const ROOT = path.join(__dirname, '..');
+const PY = path.join(ROOT, 'scripts/vectorize/.venv/bin/python3');
+
+function readKey() {
+ if (process.env.GEMINI_API_KEY) return process.env.GEMINI_API_KEY;
+ const m = fs.readFileSync(path.join(ROOT, '.env'), 'utf8').match(/^GEMINI_API_KEY=(.+)$/m);
+ if (!m) throw new Error('GEMINI_API_KEY missing'); return m[1].trim();
+}
+async function geminiImage(prompt) {
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${readKey()}`;
+ const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseModalities: ['IMAGE'] } }) });
+ if (!r.ok) throw new Error(`gemini ${r.status}: ${(await r.text()).slice(0, 160)}`);
+ const j = await r.json();
+ const part = j?.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
+ const b64 = part?.inline_data?.data || part?.inlineData?.data;
+ if (!b64) throw new Error('no image'); return b64;
+}
+const SETTLEMENT_Q = "Does this image contain any bird, butterfly, banana, banana plant or leaf, or bunch of grapes? Answer yes only if one of those is clearly visible.";
+function settlementViolation(pngPath) {
+ try { return execSync(`${PY} scripts/vision-contains.py ${JSON.stringify(pngPath)} ${JSON.stringify(SETTLEMENT_Q)}`,
+ { cwd: ROOT, encoding: 'utf8' }).trim() === 'yes'; } catch { return false; }
+}
+
+const INKS = [
+ { name: 'garden', palette: 'hedge green #3E4A38, olive #8C9472, sunset terracotta #B0512F, dusty rose #9F5664 and brass #B08D3F', ground: 'natural flax linen ivory' },
+ { name: 'jacaranda spring',palette: 'jacaranda violet #7C6FA6, hedge green #3E4A38, olive #8C9472, blush stucco #E8B7AC and sand #C9B79C', ground: 'warm ivory linen' },
+ { name: 'bougainvillea', palette: 'bougainvillea magenta #C2417B, cypress #2C3A30, olive #8C9472 and brass #B08D3F', ground: 'natural flax linen' },
+ { name: 'pacific', palette: 'pacific blue #4E7C93, sky #A9C6D4, sage #8C9472, sand #E4D6BF and charcoal #3A352E', ground: 'pale flax linen' },
+ { name: 'sunset', palette: 'sunset coral #E3795F, terracotta #B0512F, apricot #E8A06A, olive #8C9472 and brass #B08D3F', ground: 'peach-dyed linen' },
+ { name: 'blush sunshine', palette: 'dusty rose #9F5664, blush #E8B7AC, apricot #E8A06A, soft sage #A9B6A0 and champagne gold #CBB179', ground: 'blush-dyed linen' },
+ { name: 'brass & green', palette: 'brass #B08D3F, champagne gold #CBB179, hedge green #3E4A38, olive #8C9472 and noir #15140F', ground: 'natural flax linen' },
+ { name: 'noir & rose', palette: 'black-tie noir #15140F, charcoal #3A352E, dusty rose #9F5664, sage #8C9472 and brass #B08D3F', ground: 'marble-tone linen ivory' },
+];
+const SCENES = [
+ 'the Hollywood Sign on a chaparral hillside with Griffith Observatory domes below',
+ 'the Venice Beach boardwalk with the fishing pier, beach cruisers and rolling surf',
+ 'the downtown Los Angeles skyline with City Hall tower and Art-Deco high-rises',
+ 'a jacaranda-lined Sunset Boulevard with vintage streetlamps and stucco storefronts',
+ 'Beverly Hills Spanish-revival bungalows with tiled fountains and clipped hedges',
+ 'the Santa Monica Pier Ferris wheel, arcade and ocean waves',
+ 'the Chinatown paifang gate with hanging lanterns and pagoda rooflines',
+ 'Echo Park Lake with paddle boats, lotus blossoms and the downtown skyline beyond',
+ 'mid-century Case Study glass houses cantilevered over the canyon hills',
+ 'vintage 1960s convertibles cruising the curves of Mulholland Drive',
+ 'the Watts Towers mosaic spires rising over small craftsman rooftops',
+ 'the Los Angeles Aqueduct cascade against the San Gabriel mountains',
+ 'Olvera Street market stalls with woven baskets, pottery and string-light arcades',
+ 'Griffith Park trails winding through oak groves with a grazing mule deer',
+ 'the Malibu coastline with longboard surfers, sea cliffs and a lighthouse',
+ 'Art-Deco movie palaces with marquees, ticket booths and klieg-light beams',
+ 'old-Los-Angeles citrus groves with orange crates, packing sheds and wagons',
+ 'Union Station archways with a vintage streamliner train and a magnolia courtyard',
+ 'a golden-age studio backlot with sound stages, klieg lights and camera cranes',
+ 'Pasadena Rose Parade floats with marching bands and ranunculus garlands',
+ 'the Angels Flight funicular railway climbing Bunker Hill brownstones',
+ 'the La Brea Tar Pits with woolly mammoths and sabertooth silhouettes',
+ 'the Bradbury Building wrought-iron atrium stairs and birdcage elevators',
+ 'a Mulholland overlook at dusk with a lone coyote and city lights below',
+ 'Silver Lake reservoir with joggers, eucalyptus trees and modernist homes',
+ 'the Original Farmers Market clock tower with the Grove trolley and produce carts',
+ 'Topanga Canyon cabins tucked among coast live oaks and boulders',
+ 'the Googie LAX Theme Building arches with finned cars and jet contrails',
+ 'Dodger Stadium grandstands ringed by eucalyptus and the Elysian hills',
+ 'jacaranda-lined residential streets in full bloom with craftsman porches',
+];
+
+function buildPrompt(scene, ink) {
+ return `A true Toile de Jouy wallpaper depicting ${scene}. ` +
+ `Render as fine copperplate ENGRAVING with delicate line hatching and stippling, in the heritage manner of ` +
+ `Pierre Frey, Zuber and Brunschwig & Fils. The pictorial vignettes must be RECOGNIZABLE and LITERAL — a viewer ` +
+ `should clearly identify each landmark and element exactly as described. Small detailed scenes repeat ALL OVER ` +
+ `the sheet, edge to edge, densely filling the field on a ${ink.ground} ground. ` +
+ `Beverly Hills multi-color colorway: ${ink.palette}. Aged, sun-bleached pigments — never neon. ` +
+ `Seamless repeating wallpaper, flat, no perspective, no border, no frame, no text or lettering except any ` +
+ `landmark sign literally named. Trees are Italian cypress, coast live oak, olive, jacaranda, magnolia and ` +
+ `eucalyptus — NO palm trees. NO birds, NO butterflies, NO bananas, NO grapes.`;
+}
+
+(async () => {
+ const start = parseInt(process.argv[2] || '0', 10);
+ const count = parseInt(process.argv[3] || String(SCENES.length), 10);
+ const ROLLS = 3;
+ const winners = {};
+ for (let i = start; i < Math.min(start + count, SCENES.length); i++) {
+ const ink = INKS[i % INKS.length];
+ const slug = 'nb-toile-' + String(i + 1).padStart(2, '0');
+ const dest = `/tmp/${slug}.png`;
+ let won = false;
+ for (let r = 1; r <= ROLLS && !won; r++) {
+ try {
+ const t0 = Date.now();
+ const b64 = await geminiImage(buildPrompt(SCENES[i], ink));
+ fs.writeFileSync(dest, Buffer.from(b64, 'base64'));
+ const bad = settlementViolation(dest);
+ const dt = ((Date.now() - t0) / 1000).toFixed(1);
+ if (bad) { console.log(` ${slug} roll ${r}: SETTLEMENT violation (bird/etc) — re-roll (${dt}s)`); continue; }
+ console.log(`[${i + 1}/30] ${slug} OK (${ink.name}) -> ${dest} (${dt}s)`);
+ winners[slug] = dest; won = true;
+ } catch (e) { console.log(` ${slug} roll ${r}: FAIL ${e.message}`); }
+ }
+ if (!won) console.log(`[${i + 1}/30] ${slug} — no clean roll in ${ROLLS} (kept last)`);
+ }
+ fs.writeFileSync('/tmp/nb-toile-winners.json', JSON.stringify(winners, null, 2));
+ console.log(`\n${Object.keys(winners).length} scenes generated → /tmp/nb-toile-winners.json`);
+})();
← b4692f9 yolo audit: APPLIED+DEPLOYED 3 unpublish + 51 kind-flip + 12
·
back to Wallco Ai
·
LA Toile: stage+publish 30 Nano Banana toiles to live (peer- 2ef1803 →