← back to Designer Wallcoverings
copygen: env-configurable Ollama host/model/prompt-prefix (OLLAMA_HOST/COPYGEN_MODEL/COPYGEN_PROMPT_PREFIX) — relocate heavy LLM load to a box with headroom; defaults unchanged
6780186887bf26ae6f0b7b48e4db71c08ba6a099 · 2026-06-12 11:59:58 -0700 · SteveStudio2
Files touched
A scripts/wallquest-copygen/copygen.cjs
Diff
commit 6780186887bf26ae6f0b7b48e4db71c08ba6a099
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Fri Jun 12 11:59:58 2026 -0700
copygen: env-configurable Ollama host/model/prompt-prefix (OLLAMA_HOST/COPYGEN_MODEL/COPYGEN_PROMPT_PREFIX) — relocate heavy LLM load to a box with headroom; defaults unchanged
---
scripts/wallquest-copygen/copygen.cjs | 52 +++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/scripts/wallquest-copygen/copygen.cjs b/scripts/wallquest-copygen/copygen.cjs
new file mode 100644
index 00000000..e115df8b
--- /dev/null
+++ b/scripts/wallquest-copygen/copygen.cjs
@@ -0,0 +1,52 @@
+// Hermes 3 copy-gen for Malibu Wallpaper (WallQuest private label) + deterministic validator rail.
+// In: products JSONL (sku,title,specs). Out: {sku,title,body,tags,source}. NO vendor-name leak.
+const fs=require('fs');
+const BANNED=/wallquest|chesapeake|brewster|nextwall|seabrook|wendy|voyage|william\s*morris|miyabi/i;
+const VENDOR='Malibu Wallpaper';
+// Ollama host + model are env-overridable so the heavy LLM load can be aimed at
+// whichever box has headroom (e.g. relocate off Mac2 onto Mac1) without touching
+// the orchestration. Defaults preserve the original behavior for existing callers.
+const OLLAMA_HOST=(process.env.OLLAMA_HOST||'http://127.0.0.1:11434').replace(/\/$/,'');
+const COPYGEN_MODEL=process.env.COPYGEN_MODEL||'hermes3:8b';
+// Optional prompt prefix — e.g. "/no_think " to disable qwen3's reasoning tokens
+// when reusing an already-resident qwen3 model on a remote host (faster, cleaner JSON).
+const PROMPT_PREFIX=process.env.COPYGEN_PROMPT_PREFIX||'';
+const norm=s=>String(s||'').replace(/(?<!malibu\s)wallpaper/ig,m=>m[0]===m[0].toUpperCase()?'Wallcovering':'wallcovering');
+const scrub=s=>String(s||'').replace(/wallquest|chesapeake|brewster|nextwall|seabrook|wendy|voyage|william\s*morris|miyabi/ig,'').replace(/\s{2,}/g,' ').replace(/^[\s\-|]+|[\s\-|]+$/g,'').trim();
+async function hermes(prompt){
+ const r=await fetch(`${OLLAMA_HOST}/api/generate`,{method:'POST',body:JSON.stringify({model:COPYGEN_MODEL,prompt:PROMPT_PREFIX+prompt,stream:false,options:{temperature:0.6}})}).then(r=>r.json());
+ return (r.response||'').trim();
+}
+function baseName(p){ const s=p.specs||{}; const dn=s['Design Name']||'', cn=s['Colorway Name']||s['Color']||''; let t=p.title||((dn+(cn?' - '+cn:'')).trim()); return t||'Wallcovering'; }
+function templateTitle(p){ return scrub(baseName(p)).replace(/wallpaper/ig,'Wallcovering')||'Wallcovering'; }
+function templateTags(p){ const s=p.specs||{}; return ['Wallcovering',s['Design Style'],s['Color Type'],s['Material']].filter(Boolean).map(scrub).filter(Boolean); }
+function templateBody(p){ const s=p.specs||{}; return '<p>'+templateTitle(p)+' by '+VENDOR+'.</p><ul>'+Object.entries(s).filter(([k])=>!/sku|name/i.test(k)).map(([k,v])=>`<li><strong>${k}:</strong> ${scrub(v)}</li>`).join('')+'</ul>'; }
+async function genOne(p){
+ const specStr=Object.entries(p.specs||{}).map(([k,v])=>`${k}: ${scrub(v)}`).join('; ');
+ const baseTitle=templateTitle(p);
+ const prompt=`You are a luxury wallcovering copywriter for the brand "${VENDOR}". Write product copy for this wallcovering. NEVER mention any other brand or vendor name. Pattern/colorway: "${baseTitle}". Specs: ${specStr}.
+Return ONLY valid JSON: {"title":"<short product title, max 60 chars, no brand names>","body":"<2-sentence HTML <p> description, luxurious, accurate to specs, no brand names>","tags":["3-6 short tags"]}`;
+ for(let i=0;i<3;i++){
+ try{
+ const raw=await hermes(prompt);
+ const m=raw.match(/\{[\s\S]*\}/); if(!m) continue;
+ const o=JSON.parse(m[0]);
+ const title=scrub(o.title), body=o.body||'', tags=(o.tags||[]).map(scrub).filter(Boolean);
+ // VALIDATOR: reject banned terms or malformed
+ if(BANNED.test(title+' '+body+' '+tags.join(' '))) continue;
+ if(!title||title.length<3||!body||body.length<15||tags.length<2) continue;
+ return {sku:p.sku,title:norm(title),body:norm(body),tags:tags.map(norm),source:'hermes'};
+ }catch(e){}
+ }
+ // FALLBACK: deterministic template (guaranteed clean)
+ return {sku:p.sku,title:baseTitle,body:templateBody(p),tags:templateTags(p),source:'template'};
+}
+(async()=>{
+ const inFile=process.argv[2], limit=parseInt(process.argv[3]||'0');
+ let prods=fs.readFileSync(inFile,'utf8').split('\n').filter(Boolean).map(l=>{try{return JSON.parse(l)}catch{return null}}).filter(p=>p&&p.specs&&Object.keys(p.specs).length);
+ if(limit) prods=prods.slice(0,limit);
+ const out=fs.createWriteStream('/tmp/wq-copy.jsonl');
+ let h=0,t=0;
+ for(const p of prods){ const c=await genOne(p); if(c.source==='hermes')h++;else t++; out.write(JSON.stringify(c)+'\n'); console.error(`${c.source==='hermes'?'✓':'·'} ${c.sku}: "${c.title}"`); }
+ out.end(); console.error(`COPYGEN DONE: ${prods.length} (hermes=${h} template-fallback=${t})`);
+})();
← 139f7c9b scan-active-dups: filter to exact PREFIX- bases (loose Shopi
·
back to Designer Wallcoverings
·
Color collection cleaner v2: Gemini blueness gate + reversib b4a3d85e →