← back to Wallco Ai
add refiner_compare.js — 25-prompt base vs base+refiner side-by-side eval
9bda674d35e818e0b6bb11c1039a859f84e22674 · 2026-05-19 11:15:03 -0700 · SteveStudio2
Files touched
A scripts/refiner_compare.js
Diff
commit 9bda674d35e818e0b6bb11c1039a859f84e22674
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Tue May 19 11:15:03 2026 -0700
add refiner_compare.js — 25-prompt base vs base+refiner side-by-side eval
---
scripts/refiner_compare.js | 137 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 137 insertions(+)
diff --git a/scripts/refiner_compare.js b/scripts/refiner_compare.js
new file mode 100644
index 0000000..680dab7
--- /dev/null
+++ b/scripts/refiner_compare.js
@@ -0,0 +1,137 @@
+'use strict';
+/**
+ * Refiner comparison — 25 wallcovering prompts, each rendered TWICE via ComfyUI:
+ * base-only SDXL vs base + refiner (expert-ensemble)
+ * Identical prompt + identical seed per pair, so any difference is the refiner.
+ *
+ * Does NOT touch the database. Writes images + a side-by-side compare page to
+ * public/refiner-compare/ — served at http://127.0.0.1:9905/refiner-compare/compare.html
+ *
+ * node scripts/refiner_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 COMFY = process.env.COMFY_URL || 'http://127.0.0.1:8188';
+const BASE_MODEL = 'sd_xl_base_1.0.safetensors';
+const REFINER_MODEL = 'sd_xl_refiner_1.0.safetensors';
+const OUT = path.join(__dirname, '..', 'public', 'refiner-compare');
+fs.mkdirSync(OUT, { recursive: true });
+
+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 NEG = 'low quality, blurry, edges, seam, border, frame, signature, watermark, text, hands, fingers, ' +
+ 'transparent, see-through, translucent, unfilled shapes, hollow outlines, outline-only motifs, ghosted layers, ' +
+ 'washed out, faded, low opacity, gaps in the ink, missing fill, gradient bleed';
+
+function buildPos(style, pal) {
+ 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';
+}
+
+function baseWorkflow(seed, POS) {
+ return {
+ '3': { class_type: 'KSampler', inputs: { seed, steps: 28, cfg: 7.5, sampler_name: 'euler', scheduler: 'normal', denoise: 1.0, model: ['4', 0], positive: ['6', 0], negative: ['7', 0], latent_image: ['5', 0] } },
+ '4': { class_type: 'CheckpointLoaderSimple', inputs: { ckpt_name: BASE_MODEL } },
+ '5': { class_type: 'EmptyLatentImage', inputs: { width: 1024, height: 1024, batch_size: 1 } },
+ '6': { class_type: 'CLIPTextEncode', inputs: { text: POS, clip: ['4', 1] } },
+ '7': { class_type: 'CLIPTextEncode', inputs: { text: NEG, clip: ['4', 1] } },
+ '8': { class_type: 'VAEDecode', inputs: { samples: ['3', 0], vae: ['4', 2] } },
+ '9': { class_type: 'SaveImage', inputs: { filename_prefix: `cmp_base_${seed}`, images: ['8', 0] } },
+ };
+}
+
+function refinerWorkflow(seed, POS) {
+ const STEPS = 30, SWITCH = 24;
+ return {
+ '4': { class_type: 'CheckpointLoaderSimple', inputs: { ckpt_name: BASE_MODEL } },
+ '15': { class_type: 'CheckpointLoaderSimple', inputs: { ckpt_name: REFINER_MODEL } },
+ '5': { class_type: 'EmptyLatentImage', inputs: { width: 1024, height: 1024, batch_size: 1 } },
+ '6': { class_type: 'CLIPTextEncode', inputs: { text: POS, clip: ['4', 1] } },
+ '7': { class_type: 'CLIPTextEncode', inputs: { text: NEG, clip: ['4', 1] } },
+ '16': { class_type: 'CLIPTextEncode', inputs: { text: POS, clip: ['15', 1] } },
+ '17': { class_type: 'CLIPTextEncode', inputs: { text: NEG, clip: ['15', 1] } },
+ '10': { class_type: 'KSamplerAdvanced', inputs: { add_noise: 'enable', noise_seed: seed, steps: STEPS, cfg: 7.5, sampler_name: 'euler', scheduler: 'normal', start_at_step: 0, end_at_step: SWITCH, return_with_leftover_noise: 'enable', model: ['4', 0], positive: ['6', 0], negative: ['7', 0], latent_image: ['5', 0] } },
+ '11': { class_type: 'KSamplerAdvanced', inputs: { add_noise: 'disable', noise_seed: seed, steps: STEPS, cfg: 7.5, sampler_name: 'euler', scheduler: 'normal', start_at_step: SWITCH, end_at_step: 10000, return_with_leftover_noise: 'disable', model: ['15', 0], positive: ['16', 0], negative: ['17', 0], latent_image: ['10', 0] } },
+ '8': { class_type: 'VAEDecode', inputs: { samples: ['11', 0], vae: ['15', 2] } },
+ '9': { class_type: 'SaveImage', inputs: { filename_prefix: `cmp_ref_${seed}`, images: ['8', 0] } },
+ };
+}
+
+function render(workflow, outPath) {
+ const submit = execSync(`curl -sf -m 30 -H 'Content-Type: application/json' -X POST '${COMFY}/prompt' -d @-`,
+ { input: JSON.stringify({ prompt: workflow, client_id: 'refiner-compare' }), encoding: 'utf8' });
+ const pid = JSON.parse(submit).prompt_id;
+ if (!pid) throw new Error('no prompt_id');
+ const start = Date.now();
+ while (Date.now() - start < 6 * 60_000) {
+ execSync('sleep 2');
+ let hist;
+ try { hist = JSON.parse(execSync(`curl -sf -m 5 '${COMFY}/history/${pid}'`, { encoding: 'utf8' })); }
+ catch { continue; }
+ const e = hist[pid];
+ if (e && e.outputs && e.outputs['9'] && e.outputs['9'].images) {
+ const im = e.outputs['9'].images[0];
+ const url = `${COMFY}/view?filename=${encodeURIComponent(im.filename)}&subfolder=${encodeURIComponent(im.subfolder || '')}&type=${encodeURIComponent(im.type || 'output')}`;
+ execSync(`curl -sf -m 60 -o ${JSON.stringify(outPath)} ${JSON.stringify(url)}`);
+ return fs.existsSync(outPath) && fs.statSync(outPath).size > 1000;
+ }
+ }
+ return false;
+}
+
+(async () => {
+ const rows = [];
+ const t0 = Date.now();
+ for (let i = 0; i < STYLES.length; i++) {
+ const style = STYLES[i];
+ const pal = FASHION_PALETTES[i % FASHION_PALETTES.length];
+ const seed = 770000 + i;
+ const POS = buildPos(style, pal);
+ const baseFile = `${i}_base.png`, refFile = `${i}_refiner.png`;
+ let okB = false, okR = false;
+ try { okB = render(baseWorkflow(seed, POS), path.join(OUT, baseFile)); } catch (e) { console.log(` ${i} base ERR ${e.message}`); }
+ try { okR = render(refinerWorkflow(seed, POS), path.join(OUT, refFile)); } catch (e) { console.log(` ${i} refiner ERR ${e.message}`); }
+ rows.push({ i, style, brand: pal.brand, seed, base: okB ? baseFile : null, refiner: okR ? refFile : null });
+ console.log(`[${i + 1}/25] ${style} · ${pal.brand} — base:${okB ? 'ok' : 'FAIL'} refiner:${okR ? 'ok' : 'FAIL'} · ${((Date.now() - t0) / 1000 / 60).toFixed(1)}min`);
+ fs.writeFileSync(path.join(OUT, 'progress.json'), JSON.stringify({ done: i + 1, total: 25, rows }, null, 1));
+ }
+
+ // compare page
+ const esc = s => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
+ const cards = rows.map(r => `
+ <div class="row">
+ <div class="meta"><b>#${r.i + 1}</b> · ${esc(r.style)} · <span class="brand">${esc(r.brand)}</span> · seed ${r.seed}</div>
+ <div class="pair">
+ <figure>${r.base ? `<img src="${r.base}" loading="lazy">` : '<div class=miss>FAILED</div>'}<figcaption>base-only SDXL</figcaption></figure>
+ <figure>${r.refiner ? `<img src="${r.refiner}" loading="lazy">` : '<div class=miss>FAILED</div>'}<figcaption>base + refiner</figcaption></figure>
+ </div>
+ </div>`).join('');
+ const html = `<!doctype html><meta charset=utf-8><title>Refiner Comparison — wallco.ai</title>
+<style>
+body{font:15px/1.5 -apple-system,system-ui,sans-serif;background:#1a1712;color:#f4f1ea;margin:0;padding:32px}
+h1{font-weight:600;margin:0 0 4px}.sub{color:#9b8f7a;margin-bottom:28px;font-size:13px}
+.row{margin-bottom:34px;border-bottom:1px solid #3a342a;padding-bottom:24px}
+.meta{font-size:13px;color:#c9bfa7;margin-bottom:10px}.brand{color:#d9a441}
+.pair{display:grid;grid-template-columns:1fr 1fr;gap:18px;max-width:1100px}
+figure{margin:0}figure img{width:100%;border-radius:8px;display:block;background:#000}
+figcaption{text-align:center;font-size:12px;color:#9b8f7a;margin-top:6px;text-transform:uppercase;letter-spacing:.1em}
+.miss{aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:#3a261e;color:#ff8a7a;border-radius:8px}
+</style>
+<h1>SDXL Refiner Comparison</h1>
+<p class="sub">25 wallcovering prompts · identical prompt + seed per pair · left = base-only, right = base + refiner. Generated ${new Date().toISOString()}.</p>
+${cards}`;
+ fs.writeFileSync(path.join(OUT, 'compare.html'), html);
+ console.log(`\nDONE — compare page: http://127.0.0.1:9905/refiner-compare/compare.html`);
+})();
← 25f96e9 comfy: optional SDXL base+refiner workflow (COMFY_REFINER_MO
·
back to Wallco Ai
·
generator: ban halftones, allow 3D/dimensional designs, add af70673 →