← back to Designer Wallcoverings
ai-enrich-gaps2.js
134 lines
const https = require('https');
const http = require('http');
const { Client } = require('pg');
const GEMINI_KEY = '${GOOGLE_API_KEY}';
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent?key=${GEMINI_KEY}`;
const PRODUCTS = [
{id:123,vendor:'arte',sku:'19433',name:'Horus Desert Sun',img:'https://edge.arte-international.com/media/4960d9f1-bc0a-4bcb-9a82-c09d9e5e7d98/conversions/EssentialsLuxor_Horus_19436_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:16,vendor:'arte',sku:'23401',name:'Amarna Bister',img:'https://edge.arte-international.com/media/cc56d30c-5e1c-4b22-878e-b437681e6f1b/conversions/Memphis_Amarna_23400_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:15,vendor:'arte',sku:'23412',name:'Sakkara Antique Gold',img:'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:172,vendor:'arte',sku:'23413',name:'Sakkara Off-white',img:'https://edge.arte-international.com/media/a580214f-25ff-472e-b88c-6a2bd8dfab64/conversions/Memphis_Sakkara_23410_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:72,vendor:'arte',sku:'23421',name:'Ibis Biscuit',img:'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:75,vendor:'arte',sku:'23422',name:'Ibis Chestnut',img:'https://edge.arte-international.com/media/2183e2bc-0620-43c4-81ae-6364abed3937/conversions/Memphis_Ibis_23420_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:78,vendor:'arte',sku:'23431',name:'Siwa Bone',img:'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:83,vendor:'arte',sku:'23433',name:'Siwa Biscuit',img:'https://edge.arte-international.com/media/428898de-d288-40f3-b463-1714c59de939/conversions/Memphis_Siwa_23432_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:183,vendor:'arte',sku:'29221',name:'Galon Grey Linen',img:'https://edge.arte-international.com/media/8571da7a-7bf9-4abc-adf0-76863cee5413/conversions/Allures_Galon9224_1_Roomshot_Web_LR-medium-two-thirds.jpg'},
{id:1751,vendor:'black_edition',sku:'W924/01',name:'Mizumi Panel Carbon',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-01FP-mizumi-panel-charcoal_00.jpg'},
{id:1752,vendor:'black_edition',sku:'W924/02',name:'Mizumi Panel Midnight',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-02FP-mizumi-panel-midnight_00.jpg'},
{id:1753,vendor:'black_edition',sku:'W924/03',name:'Mizumi Panel Basalt',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-03FP-mizumi-panel-stone_00.jpg'},
{id:1754,vendor:'black_edition',sku:'W924/04',name:'Mizumi Panel Viridian',img:'https://static.theromogroup.com/rb/cache/image/720x720/catalog/product/W/9/W924-04FP-mizumi-panel-teal_02.jpg'},
];
const PROMPT = `You are an interior design wallcovering product analyst. Analyze this wallcovering image and return ONLY valid JSON (no markdown, no backticks):
{
"colors": ["<list 3-6 actual colors you see, e.g. Warm Beige, Soft Gold>"],
"backgroundColor": "<the single dominant background color>",
"styles": ["<1-3 design styles, e.g. Contemporary, Art Deco, Minimalist>"],
"patterns": ["<1-2 pattern types, e.g. Geometric, Textured, Striped>"],
"tags": ["<5-8 interior design tags, e.g. luxury, neutral, organic>"],
"description": "<2 sentences describing this wallcovering for a commercial interior designer>"
}`;
function fetchImage(url) {
const mod = url.startsWith('https') ? https : http;
return new Promise((resolve, reject) => {
mod.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
return fetchImage(res.headers.location).then(resolve).catch(reject);
}
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => resolve(Buffer.concat(chunks)));
res.on('error', reject);
}).on('error', reject);
});
}
function callGemini(b64, productName) {
const body = JSON.stringify({
contents: [{ parts: [
{ text: `Product: ${productName}\n\n${PROMPT}` },
{ inlineData: { mimeType: 'image/jpeg', data: b64 } }
]}],
generationConfig: { temperature: 0.2, maxOutputTokens: 1024, thinkingConfig: { thinkingBudget: 0 } }
});
return new Promise((resolve, reject) => {
const url = new URL(GEMINI_URL);
const req = https.request({
hostname: url.hostname, path: url.pathname + url.search,
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, (res) => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.error) return reject(new Error(json.error.message));
const text = json.candidates?.[0]?.content?.parts?.[0]?.text || '';
const clean = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
resolve(JSON.parse(clean));
} catch (e) { reject(new Error(`Parse: ${e.message}`)); }
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
async function main() {
const db = new Client({ connectionString: (process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified') });
await db.connect();
let success = 0, fail = 0;
for (let i = 0; i < PRODUCTS.length; i++) {
const p = PRODUCTS[i];
console.log(`\n[${i+1}/13] ${p.vendor} ${p.sku} - ${p.name}`);
try {
const imgBuf = await fetchImage(p.img);
console.log(` Image: ${(imgBuf.length/1024).toFixed(0)}KB`);
const b64 = imgBuf.toString('base64');
const ai = await callGemini(b64, p.name);
console.log(` Colors: ${(ai.colors||[]).join(', ')}`);
console.log(` BG: ${ai.backgroundColor} | Styles: ${(ai.styles||[]).join(', ')}`);
const res = await db.query(`
UPDATE vendor_catalog SET
ai_colors = $1, ai_background_color = $2, ai_styles = $3,
ai_patterns = $4, ai_tags = $5, ai_description = $6
WHERE id = $7
`, [ai.colors, ai.backgroundColor, ai.styles, ai.patterns, ai.tags, ai.description, p.id]);
if (p.vendor === 'arte') {
await db.query(`
UPDATE arte_catalog SET
ai_colors = $1, ai_background_color = $2, ai_styles = $3,
ai_patterns = $4, ai_tags = $5, ai_description = $6
WHERE arte_sku = $7
`, [ai.colors, ai.backgroundColor, ai.styles, ai.patterns, ai.tags, ai.description, p.sku]);
}
console.log(` Saved (${res.rowCount} row)`);
success++;
} catch (err) {
console.log(` FAIL: ${err.message}`);
fail++;
}
if (i < PRODUCTS.length - 1) await new Promise(r => setTimeout(r, 1200));
}
await db.end();
console.log(`\nDONE: ${success} success, ${fail} failed out of 13`);
}
main().catch(console.error);