← back to Designer Wallcoverings

gemini-enrich-arte.js

121 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' },
];

function downloadImage(url) {
  return new Promise((resolve, reject) => {
    const get = (u, redirects = 0) => {
      if (redirects > 5) return reject(new Error('Too many redirects'));
      https.get(u, { headers: { 'User-Agent': 'Mozilla/5.0' } }, res => {
        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
          return get(res.headers.location, redirects + 1);
        }
        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)));
      }).on('error', reject);
    };
    get(url);
  });
}

function callGemini(body) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify(body);
    const req = https.request(GEMINI_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } }, res => {
      let buf = '';
      res.on('data', c => buf += c);
      res.on('end', () => { try { resolve(JSON.parse(buf)); } catch(e) { reject(new Error(buf.slice(0,500))); } });
    });
    req.on('error', reject);
    req.write(data);
    req.end();
  });
}

const PROMPT = `You are an interior design wallcovering analyst. Analyze this wallcovering image and return ONLY valid JSON (no markdown, no backticks):
{
  "colors": ["<list 3-6 dominant colors you actually see>"],
  "backgroundColor": "<the single dominant background color>",
  "styles": ["<1-3 design styles>"],
  "patterns": ["<1-3 pattern types>"],
  "tags": ["<5-10 interior design tags>"],
  "description": "<2-sentence commercial description>"
}
The product is: PRODUCT_NAME. Focus on what you actually see.`;

async function analyzeOne(product) {
  console.log(`  Downloading image...`);
  const imgBuf = await downloadImage(product.img);
  console.log(`  Downloaded ${(imgBuf.length/1024).toFixed(0)}KB, calling Gemini...`);
  const b64 = imgBuf.toString('base64');
  
  const body = {
    contents: [{
      parts: [
        { text: PROMPT.replace('PRODUCT_NAME', product.name) },
        { inlineData: { mimeType: 'image/jpeg', data: b64 } }
      ]
    }],
    generationConfig: { temperature: 0.2, thinkingConfig: { thinkingBudget: 0 } }
  };
  
  const res = await callGemini(body);
  if (res.error) throw new Error(res.error.message);
  const text = res.candidates?.[0]?.content?.parts?.[0]?.text || '';
  return JSON.parse(text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim());
}

async function main() {
  const client = new Client({ connectionString: (process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified') });
  await client.connect();
  console.log('Processing 9 Arte products with base64 image download...\n');
  
  let success = 0, failed = 0;
  
  for (const p of products) {
    console.log(`[${success+failed+1}/9] ${p.sku} (${p.name})`);
    try {
      const a = await analyzeOne(p);
      const colors = a.colors?.join(', ') || '';
      const bg = a.backgroundColor || '';
      const styles = a.styles?.join(', ') || '';
      const patterns = a.patterns?.join(', ') || '';
      const tags = a.tags?.join(', ') || '';
      const desc = a.description || '';
      
      await client.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`,
        [colors, bg, styles, patterns, tags, desc, p.id]);
      await client.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`,
        [colors, bg, styles, patterns, tags, desc, p.sku]);
      
      console.log(`  ✅ Colors: ${colors}`);
      success++;
    } catch(e) {
      console.log(`  ❌ ${e.message}`);
      failed++;
    }
    await new Promise(r => setTimeout(r, 300));
  }
  
  console.log(`\nDone: ${success} succeeded, ${failed} failed`);
  await client.end();
}

main().catch(e => { console.error(e); process.exit(1); });