← back to Wallco Ai
scripts/da4-preview-batch.js
127 lines
#!/usr/bin/env node
/**
* DA-4: heterogeneous preview batch for the drunk-animals refresh.
* Generates 6 designs spanning 4 categories, curator-mode (is_published=
* false because generate_designs.js inserts FALSE by default), local
* ComfyUI backend ($0). Writes data/logs/drunk-animals-preview-batch.json.
*
* Settlement-gate runs inline via scripts/settlement_tick_guard.js, so
* BLOCK / NEEDS_REVIEW will skip that prompt and try another.
*/
'use strict';
const path = require('path');
const fs = require('fs');
const { spawnSync, execSync } = require('child_process');
const ROOT = path.join(__dirname, '..');
const OUT_JSON = path.join(ROOT, 'data', 'logs', 'drunk-animals-preview-batch.json');
const { buildN: buildDA } = require('./drunk_animal_prompts');
const { buildN: buildV2 } = require('./drunk_monkeys_v2_prompts');
const { buildN: buildSub } = require('./drunk_monkeys_birds_subdued_prompts');
const SHARED = require('./drunk_motif_shared');
// Build a drunk-zoo-36 prompt by lifting ZOO_VIGNETTES from night-builder
// (it's not exported there) and SHARED + composing it here.
const NB = fs.readFileSync(path.join(ROOT, 'scripts', 'night-builder.js'), 'utf8');
const VIGS_MATCH = NB.match(/const ZOO_VIGNETTES = \[([\s\S]*?)\];/);
const VIGS = VIGS_MATCH[1].split(/'\s*,\s*\n\s*'/).map(s => s.replace(/^[\s']*|['\s,]*$/g, ''));
const ARTDIR_MATCH = NB.match(/const SHARED_ARTDIR = ([\s\S]*?);\s*\n/);
const SHARED_ARTDIR = ARTDIR_MATCH ? eval(ARTDIR_MATCH[1]) : '';
const ZOO_CWS = [
{ name: 'Bordeaux Velvet', tone: 'deep burgundy', hex: '#5a1818' },
{ name: 'Forest Loden', tone: 'dark gallery green', hex: '#22443a' },
];
function composeDrunkZoo36(cw) {
const vig = VIGS[Math.floor(Math.random() * VIGS.length)];
const tex = SHARED.TEXTURE_GROUNDS[Math.floor(Math.random() * SHARED.TEXTURE_GROUNDS.length)];
const bb = SHARED.BOTTLE_BACKDROPS[Math.floor(Math.random() * SHARED.BOTTLE_BACKDROPS.length)];
const ref = SHARED.pickReferenceClause('drunk-zoo-36');
return (
`Drunk Zoo 36" repeat wallpaper — ${cw.name} colorway. ` +
`Substrate: real ${tex.slug} (${tex.voice.replace(/^rendered on /, '')}). ` +
`Ground: solid ${cw.tone} (${cw.hex}) tinted over the natural-fiber substrate so the texture grain still reads through. ` +
`Figure: bone-ivory (#F2EADB) single-ink. ` +
`Vignette: ${vig}. Renders at wall-scale 36-inch seamless tile — motifs are LARGE, anchored on the central vertical axis of symmetry, generous breathing room of clean ground around each motif. ` +
(ref ? `Reference anchor: ${ref}. ` : '') +
`Secondary detail in negative space: ${bb} (rendered as a tiny bone-ivory ink silhouette at the very back, smaller scale than the main pair, not competing). ` +
`MANDATORY motifs visible in EVERY vignette: (1) alcohol being consumed, (2) a lit handrolled cannabis joint with a visible curl of smoke, (3) the animal(s) swinging from something. ` +
`Each animal is one FLAT STENCIL SILHOUETTE in bone-ivory ink — no interior shading. The joint and smoke wisp are tiny silhouette props inside the bone-ivory ink layer. NO cannabis leaf, NO marijuana leaf, NO foliage. ` +
`Whimsical playful joyful tone. Lean toward BOTANICALS over leaves. ` +
SHARED_ARTDIR
);
}
// Compose the 6 prompts
const batch = [
{ cat: 'drunk-animals', prompt: buildDA(1)[0] },
{ cat: 'drunk-animals', prompt: buildDA(1)[0] },
{ cat: 'drunk-zoo-36', prompt: composeDrunkZoo36(ZOO_CWS[0]) },
{ cat: 'drunk-zoo-36', prompt: composeDrunkZoo36(ZOO_CWS[1]) },
{ cat: 'drunk-monkeys-v2', prompt: buildV2(1)[0].prompt },
{ cat: 'drunk-animals-designer', prompt: buildSub(1)[0].prompt },
];
function psqlMaxId() {
try {
return parseInt(execSync(`psql dw_unified -At -c "SELECT COALESCE(max(id),0) FROM spoon_all_designs;"`, { encoding: 'utf8' }).trim(), 10) || 0;
} catch { return 0; }
}
const results = [];
const startedAt = new Date().toISOString();
for (let i = 0; i < batch.length; i++) {
const { cat, prompt } = batch[i];
console.log(`\n[${i+1}/${batch.length}] cat=${cat} — kicking off generate_designs.js`);
const maxBefore = psqlMaxId();
const t0 = Date.now();
const r = spawnSync('node', [
path.join(ROOT, 'scripts', 'generate_designs.js'),
'--n', '1',
'--kind', 'seamless_tile',
'--category', cat,
'--prompts', prompt,
], { stdio: 'inherit', cwd: ROOT, timeout: 5 * 60_000 });
const elapsedSec = ((Date.now() - t0) / 1000).toFixed(1);
let newId = null;
try {
const raw = execSync(
`psql dw_unified -At -c "SELECT id FROM spoon_all_designs WHERE category='${cat.replace(/'/g, "''")}' AND id > ${maxBefore} ORDER BY id DESC LIMIT 1;"`,
{ encoding: 'utf8' }).trim();
newId = raw ? parseInt(raw, 10) : null;
} catch {}
results.push({
idx: i + 1,
category: cat,
new_design_id: newId,
elapsed_sec: parseFloat(elapsedSec),
exit_status: r.status,
view_url: newId ? `http://127.0.0.1:9878/design/${newId}` : null,
image_url: newId ? `http://127.0.0.1:9878/designs/img/by-id/${newId}` : null,
prompt_head: prompt.slice(0, 200),
});
console.log(` → ${newId ? `id=${newId} (${elapsedSec}s)` : 'NO NEW DESIGN'}`);
}
const out = {
generated_at: startedAt,
finished_at: new Date().toISOString(),
total_designs: results.length,
successful: results.filter(r => r.new_design_id).length,
results,
};
fs.mkdirSync(path.dirname(OUT_JSON), { recursive: true });
fs.writeFileSync(OUT_JSON, JSON.stringify(out, null, 2));
console.log(`\n✓ wrote ${OUT_JSON}`);
console.log(` ${out.successful} / ${out.total_designs} generated successfully`);
for (const r of results) {
console.log(` ${r.category}\tid=${r.new_design_id}\t${r.view_url || '(none)'}`);
}