← back to Dw Universe
scripts/gen-niche-poems.js
121 lines
// Generate one short editorial line per niche via Mac1 Ollama qwen3:14b.
// Stores at ~/Projects/dw-universe/data/niche-poems.json.
// Idempotent: skips niches already present.
const fs = require('fs');
const path = require('path');
const http = require('http');
const OLLAMA = 'http://192.168.1.133:11434';
const MODEL = 'qwen3:14b';
const OUT = path.join(__dirname, '..', 'data', 'niche-poems.json');
const NICHES = [
['silkwallpaper', 'Silk', 'A pure-silk wallcovering line — finest hand-woven silk from heritage mills.'],
['silkwallcoverings', 'Silk Wallcoverings', 'Silk wallcoverings cut to commercial / contract spec.'],
['linenwallpaper', 'Linen', 'Natural linen, woven and grasscloth-style.'],
['jutewallpaper', 'Jute', 'Coarse, honest jute fiber wallcovering.'],
['raffiawallcoverings', 'Raffia', 'Hand-woven raffia palm fiber, contract spec.'],
['raffiawalls', 'Raffia Walls', 'Raffia residential storefront — same fiber, residential edit.'],
['corkwallcovering', 'Cork', 'Sustainable cork wallcovering — bark of the cork oak.'],
['fabricwallpaper', 'Fabric', 'Anything fabric-on-paper: textiles backed for walls.'],
['textilewallpaper', 'Textile', 'Woven textile-effect wallpapers.'],
['metallicwallpaper', 'Metallic', 'Foil and metallic-leaf grounds — silver, gold, copper, gun-metal.'],
['silverleafwallpaper', 'Silver Leaf', 'Pure silver leaf grounds — hand-applied.'],
['vinylwallpaper', 'Vinyl', 'Type II commercial vinyl — hospitality-grade durability.'],
['micawallpaper', 'Mica', 'Hand-applied mica — flecks of fired mineral.'],
['madagascarwallpaper', 'Madagascar', 'Madagascar grasscloth — woven natural fibers.'],
['mylarwallpaper', 'Mylar', 'Reflective mylar — dimensional sheen, mid-century moods.'],
['suedewallpaper', 'Suede', 'Suede-textured walls — soft, light-eating, dramatic.'],
['1800swallpaper', '1800s', 'Documentary 19th-century reproduction wallpapers.'],
['1890swallpaper', '1890s', 'Late-Victorian Aesthetic-movement & Arts-and-Crafts patterns.'],
['1900swallpaper', '1900s', 'Edwardian and turn-of-century repeats.'],
['1920swallpaper', '1920s', 'Art Deco — geometric, gold, jazz-age.'],
['1930swallpaper', '1930s', 'Streamlined Moderne — late Deco bleeding into Hollywood Regency.'],
['1940swallpaper', '1940s', 'Post-war floral & restraint — kitchens, bedrooms.'],
['1950swallpaper', '1950s', 'Atomic-age, mid-century optimism.'],
['1960swallpaper', '1960s', 'Op-art, mod, psychedelia, swirls.'],
['1970swallpaper', '1970s', 'Earth-tone florals, paneling, harvest gold.'],
['1980swallpaper', '1980s', 'Memphis, neon, geometric pop, brass-and-glass excess.'],
['retrowalls', 'Retro Walls', 'Catch-all retro/vintage — across all decades.'],
['wallpapersback', 'Wallpapers Back', 'Statement: wallpaper is back — broad selection.'],
['agedwallpaper', 'Aged', 'Patina-aged finishes, faux-distressed.'],
['handcraftedwallpaper', 'Handcrafted', 'Block-printed, hand-screened, artisan-made.'],
['museumwallpaper', 'Museum', 'Wallcoverings fit for institutional / gallery walls.'],
['restorationwallpaper', 'Restoration', 'Period-accurate reproductions for historic homes.'],
['pastelwallpaper', 'Pastel', 'Soft, light-color palette across patterns.'],
['glitterwalls', 'Glitter', 'Genuine glitter and glass-bead grounds.'],
['greenwallcoverings', 'Green', 'Sustainable, FSC, low-VOC — eco-conscious wallcovering.'],
['naturalwallcoverings', 'Natural', 'Plant-fiber wallcoverings: jute, sisal, abaca, raffia.'],
['saloonwallpaper', 'Saloon', 'Old-West, woodcut, oxblood-and-cream saloon-print aesthetic.'],
['contractwallpaper', 'Contract', 'Type II commercial contract wallcovering.'],
['hotelwallcoverings', 'Hotel', 'Hospitality-grade, lobby-and-corridor durability.'],
['hospitalitywallpaper', 'Hospitality', 'Hotel + restaurant + cruise — durable, designed.'],
['healthcarewallpaper', 'Healthcare', 'Cleanable, anti-microbial, medical-environment rated.'],
['restaurantwallpaper', 'Restaurant', 'Front-of-house wallcovering — moody, photogenic, washable.'],
['architecturalwallcoverings', 'Architectural', 'Architect-spec wallcoverings — A&D channel.'],
];
function ollama(prompt) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({
model: MODEL, prompt, stream: false,
options: { temperature: 0.85, top_p: 0.95, num_predict: 80 }
});
const req = http.request(`${OLLAMA}/api/generate`, {
method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': body.length },
timeout: 60000,
}, (res) => {
let chunks = '';
res.on('data', d => chunks += d);
res.on('end', () => {
try { resolve(JSON.parse(chunks).response || ''); } catch (e) { reject(e); }
});
});
req.on('error', reject);
req.write(body); req.end();
});
}
const PROMPT = (label, hint) => `Write ONE single sentence — fewer than 14 words — that describes "${label}" wallcoverings as if you were a high-end interior magazine editor. Tone: literary, sensory, not salesy. Avoid clichés ("timeless", "elegance", "sophistication"). Avoid quotation marks. Hint: ${hint}
Just the sentence. Nothing else. No "Here is" or preamble. No explanation. No newlines. No <think> tags or reasoning — output ONLY the final sentence.`;
function clean(s) {
// Strip <think> blocks (qwen3 sometimes adds reasoning)
s = s.replace(/<think>[\s\S]*?<\/think>/gi, '');
// Strip leading "Here is..." / quotes / leading bullets
s = s.replace(/^["'\s\-•*]+/, '').replace(/["'\s]+$/, '').trim();
// Take first sentence only
const firstSentence = s.match(/^[^.!?\n]+[.!?]?/);
return (firstSentence ? firstSentence[0] : s).trim();
}
(async () => {
fs.mkdirSync(path.dirname(OUT), { recursive: true });
let existing = {};
try { existing = JSON.parse(fs.readFileSync(OUT, 'utf8')); } catch (e) {}
let done = 0, skipped = 0;
for (const [slug, label, hint] of NICHES) {
if (existing[slug] && existing[slug].length > 8) { skipped++; continue; }
const t0 = Date.now();
try {
const raw = await ollama(PROMPT(label, hint));
const line = clean(raw);
if (!line || line.length < 8) {
console.log(` ⚠ ${slug}: empty/short — kept raw fragment`);
existing[slug] = raw.slice(0, 200);
} else {
existing[slug] = line;
done++;
}
console.log(` ${slug.padEnd(28)} (${(Date.now()-t0)/1000|0}s) → ${existing[slug]}`);
fs.writeFileSync(OUT, JSON.stringify(existing, null, 2)); // checkpoint each
} catch (e) {
console.error(` ✗ ${slug}: ${e.message}`);
}
}
console.log(`done: ${done} new, ${skipped} skipped, total ${Object.keys(existing).length}`);
})();