← back to Wallco Ai
refiner-compare: 3 more render columns (Gemini 2.5 Flash Image, Flux 1.1 Pro, gpt-image-1)
a109d4fd237794e70395138eef9a895570051e2e · 2026-05-19 15:22:11 -0700 · SteveStudio2
- scripts/compare_progress.js: shared 6-field progress.json writer derived from
disk so 4 concurrent cloud backends + 2 ComfyUI shards never clobber each other
- scripts/gemini_compare.js: Gemini 2.5 Flash Image (~$0.04/img, 25 renders)
- scripts/flux_compare.js: Replicate black-forest-labs/flux-1.1-pro ($0.04, 25)
- scripts/dalle_compare.js: OpenAI gpt-image-1 ($0.04, 25) — sk-proj key
doesn't expose dall-e-3 and the response_format/style params are rejected;
set OPENAI_IMAGE_MODEL=dall-e-3 to override if available on another key
- refiner_compare.js + replicate_compare.js: use shared save() so they don't
drop the 3 new cloud columns when racing the new backends
- compare.html: .trio (3-col) → .hex (6-col, wraps 3-3 at <1500px, 2-2-2 at <900px);
COST + costPill teach about gemini/flux/dalle; status line and total spend
show all 4 cloud counts; cache-buster keys on every column's count
Verified: Gemini 23/25, Flux 25/25, gpt-image-1 in flight. Routed OPENAI_API_KEY
through secrets-manager (added wallco-ai/.env destination under services.OPENAI_API_KEY).
Files touched
A scripts/compare_progress.jsA scripts/dalle_compare.jsA scripts/flux_compare.jsA scripts/gemini_compare.jsM scripts/refiner_compare.jsM scripts/replicate_compare.js
Diff
commit a109d4fd237794e70395138eef9a895570051e2e
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 19 15:22:11 2026 -0700
refiner-compare: 3 more render columns (Gemini 2.5 Flash Image, Flux 1.1 Pro, gpt-image-1)
- scripts/compare_progress.js: shared 6-field progress.json writer derived from
disk so 4 concurrent cloud backends + 2 ComfyUI shards never clobber each other
- scripts/gemini_compare.js: Gemini 2.5 Flash Image (~$0.04/img, 25 renders)
- scripts/flux_compare.js: Replicate black-forest-labs/flux-1.1-pro ($0.04, 25)
- scripts/dalle_compare.js: OpenAI gpt-image-1 ($0.04, 25) — sk-proj key
doesn't expose dall-e-3 and the response_format/style params are rejected;
set OPENAI_IMAGE_MODEL=dall-e-3 to override if available on another key
- refiner_compare.js + replicate_compare.js: use shared save() so they don't
drop the 3 new cloud columns when racing the new backends
- compare.html: .trio (3-col) → .hex (6-col, wraps 3-3 at <1500px, 2-2-2 at <900px);
COST + costPill teach about gemini/flux/dalle; status line and total spend
show all 4 cloud counts; cache-buster keys on every column's count
Verified: Gemini 23/25, Flux 25/25, gpt-image-1 in flight. Routed OPENAI_API_KEY
through secrets-manager (added wallco-ai/.env destination under services.OPENAI_API_KEY).
---
scripts/compare_progress.js | 54 ++++++++++++++++++++++
scripts/dalle_compare.js | 95 +++++++++++++++++++++++++++++++++++++++
scripts/flux_compare.js | 105 +++++++++++++++++++++++++++++++++++++++++++
scripts/gemini_compare.js | 92 +++++++++++++++++++++++++++++++++++++
scripts/refiner_compare.js | 19 ++------
scripts/replicate_compare.js | 17 ++-----
6 files changed, 353 insertions(+), 29 deletions(-)
diff --git a/scripts/compare_progress.js b/scripts/compare_progress.js
new file mode 100644
index 0000000..c98f139
--- /dev/null
+++ b/scripts/compare_progress.js
@@ -0,0 +1,54 @@
+'use strict';
+/**
+ * Shared progress.json writer for the refiner-compare 6-column grid.
+ *
+ * Every backend (refiner_compare, replicate_compare, gemini_compare,
+ * flux_compare, dalle_compare) derives the row state fresh from disk on
+ * every write, so concurrent writers never clobber each other — the last
+ * writer is always correct, and the file always reflects the union of all
+ * 6 columns currently on disk.
+ */
+const fs = require('fs');
+const path = require('path');
+const { FASHION_PALETTES } = require('./fashion_palettes');
+
+const STYLES = [
+ 'damask', 'arabesque trellis', 'geometric art-deco', 'botanical floral', 'ogee medallion',
+ 'stripe and sprig', 'block-print paisley', 'scrollwork acanthus', 'chinoiserie scenic', 'herringbone tonal',
+ 'guilloché lattice', 'stylized fan', 'marbled swirl', 'star tessellation', 'vine and berry',
+ 'diamond harlequin', 'rosette grid', 'wave and shell', 'feathered plume', 'interlocking fretwork key',
+ 'arts-and-crafts leaf', 'moroccan zellige', 'pinstripe ticking', 'sunburst medallion', 'quatrefoil net',
+];
+
+const OUT = path.join(__dirname, '..', 'public', 'refiner-compare');
+const haveImg = p => { try { return fs.statSync(p).size > 1000; } catch { return false; } };
+
+function buildRows() {
+ return STYLES.map((style, i) => ({
+ i, style,
+ brand: FASHION_PALETTES[i % FASHION_PALETTES.length].brand,
+ seed: 770000 + i,
+ base: haveImg(path.join(OUT, `${i}_base.png`)) ? `${i}_base.png` : null,
+ refiner: haveImg(path.join(OUT, `${i}_refiner.png`)) ? `${i}_refiner.png` : null,
+ replicate: haveImg(path.join(OUT, `${i}_replicate.png`)) ? `${i}_replicate.png` : null,
+ gemini: haveImg(path.join(OUT, `${i}_gemini.png`)) ? `${i}_gemini.png` : null,
+ flux: haveImg(path.join(OUT, `${i}_flux.png`)) ? `${i}_flux.png` : null,
+ dalle: haveImg(path.join(OUT, `${i}_dalle.png`)) ? `${i}_dalle.png` : null,
+ }));
+}
+
+function save() {
+ const rows = buildRows();
+ // `done` still tracks the local-ComfyUI base+refiner count (the original
+ // progress bar semantics). Cloud columns are surfaced as separate counts
+ // in the status line on compare.html, derived directly from `rows`.
+ const done = rows.reduce((n, r) => n + (r.base ? 1 : 0) + (r.refiner ? 1 : 0), 0);
+ const total = STYLES.length * 2;
+ fs.writeFileSync(
+ path.join(OUT, 'progress.json'),
+ JSON.stringify({ done, total, rows }, null, 1),
+ );
+ return { done, total, rows };
+}
+
+module.exports = { STYLES, OUT, haveImg, buildRows, save };
diff --git a/scripts/dalle_compare.js b/scripts/dalle_compare.js
new file mode 100644
index 0000000..5484124
--- /dev/null
+++ b/scripts/dalle_compare.js
@@ -0,0 +1,95 @@
+'use strict';
+/**
+ * Column 6 of the refiner comparison — OpenAI image generation, 1024×1024.
+ *
+ * Defaults to `gpt-image-1` (OpenAI's current image model, ~$0.04/image at
+ * high quality 1024×1024). The sk-proj key we route doesn't expose dall-e-3,
+ * and gpt-image-1 is the state-of-the-art replacement anyway.
+ *
+ * Same 25 prompts as refiner_compare.js. gpt-image-1 doesn't accept a seed,
+ * and the prompt-rewriter behavior differs by model. We pass the explicit
+ * "I NEED to test … AS-IS" preamble to discourage automatic rewriting.
+ *
+ * node scripts/dalle_compare.js
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const { FASHION_PALETTES, paletteString } = require('./fashion_palettes');
+const { STYLES, OUT, haveImg, save } = require('./compare_progress');
+
+const TAG = '[dalle]';
+// gpt-image-1 = OpenAI's current image model (DALL-E 3's replacement).
+// Set OPENAI_IMAGE_MODEL=dall-e-3 to override if your key has dall-e-3 access.
+const MODEL = process.env.OPENAI_IMAGE_MODEL || 'gpt-image-1';
+
+function loadKey() { return process.env.OPENAI_API_KEY || null; }
+
+function buildPrompt(style, pal) {
+ // "I NEED to test" is the OpenAI-documented preamble that suppresses
+ // DALL-E 3's automatic prompt rewriting — we want the exact prompt to
+ // hit the model so column-vs-column is a fair comparison.
+ return 'I NEED to test how the model tokenizes this exact request. ' +
+ 'DO NOT add any detail, just use it AS-IS: ' +
+ `A seamless wallpaper pattern, ${style} motif, ${paletteString(pal)}, ` +
+ 'seamless tile, repeating pattern, no edges, fabric pattern, wallpaper design, archival quality, high detail, ' +
+ 'screen-printed wallpaper, solid opaque flat ink, every motif fully filled with solid color, ' +
+ 'no halftone, no dot screens, no gradients, no text, no watermark, no border.';
+}
+
+function genDalle(prompt, outPath) {
+ const KEY = loadKey();
+ if (!KEY) throw new Error('OPENAI_API_KEY not set in wallco-ai/.env');
+
+ const sh = (cmd, opts) => {
+ try { return execSync(cmd, opts); }
+ catch (e) { throw new Error(String((e && e.message) || e).split(KEY).join('sk-***REDACTED')); }
+ };
+
+ // gpt-image-1 always returns b64 (no response_format needed) and has a
+ // 4-tier quality enum (low/medium/high/auto). For dall-e-3, override:
+ // OPENAI_IMAGE_MODEL=dall-e-3 (uses standard/hd quality)
+ const isGpt = MODEL.startsWith('gpt-image');
+ const body = {
+ model: MODEL,
+ prompt,
+ n: 1,
+ size: '1024x1024',
+ quality: isGpt ? 'high' : 'hd',
+ ...(isGpt ? {} : { response_format: 'b64_json' }),
+ };
+
+ const raw = sh(
+ `curl -sf -m 120 -H 'Authorization: Bearer ${KEY}' -H 'Content-Type: application/json' -X POST 'https://api.openai.com/v1/images/generations' -d @-`,
+ { input: JSON.stringify(body), encoding: 'utf8', maxBuffer: 40 * 1024 * 1024 },
+ );
+
+ let res;
+ try { res = JSON.parse(raw); }
+ catch { throw new Error(`DALL-E non-JSON response: ${raw.slice(0, 300)}`); }
+ if (res.error) throw new Error(`DALL-E error: ${res.error.message || JSON.stringify(res.error)}`);
+
+ const b64 = res.data && res.data[0] && res.data[0].b64_json;
+ if (!b64) throw new Error(`DALL-E returned no image: ${JSON.stringify(res).slice(0, 300)}`);
+ fs.writeFileSync(outPath, Buffer.from(b64, 'base64'));
+ if (!haveImg(outPath)) throw new Error('DALL-E wrote a sub-1KB file (likely empty)');
+ return outPath;
+}
+
+(async () => {
+ const t0 = Date.now();
+ let ok = 0, fail = 0, skip = 0;
+ console.log(`${TAG} 25 renders via OpenAI ${MODEL} (HD)`);
+ for (let i = 0; i < STYLES.length; i++) {
+ const style = STYLES[i], pal = FASHION_PALETTES[i % FASHION_PALETTES.length];
+ const outPath = path.join(OUT, `${i}_dalle.png`);
+ if (haveImg(outPath)) { skip++; save(); console.log(` ${i} dalle — skip`); continue; }
+ try { genDalle(buildPrompt(style, pal), outPath); ok++; }
+ catch (e) { fail++; console.log(` ${i} dalle ERR ${e.message.slice(0, 200)}`); }
+ save();
+ console.log(`${TAG} [${i + 1}/25] ${style} · ${pal.brand} — ${haveImg(outPath) ? 'ok' : 'FAIL'} · ${((Date.now() - t0) / 60000).toFixed(1)}min`);
+ }
+ save();
+ console.log(`${TAG} DONE — ${ok} rendered, ${skip} skipped, ${fail} failed · ${((Date.now() - t0) / 60000).toFixed(1)}min`);
+})().catch(e => { console.error(`${TAG} FATAL`, e.message); process.exit(1); });
diff --git a/scripts/flux_compare.js b/scripts/flux_compare.js
new file mode 100644
index 0000000..ab676f3
--- /dev/null
+++ b/scripts/flux_compare.js
@@ -0,0 +1,105 @@
+'use strict';
+/**
+ * Column 5 of the refiner comparison — Flux 1.1 Pro via Replicate.
+ *
+ * Same 25 prompts + seeds as refiner_compare.js, rendered through Replicate's
+ * `black-forest-labs/flux-1.1-pro` model. Flux 1.1 Pro accepts a seed input
+ * natively, so columns 1/2/3/5 share identical seeds.
+ *
+ * Cost ≈ $0.040 per image × 25 = $1.00 total.
+ *
+ * node scripts/flux_compare.js
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const { FASHION_PALETTES, paletteString } = require('./fashion_palettes');
+const { STYLES, OUT, haveImg, save } = require('./compare_progress');
+
+const TAG = '[flux]';
+const MODEL = 'black-forest-labs/flux-1.1-pro';
+
+function loadToken() { return process.env.REPLICATE_API_TOKEN || null; }
+
+function buildPrompt(style, pal) {
+ // Flux 1.1 Pro doesn't have a separate negative_prompt; we bake the
+ // "screen-printed, fully filled, no halftone" cues into the positive
+ // prompt directly, mirroring the SDXL prompt without the negative chunk.
+ return `A seamless wallpaper pattern, ${style} motif, ${paletteString(pal)}, ` +
+ 'seamless tile, repeating pattern, no edges, fabric pattern, wallpaper design, archival quality, high detail, ' +
+ 'screen-printed wallpaper, solid opaque flat ink, every motif fully filled with solid color, crisp clean color separation, ' +
+ 'no halftone, no dot screens, no gradients, no text, no watermark, no border';
+}
+
+function genFlux(prompt, seed, outPath) {
+ const TOKEN = loadToken();
+ if (!TOKEN) throw new Error('REPLICATE_API_TOKEN not set in wallco-ai/.env');
+
+ const sh = (cmd, opts) => {
+ try { return execSync(cmd, opts); }
+ catch (e) { throw new Error(String((e && e.message) || e).split(TOKEN).join('r8_***REDACTED')); }
+ };
+
+ // Replicate "official model" predictions endpoint — no version pin needed
+ // because Flux 1.1 Pro publishes as a tracked-latest official model.
+ const body = {
+ input: {
+ prompt,
+ aspect_ratio: '1:1',
+ output_format: 'png',
+ output_quality: 90,
+ safety_tolerance: 5, // looser tolerance for design patterns (default 2)
+ prompt_upsampling: false, // we already crafted the prompt — keep it stable
+ seed,
+ },
+ };
+
+ const submit = sh(
+ `curl -sf -m 30 -H 'Authorization: Bearer ${TOKEN}' -H 'Content-Type: application/json' -X POST 'https://api.replicate.com/v1/models/${MODEL}/predictions' -d @-`,
+ { input: JSON.stringify(body), encoding: 'utf8' },
+ );
+ let pred;
+ try { pred = JSON.parse(submit); } catch { throw new Error('Flux submit non-JSON: ' + submit.slice(0, 200)); }
+ if (!pred.id) throw new Error('Flux: no prediction id — ' + JSON.stringify(pred).slice(0, 300));
+
+ const start = Date.now();
+ let final;
+ while (Date.now() - start < 3 * 60_000) {
+ execSync('sleep 3');
+ const poll = sh(
+ `curl -sf -m 10 -H 'Authorization: Bearer ${TOKEN}' 'https://api.replicate.com/v1/predictions/${pred.id}'`,
+ { encoding: 'utf8' },
+ );
+ const p = JSON.parse(poll);
+ if (p.status === 'succeeded') { final = p; break; }
+ if (p.status === 'failed' || p.status === 'canceled') {
+ throw new Error(`Flux ${p.status}: ${JSON.stringify(p.error || p.logs || '').slice(0, 300)}`);
+ }
+ }
+ if (!final) throw new Error(`Flux timed out after 3 min (id=${pred.id})`);
+
+ const imageUrl = Array.isArray(final.output) ? final.output[0] : final.output;
+ if (!imageUrl) throw new Error('Flux returned no image URL');
+ execSync(`curl -sf -m 60 -L -o ${JSON.stringify(outPath)} ${JSON.stringify(imageUrl)}`);
+ if (!haveImg(outPath)) throw new Error(`Flux image download failed (${imageUrl})`);
+ return outPath;
+}
+
+(async () => {
+ const t0 = Date.now();
+ let ok = 0, fail = 0, skip = 0;
+ console.log(`${TAG} 25 renders via Replicate ${MODEL}`);
+ for (let i = 0; i < STYLES.length; i++) {
+ const style = STYLES[i], pal = FASHION_PALETTES[i % FASHION_PALETTES.length];
+ const seed = 770000 + i;
+ const outPath = path.join(OUT, `${i}_flux.png`);
+ if (haveImg(outPath)) { skip++; save(); console.log(` ${i} flux — skip`); continue; }
+ try { genFlux(buildPrompt(style, pal), seed, outPath); ok++; }
+ catch (e) { fail++; console.log(` ${i} flux ERR ${e.message.slice(0, 200)}`); }
+ save();
+ console.log(`${TAG} [${i + 1}/25] ${style} · ${pal.brand} — ${haveImg(outPath) ? 'ok' : 'FAIL'} · ${((Date.now() - t0) / 60000).toFixed(1)}min`);
+ }
+ save();
+ console.log(`${TAG} DONE — ${ok} rendered, ${skip} skipped, ${fail} failed · ${((Date.now() - t0) / 60000).toFixed(1)}min`);
+})().catch(e => { console.error(`${TAG} FATAL`, e.message); process.exit(1); });
diff --git a/scripts/gemini_compare.js b/scripts/gemini_compare.js
new file mode 100644
index 0000000..5750d31
--- /dev/null
+++ b/scripts/gemini_compare.js
@@ -0,0 +1,92 @@
+'use strict';
+/**
+ * Column 4 of the refiner comparison — Gemini 2.5 Flash Image.
+ *
+ * Same 25 prompts + seeds as refiner_compare.js, rendered through Google's
+ * generative-language REST API. Gemini 2.5 Flash Image is a hard-prompt
+ * model (no native seed input), so we append the seed integer to the prompt
+ * to bias toward reproducibility — same as the practice in cloud SDXL.
+ *
+ * Cost ≈ $30 per 1M output tokens × ~1290 tokens/image = ~$0.0387/image
+ * × 25 = ~$0.97 total.
+ *
+ * node scripts/gemini_compare.js
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+const { FASHION_PALETTES, paletteString } = require('./fashion_palettes');
+const { STYLES, OUT, haveImg, save } = require('./compare_progress');
+
+const TAG = '[gemini]';
+const MODEL = process.env.GEMINI_IMAGE_MODEL || 'gemini-2.5-flash-image';
+
+function loadKey() {
+ return process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || null;
+}
+
+function buildPrompt(style, pal, seed) {
+ // mirrors refiner_compare.js buildPos() with the same screen-print suffix
+ return `A seamless wallpaper pattern, ${style} motif, ${paletteString(pal)}, ` +
+ 'seamless tile, repeating pattern, no edges, fabric pattern, wallpaper design, high detail, archival quality, ' +
+ 'screen-printed wallpaper, solid opaque flat ink, flat color blocks, every shape fully filled with solid color, ' +
+ 'even saturated pigment coverage, crisp screen-print layers. ' +
+ // pseudo-seed nudge — Gemini doesn't take seeds natively but a stable
+ // integer in the prompt biases regeneration toward consistency.
+ `Pattern seed reference: ${seed}.`;
+}
+
+function genGemini(prompt, outPath) {
+ const KEY = loadKey();
+ if (!KEY) throw new Error('GEMINI_API_KEY not set in wallco-ai/.env');
+
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent?key=${KEY}`;
+ const body = {
+ contents: [{ parts: [{ text: prompt }] }],
+ generationConfig: { responseModalities: ['IMAGE'] },
+ };
+
+ const sh = (cmd, opts) => {
+ try { return execSync(cmd, opts); }
+ catch (e) { throw new Error(String((e && e.message) || e).split(KEY).join('AIza***REDACTED')); }
+ };
+
+ const raw = sh(
+ `curl -sf -m 90 -H 'Content-Type: application/json' -X POST ${JSON.stringify(url)} -d @-`,
+ { input: JSON.stringify(body), encoding: 'utf8', maxBuffer: 40 * 1024 * 1024 },
+ );
+
+ let res;
+ try { res = JSON.parse(raw); }
+ catch { throw new Error(`Gemini non-JSON response: ${raw.slice(0, 300)}`); }
+
+ const cands = res.candidates || [];
+ const parts = (cands[0] && cands[0].content && cands[0].content.parts) || [];
+ const imgPart = parts.find(p => p.inlineData && p.inlineData.data);
+ if (!imgPart) {
+ const txt = parts.find(p => p.text)?.text || '';
+ throw new Error(`Gemini returned no image (text: ${txt.slice(0, 200) || '<none>'})`);
+ }
+ fs.writeFileSync(outPath, Buffer.from(imgPart.inlineData.data, 'base64'));
+ if (!haveImg(outPath)) throw new Error('Gemini wrote a sub-1KB file (likely empty)');
+ return outPath;
+}
+
+(async () => {
+ const t0 = Date.now();
+ let ok = 0, fail = 0, skip = 0;
+ console.log(`${TAG} 25 renders via Gemini ${MODEL}`);
+ for (let i = 0; i < STYLES.length; i++) {
+ const style = STYLES[i], pal = FASHION_PALETTES[i % FASHION_PALETTES.length];
+ const seed = 770000 + i;
+ const outPath = path.join(OUT, `${i}_gemini.png`);
+ if (haveImg(outPath)) { skip++; save(); console.log(` ${i} gemini — skip`); continue; }
+ try { genGemini(buildPrompt(style, pal, seed), outPath); ok++; }
+ catch (e) { fail++; console.log(` ${i} gemini ERR ${e.message.slice(0, 200)}`); }
+ save();
+ console.log(`${TAG} [${i + 1}/25] ${style} · ${pal.brand} — ${haveImg(outPath) ? 'ok' : 'FAIL'} · ${((Date.now() - t0) / 60000).toFixed(1)}min`);
+ }
+ save();
+ console.log(`${TAG} DONE — ${ok} rendered, ${skip} skipped, ${fail} failed · ${((Date.now() - t0) / 60000).toFixed(1)}min`);
+})().catch(e => { console.error(`${TAG} FATAL`, e.message); process.exit(1); });
diff --git a/scripts/refiner_compare.js b/scripts/refiner_compare.js
index 67e2887..f8dcdfc 100644
--- a/scripts/refiner_compare.js
+++ b/scripts/refiner_compare.js
@@ -103,21 +103,10 @@ const TAG = SHARDS > 1 ? `[shard ${SHARD}/${SHARDS}] ` : '';
const t0 = Date.now();
const TOTAL = STYLES.length * 2; // 50 renders total across all shards
const haveImg = p => { try { return fs.statSync(p).size > 1000; } catch { return false; } };
- // progress.json is derived fresh from disk on every write, so multiple shard
- // processes can each write it without clobbering — the last writer is always
- // correct. Resume-safety also keys off these same on-disk files.
- const save = () => {
- const rows = STYLES.map((style, i) => ({
- i, style, brand: FASHION_PALETTES[i % FASHION_PALETTES.length].brand, seed: 770000 + i,
- base: haveImg(path.join(OUT, `${i}_base.png`)) ? `${i}_base.png` : null,
- refiner: haveImg(path.join(OUT, `${i}_refiner.png`)) ? `${i}_refiner.png` : null,
- // 3rd column — wallco.ai's live app render via Replicate, filled in by
- // replicate_compare.js. Counted separately, not in the ComfyUI `done`.
- replicate: haveImg(path.join(OUT, `${i}_replicate.png`)) ? `${i}_replicate.png` : null,
- }));
- const done = rows.reduce((n, r) => n + (r.base ? 1 : 0) + (r.refiner ? 1 : 0), 0);
- fs.writeFileSync(path.join(OUT, 'progress.json'), JSON.stringify({ done, total: TOTAL, rows }, null, 1));
- };
+ // Shared 6-column progress writer — derives row state fresh from disk
+ // including the 4 cloud columns (replicate/gemini/flux/dalle) so this
+ // writer doesn't clobber them when it races the cloud-backend scripts.
+ const { save } = require('./compare_progress');
console.log(`${TAG}COMFY=${COMFY}`);
save();
diff --git a/scripts/replicate_compare.js b/scripts/replicate_compare.js
index 9f33388..ba3d949 100644
--- a/scripts/replicate_compare.js
+++ b/scripts/replicate_compare.js
@@ -92,20 +92,9 @@ function genReplicate(prompt, seed, outPath) {
return outPath;
}
-// ---- progress.json: disk-derived (same schema as refiner_compare.js) so this
-// process and the two shard processes can all write it without clobbering ---
-const TOTAL = STYLES.length * 2; // 50 = the local ComfyUI render count
-const haveImg = p => { try { return fs.statSync(p).size > 1000; } catch { return false; } };
-const save = () => {
- const rows = STYLES.map((style, i) => ({
- i, style, brand: FASHION_PALETTES[i % FASHION_PALETTES.length].brand, seed: 770000 + i,
- base: haveImg(path.join(OUT, `${i}_base.png`)) ? `${i}_base.png` : null,
- refiner: haveImg(path.join(OUT, `${i}_refiner.png`)) ? `${i}_refiner.png` : null,
- replicate: haveImg(path.join(OUT, `${i}_replicate.png`)) ? `${i}_replicate.png` : null,
- }));
- const done = rows.reduce((n, r) => n + (r.base ? 1 : 0) + (r.refiner ? 1 : 0), 0);
- fs.writeFileSync(path.join(OUT, 'progress.json'), JSON.stringify({ done, total: TOTAL, rows }, null, 1));
-};
+// ---- progress.json: 6-column writer (shared helper, derives from disk so
+// all 4 backends + 2 ComfyUI shards can write concurrently without clobber).
+const { save, haveImg } = require('./compare_progress');
(async () => {
const t0 = Date.now();
← 187a96b /designs HTML: filter cards by designHasImage (mirror /api/d
·
back to Wallco Ai
·
report: dw-stale-urls trim-B HALTED (0 orphans, outside band fd7b0dc →