← 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); });