/** * POC Workflow Carrier V3: 高保真、零推理工作流 * * 核心策略: * 1. 强力约束:锁定五金细节、文字大小写、精确尺寸数据。 * 2. 视觉统一:强制 AI 使用统一背景颜色、字体风格和标注样式。 * 3. 素材绑定:每张图绑定最准确的源文件,减少 AI “脑补”。 */ require('dotenv').config(); const axios = require('axios'); const fs = require('fs'); const path = require('path'); const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); const imageProcessor = require('./lib/image-processor'); const API_KEY = 'G9rXx3Ag2Xfa7Gs8zou6t6HqeZ'; const API_BASE = 'https://api.wuyinkeji.com/api'; const r2Client = new S3Client({ region: 'auto', endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, credentials: { accessKeyId: process.env.R2_ACCESS_KEY_ID, secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, }, forcePathStyle: true, }); // ============================================================ // 统一视觉标准 (Global UI Standards) // ============================================================ const UI_STANDARDS = { font: "Modern geometric sans-serif font (OPPOSans style), clean and highly readable", background: "Clean unified soft beige gradient background (#F5EDE4 to #FFFFFF)", calloutStyle: "Minimalist thin solid lines with small dots, professional and tidy", brandText: "touchdog (ALWAYS LOWERCASE, no exceptions)", dimensions: "L: 15.35\" x W: 6.1\" x H: 8.27\" (Height: 28 cm)" }; // ============================================================ // 工具函数 // ============================================================ async function uploadToR2(buffer, filename) { const fileName = `carrier-v3-${Date.now()}-${filename}`; await r2Client.send(new PutObjectCommand({ Bucket: process.env.R2_BUCKET_NAME || 'ai-flow', Key: fileName, Body: buffer, ContentType: 'image/jpeg', })); return `${process.env.R2_PUBLIC_DOMAIN}/${fileName}`; } async function submitTask(prompt, imgUrl, aspectRatio) { const payload = { key: API_KEY, prompt: prompt, img_url: imgUrl, aspectRatio: aspectRatio, imageSize: '4K' // 维持 4K 高清 }; const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload, { timeout: 60000 }); return response.data.data?.id || response.data.id; } async function pollResult(taskId) { let attempts = 0; while (attempts < 100) { const res = await axios.get(`${API_BASE}/img/drawDetail`, { params: { key: API_KEY, id: taskId } }); if (res.data.data?.status === 2) return res.data.data.image_url; if (res.data.data?.status === 3) throw new Error('Failed: ' + res.data.data.fail_reason); process.stdout.write('.'); await new Promise(r => setTimeout(r, 2000)); attempts++; } throw new Error('Timeout'); } // ============================================================ // V3 高保真 Prompt 构建器 // ============================================================ function buildFidelityPrompt(config) { return ` [IMAGE EDITING TASK - STRICT FIDELITY MODE] 1. REFERENCE ANALYSIS: Source product is a luxury pet carrier. KEY DETAIL: The front pockets use SILVER TUCK-LOCK CLASPS (round/oval metal locks), NOT prong buckles. KEY DETAIL: Brand logo is "touchdog" in LOWERCASE. 2. CRITICAL PRESERVATION (DO NOT MODIFY PRODUCT): - KEEP the exact hardware style: Silver metal tuck-lock clasps. - KEEP the "touchdog" logo in LOWERCASE. - KEEP the diamond quilted texture and mocha brown color (#59483D). - DO NOT add side pockets if they are not in the reference. 3. EDITING TASKS: ${config.tasks.join('\n')} 4. UI & STYLE STANDARDS: - Background: ${UI_STANDARDS.background} - Font: ${UI_STANDARDS.font} - All text must be in English. - Use ${UI_STANDARDS.calloutStyle} for any annotations. 5. TEXT CONTENT: ${config.textContent.join('\n')} OUTPUT: 4K High Definition, Professional Amazon US Listing Style. `.trim(); } // ============================================================ // 任务执行逻辑 // ============================================================ async function runV3() { const materialDir = path.join(__dirname, '素材/登机包'); const outputDir = path.join(__dirname, `output_carrier_v3_Final_${new Date().toISOString().slice(0, 10)}`); if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true }); console.log('🚀 启动 V3 高保真生成工作流 (对齐客户反馈)...'); const tasks = [ { id: 'APlus_02', source: 'P1191464.JPG', // 侧面结构图 config: { tasks: [ "Place the bag under an airplane seat in a high-end cabin setting.", "Add airline approval icons." ], textContent: [ "Title: AIRLINE APPROVED", "Bullet 1: Height: 28 cm (Fits Most Airlines)", "Bullet 2: TSA Compliant Design" ] } }, { id: 'APlus_03', source: '1023_0151.JPG', // 特写图(有正确扣件) config: { tasks: [ "Maintain the split-screen detail layout.", "Enhance the water-shedding droplets on the left side.", "Sharpen the silver tuck-lock clasp on the right side." ], textContent: [ "Logo: touchdog (LOWERCASE)", "Main Text: LUXURIOUS FEEL & STYLISH DESIGN", "Subtext: Premium textured finish with light-luxury sheen" ] } }, { id: 'Main_02', source: 'P1191451.JPG', // 侧面透气图 config: { tasks: [ "Highlight the privacy curtains and mesh windows.", "Add clean blue airflow arrows.", "Replace background with unified soft beige gradient.", "CRITICAL: REMOVE the short carrying handle on top of the bag. The top should be clean and flat without any short briefcase-style handle." ], textContent: [ "Text: 360° BREATHABLE COMFORT", "Label: Adjustable Privacy Curtains", "Label: Dual 2.5cm Vent Holes" ] } }, { id: 'Main_05', source: 'P1191464.JPG', // 正面图用于尺寸 config: { tasks: [ "Place bag on unified white/beige background.", "Add dimension lines with precision." ], textContent: [ `Dimensions: ${UI_STANDARDS.dimensions}`, "Text: FITS UNDER SEAT", "Weight: 0.56 KG Lightweight" ] } } ]; for (const task of tasks) { console.log(`\n🎨 正在处理 [${task.id}] 使用素材: ${task.source}`); try { const prompt = buildFidelityPrompt(task.config); const imgBuffer = fs.readFileSync(path.join(materialDir, task.source)); const imgUrl = await uploadToR2(imgBuffer, task.source); const taskId = await submitTask(prompt, imgUrl, task.id.startsWith('APlus') ? '3:2' : '1:1'); const resultUrl = await pollResult(taskId); const resultBuffer = await axios.get(resultUrl, { responseType: 'arraybuffer' }); fs.writeFileSync(path.join(outputDir, `${task.id}.jpg`), Buffer.from(resultBuffer.data)); fs.writeFileSync(path.join(outputDir, `${task.id}_prompt.txt`), prompt); console.log(`✅ ${task.id} 成功`); } catch (e) { console.error(`❌ ${task.id} 失败: ${e.message}`); } } } runV3().catch(console.error);