877 lines
33 KiB
JavaScript
877 lines
33 KiB
JavaScript
/**
|
||
* POC Workflow V6: 优化Prompt控制策略
|
||
*
|
||
* 核心改进:
|
||
* 1. 修复素材选择(使用猫咪图而非狗狗图)
|
||
* 2. 优化Prompt - 更精准地指示AI保持主体不变
|
||
* 3. 分类处理不同复杂度的图片
|
||
*/
|
||
|
||
require('dotenv').config();
|
||
const axios = require('axios');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const sharp = require('sharp');
|
||
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';
|
||
|
||
// R2客户端
|
||
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,
|
||
});
|
||
|
||
// ============================================================
|
||
// 工具函数
|
||
// ============================================================
|
||
|
||
async function uploadToR2(buffer, filename) {
|
||
const fileName = `v6-${Date.now()}-${filename}`;
|
||
|
||
await r2Client.send(new PutObjectCommand({
|
||
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
|
||
Key: fileName,
|
||
Body: buffer,
|
||
ContentType: filename.endsWith('.png') ? 'image/png' : 'image/jpeg',
|
||
}));
|
||
|
||
return process.env.R2_PUBLIC_DOMAIN
|
||
? `${process.env.R2_PUBLIC_DOMAIN}/${fileName}`
|
||
: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET_NAME}/${fileName}`;
|
||
}
|
||
|
||
async function submitImageTask(prompt, refImageUrl = null, aspectRatio = '1:1') {
|
||
const payload = {
|
||
key: API_KEY,
|
||
prompt: prompt,
|
||
aspectRatio: aspectRatio,
|
||
imageSize: '1K'
|
||
};
|
||
|
||
if (refImageUrl) {
|
||
payload.img_url = refImageUrl;
|
||
}
|
||
|
||
const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload, { timeout: 30000 });
|
||
const taskId = response.data.data?.id;
|
||
|
||
if (!taskId) {
|
||
throw new Error('No task ID returned');
|
||
}
|
||
return taskId;
|
||
}
|
||
|
||
async function pollImageResult(taskId) {
|
||
let attempts = 0;
|
||
const maxAttempts = 90;
|
||
|
||
while (attempts < maxAttempts) {
|
||
try {
|
||
const response = await axios.get(`${API_BASE}/img/drawDetail`, {
|
||
params: { key: API_KEY, id: taskId },
|
||
timeout: 10000
|
||
});
|
||
|
||
const data = response.data.data;
|
||
|
||
if (data && data.status === 2 && data.image_url) {
|
||
return data.image_url;
|
||
} else if (data && data.status === 3) {
|
||
throw new Error('Generation failed: ' + (data.fail_reason || 'Unknown'));
|
||
}
|
||
|
||
process.stdout.write('.');
|
||
await new Promise(r => setTimeout(r, 2000));
|
||
attempts++;
|
||
} catch (error) {
|
||
if (error.message.includes('Generation failed')) throw error;
|
||
process.stdout.write('x');
|
||
await new Promise(r => setTimeout(r, 3000));
|
||
attempts++;
|
||
}
|
||
}
|
||
throw new Error('Timeout');
|
||
}
|
||
|
||
async function downloadImage(url) {
|
||
const response = await axios.get(url, { responseType: 'arraybuffer', timeout: 30000 });
|
||
return Buffer.from(response.data);
|
||
}
|
||
|
||
async function generateAIImage(prompt, refImageUrl = null, aspectRatio = '1:1') {
|
||
console.log(' 提交AI任务...');
|
||
const taskId = await submitImageTask(prompt, refImageUrl, aspectRatio);
|
||
console.log(` Task ID: ${taskId}`);
|
||
|
||
const imageUrl = await pollImageResult(taskId);
|
||
console.log('\n ✓ AI生成完成');
|
||
|
||
return await downloadImage(imageUrl);
|
||
}
|
||
|
||
// ============================================================
|
||
// Prompt模板 - 核心优化
|
||
// ============================================================
|
||
|
||
/**
|
||
* 构建精准的编辑prompt
|
||
* 核心策略:明确告诉AI这是"编辑"任务,精确描述要保留的元素
|
||
*/
|
||
function buildEditPrompt(config) {
|
||
const {
|
||
subjectDescription, // 主体描述(产品+宠物)
|
||
preserveElements, // 必须保留的元素
|
||
editTasks, // 编辑任务
|
||
layoutDescription, // 排版描述
|
||
styleGuide, // 风格指南
|
||
aspectRatio // 比例
|
||
} = config;
|
||
|
||
return `
|
||
[IMAGE EDITING TASK - NOT GENERATION]
|
||
|
||
REFERENCE IMAGE ANALYSIS:
|
||
The reference image shows: ${subjectDescription}
|
||
|
||
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
|
||
${preserveElements.map((e, i) => `${i + 1}. ${e}`).join('\n')}
|
||
|
||
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
|
||
${editTasks.map((t, i) => `${i + 1}. ${t}`).join('\n')}
|
||
|
||
LAYOUT SPECIFICATION:
|
||
${layoutDescription}
|
||
|
||
STYLE GUIDE:
|
||
${styleGuide}
|
||
|
||
OUTPUT: ${aspectRatio} ratio, professional Amazon listing quality
|
||
|
||
FINAL CHECK: Before output, verify that the pet and product in the result match the reference image EXACTLY in:
|
||
- Species, breed, fur color and pattern
|
||
- Product color (#B5E5E8 ice blue), shape (flower/petal cone), brand text "TOUCHDOG"
|
||
- Pose and positioning relative to original
|
||
`.trim();
|
||
}
|
||
|
||
// ============================================================
|
||
// 12张图的生成逻辑
|
||
// ============================================================
|
||
|
||
/**
|
||
* Main_01: 场景首图+卖点
|
||
*/
|
||
async function generateMain01(materials, config, outputDir) {
|
||
console.log('\n🎨 [Main_01] 场景首图+卖点');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'cat-worn.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat (white/cream fur with light brown markings, blue eyes) wearing an ice-blue soft recovery cone collar (brand: TOUCHDOG). The cat is in a home environment.`,
|
||
|
||
preserveElements: [
|
||
`The cat MUST remain a Ragdoll with blue eyes, white/cream fur, brown ear markings - EXACTLY as shown`,
|
||
`The cone collar MUST remain ice-blue (#B5E5E8), flower/petal shaped, with "TOUCHDOG" branding`,
|
||
`The cat's pose, expression, and the way the collar sits on the cat`,
|
||
`The product's texture, stitching pattern, and velcro closure detail`
|
||
],
|
||
|
||
editTasks: [
|
||
`Enhance background to warm, cozy home interior (fireplace, blanket, wooden furniture)`,
|
||
`Add soft warm lighting with gentle shadows`,
|
||
`Add a curved blue banner (#4A7C9B) across middle area with text: "DESIGNED FOR COMFORTABLE RECOVERY"`,
|
||
`Add 3 white feature boxes at bottom with icons and text:
|
||
- Egg icon + "LIGHTER THAN AN EGG"
|
||
- Water drop icon + "WATERPROOF & EASY WIPE"
|
||
- Cloud icon + "BREATHABLE COTTON LINING"`,
|
||
`Add subtle paw print watermarks in light blue on the banner area`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Top 60%: Cat wearing product in enhanced home scene
|
||
- Middle: Curved banner with main tagline
|
||
- Bottom 25%: Three feature callout boxes in a row`,
|
||
|
||
styleGuide: `Professional Amazon main image style. Warm color palette. Text should be crisp and readable. The cat and product should be the clear focal point.`,
|
||
|
||
aspectRatio: '1:1 square'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '1:1');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_01.jpg'));
|
||
|
||
// 保存prompt供调试
|
||
fs.writeFileSync(path.join(outputDir, 'Main_01_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Main_03: 功能调节展示 (参考效果最好的那张)
|
||
*/
|
||
async function generateMain03(materials, config, outputDir) {
|
||
console.log('\n🎨 [Main_03] 功能调节展示');
|
||
|
||
// 使用平铺图作为主参考
|
||
const flatBuffer = fs.readFileSync(materials.flatImage);
|
||
const flatUrl = await uploadToR2(flatBuffer, 'flat.png');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `An ice-blue soft pet recovery cone collar (TOUCHDOG brand) shown in flat lay position. The product has a flower/petal shape with 8 segments, velcro closure, and green inner rim.`,
|
||
|
||
preserveElements: [
|
||
`The product MUST remain EXACTLY as shown: ice-blue color (#B5E5E8), flower shape, TOUCHDOG branding`,
|
||
`The velcro strap detail on the left side`,
|
||
`The green/teal inner rim around the neck hole`,
|
||
`The 8-petal segmented design with visible stitching`
|
||
],
|
||
|
||
editTasks: [
|
||
`Place the product on a soft blue background (#6B9AC4) that complements the product color`,
|
||
`Add title text at top-left: "ADJUSTABLE STRAP FOR A SECURE FIT" in white, bold`,
|
||
`Add 2 circular detail callout images on the right side:
|
||
- Top circle: Close-up of the velcro strap, caption "SECURE THE ADJUSTABLE STRAP"
|
||
- Bottom circle: Product being worn by a pet showing fit, caption "ADJUST FOR A SNUG FIT"`,
|
||
`Add decorative white paw prints scattered in the background`,
|
||
`Add thin white circular borders around the detail callouts`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Left side (60%): Main product image, slightly angled
|
||
- Top-left corner: Title text
|
||
- Right side (40%): Two stacked circular detail images with captions
|
||
- Scattered paw prints as decoration`,
|
||
|
||
styleGuide: `Clean infographic style. Blue color scheme matching the product. Professional Amazon A+ content quality. High contrast text for readability.`,
|
||
|
||
aspectRatio: '1:1 square'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, flatUrl, '1:1');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_03.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'Main_03_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Main_04: 多场景使用 - 关键优化
|
||
* 这是最难的图,需要在4个场景中保持同一产品和宠物
|
||
*/
|
||
async function generateMain04(materials, config, outputDir) {
|
||
console.log('\n🎨 [Main_04] 多场景使用 (4宫格)');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'cat-worn-grid.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat (white/cream long fur, light brown markings on ears and face, distinctive blue eyes, pink nose) wearing an ice-blue TOUCHDOG soft recovery cone collar.`,
|
||
|
||
preserveElements: [
|
||
`ALL 4 SCENES MUST SHOW THE EXACT SAME CAT: Ragdoll breed, blue eyes, white/cream fur, brown ear markings`,
|
||
`ALL 4 SCENES MUST SHOW THE EXACT SAME PRODUCT: Ice-blue (#B5E5E8) flower-shaped soft cone, TOUCHDOG brand`,
|
||
`The cat's fur texture and coloring must be consistent across all 4 images`,
|
||
`The product's color, shape, and branding must be identical in all 4 scenes`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create a 2x2 grid layout with 4 scenes, each in a rounded rectangle frame`,
|
||
`TOP-LEFT scene: Cat standing alert, wearing the cone - Caption: "• HYGIENIC & EASY TO CLEAN"`,
|
||
`TOP-RIGHT scene: Cat eating/drinking from a bowl while wearing cone - Caption: "• UNRESTRICTED EATING/DRINKING"`,
|
||
`BOTTOM-LEFT scene: Cat stretching or playing while wearing cone - Caption: "• REVERSIBLE WEAR"`,
|
||
`BOTTOM-RIGHT scene: Cat sleeping peacefully curled up with cone - Caption: "• 360° COMFORT"`,
|
||
`Add light paw print watermarks in corners of the overall image`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Background: Warm beige/cream (#F5EBE0)
|
||
- 2x2 grid of equal-sized rounded rectangles
|
||
- Each scene has the same cat in different poses
|
||
- Caption text below each scene in brown (#8B7355)
|
||
- Subtle paw prints in corners`,
|
||
|
||
styleGuide: `
|
||
Lifestyle photography style. Warm, inviting colors.
|
||
The cat should look comfortable and happy in all scenes.
|
||
Consistent lighting across all 4 scenes.
|
||
CRITICAL: The cat must be recognizably the SAME individual cat in all scenes.`,
|
||
|
||
aspectRatio: '1:1 square'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '1:1');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_04.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'Main_04_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Main_05: 尺寸图
|
||
*/
|
||
async function generateMain05(materials, config, outputDir) {
|
||
console.log('\n🎨 [Main_05] 尺寸图');
|
||
|
||
const flatBuffer = fs.readFileSync(materials.flatImage);
|
||
const flatUrl = await uploadToR2(flatBuffer, 'flat-size.png');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `An ice-blue TOUCHDOG soft pet recovery cone collar in flat lay position, showing its flower/petal shape design.`,
|
||
|
||
preserveElements: [
|
||
`The product MUST remain EXACTLY as shown: ice-blue color, flower shape, TOUCHDOG branding`,
|
||
`The product's proportions and shape`,
|
||
`The velcro closure and inner rim details`
|
||
],
|
||
|
||
editTasks: [
|
||
`Place product on warm beige background (#F5EDE4)`,
|
||
`Add title "PRODUCT SIZE" at top center in dark text`,
|
||
`Add a size chart table below the product with columns: SIZE | NECK | DEPTH`,
|
||
`Table data:
|
||
XS: 5.6-6.8IN | 3.2IN
|
||
S: 7.2-8.4IN | 4IN
|
||
M: 8.8-10.4IN | 5IN
|
||
L: 10.8-12.4IN | 6IN
|
||
XL: 12.8-14.4IN | 7IN`,
|
||
`Add note in coral/salmon color: "NOTE: ALWAYS MEASURE YOUR PET'S NECK BEFORE SELECTING A SIZE"`,
|
||
`Add subtle paw print decorations in beige tones`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Top: Title
|
||
- Center: Product image (main focus)
|
||
- Bottom: Size chart table with clean design`,
|
||
|
||
styleGuide: `Clean, informative infographic style. Easy to read table. Warm neutral background. Professional Amazon listing quality.`,
|
||
|
||
aspectRatio: '1:1 square'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, flatUrl, '1:1');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_05.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'Main_05_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Main_02: 白底平铺+细节放大
|
||
*/
|
||
async function generateMain02(materials, config, outputDir) {
|
||
console.log('\n🎨 [Main_02] 白底平铺+细节放大');
|
||
|
||
const flatBuffer = fs.readFileSync(materials.flatImage);
|
||
const flatUrl = await uploadToR2(flatBuffer, 'flat-detail.png');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `An ice-blue TOUCHDOG soft pet recovery cone collar in flat lay position on black background. The product has a flower/petal shape with 8 segments, velcro closure on left, and green inner rim.`,
|
||
|
||
preserveElements: [
|
||
`The product MUST remain EXACTLY as shown: ice-blue color (#B5E5E8), flower shape, 8 petal segments`,
|
||
`The TOUCHDOG branding text on the product`,
|
||
`The velcro strap detail and green/teal inner rim`,
|
||
`The product's proportions and stitching pattern`
|
||
],
|
||
|
||
editTasks: [
|
||
`Change background from black to clean white`,
|
||
`Add a dark green (#2D4A3E) banner at top with text: "DURABLE WATERPROOF PU LAYER"`,
|
||
`Add 2 circular magnified detail callouts at bottom:
|
||
- Left circle: Close-up of outer material texture, label "DURABLE WATERPROOF PU LAYER"
|
||
- Right circle: Close-up of inner lining, label "DOUBLE-LAYER COMFORT"`,
|
||
`Add thin green borders around detail circles`,
|
||
`Keep layout clean and professional`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Top: Dark green banner with title
|
||
- Center: Main product image on white background
|
||
- Bottom: Two circular detail magnifications with labels`,
|
||
|
||
styleGuide: `Clean white background product photography. Professional Amazon main image style. High contrast, sharp details.`,
|
||
|
||
aspectRatio: '1:1 square'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, flatUrl, '1:1');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_02.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'Main_02_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Main_06: 多角度展示
|
||
*/
|
||
async function generateMain06(materials, config, outputDir) {
|
||
console.log('\n🎨 [Main_06] 多角度展示');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'multi-angle.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat (white/cream fur, brown ear markings, blue eyes) wearing an ice-blue TOUCHDOG soft recovery cone collar.`,
|
||
|
||
preserveElements: [
|
||
`The cat MUST remain a Ragdoll: blue eyes, white/cream fur, brown markings`,
|
||
`The product MUST remain ice-blue (#B5E5E8), flower-shaped, TOUCHDOG branded`,
|
||
`Both views must show the EXACT SAME cat and product`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create a split image with curved decorative divider in the middle`,
|
||
`LEFT SIDE: Front view of cat wearing the cone (from reference)`,
|
||
`RIGHT SIDE: Side/profile view of the same cat wearing the same cone`,
|
||
`Warm home interior background in both halves`,
|
||
`NO text overlays - pure lifestyle imagery`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Split layout with elegant curved divider
|
||
- Left: Front view
|
||
- Right: Side view
|
||
- Consistent warm lighting across both`,
|
||
|
||
styleGuide: `Lifestyle photography. Warm, cozy atmosphere. The cat should look comfortable. NO text.`,
|
||
|
||
aspectRatio: '1:1 square'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '1:1');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_06.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'Main_06_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* APlus_01: 品牌横幅
|
||
*/
|
||
async function generateAPlus01(materials, config, outputDir) {
|
||
console.log('\n🎨 [APlus_01] 品牌横幅');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'brand-banner.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar in a home setting.`,
|
||
|
||
preserveElements: [
|
||
`The cat MUST remain a Ragdoll with blue eyes and white/cream fur`,
|
||
`The product MUST remain ice-blue, flower-shaped, TOUCHDOG branded`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create a horizontal banner layout (3:2 ratio)`,
|
||
`LEFT 40%: Brand text area with:
|
||
- "TOUCHDOG" in playful coral/salmon color (#E8A87C)
|
||
- Small paw print icons around the brand name
|
||
- "CAT SOFT CONE COLLAR" below in gray`,
|
||
`RIGHT 60%: The cat wearing the product in a warm home setting`,
|
||
`Soft, warm color palette throughout`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Left side: Brand name and product title
|
||
- Right side: Hero image of cat with product
|
||
- Warm, cohesive color scheme`,
|
||
|
||
styleGuide: `Amazon A+ brand story style. Warm and inviting. Professional yet friendly.`,
|
||
|
||
aspectRatio: '3:2 landscape'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '3:2');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_01.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'APlus_01_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* APlus_02: 对比图
|
||
*/
|
||
async function generateAPlus02(materials, config, outputDir) {
|
||
console.log('\n🎨 [APlus_02] 对比图');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'comparison.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar.`,
|
||
|
||
preserveElements: [
|
||
`LEFT SIDE: The cat and product MUST match the reference - Ragdoll cat, ice-blue soft cone`,
|
||
`The product color (#B5E5E8), flower shape, and TOUCHDOG branding on left side`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create a side-by-side comparison layout`,
|
||
`LEFT SIDE (60% width, colorful):
|
||
- Happy Ragdoll cat wearing our ice-blue soft cone (from reference)
|
||
- Green checkmark icon
|
||
- "OUR" label on coral/orange banner
|
||
- 3 benefits in white: "CLOUD-LIGHT COMFORT", "WIDER & CLEARER", "FOLDABLE & PORTABLE"`,
|
||
`RIGHT SIDE (40% width, grayscale):
|
||
- Sad-looking cat wearing a DIFFERENT product: hard plastic transparent cone (traditional e-collar)
|
||
- Red X icon
|
||
- "OTHER" label on gray banner
|
||
- 3 drawbacks in gray: "HEAVY & BULKY", "BLOCKS VISION & MOVEMENT", "HARD TO STORE"`,
|
||
`Warm beige background with paw print watermarks`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Split layout with curved divider
|
||
- Left side larger, full color, positive mood
|
||
- Right side smaller, desaturated, negative mood
|
||
- Text overlays on each side`,
|
||
|
||
styleGuide: `
|
||
Clear visual contrast between "us" and "them".
|
||
Left side warm and inviting, right side cold and uncomfortable.
|
||
IMPORTANT: Right side must show a DIFFERENT type of cone (hard plastic), not our product.`,
|
||
|
||
aspectRatio: '3:2 landscape'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '3:2');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_02.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'APlus_02_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* APlus_03: 功能细节展示
|
||
*/
|
||
async function generateAPlus03(materials, config, outputDir) {
|
||
console.log('\n🎨 [APlus_03] 功能细节展示');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'feature-detail.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar.`,
|
||
|
||
preserveElements: [
|
||
`The cat MUST remain a Ragdoll with blue eyes`,
|
||
`The product MUST remain ice-blue, flower-shaped, TOUCHDOG branded`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create horizontal layout (3:2) with title and 3 detail panels`,
|
||
`Title at top: "ENGINEERED FOR UNCOMPROMISED COMFORT" in dark text`,
|
||
`3 detail images in a row below:
|
||
- Panel 1: Close-up of inner cotton lining texture, caption "STURDY AND BREATHABLE"
|
||
- Panel 2: Cat wearing the product looking comfortable, caption "EASY TO CLEAN, STYLISH"
|
||
- Panel 3: Close-up of stitching detail, caption "REINFORCED STITCHING"`,
|
||
`Warm beige background (#F5EBE0) with subtle paw prints`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Top: Title banner
|
||
- Bottom: Three equal-width panels with captions`,
|
||
|
||
styleGuide: `Amazon A+ feature module style. Clean, informative. Warm color palette.`,
|
||
|
||
aspectRatio: '3:2 landscape'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '3:2');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_03.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'APlus_03_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* APlus_04: 多场景横版
|
||
*/
|
||
async function generateAPlus04(materials, config, outputDir) {
|
||
console.log('\n🎨 [APlus_04] 多场景横版');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'scenes-horizontal.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat (white/cream fur, blue eyes, brown ear markings) wearing an ice-blue TOUCHDOG soft recovery cone collar.`,
|
||
|
||
preserveElements: [
|
||
`ALL 4 SCENES MUST SHOW THE EXACT SAME CAT: Ragdoll breed, blue eyes, white/cream fur`,
|
||
`ALL 4 SCENES MUST SHOW THE EXACT SAME PRODUCT: Ice-blue flower-shaped soft cone`,
|
||
`Consistent cat appearance and product across all panels`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create horizontal layout (3:2) with 4 scenes in a row`,
|
||
`Scene 1: Cat standing - "HYGIENIC & EASY TO CLEAN"`,
|
||
`Scene 2: Cat eating from bowl - "UNRESTRICTED EATING"`,
|
||
`Scene 3: Cat playing/stretching - "REVERSIBLE WEAR"`,
|
||
`Scene 4: Cat sleeping curled up - "360° COMFORT"`,
|
||
`Each scene in a rounded rectangle frame`,
|
||
`Warm beige background with paw print decorations`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- 4 equal panels arranged horizontally
|
||
- Each panel has image + caption below
|
||
- Consistent warm lighting across all`,
|
||
|
||
styleGuide: `Lifestyle photography. Same cat in all scenes. Warm, cozy feel.`,
|
||
|
||
aspectRatio: '3:2 landscape'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '3:2');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_04.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'APlus_04_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* APlus_05: 多角度横版
|
||
*/
|
||
async function generateAPlus05(materials, config, outputDir) {
|
||
console.log('\n🎨 [APlus_05] 多角度横版');
|
||
|
||
const wornBuffer = fs.readFileSync(materials.catWornImage);
|
||
const wornUrl = await uploadToR2(wornBuffer, 'angles-horizontal.jpg');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar.`,
|
||
|
||
preserveElements: [
|
||
`Both views must show the SAME Ragdoll cat with blue eyes`,
|
||
`Both views must show the SAME ice-blue TOUCHDOG cone`
|
||
],
|
||
|
||
editTasks: [
|
||
`Create horizontal split layout (3:2)`,
|
||
`LEFT: Front view of cat wearing cone`,
|
||
`RIGHT: Side/profile view of same cat with cone`,
|
||
`Elegant curved divider between the two views`,
|
||
`Warm home background in both`,
|
||
`NO text - pure visual showcase`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Two equal halves with curved divider
|
||
- Left: Front angle
|
||
- Right: Side angle
|
||
- Warm consistent lighting`,
|
||
|
||
styleGuide: `High-end lifestyle photography. No text. Warm atmosphere.`,
|
||
|
||
aspectRatio: '3:2 landscape'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, wornUrl, '3:2');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_05.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'APlus_05_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* APlus_06: 尺寸表横版
|
||
* 优化:明确描述产品的C形开口特征
|
||
*/
|
||
async function generateAPlus06(materials, config, outputDir) {
|
||
console.log('\n🎨 [APlus_06] 尺寸表横版');
|
||
|
||
const flatBuffer = fs.readFileSync(materials.flatImage);
|
||
const flatUrl = await uploadToR2(flatBuffer, 'size-chart-h.png');
|
||
|
||
const prompt = buildEditPrompt({
|
||
subjectDescription: `An ice-blue TOUCHDOG soft pet recovery cone collar in flat lay position.
|
||
CRITICAL SHAPE DETAIL: The product has a C-SHAPED OPENING (not a closed circle) - there is a GAP on the left side where the velcro strap attaches. This opening allows the collar to wrap around the pet's neck.`,
|
||
|
||
preserveElements: [
|
||
`The product MUST keep its C-SHAPED OPENING - DO NOT close the gap into a full circle`,
|
||
`The velcro strap visible on the left side of the opening`,
|
||
`Ice-blue color (#B5E5E8), flower/petal shape with 8 segments`,
|
||
`TOUCHDOG brand text on the product`,
|
||
`Green/teal inner rim around the neck hole`
|
||
],
|
||
|
||
editTasks: [
|
||
`Change background from black to warm beige (#F5EDE4)`,
|
||
`Add title "PRODUCT SIZE" at top center in dark text`,
|
||
`Add dimension labels: "NECK" pointing to inner circle, "DEPTH" pointing outward`,
|
||
`Add size chart table on the right:
|
||
SIZE | NECK | DEPTH
|
||
XS | 5.6-6.8IN | 3.2IN
|
||
S | 7.2-8.4IN | 4IN
|
||
M | 8.8-10.4IN | 5IN
|
||
L | 10.8-12.4IN | 6IN
|
||
XL | 12.8-14.4IN | 7IN`,
|
||
`Add note in coral: "ALWAYS MEASURE YOUR PET'S NECK BEFORE SELECTING A SIZE"`,
|
||
`Add subtle paw print watermarks`
|
||
],
|
||
|
||
layoutDescription: `
|
||
- Left 45%: Product image maintaining C-shape opening
|
||
- Right 55%: Size chart table
|
||
- Top: Title
|
||
- Bottom: Note text`,
|
||
|
||
styleGuide: `Clean infographic style. The product's C-shaped opening must be clearly visible - this is a key feature showing how it wraps around the neck.`,
|
||
|
||
aspectRatio: '3:2 landscape'
|
||
});
|
||
|
||
const result = await generateAIImage(prompt, flatUrl, '3:2');
|
||
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_06.jpg'));
|
||
fs.writeFileSync(path.join(outputDir, 'APlus_06_prompt.txt'), prompt);
|
||
return true;
|
||
}
|
||
|
||
// ============================================================
|
||
// 主流程
|
||
// ============================================================
|
||
|
||
async function main() {
|
||
const args = process.argv.slice(2);
|
||
let materialDir = path.join(__dirname, '素材/素材/已有的素材');
|
||
let skuName = 'Touchdog冰蓝色伊丽莎白圈';
|
||
|
||
// 可选:只生成指定的图
|
||
let onlyGenerate = null; // e.g., ['Main_01', 'Main_04']
|
||
|
||
for (const arg of args) {
|
||
if (arg.startsWith('--material-dir=')) {
|
||
materialDir = arg.split('=')[1];
|
||
} else if (arg.startsWith('--sku-name=')) {
|
||
skuName = arg.split('=')[1];
|
||
} else if (arg.startsWith('--only=')) {
|
||
onlyGenerate = arg.split('=')[1].split(',');
|
||
}
|
||
}
|
||
|
||
const timestamp = new Date().toISOString().slice(0, 10);
|
||
const timeStr = new Date().toTimeString().slice(0, 8).replace(/:/g, '-');
|
||
const safeName = skuName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_').slice(0, 20);
|
||
const outputDir = path.join(__dirname, `output_v6_${safeName}_${timestamp}-${timeStr}`);
|
||
|
||
console.log('\n' + '═'.repeat(70));
|
||
console.log('🚀 POC Workflow V6 - 优化Prompt控制策略');
|
||
console.log('═'.repeat(70));
|
||
console.log('\n📋 核心策略: 精准Prompt控制,明确保留元素,限定编辑范围');
|
||
console.log(' 素材目录:', materialDir);
|
||
console.log(' 输出目录:', outputDir);
|
||
|
||
if (!fs.existsSync(outputDir)) {
|
||
fs.mkdirSync(outputDir, { recursive: true });
|
||
}
|
||
|
||
// 扫描素材
|
||
const files = fs.readdirSync(materialDir);
|
||
const images = files.filter(f => /\.(jpg|jpeg|png)$/i.test(f));
|
||
|
||
console.log('\n📁 可用素材:');
|
||
images.forEach(img => console.log(` - ${img}`));
|
||
|
||
// ========== 修复:正确识别素材 ==========
|
||
// 平铺图:IMG_5683.png(黑底抠图)
|
||
const flatImage = images.find(i => i.includes('5683')) || images.find(i => i.endsWith('.png'));
|
||
|
||
// 猫咪佩戴图:IMG_6216 或 IMG_6229(不是6514的狗狗图!)
|
||
const catWornImage = images.find(i => i.includes('6216')) ||
|
||
images.find(i => i.includes('6229')) ||
|
||
images.find(i => i.toLowerCase().includes('cat'));
|
||
|
||
// 狗狗佩戴图(备用)
|
||
const dogWornImage = images.find(i => i.includes('6514'));
|
||
|
||
if (!flatImage) {
|
||
console.error('❌ 找不到平铺图素材');
|
||
return;
|
||
}
|
||
if (!catWornImage) {
|
||
console.error('❌ 找不到猫咪佩戴图素材');
|
||
console.log(' 可用文件:', images);
|
||
return;
|
||
}
|
||
|
||
const materials = {
|
||
flatImage: path.join(materialDir, flatImage),
|
||
catWornImage: path.join(materialDir, catWornImage),
|
||
dogWornImage: dogWornImage ? path.join(materialDir, dogWornImage) : null
|
||
};
|
||
|
||
console.log('\n✅ 素材选择:');
|
||
console.log(' 平铺图:', flatImage);
|
||
console.log(' 猫咪佩戴图:', catWornImage, '← 使用这个');
|
||
if (dogWornImage) console.log(' 狗狗佩戴图:', dogWornImage, '(备用)');
|
||
|
||
const config = { brandName: 'TOUCHDOG', productName: 'CAT SOFT CONE COLLAR' };
|
||
const results = [];
|
||
|
||
// ========== 生成图片 ==========
|
||
console.log('\n' + '─'.repeat(70));
|
||
console.log('🎨 开始生成图片');
|
||
console.log('─'.repeat(70));
|
||
|
||
const generators = {
|
||
'Main_01': () => generateMain01(materials, config, outputDir),
|
||
'Main_02': () => generateMain02(materials, config, outputDir),
|
||
'Main_03': () => generateMain03(materials, config, outputDir),
|
||
'Main_04': () => generateMain04(materials, config, outputDir),
|
||
'Main_05': () => generateMain05(materials, config, outputDir),
|
||
'Main_06': () => generateMain06(materials, config, outputDir),
|
||
'APlus_01': () => generateAPlus01(materials, config, outputDir),
|
||
'APlus_02': () => generateAPlus02(materials, config, outputDir),
|
||
'APlus_03': () => generateAPlus03(materials, config, outputDir),
|
||
'APlus_04': () => generateAPlus04(materials, config, outputDir),
|
||
'APlus_05': () => generateAPlus05(materials, config, outputDir),
|
||
'APlus_06': () => generateAPlus06(materials, config, outputDir),
|
||
};
|
||
|
||
for (const [id, generator] of Object.entries(generators)) {
|
||
if (onlyGenerate && !onlyGenerate.includes(id)) {
|
||
console.log(`\n⏭️ [${id}] 跳过`);
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
results.push({ id, success: await generator() });
|
||
} catch (e) {
|
||
console.error(`\n❌ ${id} 失败:`, e.message);
|
||
results.push({ id, success: false, error: e.message });
|
||
}
|
||
}
|
||
|
||
// ========== 总结 ==========
|
||
console.log('\n' + '═'.repeat(70));
|
||
console.log('📊 生成完成');
|
||
console.log('═'.repeat(70));
|
||
|
||
const successCount = results.filter(r => r.success).length;
|
||
console.log(`\n✅ 成功: ${successCount}/${results.length}`);
|
||
|
||
if (successCount < results.length) {
|
||
console.log('\n❌ 失败:');
|
||
results.filter(r => !r.success).forEach(r => console.log(` - ${r.id}: ${r.error}`));
|
||
}
|
||
|
||
console.log(`\n📁 输出目录: ${outputDir}`);
|
||
console.log(' 每张图都保存了对应的 _prompt.txt 供调试');
|
||
|
||
// 保存所有prompt到一个文件
|
||
const allPrompts = {};
|
||
for (const id of Object.keys(generators)) {
|
||
const promptFile = path.join(outputDir, `${id}_prompt.txt`);
|
||
if (fs.existsSync(promptFile)) {
|
||
allPrompts[id] = fs.readFileSync(promptFile, 'utf-8');
|
||
}
|
||
}
|
||
fs.writeFileSync(path.join(outputDir, 'all-prompts.json'), JSON.stringify(allPrompts, null, 2));
|
||
fs.writeFileSync(path.join(outputDir, 'results.json'), JSON.stringify(results, null, 2));
|
||
}
|
||
|
||
main().catch(console.error);
|
||
|