← back to Designer Wallcoverings
gemini-enrich-arte9.js
154 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 db = new Client({ connectionString: (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 (it may be a room setting showing the wallcovering installed on walls).
Focus on the WALLCOVERING itself, not furniture or decor.
Return ONLY valid JSON with these fields:
{
"colors": ["<list of 3-6 dominant colors in the wallcovering, e.g. Gold, Cream, Charcoal>"],
"backgroundColor": "<the single dominant background color of the wallcovering>",
"tags": ["<5-8 interior design tags, e.g. Luxury, Textured, Organic, Hospitality>"],
"styles": ["<1-3 design style periods, e.g. Contemporary, Art Deco, Mid-Century Modern>"],
"patterns": ["<1-3 pattern types, e.g. Geometric, Abstract, Textural, Striped>"],
"description": "<2 sentence commercial description for an architect or interior designer>"
}
No markdown, no code fences, just raw JSON.`;
// Use vendor_catalog room shot URLs (confirmed working 200 OK)
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' },
];
function fetchImageAsBase64(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 fetchImageAsBase64(res.headers.location).then(resolve).catch(reject);
}
if (res.statusCode !== 200) {
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
}
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => {
const buf = Buffer.concat(chunks);
if (buf.length < 1000) return reject(new Error(`Image too small: ${buf.length} bytes`));
resolve(buf.toString('base64'));
});
res.on('error', reject);
}).on('error', reject);
});
}
function callGemini(b64, productName) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({
contents: [{
parts: [
{ text: `Product: ${productName}\n\n${PROMPT}` },
{ inline_data: { mime_type: 'image/jpeg', data: b64 } }
]
}],
generationConfig: { thinkingConfig: { thinkingBudget: 0 } }
});
const url = new URL(GEMINI_URL);
const options = {
hostname: url.hostname,
path: url.pathname + url.search,
method: 'POST',
headers: { 'Content-Type': 'application/json' }
};
const req = https.request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
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 cleaned = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
resolve(JSON.parse(cleaned));
} catch (e) {
reject(new Error(`Parse: ${e.message} — ${data.substring(0, 200)}`));
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
}
async function updateDB(product, analysis) {
// vendor_catalog uses text columns — postgres array literal
const pgArr = arr => `{${arr.map(c => `"${c.replace(/"/g, '')}"`).join(',')}}`;
const vc_colors = pgArr(analysis.colors || []);
const vc_tags = pgArr(analysis.tags || []);
const vc_styles = pgArr(analysis.styles || []);
const vc_patterns = pgArr(analysis.patterns || []);
// arte_catalog uses jsonb columns — JSON array
const ac_colors = JSON.stringify(analysis.colors || []);
const ac_tags = JSON.stringify(analysis.tags || []);
const ac_styles = JSON.stringify(analysis.styles || []);
const ac_patterns = JSON.stringify(analysis.patterns || []);
// Update vendor_catalog (text columns)
await db.query(`
UPDATE vendor_catalog SET
ai_colors = $1, ai_background_color = $2, ai_tags = $3,
ai_styles = $4, ai_patterns = $5, ai_description = $6
WHERE id = $7
`, [vc_colors, analysis.backgroundColor, vc_tags, vc_styles, vc_patterns, analysis.description, product.id]);
// Update arte_catalog (jsonb columns)
await db.query(`
UPDATE arte_catalog SET
ai_colors = $1::jsonb, ai_background_color = $2, ai_tags = $3::jsonb,
ai_styles = $4::jsonb, ai_patterns = $5::jsonb, ai_description = $6
WHERE arte_sku = $7
`, [ac_colors, analysis.backgroundColor, ac_tags, ac_styles, ac_patterns, analysis.description, product.sku]);
}
async function main() {
await db.connect();
console.log('Connected. Processing 9 Arte products...\n');
let ok = 0, fail = 0;
for (const p of products) {
try {
process.stdout.write(`${p.sku} ${p.name}... `);
const b64 = await fetchImageAsBase64(p.img);
console.log(`img ${Math.round(b64.length/1024)}KB`);
const analysis = await callGemini(b64, p.name);
console.log(` ✅ ${analysis.colors.join(', ')} | bg: ${analysis.backgroundColor}`);
await updateDB(p, analysis);
ok++;
await new Promise(r => setTimeout(r, 1000));
} catch (e) {
console.log(` ❌ ${e.message}`);
fail++;
}
}
console.log(`\nDone: ${ok}/9 succeeded, ${fail} failed`);
await db.end();
}
main().catch(e => { console.error(e); process.exit(1); });