← back to Wallco Ai
regen 10 BLEED_GHOST_REGEN lips via SDXL→Gemini 2-pass — new IDs 39155-39164, originals (11035/11168/11233/11374/11520/11588/11723/11929/12142/12340) unpublished + notes linked to successors. Recipe: feedback_wallco_gemini_edit_recovers_sdxl_aesthetic.md. First pass failed on kind='pattern_repeat' (CHECK constraint allows seamless_tile|mural|mural_panel|best_seller_seed); finalize script reuses orphan PNGs to avoid double-spend on Gemini.
38532bd1e4eb82769c310ff4d6ca26f01ccebe4d · 2026-05-21 19:45:16 -0700 · Steve Abrams
Files touched
A scripts/gemini-regen-lips-finalize.jsA scripts/gemini-regen-lips.js
Diff
commit 38532bd1e4eb82769c310ff4d6ca26f01ccebe4d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu May 21 19:45:16 2026 -0700
regen 10 BLEED_GHOST_REGEN lips via SDXL→Gemini 2-pass — new IDs 39155-39164, originals (11035/11168/11233/11374/11520/11588/11723/11929/12142/12340) unpublished + notes linked to successors. Recipe: feedback_wallco_gemini_edit_recovers_sdxl_aesthetic.md. First pass failed on kind='pattern_repeat' (CHECK constraint allows seamless_tile|mural|mural_panel|best_seller_seed); finalize script reuses orphan PNGs to avoid double-spend on Gemini.
---
scripts/gemini-regen-lips-finalize.js | 135 +++++++++++++++++++++++
scripts/gemini-regen-lips.js | 200 ++++++++++++++++++++++++++++++++++
2 files changed, 335 insertions(+)
diff --git a/scripts/gemini-regen-lips-finalize.js b/scripts/gemini-regen-lips-finalize.js
new file mode 100644
index 0000000..b15b5d3
--- /dev/null
+++ b/scripts/gemini-regen-lips-finalize.js
@@ -0,0 +1,135 @@
+#!/usr/bin/env node
+/**
+ * Finalizer for gemini-regen-lips.js — the first run failed at INSERT
+ * (kind='pattern_repeat' violated CHECK; allowed = seamless_tile|mural|
+ * mural_panel|best_seller_seed). The 10 PNGs were already written to disk,
+ * so this re-inserts them without re-calling Gemini.
+ *
+ * Maps source-id → on-disk PNG by filename pattern lips_regen_<srcId>_*.png.
+ * Idempotency guard: skips if parent_design_id=<srcId> + generator=
+ * 'gemini-2.5-flash-image-edit' already exists.
+ */
+
+'use strict';
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+const { execSync, spawnSync } = require('child_process');
+
+const OUT_DIR = path.join(__dirname, '..', 'data', 'generated');
+const DB = 'dw_unified';
+const SRC_IDS = [11035, 11168, 11233, 11374, 11520, 11588, 11723, 11929, 12142, 12340];
+
+function psql(sql) {
+ return execSync(`psql ${DB} -At -q -F'|'`, { input: sql, encoding: 'utf8' }).trim();
+}
+function esc(v) { if (v == null) return 'NULL'; return "'" + String(v).replace(/'/g, "''") + "'"; }
+
+function findOrphanPng(srcId) {
+ const matches = fs.readdirSync(OUT_DIR).filter(f => f.startsWith(`lips_regen_${srcId}_`) && f.endsWith('.png'));
+ if (matches.length === 0) return null;
+ matches.sort();
+ return path.join(OUT_DIR, matches[matches.length - 1]);
+}
+
+function extractPalette(pngPath) {
+ const py = spawnSync('python3', ['-c', `
+from PIL import Image
+from collections import Counter
+import json, sys
+img = Image.open(sys.argv[1]).convert('RGB')
+img.thumbnail((300, 300))
+pal = img.quantize(colors=5, method=Image.Quantize.MEDIANCUT).convert('RGB')
+pixels = list(pal.getdata())
+cnt = Counter(pixels)
+total = sum(cnt.values())
+palette = [{'hex':'#{:02x}{:02x}{:02x}'.format(*rgb), 'pct':round(n/total*100,2)} for rgb,n in cnt.most_common(5)]
+print(json.dumps(palette))
+`, pngPath], { encoding: 'utf8' });
+ try { return JSON.parse(py.stdout.trim()); } catch { return []; }
+}
+
+function finalizeOne(srcId) {
+ const existing = psql(
+ `SELECT id FROM spoon_all_designs
+ WHERE parent_design_id = ${srcId}
+ AND generator = 'gemini-2.5-flash-image-edit'
+ ORDER BY id DESC LIMIT 1`
+ );
+ if (existing) {
+ console.log(`#${srcId} already finalized → #${existing} — skip`);
+ return { srcId, newId: parseInt(existing, 10), ok: true, action: 'skip-existing' };
+ }
+
+ const pngPath = findOrphanPng(srcId);
+ if (!pngPath) {
+ console.log(`#${srcId} no orphan PNG found — skip`);
+ return { srcId, newId: null, ok: false, action: 'no-png' };
+ }
+
+ const row = psql(
+ `SELECT width_in, height_in, panels, dominant_hex
+ FROM spoon_all_designs WHERE id = ${srcId}`
+ );
+ const [w, h, panels, dom] = row.split('|');
+ const palette = extractPalette(pngPath);
+ const dominant = palette[0]?.hex || dom;
+
+ const newSeed = crypto.randomInt(1, 2 ** 31 - 1);
+ const notes = `Gemini 2-pass regen of #${srcId} — original had motif ghost layer + multi-color contamination from SDXL. Recipe: feedback_wallco_gemini_edit_recovers_sdxl_aesthetic.md`;
+ const promptStub = `Pass 2 (Gemini regen of #${srcId} — flatten ghost layer to strict 2-tone designer-flat lips). Source colorway preserved; ground + figure hex extracted from #${srcId} prompt.`;
+
+ let newId;
+ try {
+ const ins = psql(`
+INSERT INTO spoon_all_designs
+ (kind, brand, width_in, height_in, panels, generator, prompt, seed,
+ image_url, local_path, dominant_hex, palette, motifs, tags, category,
+ parent_design_id, is_published, notes)
+VALUES
+ ('seamless_tile', 'wallco.ai', ${w || 24}, ${h || 24}, ${panels || 1},
+ 'gemini-2.5-flash-image-edit',
+ ${esc(promptStub)},
+ ${newSeed},
+ '/designs/img/by-id/__NEW__',
+ ${esc(pngPath)},
+ ${esc(dominant)},
+ ${esc(JSON.stringify(palette))}::jsonb,
+ ARRAY['lips']::text[],
+ ARRAY['designer-flat-lips','gemini-regen']::text[],
+ 'designer-flat-lips',
+ ${srcId},
+ TRUE,
+ ${esc(notes)})
+RETURNING id`);
+ newId = parseInt(ins, 10);
+ if (!Number.isFinite(newId)) throw new Error(`bad RETURNING id: ${ins}`);
+ } catch (err) {
+ console.log(`#${srcId} INSERT failed: ${err.message.split('\n')[0]}`);
+ return { srcId, newId: null, ok: false, action: 'insert-fail', err: err.message };
+ }
+
+ try {
+ psql(`UPDATE spoon_all_designs SET image_url = '/designs/img/by-id/${newId}' WHERE id = ${newId}`);
+ psql(`UPDATE spoon_all_designs
+ SET notes = COALESCE(notes,'') || ' — replaced by #${newId} (Gemini regen ${new Date().toISOString().slice(0,10)})'
+ WHERE id = ${srcId}`);
+ } catch (err) {
+ console.log(`#${srcId} (new=${newId}) post-INSERT UPDATE failed: ${err.message.split('\n')[0]}`);
+ return { srcId, newId, ok: true, action: 'inserted-but-update-failed', err: err.message };
+ }
+
+ console.log(`#${srcId} → new #${newId}`);
+ return { srcId, newId, ok: true, action: 'inserted' };
+}
+
+(function main() {
+ const summary = SRC_IDS.map(finalizeOne);
+ const ok = summary.filter(s => s.ok).length;
+ console.log('\n=== SUMMARY ===');
+ for (const s of summary) {
+ console.log(` ${s.srcId} → ${s.newId || '—'} [${s.action}]`);
+ }
+ console.log(`\n${ok}/${SRC_IDS.length} finalized`);
+})();
diff --git a/scripts/gemini-regen-lips.js b/scripts/gemini-regen-lips.js
new file mode 100644
index 0000000..9ca2f99
--- /dev/null
+++ b/scripts/gemini-regen-lips.js
@@ -0,0 +1,200 @@
+#!/usr/bin/env node
+/**
+ * Regen the 10 BLEED_GHOST_REGEN lips designs via SDXL → Gemini 2-pass.
+ *
+ * IDs: 11035 11168 11233 11374 11520 11588 11723 11929 12142 12340
+ * Recipe: feedback_wallco_gemini_edit_recovers_sdxl_aesthetic.md (2026-05-21)
+ * Cost: ~$0.04/edit × 10 = ~$0.40
+ *
+ * For each source row:
+ * 1. Read existing PNG (the failed SDXL render)
+ * 2. Send to gemini-2.5-flash-image-edit with corrective brief:
+ * - extract ground hex + figure hex from the original prompt
+ * - instruct: flatten to 2 colors total, kill ghost layer + halftone
+ * 3. Save new PNG, insert new row (category='designer-flat-lips',
+ * parent_design_id=<old>, is_published=TRUE, image_url=/designs/img/by-id/<new>)
+ * 4. Mark old row is_published=FALSE, append " — replaced by #<new>" to notes
+ *
+ * Idempotent guard: skips if a child row already exists with
+ * parent_design_id = <old> and generator = 'gemini-2.5-flash-image-edit'.
+ */
+
+'use strict';
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+const { execSync, spawnSync } = require('child_process');
+
+const KEY = process.env.GEMINI_API_KEY;
+if (!KEY) { console.error('GEMINI_API_KEY missing'); process.exit(1); }
+
+const OUT_DIR = path.join(__dirname, '..', 'data', 'generated');
+const DB = 'dw_unified';
+const SRC_IDS = [11035, 11168, 11233, 11374, 11520, 11588, 11723, 11929, 12142, 12340];
+
+function psql(sql) {
+ return execSync(`psql ${DB} -At -q -F'|'`, { input: sql, encoding: 'utf8' }).trim();
+}
+function esc(v) { if (v == null) return 'NULL'; return "'" + String(v).replace(/'/g, "''") + "'"; }
+
+function parsePalette(prompt) {
+ // "Ground: solid ivory-couture cream (#FFFEF8). Figure: single-ink classic Chanel rouge (#A6182E)"
+ const ground = (prompt.match(/Ground:[^()]*?\((#[A-Fa-f0-9]{6})\)/) || [])[1] || null;
+ const figure = (prompt.match(/Figure:[^()]*?\((#[A-Fa-f0-9]{6})\)/) || [])[1] || null;
+ const colorway = (prompt.match(/Lips Repeat\s*—\s*([^.]+?)\s*colorway/i) || [])[1] || 'unknown';
+ return { ground, figure, colorway };
+}
+
+function buildEditPrompt(ground, figure, colorway) {
+ return (
+ `Transform this image into a STRICTLY 2-tone designer flat-stencil wallpaper repeat.\n\n` +
+ `PALETTE — exactly TWO colors total, no other tones permitted:\n` +
+ ` • Ground: flat solid ${ground || '#FFFEF8'} edge to edge\n` +
+ ` • All lip silhouettes: flat solid ${figure || '#A6182E'} (every lip in the EXACT same single ink)\n\n` +
+ `MANDATORY corrections to the source image:\n` +
+ ` • REMOVE every ghost layer, ghosted duplicate, and faded copy behind the lips\n` +
+ ` • REMOVE any halftone, ben-day dots, dot screen, crosshatching, or stippling\n` +
+ ` • REMOVE atmospheric haze, airbrush blur, glow, soft edges\n` +
+ ` • REMOVE interior shading, highlight gradients, lipstick texture inside the silhouette\n` +
+ ` • REMOVE any third or fourth color contamination — only the ground hex and the figure hex may appear\n` +
+ ` • Razor-sharp silhouette edges, single-layer screenprint register\n\n` +
+ `Keep the lip composition (count, position, scale, horizontal orientation) UNCHANGED — ` +
+ `this is a corrective re-render, not a new layout. Tessellation must remain seamless.\n\n` +
+ `Aesthetic anchor: Tom Ford / YSL flagship-boutique wallcovering, ${colorway} colorway, ` +
+ `archival designer flat-stencil illustration. Reads as a hand-painted gouache silhouette ` +
+ `in 2-tone couture register.\n\n` +
+ `No watermark, no text, no border, no signature, no people, no faces, no eyes, no teeth.`
+ );
+}
+
+async function regenOne(srcId) {
+ const row = psql(
+ `SELECT id, local_path, prompt, width_in, height_in, panels, dominant_hex
+ FROM spoon_all_designs WHERE id = ${srcId}`
+ );
+ if (!row) { console.log(`#${srcId} NOT FOUND — skip`); return null; }
+ const [_id, srcPath, srcPrompt, w, h, panels, dom] = row.split('|');
+ if (!fs.existsSync(srcPath)) { console.log(`#${srcId} PNG missing on disk — skip`); return null; }
+
+ // Idempotency: child already exists?
+ const existing = psql(
+ `SELECT id FROM spoon_all_designs
+ WHERE parent_design_id = ${srcId}
+ AND generator = 'gemini-2.5-flash-image-edit'
+ ORDER BY id DESC LIMIT 1`
+ );
+ if (existing) { console.log(`#${srcId} already regenerated → #${existing} — skip`); return null; }
+
+ const { ground, figure, colorway } = parsePalette(srcPrompt);
+ const editPrompt = buildEditPrompt(ground, figure, colorway);
+
+ console.log(`#${srcId} (${colorway}) — gem 2-pass: ${ground} ground × ${figure} figure`);
+ const imageB64 = fs.readFileSync(srcPath).toString('base64');
+ const body = {
+ contents: [{
+ parts: [
+ { inline_data: { mime_type: 'image/png', data: imageB64 } },
+ { text: editPrompt },
+ ],
+ }],
+ generationConfig: { responseModalities: ['IMAGE'] },
+ };
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${KEY}`;
+
+ const t0 = Date.now();
+ const r = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ });
+ if (!r.ok) {
+ console.log(` HTTP ${r.status}: ${(await r.text()).slice(0, 200)} — skip`);
+ return null;
+ }
+ const j = await r.json();
+ try {
+ const { logGemini } = require(require('os').homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js');
+ logGemini(j, { app: 'wallco-ai', note: `regen-lips-${srcId}`, model: 'gemini-2.5-flash-image' });
+ } catch { /* non-fatal */ }
+ const part = j.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
+ const data = part?.inline_data?.data || part?.inlineData?.data;
+ if (!data) {
+ const text = j.candidates?.[0]?.content?.parts?.find(p => p.text)?.text;
+ console.log(` no image (${j.candidates?.[0]?.finishReason || 'unknown'}): ${(text || '').slice(0, 200)} — skip`);
+ return null;
+ }
+
+ const newSeed = crypto.randomInt(1, 2 ** 31 - 1);
+ const filename = `lips_regen_${srcId}_${Date.now()}_${newSeed}.png`;
+ const outPath = path.join(OUT_DIR, filename);
+ fs.writeFileSync(outPath, Buffer.from(data, 'base64'));
+ console.log(` wrote ${filename} (${((Date.now() - t0) / 1000).toFixed(1)}s)`);
+
+ // Re-extract palette
+ const py = spawnSync('python3', ['-c', `
+from PIL import Image
+from collections import Counter
+import json, sys
+img = Image.open(sys.argv[1]).convert('RGB')
+img.thumbnail((300, 300))
+pal = img.quantize(colors=5, method=Image.Quantize.MEDIANCUT).convert('RGB')
+pixels = list(pal.getdata())
+cnt = Counter(pixels)
+total = sum(cnt.values())
+palette = [{'hex':'#{:02x}{:02x}{:02x}'.format(*rgb), 'pct':round(n/total*100,2)} for rgb,n in cnt.most_common(5)]
+print(json.dumps(palette))
+`, outPath], { encoding: 'utf8' });
+ let palette = []; let dominant = dom;
+ try { palette = JSON.parse(py.stdout.trim()); dominant = palette[0]?.hex || dom; } catch {}
+
+ const newPrompt = `Pass 2 (Gemini regen of #${srcId} — flatten ghost layer to 2-tone): ` + editPrompt.slice(0, 1400);
+
+ const ins = psql(`
+INSERT INTO spoon_all_designs
+ (kind, brand, width_in, height_in, panels, generator, prompt, seed,
+ image_url, local_path, dominant_hex, palette, motifs, tags, category,
+ parent_design_id, is_published, notes)
+VALUES
+ ('pattern_repeat', 'wallco.ai', ${w || 24}, ${h || 24}, ${panels || 1},
+ 'gemini-2.5-flash-image-edit',
+ ${esc(newPrompt)},
+ ${newSeed},
+ '/designs/img/by-id/__NEW__',
+ ${esc(outPath)},
+ ${esc(dominant)},
+ ${esc(JSON.stringify(palette))}::jsonb,
+ ARRAY['lips']::text[],
+ ARRAY['designer-flat-lips','gemini-regen']::text[],
+ 'designer-flat-lips',
+ ${srcId},
+ TRUE,
+ ${esc(`Gemini 2-pass regen of #${srcId} — original had motif ghost layer + multi-color contamination from SDXL. Recipe: feedback_wallco_gemini_edit_recovers_sdxl_aesthetic.md`)})
+RETURNING id`);
+ const newId = parseInt(ins, 10);
+ 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,
+ notes = COALESCE(notes,'') || ' — replaced by #${newId} (Gemini regen ${new Date().toISOString().slice(0,10)})'
+ WHERE id = ${srcId}`);
+
+ console.log(` → new id #${newId}; #${srcId} unpublished`);
+ return newId;
+}
+
+(async function main() {
+ const summary = [];
+ for (const id of SRC_IDS) {
+ try {
+ const newId = await regenOne(id);
+ summary.push({ srcId: id, newId, ok: !!newId });
+ } catch (err) {
+ console.log(`#${id} ERROR: ${err.message}`);
+ summary.push({ srcId: id, newId: null, ok: false, err: err.message });
+ }
+ }
+ console.log('\n=== SUMMARY ===');
+ console.log(JSON.stringify(summary, null, 2));
+ const ok = summary.filter(s => s.ok).length;
+ console.log(`${ok}/${SRC_IDS.length} regenerated`);
+})();
← 5782b51 feat(scenic+ux): 6 Gracie-language colorways + 3 reviewer-re
·
back to Wallco Ai
·
feat(studio): 12 new Gracie-language scenic compositions (St b6510d8 →