← back to Wallco Ai
gen-luxe v2: 5 heritage-house variants + DW-texture ground anchor
77c507c6ca0b70c771c43fac607200d4211a5f4f · 2026-05-25 00:58:54 -0700 · Steve Abrams
Five luxury aesthetic variants — every one a heritage house Steve actually
carries at $100-1000/yd:
A) de Gournay / Zuber / Gracie — hand-painted chinoiserie silk
B) House of Hackney — maximalist animal-pattern jewel-tone
C) 1838 Wallcoverings (NEW) — flocked-velvet damask, 2-color w/
soft raised-pile edges + drop shadow
D) Cole & Son Whimsical (NEW) — Stag Trail / Hummingbirds engraved
naturalist energy
E) Brunschwig & Fils / Lee Jofa — archive brick repeat (was V-B before)
Composition gate now cycles MAX=5 attempts (was 3) so every variant gets
one shot. Failure mode codified from yesterday's "way too child like" frog
regen — old prompts had Zuber/de Gournay/Brunschwig but missed the
heritage animal-pattern houses Steve named explicitly (House of Hackney,
1838 for flock effect, Cole & Son).
NEW: pickTextureBrief() pulls a real DW shopify_products row matching an
optional --texture-cat=<grasscloth|linen|silk|cork|raffia|...> and joins
shopify_color_enrichment for the dominant hex + family. The result lands
in the prompt as a concrete GROUND clause — e.g. "Brown cork (matched DW
SKU Kanda Natural Cork, dominant #D2B48C)" — so the LLM paints the motif
on top of a material we actually sell instead of hallucinating "ivory
paper". When --texture-cat is omitted, picks any natural-fiber wallcovering.
Generator tag bumped to gemini-2.5-flash-image-luxe-v2.
Also stages scripts/gen-from-stored-prompt.js from earlier in the session
(regenerates a wallco design from its STORED prompt rather than from
vision-describe of the broken PNG — the bullfrog-rescue tool that produced
#41536 from #28066's dropped-subject failure).
Files touched
A scripts/gen-from-stored-prompt.jsA scripts/gen-luxe.js
Diff
commit 77c507c6ca0b70c771c43fac607200d4211a5f4f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 25 00:58:54 2026 -0700
gen-luxe v2: 5 heritage-house variants + DW-texture ground anchor
Five luxury aesthetic variants — every one a heritage house Steve actually
carries at $100-1000/yd:
A) de Gournay / Zuber / Gracie — hand-painted chinoiserie silk
B) House of Hackney — maximalist animal-pattern jewel-tone
C) 1838 Wallcoverings (NEW) — flocked-velvet damask, 2-color w/
soft raised-pile edges + drop shadow
D) Cole & Son Whimsical (NEW) — Stag Trail / Hummingbirds engraved
naturalist energy
E) Brunschwig & Fils / Lee Jofa — archive brick repeat (was V-B before)
Composition gate now cycles MAX=5 attempts (was 3) so every variant gets
one shot. Failure mode codified from yesterday's "way too child like" frog
regen — old prompts had Zuber/de Gournay/Brunschwig but missed the
heritage animal-pattern houses Steve named explicitly (House of Hackney,
1838 for flock effect, Cole & Son).
NEW: pickTextureBrief() pulls a real DW shopify_products row matching an
optional --texture-cat=<grasscloth|linen|silk|cork|raffia|...> and joins
shopify_color_enrichment for the dominant hex + family. The result lands
in the prompt as a concrete GROUND clause — e.g. "Brown cork (matched DW
SKU Kanda Natural Cork, dominant #D2B48C)" — so the LLM paints the motif
on top of a material we actually sell instead of hallucinating "ivory
paper". When --texture-cat is omitted, picks any natural-fiber wallcovering.
Generator tag bumped to gemini-2.5-flash-image-luxe-v2.
Also stages scripts/gen-from-stored-prompt.js from earlier in the session
(regenerates a wallco design from its STORED prompt rather than from
vision-describe of the broken PNG — the bullfrog-rescue tool that produced
#41536 from #28066's dropped-subject failure).
---
scripts/gen-from-stored-prompt.js | 137 ++++++++++++++++++++++++++
scripts/gen-luxe.js | 200 ++++++++++++++++++++++++++++++++++++++
2 files changed, 337 insertions(+)
diff --git a/scripts/gen-from-stored-prompt.js b/scripts/gen-from-stored-prompt.js
new file mode 100644
index 0000000..4b91105
--- /dev/null
+++ b/scripts/gen-from-stored-prompt.js
@@ -0,0 +1,137 @@
+#!/usr/bin/env node
+// gen-from-stored-prompt — regenerate a wallco design from its STORED
+// generation prompt (not from vision-describe of the current PNG).
+//
+// Why this exists: 28066 was prompted "drunken bullfrog with champagne
+// among bluebonnets" but SDXL/comfy produced ONLY the bluebonnets — no
+// frog. Vision-describe on the broken output sees "white flowers" and
+// loses the bullfrog intent. To actually fix it we need the ORIGINAL
+// PROMPT, distilled to a multi-instance motif description and run
+// through lib/repeat-prompt + Gemini-2.5-flash-image.
+//
+// Usage: node scripts/gen-from-stored-prompt.js <src_id> [<motif_override>]
+
+const fs = require('fs');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const { buildRepeatPrompt, inferDensity } = require('../lib/repeat-prompt');
+const { gateComposition } = require('../lib/composition-detector');
+
+const SRC_ID = parseInt(process.argv[2], 10);
+const MOTIF_OVERRIDE = process.argv[3] || null;
+if (!Number.isFinite(SRC_ID)) { console.error('usage: <src_id>'); process.exit(2); }
+
+const ROOT = path.join(__dirname, '..');
+const GEN_DIR = path.join(ROOT, 'data', 'generated');
+
+function readKey() {
+ if (process.env.GEMINI_API_KEY) return process.env.GEMINI_API_KEY;
+ const env = fs.readFileSync(path.join(ROOT, '.env'), 'utf8');
+ const m = env.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, 200)}`);
+ 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('gemini returned no image');
+ return b64;
+}
+
+function psql(sql) {
+ const r = spawnSync('psql', ['dw_unified', '-At', '-q'], { input: sql, encoding: 'utf8' });
+ if (r.status !== 0) throw new Error(r.stderr || 'psql failed');
+ return r.stdout.trim();
+}
+
+// Distill a comfy/SDXL prompt down to ONE clean motif description that
+// describes a SINGLE instance of the subject. Strip the catalog-palette
+// boilerplate and the negative anchors.
+function distillMotif(storedPrompt) {
+ if (!storedPrompt) return null;
+ // Cut everything from the first "Professional designer-wallpaper palette"
+ // marker onward — that's the boilerplate catalog tail.
+ let s = storedPrompt.split(/Professional designer-wallpaper palette/i)[0].trim();
+ // Strip the prompt's NO-foo anti-prompts (they don't help a re-render)
+ s = s.replace(/\bNO\s+[a-z-]+(?:\s*,\s*NO\s+[a-z-]+)*/gi, '').trim();
+ s = s.replace(/\bno\s+text\s+or\s+watermark\b/gi, '').trim();
+ s = s.replace(/,\s*,/g, ',').replace(/,\s*$/, '').trim();
+ return s;
+}
+
+(async () => {
+ const row = psql(`SELECT row_to_json(t) FROM (SELECT id, category, prompt, dominant_hex, local_path FROM spoon_all_designs WHERE id=${SRC_ID}) t`);
+ if (!row) { console.error(`no row for id=${SRC_ID}`); process.exit(2); }
+ const d = JSON.parse(row);
+ console.log(`[gen-stored] src #${SRC_ID} · category=${d.category}`);
+ const motif = MOTIF_OVERRIDE || distillMotif(d.prompt);
+ if (!motif || motif.length < 20) { console.error('motif too short'); process.exit(2); }
+ console.log(`[gen-stored] motif: ${motif.slice(0, 200)}${motif.length>200?'...':''}`);
+ // Density: sparse for hero-scale subjects (a frog is a hero)
+ const density = inferDensity(motif);
+ console.log(`[gen-stored] density=${density}`);
+ const groundHex = d.dominant_hex || '#9ed0d0';
+ const groundDesc = 'tiffany robin\'s-egg blue';
+
+ let candidate = null, gate = null, attempt = 0;
+ const MAX = 3;
+ while (attempt < MAX) {
+ const prompt = buildRepeatPrompt({ motifDescription: motif, groundHex, groundDesc, density, attempt });
+ console.log(`\n[gen-stored] attempt ${attempt}`);
+ const t0 = Date.now();
+ const b64 = await geminiImage(prompt);
+ console.log(` gen: ${((Date.now()-t0)/1000).toFixed(1)}s`);
+ gate = await gateComposition(b64, { minInstances: 3 });
+ console.log(` composition: ${gate.ok ? 'PASS' : 'FAIL'} · n=${gate.result.instance_count} hero=${gate.result.hero_centered}`);
+ console.log(` saw: ${gate.result.what_you_see}`);
+ if (gate.ok) { candidate = b64; break; }
+ attempt++;
+ }
+ if (!candidate) {
+ console.log(`[gen-stored] gate failed all ${MAX} attempts — accepting last anyway`);
+ candidate = await geminiImage(buildRepeatPrompt({ motifDescription: motif, groundHex, groundDesc, density, attempt: MAX-1 }));
+ }
+
+ const newSeed = require('crypto').randomInt(1, 2 ** 31 - 1);
+ const filename = `stored_${SRC_ID}_${Date.now()}_${newSeed}.png`;
+ const outPath = path.join(GEN_DIR, filename);
+ fs.writeFileSync(outPath, Buffer.from(candidate, 'base64'));
+ console.log(`\n[gen-stored] saved → ${outPath}`);
+
+ const esc = v => v == null ? 'NULL' : "'" + String(v).replace(/'/g, "''") + "'";
+ const insertSQL = `
+INSERT INTO spoon_all_designs
+ (kind, brand, width_in, height_in, generator, prompt, seed,
+ image_url, local_path, dominant_hex, category, is_published, parent_design_id)
+VALUES
+ ('seamless_tile', 'wallco.ai', 24, 24,
+ 'gemini-2.5-flash-image-stored-prompt-v2',
+ ${esc('Regen of #' + SRC_ID + ' from stored prompt distilled to motif: ' + motif.slice(0, 1500))},
+ ${newSeed},
+ '/designs/img/by-id/__NEW__',
+ ${esc(outPath)},
+ ${esc(groundHex)},
+ ${esc(d.category)},
+ TRUE,
+ ${SRC_ID})
+RETURNING id;`;
+ const newId = parseInt(psql(insertSQL), 10);
+ if (!newId) throw new Error('insert failed');
+ psql(`UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${newId}' WHERE id=${newId}`);
+ // Unpublish the source so the redirect picks the new fix
+ psql(`UPDATE spoon_all_designs SET is_published=FALSE WHERE id=${SRC_ID}`);
+ console.log(`[gen-stored] PG id: ${newId}`);
+ console.log(`[gen-stored] view: http://127.0.0.1:9877/design/${newId}`);
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });
diff --git a/scripts/gen-luxe.js b/scripts/gen-luxe.js
new file mode 100644
index 0000000..ebef9f4
--- /dev/null
+++ b/scripts/gen-luxe.js
@@ -0,0 +1,200 @@
+#!/usr/bin/env node
+// gen-luxe — luxury-aesthetic regen for a wallco design.
+// Anchors style to the actual heritage houses Steve sells over $100/yd:
+// de Gournay + Zuber (hand-painted chinoiserie), Brunschwig & Fils + Lee Jofa
+// (heritage archive), House of Hackney (maximalist animal pattern), 1838
+// Wallcoverings (flocked velvet), Cole & Son (Whimsical Stag Trail), Mind
+// the Gap (eclectic naturalist), Gracie (museum chinoiserie), Designers
+// Guild + Scalamandré (color-confident classics), Phillip Jeffries +
+// Phillipe Romano (textile-anchored). Pulls a real DW texture from PG to
+// anchor the ground material instead of hallucinated "ivory paper".
+//
+// Explicitly NOT vinyl die-cut, NOT pop-art cartoon, NOT children's-book.
+//
+// Usage: node scripts/gen-luxe.js <src_id> "<motif description>" [--texture-cat=<cat>]
+//
+// Examples:
+// node scripts/gen-luxe.js 28066 "a bullfrog with champagne magnum and flute amid bluebonnets"
+// node scripts/gen-luxe.js 41536 "a bullfrog with champagne magnum" --texture-cat=linen
+
+const fs = require('fs');
+const path = require('path');
+const { spawnSync } = require('child_process');
+const { gateComposition } = require('../lib/composition-detector');
+
+const SRC_ID = parseInt(process.argv[2], 10);
+const MOTIF = process.argv[3];
+const TEXTURE_CAT = (process.argv.find(a => a.startsWith('--texture-cat=')) || '').split('=')[1] || '';
+if (!Number.isFinite(SRC_ID) || !MOTIF) {
+ console.error('usage: <src_id> "<motif>" [--texture-cat=<grasscloth|linen|cork|silk|mica|...>]');
+ process.exit(2);
+}
+
+const ROOT = path.join(__dirname, '..');
+const GEN_DIR = path.join(ROOT, 'data', 'generated');
+
+function readKey() {
+ if (process.env.GEMINI_API_KEY) return process.env.GEMINI_API_KEY;
+ const env = fs.readFileSync(path.join(ROOT, '.env'), 'utf8');
+ const m = env.match(/^GEMINI_API_KEY=(.+)$/m);
+ if (!m) throw new Error('GEMINI_API_KEY missing');
+ return m[1].trim();
+}
+function psql(sql) {
+ const r = spawnSync('psql', ['dw_unified', '-At', '-q'], { input: sql, encoding: 'utf8' });
+ if (r.status !== 0) throw new Error(r.stderr || 'psql failed');
+ return r.stdout.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, 200)}`);
+ 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('gemini returned no image');
+ return b64;
+}
+
+// Five variants — every one a heritage house Steve actually sells.
+// Order is intentional: A (de Gournay hand-painted), B (House of Hackney
+// maximalist), C (1838 flocked velvet), D (Cole & Son Whimsical), E
+// (Brunschwig & Fils archive). Composition-detector cycles until one
+// passes the multi-instance gate.
+//
+// Each variant takes an optional `textureBrief` describing the actual DW
+// ground material the design will sit on (pulled live from
+// shopify_products via PG, then summarized: "soft moss-green sisal" etc).
+// That goes into the GROUND clause so the LLM paints the motif on a
+// material the prompt explicitly names rather than hallucinating.
+function buildLuxePrompt(motif, attempt, textureBrief) {
+ const ground = textureBrief
+ ? `Ground: ${textureBrief} — paint the motif on top of this actual DW textile ground, with the textile's slubs and weave subtly visible behind the motif.`
+ : `Ground: hand-painted linen or grasscloth ground, muted natural pigment with subtle weave texture visible.`;
+ const variants = [
+ // A — de Gournay / Zuber / Gracie hand-painted chinoiserie
+ `A luxury hand-painted wallcovering in the tradition of de Gournay, Zuber, and Gracie — the $400-$1000/yd gold standard of hand-painted chinoiserie silk panels. The motif: ${motif}, rendered as a delicate hand-painted naturalist illustration with confident gouache brushwork. ${ground} Multiple instances of the same motif arranged as a diamond half-drop repeat (each row shifted half a column), 6 to 9 instances per 24" tile, generous breathing room between them. Aesthetic: museum-grade botanical archive, Brunschwig & Fils heritage, Anna French naturalist watercolor — refined, restrained, archival. Limited 3-4 color palette pulled from real heritage wallpaper: muted tiffany blue, ivory cream, deep sage, soft champagne. Single flat hand-painted color per shape with confident edges — NOT vinyl die-cut, NOT pop-art cartoon, NOT children's-book, NOT vector. Painterly imperfections allowed. Seamless 24" tile: motifs clipped on the right continue on the left of the SAME tile. NO centered hero motif. NO enlarged feature instance.`,
+
+ // B — House of Hackney maximalist animal-pattern
+ `Maximalist designer wallcovering in the unmistakable House of Hackney aesthetic — saturated, painterly, the motif rendered with naturalist authority but jewel-toned drama (think their "Limerence", "Hackney Empire", "Artemis" patterns). The motif: ${motif}. ${ground} Layout: drop-half repeat, 6-9 instances per 24" tile, each motif painted with confident brushwork and rich tonal modeling. Palette: 4-5 saturated heritage pigments — emerald, oxblood, antique gold, deep teal, ivory. The motif is dignified, never cute — eyes are realistic, no rosy cheeks, no anthropomorphic exaggeration. Painted on a richly-textured ground (silk satin or linen), with subtle ground variation. NOT cartoon, NOT pop-art, NOT children's-book, NOT vector illustration. Seamless 24" wrap, with motifs spanning tile edges. Reference: House of Hackney's own animal-pattern collections at $300-500/yd.`,
+
+ // C — 1838 Wallcoverings flocked velvet effect
+ `Heritage flocked-velvet wallcovering in the 1838 Wallcoverings tradition — the velvet-flock-on-silk technique that gives $400/yd damasks their dimensional texture. The motif: ${motif}, rendered as a flat velvet-flock silhouette in a single deep heritage color, with a subtle raised-pile soft edge as if the motif is physically flocked onto the ground. ${ground} Layout: diamond half-drop repeat, 6-9 instances per 24" tile, evenly spaced. Color: 2 colors only — one deep velvet flock color (emerald, oxblood, deep teal, midnight, or aubergine) plus one ground color (ivory silk, champagne, or pale gold). The motif must read as a SHADOWED FLOCKED VELVET DAMASK — NOT a flat printed sticker, NOT outlined comic, NOT children's-book illustration. Subtle drop-shadow under each motif suggesting the velvet pile catches the light. Seamless 24" tile, motifs wrap edges. Reference: 1838's Hesketh, Avington, and Capri flocked velvet collections.`,
+
+ // D — Cole & Son Whimsical (Stag Trail / Hummingbirds energy)
+ `Designer wallcovering in the Cole & Son Whimsical tradition — the same engraved-naturalist energy as their Hummingbirds, Stag Trail, and Versailles Grand patterns ($150-250/yd). The motif: ${motif}, rendered with the precision of a 19th-century engraved naturalist plate — clean confident linework, controlled shading via crosshatch or stipple, NOT cartoon shading. ${ground} Layout: a refined brick offset repeat, 6-9 instances per 24" tile, each motif scaled small enough to read as an all-over pattern (not a hero). Palette: 3 colors — a soft heritage ground (pale celadon, antique ivory, dusty sage), one mid-tone motif color (charcoal, deep teal, oxblood), and one accent. The motif is rendered with naturalist dignity — no exaggerated cuteness, no oversized eyes, no anthropomorphic cartoon energy. Think Audubon plate, not children's book. Seamless 24" tile, motifs wrap edges.`,
+
+ // E — Brunschwig & Fils / Lee Jofa archive (brick repeat)
+ `Heritage botanical wallcovering. Brunschwig & Fils + Lee Jofa archive style — the kind of $350/yd designer wallpaper sold in to-the-trade showrooms. The motif (repeated 6-9 times across a 24" tile in a refined brick offset repeat): ${motif}. ${ground} Hand-painted naturalist rendering with subtle tonal variation within each motif — gouache-on-linen aesthetic. Muted heritage palette: dusty teal, antique cream, restrained sage, vintage champagne. Each motif sits with quiet authority — no exaggerated cartoonish features, no oversized eyes, no rosy cheeks, no anthropomorphic cuteness. The subject is rendered as a serious naturalist or chinoiserie subject — Audubon plate, Zuber panel detail. Single-layer flat color per shape with confident painterly edges. Seamless 24" tile wrap left/right and top/bottom.`,
+ ];
+ return variants[attempt % variants.length];
+}
+
+// Pull one ground texture from the unified /api/bg-textures pool (or
+// directly from PG if the server isn't reachable) — returns a short
+// natural-language brief the LLM can paint over.
+function pickTextureBrief(category) {
+ const cat = (category || '').toLowerCase();
+ const where = cat
+ ? `AND p.title ~* '\\m(${cat})\\M'`
+ : `AND p.title ~* '\\m(grasscloth|linen|silk|cork|raffia|madagascar|sisal)\\M'`;
+ const sql = `
+ SELECT
+ regexp_replace(split_part(p.title, '|', 1), '\\s+(Wallpaper|Wallcoverings?|Vinyl|-\\s*Sample)\\s*$', '', 'gi') AS name,
+ c.dominant_hex,
+ c.color_family,
+ CASE
+ WHEN p.title ~* '\\mgrasscloth\\M' THEN 'grasscloth'
+ WHEN p.title ~* '\\mlinen\\M' THEN 'linen'
+ WHEN p.title ~* '\\msilk\\M' THEN 'silk'
+ WHEN p.title ~* '\\mcork\\M' THEN 'cork'
+ WHEN p.title ~* '\\mraffia\\M' THEN 'raffia'
+ WHEN p.title ~* '\\mmadagascar\\M' THEN 'madagascar'
+ WHEN p.title ~* '\\msisal\\M' THEN 'sisal'
+ ELSE 'natural textile'
+ END AS material
+ FROM shopify_products p
+ JOIN shopify_color_enrichment c ON c.shopify_id = p.shopify_id
+ WHERE p.product_type ILIKE '%wallcovering%'
+ AND p.image_url IS NOT NULL
+ AND c.dominant_hex IS NOT NULL
+ ${where}
+ ORDER BY random() LIMIT 1`;
+ try {
+ const out = psql(sql);
+ if (!out) return null;
+ const [name, hex, family, mat] = out.split('|');
+ if (!mat || !hex) return null;
+ return `${family || 'muted'} ${mat} (matched DW SKU ${name}, dominant ${hex})`;
+ } catch (e) {
+ console.warn('[gen-luxe] texture pick failed:', e.message);
+ return null;
+ }
+}
+
+(async () => {
+ const row = psql(`SELECT row_to_json(t) FROM (SELECT id, category, dominant_hex FROM spoon_all_designs WHERE id=${SRC_ID}) t`);
+ if (!row) { console.error(`no row for id=${SRC_ID}`); process.exit(2); }
+ const d = JSON.parse(row);
+ console.log(`[gen-luxe] src #${SRC_ID} · category=${d.category}`);
+ console.log(`[gen-luxe] motif: ${MOTIF}`);
+
+ const textureBrief = pickTextureBrief(TEXTURE_CAT);
+ if (textureBrief) console.log(`[gen-luxe] ground texture: ${textureBrief}`);
+ else console.log(`[gen-luxe] ground texture: generic (PG pick returned nothing)`);
+
+ let candidate = null, gate = null, attempt = 0;
+ const MAX = 5;
+ while (attempt < MAX) {
+ const prompt = buildLuxePrompt(MOTIF, attempt, textureBrief);
+ console.log(`\n[gen-luxe] attempt ${attempt} (variant ${'ABCDE'[attempt%5]})`);
+ const t0 = Date.now();
+ const b64 = await geminiImage(prompt);
+ console.log(` gen: ${((Date.now()-t0)/1000).toFixed(1)}s`);
+ gate = await gateComposition(b64, { minInstances: 3 });
+ console.log(` composition: ${gate.ok ? 'PASS' : 'FAIL'} · n=${gate.result.instance_count} hero=${gate.result.hero_centered}`);
+ console.log(` saw: ${gate.result.what_you_see}`);
+ if (gate.ok) { candidate = b64; break; }
+ attempt++;
+ }
+ if (!candidate) {
+ console.log(`[gen-luxe] gate failed all ${MAX}, accepting last`);
+ candidate = await geminiImage(buildLuxePrompt(MOTIF, MAX-1, textureBrief));
+ }
+
+ const newSeed = require('crypto').randomInt(1, 2 ** 31 - 1);
+ const filename = `luxe_${SRC_ID}_${Date.now()}_${newSeed}.png`;
+ const outPath = path.join(GEN_DIR, filename);
+ fs.writeFileSync(outPath, Buffer.from(candidate, 'base64'));
+ console.log(`\n[gen-luxe] saved → ${outPath}`);
+ const esc = v => v == null ? 'NULL' : "'" + String(v).replace(/'/g, "''") + "'";
+ const insertSQL = `
+INSERT INTO spoon_all_designs
+ (kind, brand, width_in, height_in, generator, prompt, seed,
+ image_url, local_path, dominant_hex, category, is_published, parent_design_id)
+VALUES
+ ('seamless_tile', 'wallco.ai', 24, 24,
+ 'gemini-2.5-flash-image-luxe-v2',
+ ${esc('Luxe regen of #' + SRC_ID + ' anchored to de Gournay / House of Hackney / 1838-flock / Cole & Son / Brunschwig & Fils.' + (textureBrief ? ' Ground: ' + textureBrief + '.' : '') + ' Motif: ' + MOTIF)},
+ ${newSeed},
+ '/designs/img/by-id/__NEW__',
+ ${esc(outPath)},
+ ${esc(d.dominant_hex)},
+ ${esc(d.category)},
+ TRUE,
+ ${SRC_ID})
+RETURNING id;`;
+ const newId = parseInt(psql(insertSQL), 10);
+ if (!newId) throw new Error('insert failed');
+ psql(`UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${newId}' WHERE id=${newId}`);
+ psql(`UPDATE spoon_all_designs SET is_published=FALSE WHERE id=${SRC_ID}`);
+ console.log(`[gen-luxe] PG id: ${newId}`);
+ console.log(`[gen-luxe] view: http://127.0.0.1:9877/design/${newId}`);
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });
← c6011e8 design page admin sheet: add ⎋ Logout button next to close
·
back to Wallco Ai
·
fix(edges-scan): move scanner from ~/.claude/skills into pro 5d5b7c0 →