[object Object]

← 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

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 →