← back to Designer Wallcoverings
ai-enrich-arte-fix.js
117 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 PRODUCTS = [
{id:123,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,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,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,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,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,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,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,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,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'},
];
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) {
return new Promise((resolve, reject) => {
https.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, name) {
const body = JSON.stringify({
contents: [{ parts: [
{ text: `Product: ${name}\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 || '';
resolve(JSON.parse(text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim()));
} 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 ok = 0, fail = 0;
for (let i = 0; i < PRODUCTS.length; i++) {
const p = PRODUCTS[i];
console.log(`\n[${i+1}/9] ${p.sku} - ${p.name}`);
try {
const imgBuf = await fetchImage(p.img);
console.log(` Image: ${(imgBuf.length/1024).toFixed(0)}KB`);
const ai = await callGemini(imgBuf.toString('base64'), p.name);
console.log(` Colors: ${ai.colors.join(', ')} | BG: ${ai.backgroundColor}`);
// vendor_catalog: text columns - stringify arrays as JSON strings
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
`, [JSON.stringify(ai.colors), ai.backgroundColor, JSON.stringify(ai.styles),
JSON.stringify(ai.patterns), JSON.stringify(ai.tags), ai.description, p.id]);
// arte_catalog: jsonb columns - also stringify for jsonb
const arteRes = await db.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
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(` Saved vendor_catalog + arte_catalog (${arteRes.rowCount} arte row)`);
ok++;
} 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: ${ok}/9 success, ${fail} failed`);
}
main().catch(console.error);