← back to Designer Wallcoverings
enrich-arte-fix.js
141 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_URL = (process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified');
const PRODUCTS = [
{ id: 123, sku: '19433', pattern: 'Horus', color: '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', pattern: 'Amarna', color: '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', pattern: 'Sakkara', color: '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', pattern: 'Sakkara', color: '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', pattern: 'Ibis', color: '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', pattern: 'Ibis', color: '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', pattern: 'Siwa', color: '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', pattern: 'Siwa', color: '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', pattern: 'Galon', color: '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 downloadImage(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 downloadImage(res.headers.location).then(resolve).catch(reject);
}
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, mime, prompt) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({
contents: [{ parts: [
{ text: prompt },
{ inline_data: { mime_type: mime, data: b64 } }
]}],
generationConfig: { thinkingConfig: { thinkingBudget: 0 } }
});
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', 'Content-Length': Buffer.byteLength(body) },
timeout: 30000
}, (res) => {
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => {
try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }
catch(e) { reject(e); }
});
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
req.write(body);
req.end();
});
}
function parseGeminiJson(text) {
let clean = text.replace(/^```(?:json)?\s*/m, '').replace(/\s*```\s*$/m, '').trim();
return JSON.parse(clean);
}
// For vendor_catalog (text columns): PostgreSQL array literal
function toPgArray(arr) {
if (!arr || !arr.length) return '{}';
return '{' + arr.map(s => '"' + s.replace(/"/g, '\\"') + '"').join(',') + '}';
}
async function main() {
const db = new Client({ connectionString: DB_URL });
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}/9] arte ${p.sku} (${p.pattern} - ${p.color}) ━━━`);
try {
const imgBuf = await downloadImage(p.img);
if (!imgBuf.length) throw new Error('Empty image');
const b64 = imgBuf.toString('base64');
console.log(` Downloaded: ${(imgBuf.length / 1024).toFixed(0)}KB`);
const prompt = `You are an interior design wallcovering expert. Analyze this wallcovering image. The vendor color name is "${p.color}" and the pattern is "${p.pattern}".
Return ONLY valid JSON with no markdown formatting:
{
"colors": ["list 3-6 actual colors visible in the wallcovering"],
"backgroundColor": "the dominant background color",
"tags": ["5-8 interior design tags like Luxury, Textured, Modern, Elegant"],
"styles": ["1-3 design period styles like Contemporary, Art Deco, Mid-Century"],
"patterns": ["1-3 pattern types like Geometric, Textured, Striped, Floral"],
"description": "Two-sentence commercial description for an architectural wallcovering retailer. Never use the word wallpaper."
}`;
const resp = await callGemini(b64, 'image/jpeg', prompt);
const text = resp?.candidates?.[0]?.content?.parts?.[0]?.text;
if (!text) throw new Error('No Gemini text: ' + JSON.stringify(resp?.error || 'unknown').slice(0, 200));
const data = parseGeminiJson(text);
console.log(` Colors: ${data.colors?.join(', ')}`);
console.log(` BG: ${data.backgroundColor}`);
console.log(` Tags: ${data.tags?.join(', ')}`);
// vendor_catalog uses TEXT columns (pg array format)
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`,
[toPgArray(data.colors), data.backgroundColor||'', toPgArray(data.tags), toPgArray(data.styles), toPgArray(data.patterns), data.description||'', p.id]);
// arte_catalog uses JSONB columns (JSON format)
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`,
[JSON.stringify(data.colors), data.backgroundColor||'', JSON.stringify(data.tags), JSON.stringify(data.styles), JSON.stringify(data.patterns), data.description||'', p.sku]);
// Verify both
const v1 = await db.query('SELECT ai_colors FROM vendor_catalog WHERE id=$1', [p.id]);
const v2 = await db.query('SELECT ai_colors FROM arte_catalog WHERE arte_sku=$1', [p.sku]);
const ok1 = v1.rows[0]?.ai_colors && v1.rows[0].ai_colors !== '{}';
const ok2 = v2.rows[0]?.ai_colors && JSON.stringify(v2.rows[0].ai_colors) !== '[]';
if (ok1 && ok2) {
console.log(` ✅ Saved (vendor_catalog + arte_catalog)`); success++;
} else {
console.log(` ⚠️ Partial: vc=${ok1}, ac=${ok2}`); fail++;
}
} catch (err) {
console.log(` ❌ Error: ${err.message}`); fail++;
}
await new Promise(r => setTimeout(r, 1200));
}
await db.end();
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━`);
console.log(`RESULTS: ✅ ${success} ❌ ${fail} (of 9)`);
}
main().catch(e => { console.error(e); process.exit(1); });