← back to Designer Wallcoverings
enrich-arte-gaps.js
125 lines
const https = require('https');
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 pgClient = new Client((process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified'));
const PROMPT = `You are an interior design wallcovering analyst. Analyze this wallcovering product image.
Return ONLY valid JSON with these fields:
{
"colors": ["<list 3-6 colors visible, e.g. Gold, Cream, Charcoal>"],
"backgroundColor": "<the single dominant background color>",
"tags": ["<5-8 interior design tags, e.g. Luxury, Textured, Hospitality, Modern>"],
"styles": ["<1-3 design styles, e.g. Contemporary, Art Deco, Minimalist>"],
"patterns": ["<1-3 pattern types, e.g. Geometric, Solid, Striped, Textured>"],
"description": "<2 sentence commercial description for architects and interior designers>"
}
Do NOT include markdown formatting. Return raw JSON only.`;
const ARTE = [
{ sku:'19433', url:'https://edge.arte-international.com/media/a0686a22-83f6-46a5-86fe-b17566b88245/conversions/EssentialsLuxor_Horus_19433_Packshot_Web_LR-medium-two-thirds.jpg' },
{ sku:'23401', url:'https://edge.arte-international.com/media/d4610242-66e0-4b12-8723-df90ddb40e04/conversions/Memphis_Amarna_23401_Packshot_Web_HR-medium-two-thirds.jpg' },
{ sku:'23412', url:'https://edge.arte-international.com/media/6fe8a7ec-ecdd-49c4-b398-223293c48eed/conversions/Memphis_Sakkara_23412_Packshot_Web_HR-medium-two-thirds.jpg' },
{ sku:'23413', url:'https://edge.arte-international.com/media/a45badcf-d133-44b1-bcd6-1d74b4f13141/conversions/Memphis_Sakkara_23413_Packshot_Web_HR-medium-two-thirds.jpg' },
{ sku:'23421', url:'https://edge.arte-international.com/media/fb1738af-4e34-4d36-9a3e-7392f590381a/conversions/Memphis_Ibis_23421_Packshot_Web_LR-medium-two-thirds.jpg' },
{ sku:'23422', url:'https://edge.arte-international.com/media/2899330e-abba-481e-9098-ffd76f1731cf/conversions/Memphis_Ibis_23422_Packshot_Web_LR-medium-two-thirds.jpg' },
{ sku:'23431', url:'https://edge.arte-international.com/media/2ef17968-181d-4da5-8c8a-59f719a3c0fd/conversions/Memphis_Siwa_23431_Packshot_Web_LR-medium-two-thirds.jpg' },
{ sku:'23433', url:'https://edge.arte-international.com/media/fac5d958-0b0e-437f-abbe-a07a2c147676/conversions/Memphis_Siwa_23433_Packshot_Web_LR-medium-two-thirds.jpg' },
{ sku:'29221', url:'https://edge.arte-international.com/media/f92ad97a-a59b-431c-84ad-0a7520995f6a/conversions/Allures_Galon_29221_Flatshot_Web_LR-medium-two-thirds.jpg' },
];
function fetchB64(url) {
return new Promise((resolve, reject) => {
const opts = new URL(url);
const req = https.get({
hostname: opts.hostname, path: opts.pathname + opts.search,
timeout: 15000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
'Referer': 'https://www.arte-international.com/',
}
}, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
return fetchB64(res.headers.location).then(resolve).catch(reject);
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
const c = []; res.on('data', d => c.push(d));
res.on('end', () => resolve(Buffer.concat(c).toString('base64')));
res.on('error', reject);
});
req.on('error', reject);
});
}
function callGemini(b64) {
const body = JSON.stringify({
contents: [{ parts: [
{ text: PROMPT },
{ inline_data: { mime_type: 'image/jpeg', data: b64 } }
]}],
generationConfig: { temperature: 0.2, maxOutputTokens: 1024, thinkingConfig: { thinkingBudget: 0 } }
});
return new Promise((resolve, reject) => {
const u = new URL(GEMINI_URL);
const req = https.request({
hostname: u.hostname, path: u.pathname + u.search,
method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
timeout: 30000,
}, (res) => {
const c = []; res.on('data', d => c.push(d));
res.on('end', () => {
try {
const data = JSON.parse(Buffer.concat(c).toString());
if (data.error) return reject(new Error(data.error.message));
const text = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
const clean = text.replace(/```json\n?/g,'').replace(/```\n?/g,'').trim();
resolve(JSON.parse(clean));
} catch(e) { reject(e); }
});
});
req.on('error', reject); req.write(body); req.end();
});
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function main() {
await pgClient.connect();
let ok = 0, fail = 0;
for (const p of ARTE) {
try {
process.stdout.write(`Arte ${p.sku}... `);
const b64 = await fetchB64(p.url);
const ai = await callGemini(b64);
// Update vendor_catalog
await pgClient.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 vendor_code='arte' AND mfr_sku=$7`,
[JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
// Update arte_catalog
await pgClient.query(`UPDATE arte_catalog SET
ai_colors=$1::jsonb, ai_background_color=$2, ai_styles=$3::jsonb,
ai_patterns=$4::jsonb, ai_tags=$5::jsonb, ai_description=$6, ai_accepted_at=NOW()
WHERE arte_sku=$7`,
[JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.sku]);
console.log(`OK colors=[${ai.colors.join(', ')}] bg=${ai.backgroundColor}`);
ok++; await sleep(1200);
} catch(e) { console.log(`FAIL ${e.message}`); fail++; }
}
console.log(`\nDONE: ${ok}/9 Arte enriched, ${fail} failed`);
await pgClient.end();
}
main().catch(e => { console.error(e); process.exit(1); });