Upload latest code and optimized prompts (v6)

This commit is contained in:
tony
2025-12-14 19:52:54 +08:00
commit f5cb1042ae
42 changed files with 15302 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Dependencies
node_modules/
# Environment variables
.env
# Generated files
output/
output_*/
output_v*/
*.zip
*.jpg
*.png
*.JPG
*.PNG
# System files
.DS_Store
# Large assets
素材/
pic/
data/

34
README.md Normal file
View File

@@ -0,0 +1,34 @@
# AI Image Generator Flow
## Setup
1. **Install Dependencies**:
```bash
npm install
```
2. **Environment Variables**:
A `.env` file has been created. Ensure the following keys are correct:
* `R2_ACCOUNT_ID`: Your Cloudflare Account ID.
* `R2_ACCESS_KEY_ID`: Your R2 Access Key ID.
* `R2_SECRET_ACCESS_KEY`: Your R2 Secret Access Key.
* `R2_BUCKET_NAME`: The name of your R2 bucket (Default: `ai-flow`).
* `API_KEY`: Your Wuyin Keji API Key.
3. **Run Server**:
```bash
node server.js
```
Access at `http://localhost:3000`.
## Features
1. **Upload**: Uploads reference images to Cloudflare R2.
2. **Prompt Generation**: Uses LLM to generate prompts based on `prompt.md` template.
3. **Image Generation**: Uses NanoBanana to generate images from prompts.
4. **History**: Saves generated images and prompts locally.
5. **Batch Download**: Download all generated images.
## Notes
* Ensure your R2 bucket allows public access or configure a custom domain in `.env` as `R2_PUBLIC_DOMAIN`.
* The application saves state to browser `localStorage` to prevent data loss on refresh.

20
check_buckets.js Normal file
View File

@@ -0,0 +1,20 @@
require('dotenv').config();
const { S3Client, ListBucketsCommand } = require('@aws-sdk/client-s3');
const client = 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,
},
});
(async () => {
try {
const data = await client.send(new ListBucketsCommand({}));
console.log('Buckets:', data.Buckets);
} catch (err) {
console.error('Error:', err);
}
})();

71
debug-api.js Normal file
View File

@@ -0,0 +1,71 @@
/**
* API Debug Script
*/
require('dotenv').config();
const axios = require('axios');
const API_KEY = 'G9rXx3Ag2Xfa7Gs8zou6t6HqeZ';
const API_BASE = 'https://api.wuyinkeji.com/api';
async function debug() {
console.log('🔍 Starting API Debug...');
// 1. Debug Vision
console.log('\n--- 1. Testing Vision API ---');
try {
const res = await axios.post(`${API_BASE}/chat/index`, {
key: API_KEY,
model: 'gemini-1.5-flash',
content: 'Describe this image briefly.',
image_url: 'https://pub-777656770f4a44079545474665f00072.r2.dev/poc-1765607704915-IMG_6514.JPG' // 使用刚才日志里上传成功的图
});
console.log('Vision Response:', JSON.stringify(res.data, null, 2));
} catch (e) {
console.error('Vision Error:', e.message, e.response?.data);
}
// 2. Debug Image Submit
console.log('\n--- 2. Testing Image Submit ---');
let taskId;
try {
const res = await axios.post(`${API_BASE}/img/nanoBanana-pro`, {
key: API_KEY,
prompt: 'A cute cat',
aspectRatio: '1:1'
});
console.log('Submit Response:', JSON.stringify(res.data, null, 2));
// 尝试获取ID
if (res.data.data && typeof res.data.data === 'string') {
taskId = res.data.data;
} else if (res.data.data?.id) {
taskId = res.data.data.id;
} else if (res.data.id) {
taskId = res.data.id;
}
console.log('Extracted Task ID:', taskId);
} catch (e) {
console.error('Submit Error:', e.message, e.response?.data);
}
// 3. Debug Poll
if (taskId) {
console.log('\n--- 3. Testing Poll ---');
try {
// 等几秒让它处理
await new Promise(r => setTimeout(r, 3000));
const res = await axios.get(`${API_BASE}/img/drawDetail`, {
params: { key: API_KEY, id: taskId }
});
console.log('Poll Response:', JSON.stringify(res.data, null, 2));
} catch (e) {
console.error('Poll Error:', e.message, e.response?.data);
}
}
}
debug();

2
design.md Normal file
View File

@@ -0,0 +1,2 @@
## SKU颜色纯蓝色
### 需要设计4张主图尺寸1600*1600横向和4张A+图尺寸970*600横向

23
golden-description.txt Normal file
View File

@@ -0,0 +1,23 @@
EXACT PRODUCT APPEARANCE (AUTO-EXTRACTED BY VISION):
- Shape: 7-PETAL FLOWER/FAN shape, C-shaped opening
- Color: PASTEL ICE BLUE (#C3E6E8)
- Material: soft matte/satin synthetic fabric (likely water-resistant polyester/nylon) fabric with smooth, padded/quilted panels texture
- Edge binding: Mint Green (slightly more saturated than body) ribbed knit elastic fabric around inner neck hole
- Closure: white velcro (hook and loop) on large rectangular strip covering the entire bottom-left terminal segment
- Logo: "TOUCHDOG®" embroidered on centered on the middle-right segment
UNIQUE FEATURES:
- Scalloped outer edge resembling flower petals
- Soft ribbed knit neckline for comfort
- Radial stitching lines creating distinct padded zones
- Large surface area velcro for adjustable sizing
SUMMARY FOR PROMPTS:
A soft, padded pet recovery collar in pastel ice blue with a scalloped, flower-like shape. It features a comfortable mint-green ribbed knit neck opening, sectioned padding for flexibility, and a prominent white velcro closure strip.
CRITICAL PROHIBITIONS:
- ❌ NO printed patterns or colorful fabric designs
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes (must have C-opening)
- ❌ NO random brand logos or text

332
lib/batch-processor.js Normal file
View File

@@ -0,0 +1,332 @@
/**
* 批量处理器
* 支持5000+ SKU的批量图片生成
*/
const axios = require('axios');
const fs = require('fs');
const path = require('path');
/**
* 批量处理配置
*/
const DEFAULT_CONFIG = {
concurrency: 1, // 并发数建议保持1避免API限流
retryCount: 3, // 失败重试次数
retryDelay: 5000, // 重试延迟(毫秒)
imageDelay: 1500, // 图片间延迟(毫秒)
skuDelay: 3000, // SKU间延迟毫秒
saveProgress: true, // 是否保存进度
progressFile: 'batch-progress.json'
};
/**
* 批量处理器类
*/
class BatchProcessor {
constructor(serverUrl, config = {}) {
this.serverUrl = serverUrl || 'http://localhost:3000';
this.config = { ...DEFAULT_CONFIG, ...config };
this.progress = {
total: 0,
completed: 0,
failed: 0,
results: []
};
}
/**
* 处理单个SKU
* @param {object} sku - SKU数据
* @returns {Promise<object>} 处理结果
*/
async processSKU(sku) {
const startTime = Date.now();
try {
console.log(`Processing SKU: ${sku.sku_id}`);
const response = await axios.post(`${this.serverUrl}/api/generate-sku`, {
sku
}, {
timeout: 600000 // 10分钟超时12张图
});
const duration = (Date.now() - startTime) / 1000;
console.log(`SKU ${sku.sku_id} completed in ${duration.toFixed(1)}s`);
return {
sku_id: sku.sku_id,
status: 'success',
duration,
...response.data
};
} catch (error) {
const duration = (Date.now() - startTime) / 1000;
console.error(`SKU ${sku.sku_id} failed after ${duration.toFixed(1)}s:`, error.message);
return {
sku_id: sku.sku_id,
status: 'failed',
duration,
error: error.message
};
}
}
/**
* 带重试的处理单个SKU
* @param {object} sku - SKU数据
* @returns {Promise<object>} 处理结果
*/
async processSKUWithRetry(sku) {
let lastError;
for (let attempt = 1; attempt <= this.config.retryCount; attempt++) {
try {
const result = await this.processSKU(sku);
if (result.status === 'success') {
return result;
}
lastError = result.error;
if (attempt < this.config.retryCount) {
console.log(`Retrying SKU ${sku.sku_id} (attempt ${attempt + 1}/${this.config.retryCount})...`);
await this.delay(this.config.retryDelay);
}
} catch (error) {
lastError = error.message;
if (attempt < this.config.retryCount) {
console.log(`Retrying SKU ${sku.sku_id} (attempt ${attempt + 1}/${this.config.retryCount})...`);
await this.delay(this.config.retryDelay);
}
}
}
return {
sku_id: sku.sku_id,
status: 'failed',
error: lastError,
attempts: this.config.retryCount
};
}
/**
* 批量处理SKU列表
* @param {array} skus - SKU数据数组
* @param {function} onProgress - 进度回调
* @returns {Promise<object>} 批量处理结果
*/
async processBatch(skus, onProgress = null) {
this.progress = {
total: skus.length,
completed: 0,
failed: 0,
startTime: Date.now(),
results: []
};
console.log(`\n${'='.repeat(60)}`);
console.log(`Starting batch processing: ${skus.length} SKUs`);
console.log(`${'='.repeat(60)}\n`);
// 加载之前的进度(如果有)
const existingProgress = this.loadProgress();
const processedIds = new Set(existingProgress.map(r => r.sku_id));
// 过滤掉已处理的SKU
const pendingSkus = skus.filter(sku => !processedIds.has(sku.sku_id));
this.progress.results = existingProgress;
this.progress.completed = existingProgress.filter(r => r.status === 'success').length;
this.progress.failed = existingProgress.filter(r => r.status === 'failed').length;
if (existingProgress.length > 0) {
console.log(`Resuming from previous progress: ${existingProgress.length} already processed`);
}
// 串行处理避免API限流
for (let i = 0; i < pendingSkus.length; i++) {
const sku = pendingSkus[i];
const overallIndex = existingProgress.length + i + 1;
console.log(`\n[${overallIndex}/${skus.length}] Processing ${sku.sku_id}...`);
const result = await this.processSKUWithRetry(sku);
this.progress.results.push(result);
if (result.status === 'success') {
this.progress.completed++;
} else {
this.progress.failed++;
}
// 保存进度
if (this.config.saveProgress) {
this.saveProgress();
}
// 进度回调
if (onProgress) {
onProgress({
current: overallIndex,
total: skus.length,
completed: this.progress.completed,
failed: this.progress.failed,
lastResult: result
});
}
// SKU间延迟
if (i < pendingSkus.length - 1) {
await this.delay(this.config.skuDelay);
}
}
this.progress.endTime = Date.now();
this.progress.totalDuration = (this.progress.endTime - this.progress.startTime) / 1000;
console.log(`\n${'='.repeat(60)}`);
console.log(`Batch processing complete!`);
console.log(`Total: ${skus.length}, Success: ${this.progress.completed}, Failed: ${this.progress.failed}`);
console.log(`Duration: ${this.progress.totalDuration.toFixed(1)}s`);
console.log(`${'='.repeat(60)}\n`);
return {
total: skus.length,
completed: this.progress.completed,
failed: this.progress.failed,
duration: this.progress.totalDuration,
results: this.progress.results
};
}
/**
* 延迟函数
* @param {number} ms - 毫秒数
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 保存进度到文件
*/
saveProgress() {
try {
const progressPath = path.join(process.cwd(), this.config.progressFile);
fs.writeFileSync(progressPath, JSON.stringify(this.progress.results, null, 2));
} catch (error) {
console.error('Failed to save progress:', error.message);
}
}
/**
* 加载之前的进度
* @returns {array} 之前的处理结果
*/
loadProgress() {
try {
const progressPath = path.join(process.cwd(), this.config.progressFile);
if (fs.existsSync(progressPath)) {
const content = fs.readFileSync(progressPath, 'utf-8');
return JSON.parse(content);
}
} catch (error) {
console.error('Failed to load progress:', error.message);
}
return [];
}
/**
* 清除进度文件
*/
clearProgress() {
try {
const progressPath = path.join(process.cwd(), this.config.progressFile);
if (fs.existsSync(progressPath)) {
fs.unlinkSync(progressPath);
console.log('Progress cleared');
}
} catch (error) {
console.error('Failed to clear progress:', error.message);
}
}
/**
* 生成批处理报告
* @returns {object} 报告数据
*/
generateReport() {
const successResults = this.progress.results.filter(r => r.status === 'success');
const failedResults = this.progress.results.filter(r => r.status === 'failed');
const totalImages = successResults.reduce((sum, r) => {
return sum + (r.summary?.success || 0);
}, 0);
return {
summary: {
total_skus: this.progress.total,
successful_skus: this.progress.completed,
failed_skus: this.progress.failed,
success_rate: ((this.progress.completed / this.progress.total) * 100).toFixed(1) + '%',
total_images_generated: totalImages,
total_duration: this.progress.totalDuration?.toFixed(1) + 's',
average_time_per_sku: (this.progress.totalDuration / this.progress.total).toFixed(1) + 's'
},
failed_skus: failedResults.map(r => ({
sku_id: r.sku_id,
error: r.error
})),
successful_skus: successResults.map(r => ({
sku_id: r.sku_id,
images: r.summary
}))
};
}
}
/**
* 从文件加载SKU列表
* @param {string} filePath - 文件路径JSON或CSV
* @returns {array} SKU数组
*/
function loadSKUsFromFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const ext = path.extname(filePath).toLowerCase();
if (ext === '.json') {
const data = JSON.parse(content);
return Array.isArray(data) ? data : [data];
}
// 简单CSV解析假设第一行是表头
if (ext === '.csv') {
const lines = content.split('\n').filter(line => line.trim());
const headers = lines[0].split(',').map(h => h.trim());
return lines.slice(1).map(line => {
const values = line.split(',').map(v => v.trim());
const obj = {};
headers.forEach((h, i) => {
obj[h] = values[i];
});
return obj;
});
}
throw new Error('Unsupported file format. Use .json or .csv');
}
module.exports = {
BatchProcessor,
loadSKUsFromFile,
DEFAULT_CONFIG
};

332
lib/brain.js Normal file
View File

@@ -0,0 +1,332 @@
/**
* Brain决策逻辑
* 调用LLM分析产品信息并生成12张图的规划
*/
const fs = require('fs');
const path = require('path');
const axios = require('axios');
// 读取Brain System Prompt
const brainPromptPath = path.join(__dirname, '../prompts/brain-system.md');
let brainSystemPrompt = '';
try {
brainSystemPrompt = fs.readFileSync(brainPromptPath, 'utf-8');
} catch (err) {
console.error('Failed to load brain system prompt:', err.message);
}
/**
* 调用LLM生成图片规划
* @param {object} sku - SKU数据
* @param {object} options - 选项
* @param {string} options.apiKey - API密钥
* @param {string} options.apiUrl - API地址
* @param {string} options.model - 模型名称
* @returns {Promise<object>} Brain输出的图片规划
*/
async function generateImagePlan(sku, options = {}) {
const {
apiKey = process.env.API_KEY,
apiUrl = 'https://api2img.shubiaobiao.com/v1/chat/completions',
model = 'gemini-3-pro-preview'
} = options;
if (!apiKey) {
throw new Error('API_KEY is required');
}
// 构建用户消息
const userMessage = `请根据以下产品信息规划12张电商图片6张主图 + 6张A+图)的内容策略。
## 产品信息
\`\`\`json
${JSON.stringify(sku, null, 2)}
\`\`\`
请严格按照System Prompt中的输出格式返回JSON。`;
try {
const response = await axios.post(apiUrl, {
model: model,
messages: [
{ role: 'system', content: brainSystemPrompt },
{ role: 'user', content: userMessage }
],
temperature: 0.7,
max_tokens: 8000
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
timeout: 120000 // 2分钟超时
});
// 解析响应
let content = response.data.choices?.[0]?.message?.content;
if (!content) {
throw new Error('Empty response from LLM');
}
// 清理响应内容
content = cleanLLMResponse(content);
// 解析JSON
const plan = parseJSONFromResponse(content);
// 验证输出格式
validatePlan(plan);
return plan;
} catch (error) {
console.error('Brain generation error:', error.message);
throw error;
}
}
/**
* 清理LLM响应
* @param {string} content - 原始响应内容
* @returns {string} 清理后的内容
*/
function cleanLLMResponse(content) {
// 移除 <think> 块
content = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
// 移除可能的markdown标记
content = content.replace(/^```json\s*/i, '');
content = content.replace(/\s*```$/i, '');
return content.trim();
}
/**
* 从响应中解析JSON
* @param {string} content - 响应内容
* @returns {object} 解析后的JSON对象
*/
function parseJSONFromResponse(content) {
// 尝试直接解析
try {
return JSON.parse(content);
} catch (e) {
// 尝试提取JSON块
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
return JSON.parse(jsonMatch[0]);
} catch (e2) {
// 尝试修复常见问题
let fixed = jsonMatch[0];
// 修复尾部逗号
fixed = fixed.replace(/,\s*([}\]])/g, '$1');
// 修复未闭合的字符串
fixed = fixed.replace(/:\s*"([^"]*?)(?=\s*[,}\]])/g, ': "$1"');
try {
return JSON.parse(fixed);
} catch (e3) {
throw new Error('Failed to parse JSON from LLM response: ' + e.message);
}
}
}
throw new Error('No valid JSON found in LLM response');
}
}
/**
* 验证Brain输出的计划
* @param {object} plan - 图片规划
*/
function validatePlan(plan) {
if (!plan.analysis) {
throw new Error('Missing analysis in plan');
}
if (!plan.images || !Array.isArray(plan.images)) {
throw new Error('Missing or invalid images array in plan');
}
if (plan.images.length !== 12) {
console.warn(`Expected 12 images, got ${plan.images.length}`);
}
// 检查必要字段
const requiredFields = ['id', 'type', 'ai_prompt'];
for (const image of plan.images) {
for (const field of requiredFields) {
if (!image[field]) {
throw new Error(`Missing ${field} in image ${image.id || 'unknown'}`);
}
}
}
// 检查图片ID是否正确
const expectedIds = [
'Main_01', 'Main_02', 'Main_03', 'Main_04', 'Main_05', 'Main_06',
'APlus_01', 'APlus_02', 'APlus_03', 'APlus_04', 'APlus_05', 'APlus_06'
];
const actualIds = plan.images.map(img => img.id);
const missingIds = expectedIds.filter(id => !actualIds.includes(id));
if (missingIds.length > 0) {
console.warn(`Missing image IDs: ${missingIds.join(', ')}`);
}
}
/**
* 生成默认的图片规划当Brain调用失败时使用
* @param {object} sku - SKU数据
* @returns {object} 默认的图片规划
*/
function generateDefaultPlan(sku) {
const topSellingPoints = sku.selling_points.slice(0, 3).map(sp => sp.key);
const productColor = sku.color.name;
const productName = sku.product_name;
return {
analysis: {
product_category: 'Pet Recovery Cone',
core_selling_points: topSellingPoints,
background_strategy: 'light',
logo_color: 'red',
visual_style: 'Warm, caring home environment'
},
images: [
{
id: 'Main_01',
type: 'Hero Scene + Key Benefits',
purpose: '首图决定点击率,展示产品使用状态+核心卖点',
layout_description: `上60%:猫咪佩戴${productColor}${productName}的居家场景。下40%浅色Banner展示3个核心卖点。`,
ai_prompt: `Professional Amazon main image, 1:1 square format. A beautiful cat wearing a ${productColor} soft cone collar in a cozy modern home interior. Natural soft lighting, warm atmosphere. Bottom section shows a light blue rounded banner with "DESIGNED FOR COMFORTABLE RECOVERY" text and three small product detail images highlighting key features. Clean, professional pet product photography style. --ar 1:1`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'Main_02',
type: 'Product Detail + Craftsmanship',
purpose: '展示产品结构和工艺细节',
layout_description: `产品平铺俯视图配合2-3个细节放大框展示材质和工艺`,
ai_prompt: `Professional product flat lay image, 1:1 square format. ${productColor} soft cone collar displayed from above on white background. Two circular detail callouts showing: 1) waterproof PU outer layer texture, 2) soft cotton inner lining. Clean product photography, even lighting, high detail. Brand logo "TOUCHDOG®" visible on right petal. --ar 1:1`,
logo_placement: { position: 'none', type: 'none', color: 'red' }
},
{
id: 'Main_03',
type: 'Function Demonstration',
purpose: '展示可调节绑带等功能特性',
layout_description: `主场景展示佩戴状态,配合细节图展示调节功能`,
ai_prompt: `Professional Amazon feature demonstration image, 1:1 square format. Main image shows cat wearing ${productColor} cone, looking comfortable. Detail callouts show: adjustable velcro strap being secured, snug fit around neck. Text overlay "ADJUSTABLE STRAP FOR A SECURE FIT". Light blue background with paw print decorations. --ar 1:1`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'Main_04',
type: 'Use Cases Grid',
purpose: '展示4种适用场景',
layout_description: `四宫格布局,展示术后护理、滴药、剪指甲、梳毛四个场景`,
ai_prompt: `Professional Amazon use case image, 1:1 square format. Four-panel grid showing: 1) cat resting after surgery wearing cone, 2) owner applying eye drops to cat, 3) owner trimming cat's nails, 4) cat being groomed. Each panel labeled: "POSTOPERATIVE CARE", "EYE MEDICATION", "NAIL TRIMMING", "GROOMING". Light blue header banner "APPLICABLE SCENARIOS". --ar 1:1`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'Main_05',
type: 'Size & Specifications',
purpose: '展示尺寸规格信息',
layout_description: `产品尺寸标注图配合尺码对照表`,
ai_prompt: `Professional Amazon size guide image, 1:1 square format. ${productColor} cone collar with dimension annotations showing depth measurement (${sku.specs.depth_cm}cm). Clean size chart showing XS to XL with neck circumference ranges. Professional infographic style, clear readable text. Light background. --ar 1:1`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'Main_06',
type: 'Brand & Benefits Summary',
purpose: '强化品牌认知和卖点汇总',
layout_description: `品牌Logo突出展示配合核心卖点图标`,
ai_prompt: `Professional Amazon brand summary image, 1:1 square format. Touchdog brand logo prominently displayed. Key benefit icons with text: "65g Ultra Light", "Waterproof PU", "Breathable Cotton", "Adjustable Fit", "Foldable Design". Cat wearing ${productColor} cone as background element. Premium brand aesthetic. --ar 1:1`,
logo_placement: { position: 'center', type: 'combined', color: 'red' }
},
{
id: 'APlus_01',
type: 'Brand Banner',
purpose: '品牌形象首图',
layout_description: `品牌名+产品名+生活场景大图`,
ai_prompt: `Professional Amazon A+ banner image, 970x600px landscape. Beautiful cat wearing ${productColor} Touchdog soft cone collar in warm modern home interior. Large "TOUCHDOG" brand text and "CAT SOFT CONE COLLAR" product name overlaid. Lifestyle photography, natural lighting, cozy atmosphere. --ar 3:2`,
logo_placement: { position: 'top-left', type: 'combined', color: 'red' }
},
{
id: 'APlus_02',
type: 'Competitor Comparison',
purpose: '竞品对比展示优势',
layout_description: `左侧我们的产品(彩色+优点),右侧传统产品(灰色+缺点)`,
ai_prompt: `Professional Amazon A+ comparison image, 970x600px landscape. Left side (colored): Touchdog ${productColor} soft cone with green checkmark, labeled "OUR" with benefits "CLOUD-LIGHT COMFORT", "WIDER & CLEARER", "FOLDABLE & PORTABLE". Right side (grayscale): Generic plastic cone with red X, labeled "OTHER" with drawbacks "HEAVY & BULKY", "BLOCKS VISION & MOVEMENT", "HARD TO STORE". Center shows cat wearing our product. Warm beige background with paw prints. --ar 3:2`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' },
is_comparison: true
},
{
id: 'APlus_03',
type: 'Benefits Grid',
purpose: '核心卖点展示',
layout_description: `三宫格展示3个核心卖点`,
ai_prompt: `Professional Amazon A+ benefits image, 970x600px landscape. Three-panel layout showing key features: 1) "STURDY AND BREATHABLE" with fabric texture close-up, 2) "EASY TO CLEAN" with dimension annotation ${sku.specs.depth_cm}cm, 3) "REINFORCED STITCHING" with stitching detail. Header "ENGINEERED FOR UNCOMPROMISED COMFORT". Warm beige background. --ar 3:2`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'APlus_04',
type: 'Features Grid',
purpose: '功能场景展示',
layout_description: `四宫格展示功能易清洁、不影响进食、可翻折、360°舒适`,
ai_prompt: `Professional Amazon A+ features image, 970x600px landscape. Four vertical panels: 1) "HYGIENIC & EASY TO CLEAN" showing water-resistant surface, 2) "UNRESTRICTED EATING/DRINKING" showing cat eating while wearing cone, 3) "REVERSIBLE WEAR" showing flip-over design, 4) "360° COMFORT" showing cat sleeping peacefully. Warm beige background, consistent styling. --ar 3:2`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'APlus_05',
type: 'Material & Craftsmanship',
purpose: '材质工艺展示',
layout_description: `材质剖面和工艺细节展示`,
ai_prompt: `Professional Amazon A+ material image, 970x600px landscape. Close-up details of ${productColor} cone showing: waterproof PU outer layer with water droplets, soft cotton inner lining texture, reinforced stitching seams, embroidered TOUCHDOG logo. Technical infographic style with material callouts. Light background. --ar 3:2`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
},
{
id: 'APlus_06',
type: 'Size Guide',
purpose: '尺寸选择指南',
layout_description: `完整尺码表+测量指南`,
ai_prompt: `Professional Amazon A+ size guide image, 970x600px landscape. Comprehensive size chart showing all sizes (XS-XL) with neck circumference and depth measurements in both cm and inches. Illustration showing how to measure pet's neck. Clear, readable table format. Helpful sizing tips. Professional infographic design. --ar 3:2`,
logo_placement: { position: 'bottom-right', type: 'combined', color: 'red' }
}
]
};
}
/**
* 主入口:生成图片规划
* @param {object} sku - SKU数据
* @param {object} options - 选项
* @returns {Promise<object>} 图片规划
*/
async function planImages(sku, options = {}) {
try {
// 尝试调用LLM生成计划
const plan = await generateImagePlan(sku, options);
console.log('Brain generated plan successfully');
return plan;
} catch (error) {
console.error('Brain failed, using default plan:', error.message);
// 失败时使用默认计划
return generateDefaultPlan(sku);
}
}
module.exports = {
planImages,
generateImagePlan,
generateDefaultPlan,
validatePlan
};

369
lib/constraint-injector.js Normal file
View File

@@ -0,0 +1,369 @@
/**
* 约束注入器
* 自动将各类约束追加到AI生图Prompt中
*/
const fs = require('fs');
const path = require('path');
// 读取约束模板
const constraintsDir = path.join(__dirname, '../prompts/constraints');
/**
* 简单的模板引擎,替换 {{variable}} 占位符
* @param {string} template - 模板字符串
* @param {object} data - 数据对象
* @returns {string} 替换后的字符串
*/
function renderTemplate(template, data) {
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const keys = key.trim().split('.');
let value = data;
for (const k of keys) {
if (value && typeof value === 'object') {
// 处理数组索引 selling_points[0]
const arrayMatch = k.match(/^(\w+)\[(\d+)\]$/);
if (arrayMatch) {
value = value[arrayMatch[1]]?.[parseInt(arrayMatch[2])];
} else {
value = value[k];
}
} else {
return match; // 保留原占位符
}
}
if (Array.isArray(value)) {
return value.join(', ');
}
return value !== undefined ? String(value) : match;
});
}
/**
* 处理Handlebars风格的循环 {{#each array}}...{{/each}}
* @param {string} template - 模板字符串
* @param {object} data - 数据对象
* @returns {string} 处理后的字符串
*/
function processEachBlocks(template, data) {
const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g;
return template.replace(eachRegex, (match, arrayName, content) => {
const array = data[arrayName];
if (!Array.isArray(array)) {
return '';
}
return array.map(item => {
// 处理 {{#if field}} 条件
let processed = content.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (m, field, ifContent) => {
return item[field] ? ifContent : '';
});
// 替换当前项的字段
processed = processed.replace(/\{\{(\w+)\}\}/g, (m, field) => {
return item[field] !== undefined ? String(item[field]) : m;
});
return processed;
}).join('\n');
});
}
/**
* 生成产品一致性约束
* @param {object} sku - SKU数据
* @returns {string} 约束文本
*/
function generateProductConsistencyConstraint(sku) {
const template = `
=== PRODUCT CONSISTENCY REQUIREMENTS (CRITICAL - DO NOT IGNORE) ===
The product shown in this image must EXACTLY match the reference images provided.
PRODUCT SPECIFICATIONS:
- Product Type: ${sku.product_name}
- Color: ${sku.color.hex} (${sku.color.name})
- Color Series: ${sku.color.series || 'Standard'}
- Edge/Binding Color: ${sku.color.edge_color || sku.color.hex}
STRUCTURAL REQUIREMENTS:
- Shape: Soft cone collar with petal-like segments (NOT rigid plastic cone)
- Segments: Multiple soft fabric petals radiating from center opening
- Opening: Circular neck opening with ribbed cotton binding
- Closure: Velcro strap closure system
BRAND LOGO ON PRODUCT:
- Text: "TOUCHDOG®" (UPPERCASE with ® symbol)
- Position: On one petal segment, typically right side (2-3 o'clock position when viewed from front)
- Style: Embroidered/stitched into fabric, slightly recessed
- Color: Darker or contrasting shade matching the product color
TEXTURE & MATERIAL APPEARANCE:
- Outer Layer: Smooth, slightly glossy waterproof PU fabric
- Inner Layer: Visible soft cotton lining at edges
- Stitching: Reinforced seams, visible quilted pattern on petals
- Edge Binding: Ribbed cotton binding in ${sku.color.edge_color || 'matching color'}
CRITICAL PROHIBITIONS:
- DO NOT alter the product shape or structure
- DO NOT change the color from specified hex value
- DO NOT move or resize the embroidered logo
- DO NOT add any elements not present in reference images
- DO NOT stylize, reimagine, or "improve" the product design
- DO NOT generate rigid plastic cone - must be SOFT fabric cone
=== END PRODUCT CONSISTENCY REQUIREMENTS ===
`;
return template.trim();
}
/**
* 生成卖点合规约束
* @param {object} sku - SKU数据
* @returns {string} 约束文本
*/
function generateSellingPointConstraint(sku) {
const sellingPointsList = sku.selling_points.map(sp => {
let line = `- ${sp.title_en}`;
if (sp.value) line += ` (Value: ${sp.value})`;
if (sp.description_en) line += `: ${sp.description_en}`;
return line;
}).join('\n');
const visualPromptsList = sku.selling_points
.filter(sp => sp.visual_prompt)
.map(sp => `- ${sp.key}: ${sp.visual_prompt}`)
.join('\n');
const useCasesList = sku.use_cases
.map(uc => `- ${uc.name_en}`)
.join('\n');
const template = `
=== SELLING POINTS COMPLIANCE (LEGAL REQUIREMENT) ===
ALL product claims, features, and specifications shown in this image must come EXACTLY from the provided data.
APPROVED SELLING POINTS (use ONLY these):
${sellingPointsList}
APPROVED SPECIFICATIONS (use EXACT values):
- Weight: ${sku.specs.weight}
- Depth: ${sku.specs.depth_cm}cm
- Available Sizes: ${sku.specs.sizes.join(', ')}
- Outer Material: ${sku.specs.materials?.outer || 'Waterproof PU'}
- Inner Material: ${sku.specs.materials?.inner || 'Cotton blend'}
APPROVED USE CASES:
${useCasesList}
PROHIBITED ACTIONS:
- DO NOT invent new features or benefits not listed above
- DO NOT modify numerical values (no rounding, no approximation)
- DO NOT use superlatives: "best", "first", "only", "most", "#1", "leading"
- DO NOT make comparative claims without data: "better than", "superior to"
- DO NOT add health claims not approved by regulations
- DO NOT promise specific outcomes: "guaranteed", "100% effective"
VISUAL REPRESENTATION OF SELLING POINTS:
${visualPromptsList}
=== END SELLING POINTS COMPLIANCE ===
`;
return template.trim();
}
/**
* 生成竞品合规约束(仅用于竞品对比图)
* @param {object} sku - SKU数据
* @returns {string} 约束文本
*/
function generateCompetitorConstraint(sku) {
const productCategory = sku.product_name.includes('Cone') ? 'Plastic Cone' : 'Product';
const template = `
=== COMPETITOR COMPARISON COMPLIANCE (LEGAL RED LINE) ===
This is a product comparison image. You MUST follow these legal requirements:
OUR PRODUCT SIDE (Left/Colored):
- Show the Touchdog ${sku.product_name} in FULL COLOR
- Product color: ${sku.color.hex} (${sku.color.name})
- Use green checkmark icon (✓) to indicate positive
- Label as "OUR" or "TOUCHDOG"
- List ONLY the approved selling points from provided data
- Product must match reference images exactly
COMPETITOR SIDE (Right/Grayscale):
- Label as "OTHER" or "Traditional ${productCategory}" ONLY
- DO NOT use any competitor brand names
- DO NOT use any competitor logos or trademarks
- DO NOT show any identifiable competitor product designs
- Show a GENERIC, UNBRANDED version of traditional product
- Apply GRAYSCALE filter to entire competitor side
- Use red X icon to indicate negative
APPROVED COMPETITOR DISADVANTAGES (generic industry facts only):
- "Heavy & Bulky"
- "Blocks Vision & Movement"
- "Hard to Store"
- "Uncomfortable for Extended Wear"
- "Difficult to Clean"
- "Rigid and Inflexible"
VISUAL TREATMENT:
- Our side: Vibrant, colorful, positive imagery
- Competitor side: Muted, grayscale, neutral imagery
- Clear visual contrast between the two sides
- Center divider or clear separation between sides
STRICTLY PROHIBITED:
- ❌ Any specific competitor brand name
- ❌ Any competitor logo or trademark symbol
- ❌ Any identifiable competitor product photo
- ❌ Claims like "better than [Brand]"
- ❌ Using competitor's actual product images
=== END COMPETITOR COMPARISON COMPLIANCE ===
`;
return template.trim();
}
/**
* 生成品牌VI约束
* @param {object} sku - SKU数据
* @param {object} logoPlacement - Logo放置信息
* @returns {string} 约束文本
*/
function generateBrandVIConstraint(sku, logoPlacement = {}) {
const position = logoPlacement.position || 'bottom-right';
const logoType = logoPlacement.type || 'combined';
const logoColor = logoPlacement.color || 'red';
const template = `
=== BRAND VI REQUIREMENTS ===
EMBROIDERED LOGO ON PRODUCT:
- Text: "TOUCHDOG®" (UPPERCASE with ® symbol)
- Position: Right petal segment, 2-3 o'clock position
- Style: Embroidered, stitched into fabric
- Color: Slightly darker shade of product color (${sku.color.hex})
E-COMMERCE IMAGE BRAND LOGO:
- Position: ${position}
- Type: ${logoType === 'combined' ? 'Graphic icon + "touchdog®" text + "wow pretty"' : logoType}
- Color: ${logoColor === 'red' ? 'Red (#E60012)' : 'White'}
LOGO SPECIFICATIONS:
- Clear space: Minimum 1/4 of logo height on all sides
- Minimum size: Combined logo ≥ 46px height
- Tagline: "wow pretty" (for international markets)
PROHIBITED LOGO MODIFICATIONS:
- ❌ NO tilting or rotation
- ❌ NO outlines or strokes
- ❌ NO gradient fills
- ❌ NO drop shadows
- ❌ NO proportion changes
- ❌ NO underlines
BRAND TYPOGRAPHY:
- Headlines: Clean sans-serif, bold weight
- Body text: Clean sans-serif, medium weight
- Style: Clean, modern, professional
=== END BRAND VI REQUIREMENTS ===
`;
return template.trim();
}
/**
* 注入所有约束到Prompt
* @param {string} originalPrompt - 原始Prompt
* @param {object} sku - SKU数据
* @param {object} options - 选项
* @param {boolean} options.isComparison - 是否是竞品对比图
* @param {object} options.logoPlacement - Logo放置信息
* @returns {string} 注入约束后的完整Prompt
*/
function injectConstraints(originalPrompt, sku, options = {}) {
const { isComparison = false, logoPlacement = {} } = options;
const constraints = [];
// 始终添加产品一致性约束
constraints.push(generateProductConsistencyConstraint(sku));
// 始终添加卖点合规约束
constraints.push(generateSellingPointConstraint(sku));
// 如果是竞品对比图,添加竞品合规约束
if (isComparison) {
constraints.push(generateCompetitorConstraint(sku));
}
// 始终添加品牌VI约束
constraints.push(generateBrandVIConstraint(sku, logoPlacement));
// 添加参考图提示
if (sku.ref_images && sku.ref_images.length > 0) {
constraints.push(`
=== REFERENCE IMAGES ===
The following reference images must be used to ensure product consistency:
${sku.ref_images.map((url, i) => `- Reference ${i + 1}: ${url}`).join('\n')}
Product in generated image must match these reference images exactly.
=== END REFERENCE IMAGES ===
`);
}
// 组合最终Prompt
return `${originalPrompt}
${constraints.join('\n\n')}`;
}
/**
* 提取图片的宽高比参数
* @param {string} imageId - 图片ID (Main_01 或 APlus_01)
* @returns {string} 宽高比参数
*/
function getAspectRatio(imageId) {
if (imageId.startsWith('Main_')) {
return '1:1';
} else if (imageId.startsWith('APlus_')) {
return '3:2';
}
return '1:1';
}
/**
* 获取图片尺寸
* @param {string} imageId - 图片ID
* @returns {object} 尺寸信息
*/
function getImageSize(imageId) {
if (imageId.startsWith('Main_')) {
return { width: 1600, height: 1600, label: '1600x1600' };
} else if (imageId.startsWith('APlus_')) {
return { width: 970, height: 600, label: '970x600' };
}
return { width: 1600, height: 1600, label: '1600x1600' };
}
module.exports = {
injectConstraints,
generateProductConsistencyConstraint,
generateSellingPointConstraint,
generateCompetitorConstraint,
generateBrandVIConstraint,
getAspectRatio,
getImageSize,
renderTemplate,
processEachBlocks
};

338
lib/image-processor.js Normal file
View File

@@ -0,0 +1,338 @@
/**
* 图片处理工具模块
* 使用Sharp库实现抠图、合成、叠加文字/标注
*/
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
/**
* 调整图片大小,保持比例
*/
async function resizeImage(inputPath, width, height, fit = 'contain') {
const buffer = await sharp(inputPath)
.resize(width, height, { fit, background: { r: 255, g: 255, b: 255, alpha: 0 } })
.toBuffer();
return buffer;
}
/**
* 将图片转换为PNG保持透明度
*/
async function toPng(inputPath) {
return await sharp(inputPath).png().toBuffer();
}
/**
* 创建纯色背景
*/
async function createSolidBackground(width, height, color = '#FFFFFF') {
// 解析颜色
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
return await sharp({
create: {
width,
height,
channels: 3,
background: { r, g, b }
}
}).jpeg().toBuffer();
}
/**
* 创建渐变背景
*/
async function createGradientBackground(width, height, color1 = '#F5EDE4', color2 = '#FFFFFF') {
// 创建简单的渐变效果(从上到下)
const svg = `
<svg width="${width}" height="${height}">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:${color1};stop-opacity:1" />
<stop offset="100%" style="stop-color:${color2};stop-opacity:1" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#grad)"/>
</svg>
`;
return await sharp(Buffer.from(svg)).jpeg().toBuffer();
}
/**
* 合成多个图层
* @param {Buffer} baseImage - 底图
* @param {Array} layers - 图层数组 [{buffer, left, top, width?, height?}]
*/
async function compositeImages(baseImage, layers) {
let composite = sharp(baseImage);
const compositeInputs = [];
for (const layer of layers) {
let input = layer.buffer;
// 如果需要调整大小
if (layer.width || layer.height) {
input = await sharp(layer.buffer)
.resize(layer.width, layer.height, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
.toBuffer();
}
compositeInputs.push({
input,
left: layer.left || 0,
top: layer.top || 0
});
}
return await composite.composite(compositeInputs).toBuffer();
}
/**
* 在图片上叠加文字
*/
async function addTextOverlay(baseImage, texts) {
// texts: [{text, x, y, fontSize, color, fontWeight, align}]
const metadata = await sharp(baseImage).metadata();
const width = metadata.width;
const height = metadata.height;
// 创建SVG文字层
const textElements = texts.map(t => {
const fontSize = t.fontSize || 40;
const color = t.color || '#333333';
const fontWeight = t.fontWeight || 'bold';
const textAnchor = t.align === 'center' ? 'middle' : (t.align === 'right' ? 'end' : 'start');
return `<text x="${t.x}" y="${t.y}" font-size="${fontSize}" fill="${color}"
font-weight="${fontWeight}" font-family="Arial, sans-serif" text-anchor="${textAnchor}">${escapeXml(t.text)}</text>`;
}).join('\n');
const svg = `
<svg width="${width}" height="${height}">
${textElements}
</svg>
`;
return await sharp(baseImage)
.composite([{ input: Buffer.from(svg), top: 0, left: 0 }])
.toBuffer();
}
/**
* 创建圆形裁剪的图片(用于细节放大镜效果)
*/
async function createCircularCrop(inputBuffer, diameter) {
const circle = Buffer.from(`
<svg width="${diameter}" height="${diameter}">
<circle cx="${diameter/2}" cy="${diameter/2}" r="${diameter/2}" fill="white"/>
</svg>
`);
const resized = await sharp(inputBuffer)
.resize(diameter, diameter, { fit: 'cover' })
.toBuffer();
return await sharp(resized)
.composite([{ input: circle, blend: 'dest-in' }])
.png()
.toBuffer();
}
/**
* 添加圆形边框
*/
async function addCircleBorder(inputBuffer, diameter, borderWidth = 3, borderColor = '#2D4A3E') {
const r = parseInt(borderColor.slice(1, 3), 16);
const g = parseInt(borderColor.slice(3, 5), 16);
const b = parseInt(borderColor.slice(5, 7), 16);
const borderSvg = Buffer.from(`
<svg width="${diameter}" height="${diameter}">
<circle cx="${diameter/2}" cy="${diameter/2}" r="${diameter/2 - borderWidth/2}"
fill="none" stroke="rgb(${r},${g},${b})" stroke-width="${borderWidth}"/>
</svg>
`);
return await sharp(inputBuffer)
.composite([{ input: borderSvg, top: 0, left: 0 }])
.toBuffer();
}
/**
* 创建带箭头的标注线
*/
async function createArrowLine(width, height, fromX, fromY, toX, toY, color = '#2D4A3E') {
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
// 计算箭头角度
const angle = Math.atan2(toY - fromY, toX - fromX);
const arrowLength = 15;
const arrowAngle = Math.PI / 6;
const arrow1X = toX - arrowLength * Math.cos(angle - arrowAngle);
const arrow1Y = toY - arrowLength * Math.sin(angle - arrowAngle);
const arrow2X = toX - arrowLength * Math.cos(angle + arrowAngle);
const arrow2Y = toY - arrowLength * Math.sin(angle + arrowAngle);
const svg = `
<svg width="${width}" height="${height}">
<line x1="${fromX}" y1="${fromY}" x2="${toX}" y2="${toY}"
stroke="rgb(${r},${g},${b})" stroke-width="2"/>
<polygon points="${toX},${toY} ${arrow1X},${arrow1Y} ${arrow2X},${arrow2Y}"
fill="rgb(${r},${g},${b})"/>
</svg>
`;
return Buffer.from(svg);
}
/**
* 创建标题横幅
*/
async function createTitleBanner(width, height, text, bgColor = '#2D4A3E', textColor = '#FFFFFF') {
const fontSize = Math.floor(height * 0.5);
const svg = `
<svg width="${width}" height="${height}">
<rect width="100%" height="100%" fill="${bgColor}" rx="5" ry="5"/>
<text x="${width/2}" y="${height/2 + fontSize/3}" font-size="${fontSize}"
fill="${textColor}" font-weight="bold" font-family="Arial, sans-serif"
text-anchor="middle">"${escapeXml(text)}"</text>
</svg>
`;
return await sharp(Buffer.from(svg)).png().toBuffer();
}
/**
* 将图片转为灰度(用于竞品对比)
*/
async function toGrayscale(inputBuffer) {
return await sharp(inputBuffer).grayscale().toBuffer();
}
/**
* 调整图片亮度/对比度
*/
async function adjustBrightness(inputBuffer, brightness = 1.0) {
return await sharp(inputBuffer)
.modulate({ brightness })
.toBuffer();
}
/**
* 添加阴影效果
*/
async function addShadow(inputBuffer, offsetX = 5, offsetY = 5, blur = 10, opacity = 0.3) {
const metadata = await sharp(inputBuffer).metadata();
const width = metadata.width + offsetX + blur * 2;
const height = metadata.height + offsetY + blur * 2;
// 创建阴影层
const shadowBuffer = await sharp(inputBuffer)
.greyscale()
.modulate({ brightness: 0 })
.blur(blur)
.toBuffer();
// 创建透明背景
const background = await sharp({
create: {
width,
height,
channels: 4,
background: { r: 255, g: 255, b: 255, alpha: 0 }
}
}).png().toBuffer();
// 合成
return await sharp(background)
.composite([
{ input: shadowBuffer, left: offsetX + blur, top: offsetY + blur, blend: 'over' },
{ input: inputBuffer, left: blur, top: blur }
])
.toBuffer();
}
/**
* 裁剪图片的特定区域
*/
async function cropRegion(inputBuffer, left, top, width, height) {
return await sharp(inputBuffer)
.extract({ left, top, width, height })
.toBuffer();
}
/**
* 辅助函数转义XML特殊字符
*/
function escapeXml(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
/**
* 保存图片到文件
*/
async function saveImage(buffer, outputPath, format = 'jpeg', quality = 90) {
let pipeline = sharp(buffer);
if (format === 'jpeg' || format === 'jpg') {
pipeline = pipeline.jpeg({ quality });
} else if (format === 'png') {
pipeline = pipeline.png();
}
await pipeline.toFile(outputPath);
return outputPath;
}
/**
* 读取图片为Buffer
*/
async function readImage(imagePath) {
return fs.readFileSync(imagePath);
}
/**
* 获取图片尺寸
*/
async function getImageSize(inputPath) {
const metadata = await sharp(inputPath).metadata();
return { width: metadata.width, height: metadata.height };
}
module.exports = {
resizeImage,
toPng,
createSolidBackground,
createGradientBackground,
compositeImages,
addTextOverlay,
createCircularCrop,
addCircleBorder,
createArrowLine,
createTitleBanner,
toGrayscale,
adjustBrightness,
addShadow,
cropRegion,
saveImage,
readImage,
getImageSize
};

629
lib/template-config.js Normal file
View File

@@ -0,0 +1,629 @@
/**
* 可配置的套路模板系统
* 运营人员可以自定义每张图的布局和内容
*/
// ============================================================
// 默认套路配置(基于真实交付成品分析)
// ============================================================
const DEFAULT_MAIN_TEMPLATES = {
// Main_01: 场景首图 + 卖点文字
Main_01: {
type: 'lifestyle_with_features',
name: '场景首图+卖点',
aspectRatio: '1:1',
layout: {
scene: 'left-center', // 场景图位置
title: 'center-bottom', // 标题位置
features: 'bottom-row' // 卖点位置
},
config: {
title: 'DESIGNED FOR COMFORTABLE RECOVERY',
titleStyle: 'curved-banner', // curved-banner | simple | none
features: [
{ icon: 'egg', text: 'LIGHTER THAN AN EGG' },
{ icon: 'water', text: 'WATERPROOF & EASY WIPE' },
{ icon: 'cloud', text: 'BREATHABLE COTTON LINING' }
],
background: 'warm-home'
}
},
// Main_02: 白底平铺 + 局部放大(关键改进!)
Main_02: {
type: 'product_with_callouts',
name: '白底平铺+细节放大',
aspectRatio: '1:1',
layout: {
product: 'center',
callouts: 'corners' // 放大镜效果在角落
},
config: {
title: 'DURABLE WATERPROOF PU LAYER',
titlePosition: 'top',
callouts: [
{
position: 'bottom-left',
target: 'material-edge',
label: 'DURABLE WATERPROOF PU LAYER',
hasArrow: true
},
{
position: 'bottom-right',
target: 'neck-binding',
label: 'DOUBLE-LAYER COMFORT',
hasArrow: true
}
],
background: 'white'
}
},
// Main_03: 功能调节展示
Main_03: {
type: 'feature_showcase',
name: '功能调节展示',
aspectRatio: '1:1',
layout: {
mainScene: 'left',
featureCircles: 'right-column'
},
config: {
title: 'ADJUSTABLE STRAP FOR A SECURE FIT',
titlePosition: 'top-left',
mainScene: {
type: 'cat-wearing',
background: 'lifestyle'
},
featureCircles: [
{ label: 'SECURE THE ADJUSTABLE BELLY STRAP', showDetail: 'velcro-close-up' },
{ label: 'ADJUST FOR A SNUG FIT', showDetail: 'full-product-view' }
],
background: 'matching-color' // 使用产品主色作为背景
}
},
// Main_04: 多场景使用4宫格
Main_04: {
type: 'multi_scenario_grid',
name: '多场景使用',
aspectRatio: '1:1',
layout: {
grid: '2x2',
captionPosition: 'below-each'
},
config: {
scenarios: [
{ scene: 'standing', caption: '• HYGIENIC & EASY TO CLEAN', subtext: 'WATERPROOF OUTER LAYER' },
{ scene: 'eating', caption: '• UNRESTRICTED EATING/DRINKING', subtext: 'SPECIALLY DESIGNED OPENING' },
{ scene: 'playing', caption: '• REVERSIBLE WEAR', subtext: 'FLIP-OVER DESIGN' },
{ scene: 'sleeping', caption: '• 360° COMFORT', subtext: 'FREE MOVEMENT' }
],
background: 'warm-beige'
}
},
// Main_05: 尺寸图
Main_05: {
type: 'size_chart',
name: '尺寸图',
aspectRatio: '1:1',
layout: {
product: 'top-center',
table: 'bottom'
},
config: {
title: 'PRODUCT SIZE',
measurements: ['NECK', 'WIDTH'],
sizeChart: {
XS: { neck: '5.6-6.8IN', depth: '3.2IN' },
S: { neck: '7.2-8.4IN', depth: '4IN' },
M: { neck: '8.8-10.4IN', depth: '5IN' },
L: { neck: '10.8-12.4IN', depth: '6IN' },
XL: { neck: '12.8-14.4IN', depth: '7IN' }
},
footer: 'NOTE: ALWAYS MEASURE YOUR PET\'S NECK BEFORE SELECTING A SIZE'
}
},
// Main_06: 多角度展示
Main_06: {
type: 'multiple_angles',
name: '多角度展示',
aspectRatio: '1:1',
layout: {
images: 'side-by-side',
divider: 'curved-line'
},
config: {
angles: [
{ view: 'front', petType: 'cat' },
{ view: 'side', petType: 'cat' }
],
background: 'warm-interior'
}
}
};
const DEFAULT_APLUS_TEMPLATES = {
// APlus_01: 品牌横幅
APlus_01: {
type: 'brand_banner',
name: '品牌横幅',
aspectRatio: '3:2',
config: {
brandName: 'TOUCHDOG',
productName: 'CAT SOFT CONE COLLAR',
brandStyle: 'playful-curved', // 品牌字体风格
brandColor: '#E8A87C', // 珊瑚色
scene: 'cat-on-furniture',
background: 'warm-home'
}
},
// APlus_02: 对比图(关键改进!)
APlus_02: {
type: 'comparison',
name: '对比图',
aspectRatio: '3:2',
layout: {
left: 'our-product',
right: 'competitor',
center: 'none' // 不要中间的产品图!
},
config: {
leftSide: {
label: 'OUR',
labelBg: '#E8876C',
checkmark: true,
colorful: true,
sellingPoints: [
'CLOUD-LIGHT COMFORT',
'WIDER & CLEARER',
'FOLDABLE & PORTABLE'
]
},
rightSide: {
label: 'OTHER',
labelBg: '#808080',
xmark: true,
grayscale: true,
weaknesses: [
'HEAVY & BULKY',
'BLOCKS VISION & MOVEMENT',
'HARD TO STORE'
]
},
background: 'warm-beige'
}
},
// APlus_03: 功能细节
APlus_03: {
type: 'feature_details',
name: '功能细节',
aspectRatio: '3:2',
config: {
title: 'ENGINEERED FOR UNCOMPROMISED COMFORT',
detailImages: [
{ focus: 'inner-lining', caption: 'STURDY AND BREATHABLE', subtext: 'DURABLE AND COMFORTABLE' },
{ focus: 'wearing-with-size', caption: 'EASY TO CLEAN, STYLISH', subtext: 'AND ATTRACTIVE' },
{ focus: 'stitching-detail', caption: 'REINFORCED STITCHING', subtext: 'AND DURABLE FABRIC' }
],
background: 'warm-beige'
}
},
// APlus_04: 多场景横版
APlus_04: {
type: 'multi_scenario_horizontal',
name: '多场景横版',
aspectRatio: '3:2',
config: {
scenarios: [
{ scene: 'standing', caption: '• HYGIENIC & EASY TO CLEAN' },
{ scene: 'eating', caption: '• UNRESTRICTED EATING/DRINKING' },
{ scene: 'playing', caption: '• REVERSIBLE WEAR' },
{ scene: 'sleeping', caption: '• 360° COMFORT' }
]
}
},
// APlus_05: 多角度横版
APlus_05: {
type: 'multiple_angles_horizontal',
name: '多角度横版',
aspectRatio: '3:2',
config: {
angles: ['front', 'side'],
dividerStyle: 'curved'
}
},
// APlus_06: 尺寸表横版
APlus_06: {
type: 'size_chart_horizontal',
name: '尺寸表横版',
aspectRatio: '3:2',
config: {
title: 'PRODUCT SIZE',
layout: 'product-left-table-right'
}
}
};
// ============================================================
// 从配置生成Prompt的函数
// ============================================================
function generatePromptFromConfig(templateConfig, product, skuInfo) {
const { type, config, layout } = templateConfig;
// 基础产品描述
const productDesc = product.goldenDescription || 'Pet recovery cone collar';
switch (type) {
case 'lifestyle_with_features':
return generateLifestyleWithFeatures(productDesc, config, skuInfo);
case 'product_with_callouts':
return generateProductWithCallouts(productDesc, config, skuInfo);
case 'feature_showcase':
return generateFeatureShowcase(productDesc, config, skuInfo);
case 'multi_scenario_grid':
return generateMultiScenarioGrid(productDesc, config, skuInfo);
case 'size_chart':
return generateSizeChart(productDesc, config, skuInfo);
case 'multiple_angles':
return generateMultipleAngles(productDesc, config, skuInfo);
case 'brand_banner':
return generateBrandBanner(productDesc, config, skuInfo);
case 'comparison':
return generateComparison(productDesc, config, skuInfo);
case 'feature_details':
return generateFeatureDetails(productDesc, config, skuInfo);
default:
return generateGenericPrompt(productDesc, config, skuInfo);
}
}
// ============================================================
// 各类型Prompt生成函数
// ============================================================
function generateLifestyleWithFeatures(productDesc, config, skuInfo) {
return `
[AMAZON MAIN IMAGE - LIFESTYLE WITH FEATURE TEXT]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
SCENE:
- Beautiful ${skuInfo.petType || 'cat'} wearing the product comfortably
- Warm, cozy home interior background (soft focus)
- Product clearly visible, occupies 50-60% of frame
- Pet looks comfortable and relaxed
TEXT OVERLAY REQUIREMENTS:
- ${config.titleStyle === 'curved-banner' ? 'CURVED BANNER in muted blue (#8BB8C4) across center' : 'SIMPLE TITLE at center-bottom'}
- TITLE TEXT: "${config.title}" in white, clean sans-serif font
- BOTTOM ROW: 3 feature boxes in rounded rectangles
${config.features.map((f, i) => ` - Box ${i+1}: "${f.text}" with ${f.icon} icon`).join('\n')}
STYLE:
- Professional Amazon product photography
- Warm color palette
- Clean, readable text
- Subtle paw print watermarks
- 8K quality, 1:1 aspect ratio
CRITICAL: Product must match reference image EXACTLY in shape, color, and material.
`.trim();
}
function generateProductWithCallouts(productDesc, config, skuInfo) {
return `
[AMAZON MAIN IMAGE - PRODUCT WITH DETAIL CALLOUTS]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT:
- TOP: Title "${config.title}" in dark green banner with white text
- CENTER: Product C-shape flat lay view on white/light background
- CALLOUT CIRCLES: Magnifying glass style detail views with arrows pointing to product
CALLOUT DETAILS:
${config.callouts.map(c => `- ${c.position.toUpperCase()}: Circular magnified view of ${c.target}
Label: "${c.label}"
Arrow pointing from circle to corresponding area on product`).join('\n')}
STYLE:
- Clean product photography with infographic elements
- Dark green (#2D4A3E) for title banner
- Thin lines/arrows connecting callouts to product
- Professional Amazon listing style
- 8K quality, 1:1 aspect ratio
CRITICAL: Product must show C-shaped opening clearly. Match reference image EXACTLY.
`.trim();
}
function generateFeatureShowcase(productDesc, config, skuInfo) {
return `
[AMAZON MAIN IMAGE - FEATURE SHOWCASE]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT:
- BACKGROUND: Solid color matching product main color (use product's primary color)
- TOP-LEFT: Large title "${config.title}" in dark text
- LEFT SIDE (60%): Main lifestyle scene - ${skuInfo.petType || 'cat'} wearing product, walking through doorway/arch
- RIGHT SIDE (40%): 2 feature detail circles stacked vertically
FEATURE CIRCLES:
${config.featureCircles.map((f, i) => `${i+1}. Circle showing: ${f.showDetail}
Button/label below: "${f.label}"`).join('\n')}
STYLE:
- Lifestyle photography meets infographic
- Rounded rectangle buttons for labels
- Paw print decorations scattered on background
- Modern, clean design
- 8K quality, 1:1 aspect ratio
CRITICAL: Product color and shape must match reference image EXACTLY.
`.trim();
}
function generateMultiScenarioGrid(productDesc, config, skuInfo) {
return `
[AMAZON MAIN IMAGE - MULTI-SCENARIO 2x2 GRID]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT: 2x2 grid of scenes, each in rounded rectangle frame
${config.scenarios.map((s, i) => `SCENE ${i+1} (${['TOP-LEFT', 'TOP-RIGHT', 'BOTTOM-LEFT', 'BOTTOM-RIGHT'][i]}):
- ${skuInfo.petType || 'Cat'} ${s.scene} while wearing product
- Caption below: "${s.caption}"
- Subtext: "${s.subtext}"`).join('\n\n')}
BACKGROUND: Warm beige (#F5EDE4) with subtle paw print watermarks
STYLE:
- Each scene in rounded rectangle with slight shadow
- Captions in dark text, clean sans-serif
- Professional lifestyle photography
- 8K quality, 1:1 aspect ratio
CRITICAL: Product in ALL 4 scenes must be identical and match reference image.
`.trim();
}
function generateSizeChart(productDesc, config, skuInfo) {
return `
[AMAZON MAIN IMAGE - SIZE CHART INFOGRAPHIC]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT:
- TOP: Title "${config.title}" in bold dark text
- CENTER: Product flat lay with measurement arrows and labels
- Arrow across neck opening: "NECK"
- Arrow across width: "WIDTH"
- BOTTOM: Size chart table
SIZE CHART TABLE (clean, rounded corners):
| SIZE | NECK CIRCUMFERENCE | DEPTH |
${Object.entries(config.sizeChart).map(([size, dims]) =>
`| ${size} | ${dims.neck} | ${dims.depth} |`).join('\n')}
FOOTER TEXT: "${config.footer}"
BACKGROUND: Warm beige with subtle paw prints
STYLE:
- Clean infographic design
- Table with alternating row colors
- Professional product photography
- 8K quality, 1:1 aspect ratio
`.trim();
}
function generateMultipleAngles(productDesc, config, skuInfo) {
return `
[AMAZON MAIN IMAGE - MULTIPLE ANGLES]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT:
- Split view with decorative curved divider in center
- LEFT: ${skuInfo.petType || 'Cat'} wearing product, ${config.angles[0].view} view
- RIGHT: Same ${skuInfo.petType || 'cat'} or similar, ${config.angles[1].view} view
SCENE:
- Warm home interior background
- Both pets look comfortable
- Product clearly visible from different angles
STYLE:
- Lifestyle photography
- Soft warm lighting
- Decorative curved line or wave between images
- NO text overlay
- 8K quality, 1:1 aspect ratio
CRITICAL: Product must be IDENTICAL in both views, matching reference image exactly.
`.trim();
}
function generateBrandBanner(productDesc, config, skuInfo) {
return `
[AMAZON A+ BRAND BANNER - HORIZONTAL]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT (970x600px aspect ratio):
- LEFT 40%: Brand text area with decorative elements
- RIGHT 60%: Lifestyle scene
LEFT SIDE:
- Brand name "${config.brandName}" in playful, slightly curved font
- Color: ${config.brandColor} (coral/salmon)
- Decorative paw prints around text
- Product name "${config.productName}" below in smaller gray text
RIGHT SIDE:
- ${skuInfo.petType || 'Cat'} wearing product on modern furniture
- Warm cozy interior background
STYLE:
- Professional Amazon A+ content
- Warm, inviting color palette
- 8K quality, ~1.6:1 aspect ratio
`.trim();
}
function generateComparison(productDesc, config, skuInfo) {
const { leftSide, rightSide } = config;
return `
[AMAZON A+ COMPARISON IMAGE - SPLIT SCREEN]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT: Two-column comparison, NO center product image
LEFT SIDE (OUR PRODUCT):
- GREEN CHECKMARK icon at top
- "${leftSide.label}" label on ${leftSide.labelBg} background
- ${skuInfo.petType || 'Cat'} wearing OUR PRODUCT (must match reference exactly!)
- Cat looks HAPPY and comfortable
- FULL COLOR, warm tones
- Selling points in white text on colored background:
${leftSide.sellingPoints.map(p => `${p}`).join('\n')}
RIGHT SIDE (COMPETITOR):
- RED X-MARK icon at top
- "${rightSide.label}" label on ${rightSide.labelBg} background
- ${skuInfo.petType || 'Cat'} wearing HARD PLASTIC transparent cone
- Cat looks SAD/uncomfortable
- GRAYSCALE/desaturated
- Weaknesses in gray text:
${rightSide.weaknesses.map(p => `${p}`).join('\n')}
BACKGROUND: Warm beige (#F5EDE4) with paw print watermarks
STYLE:
- Clear visual contrast between sides
- Professional comparison layout
- Clean readable text
- 8K quality, ~1.6:1 aspect ratio
CRITICAL:
- LEFT product must match reference image EXACTLY (our soft cone)
- RIGHT shows generic hard plastic cone (NOT our product)
- NO product image in the center
`.trim();
}
function generateFeatureDetails(productDesc, config, skuInfo) {
return `
[AMAZON A+ FEATURE DETAILS - HORIZONTAL]
PRODUCT (MUST MATCH REFERENCE IMAGE EXACTLY):
${productDesc}
LAYOUT:
- TOP: Large title "${config.title}" in bold dark font
- MIDDLE: 3 detail images in rounded rectangles, evenly spaced
${config.detailImages.map((d, i) => `DETAIL ${i+1}:
- Focus: ${d.focus}
- Caption: "${d.caption}"
- Subtext: "${d.subtext}"`).join('\n\n')}
BACKGROUND: Warm beige (#F5EDE4) with subtle paw print watermarks
STYLE:
- Professional product detail photography
- Clean modern typography
- Rounded rectangle frames for each detail
- 8K quality, ~1.6:1 aspect ratio
`.trim();
}
function generateGenericPrompt(productDesc, config, skuInfo) {
return `
[AMAZON PRODUCT IMAGE]
PRODUCT:
${productDesc}
REQUIREMENTS:
- Professional product photography
- Match reference image exactly
- High quality, clear details
- 8K resolution
`.trim();
}
// ============================================================
// 导出
// ============================================================
module.exports = {
DEFAULT_MAIN_TEMPLATES,
DEFAULT_APLUS_TEMPLATES,
generatePromptFromConfig,
// 生成所有12张图的Prompts
generateAllPrompts: (product, skuInfo, customConfig = {}) => {
const mainTemplates = { ...DEFAULT_MAIN_TEMPLATES, ...customConfig.main };
const aplusTemplates = { ...DEFAULT_APLUS_TEMPLATES, ...customConfig.aplus };
const prompts = [];
// 主图6张
for (const [id, template] of Object.entries(mainTemplates)) {
prompts.push({
id,
name: template.name,
aspectRatio: template.aspectRatio || '1:1',
type: template.type,
prompt: generatePromptFromConfig(template, product, skuInfo)
});
}
// A+图6张
for (const [id, template] of Object.entries(aplusTemplates)) {
prompts.push({
id,
name: template.name,
aspectRatio: template.aspectRatio || '3:2',
type: template.type,
prompt: generatePromptFromConfig(template, product, skuInfo)
});
}
return prompts;
}
};

198
lib/vision-extractor.js Normal file
View File

@@ -0,0 +1,198 @@
/**
* Vision产品特征提取器
* 支持超时重试 + 缓存
*/
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const API_KEY = 'G9rXx3Ag2Xfa7Gs8zou6t6HqeZ';
const API_BASE = 'https://api.wuyinkeji.com/api';
// Vision提取Prompt
const VISION_EXTRACT_PROMPT = `Analyze this pet product image in EXTREME detail.
Output ONLY a valid JSON object (no markdown, no explanation, no thinking):
{
"color": {
"primary": "<hex code like #C3E6E8>",
"name": "<descriptive name like 'ice blue', 'mint green'>",
"secondary": "<accent colors>"
},
"shape": {
"type": "<flower/fan/cone/donut>",
"petal_count": <number of segments>,
"opening": "<C-shaped/full-circle/adjustable>",
"description": "<detailed shape description>"
},
"material": {
"type": "<PU/nylon/polyester/fabric>",
"finish": "<glossy/matte/satin>",
"texture": "<smooth/quilted/padded>"
},
"edge_binding": {
"color": "<color of inner neck edge>",
"material": "<ribbed elastic/fabric>"
},
"closure": {
"type": "<velcro/button/snap>",
"color": "<white/matching>",
"position": "<location description>"
},
"logo": {
"text": "<brand name>",
"style": "<embroidered/printed/tag>",
"position": "<location>"
},
"unique_features": ["<list distinctive features>"],
"overall_description": "<2-3 sentence summary for image generation>"
}`;
/**
* 调用Vision API提取产品特征
* @param {string} imageUrl - 图片URL
* @param {object} options - 配置选项
* @returns {object|null} - 提取的产品特征JSON
*/
async function extractProductFeatures(imageUrl, options = {}) {
const {
maxRetries = 3,
timeout = 120000, // 120秒
retryDelay = 5000,
cacheDir = null,
cacheKey = null
} = options;
// 检查缓存
if (cacheDir && cacheKey) {
const cachePath = path.join(cacheDir, `vision-cache-${cacheKey}.json`);
if (fs.existsSync(cachePath)) {
try {
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
if (cached && cached.color) {
console.log(' 📦 使用缓存的Vision结果');
return cached;
}
} catch (e) {
// 缓存无效,继续请求
}
}
}
let lastError = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
console.log(` 🔍 Vision分析 (尝试 ${attempt}/${maxRetries})...`);
try {
const response = await axios.post(`${API_BASE}/chat/index`, {
key: API_KEY,
model: 'gemini-3-pro',
content: VISION_EXTRACT_PROMPT,
image_url: imageUrl
}, { timeout });
const content = response.data.data?.choices?.[0]?.message?.content ||
response.data.data?.content;
if (!content) {
throw new Error('Vision响应为空');
}
// 提取JSON跳过thinking部分
let jsonStr = content;
// 如果有<think>标签,跳过它
const thinkEnd = content.indexOf('</think>');
if (thinkEnd !== -1) {
jsonStr = content.substring(thinkEnd + 8);
}
// 提取JSON
const jsonMatch = jsonStr.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
// 验证必要字段
if (parsed.color && parsed.shape) {
console.log(' ✓ Vision提取成功');
// 保存缓存
if (cacheDir && cacheKey) {
const cachePath = path.join(cacheDir, `vision-cache-${cacheKey}.json`);
fs.writeFileSync(cachePath, JSON.stringify(parsed, null, 2));
}
return parsed;
}
}
throw new Error('无法解析有效的JSON');
} catch (error) {
lastError = error;
console.log(` ⚠️ 尝试 ${attempt} 失败: ${error.message}`);
if (attempt < maxRetries) {
console.log(`${retryDelay/1000}秒后重试...`);
await new Promise(r => setTimeout(r, retryDelay));
}
}
}
console.error(` ❌ Vision提取最终失败: ${lastError?.message}`);
return null;
}
/**
* 将Vision结果转换为Golden Description
*/
function buildGoldenDescription(visionResult, productType = 'pet recovery cone') {
if (!visionResult) {
return `
PRODUCT: ${productType}
- Shape: Soft flower/petal shape with C-shaped opening
- Material: Soft waterproof fabric
- Closure: Velcro strap
- Comfortable design for pets
CRITICAL: Follow the reference image EXACTLY for product shape and color.
`;
}
const r = visionResult;
return `
EXACT PRODUCT APPEARANCE (MUST MATCH REFERENCE IMAGE):
- Shape: ${r.shape?.petal_count || '7-8'}-PETAL ${(r.shape?.type || 'flower').toUpperCase()} shape
- Opening: ${r.shape?.opening || 'C-shaped'} (NOT a full circle)
- Color: ${(r.color?.name || 'ice blue').toUpperCase()} (${r.color?.primary || '#C3E6E8'})
- Material: ${r.material?.finish || 'Soft'} ${r.material?.type || 'waterproof fabric'} with ${r.material?.texture || 'padded'} texture
- Edge binding: ${r.edge_binding?.color || 'Matching color'} ${r.edge_binding?.material || 'ribbed elastic'} around inner neck hole
- Closure: ${r.closure?.color || 'White'} ${r.closure?.type || 'velcro'} on ${r.closure?.position || 'one segment'}
- Logo: "${r.logo?.text || 'TOUCHDOG'}" ${r.logo?.style || 'embroidered'} on ${r.logo?.position || 'one petal'}
UNIQUE FEATURES:
${(r.unique_features || ['Scalloped petal edges', 'Radial stitching', 'Soft padded construction']).map(f => `- ${f}`).join('\n')}
OVERALL: ${r.overall_description || 'A soft, comfortable pet recovery cone with flower-petal design.'}
CRITICAL PROHIBITIONS:
- ❌ NO printed colorful patterns (solid color only unless reference shows otherwise)
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes (must match reference C-opening)
- ❌ NO random brand logos
- ❌ MUST match reference image product EXACTLY
`.trim();
}
module.exports = {
extractProductFeatures,
buildGoldenDescription,
VISION_EXTRACT_PROMPT
};

3811
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "ai-image-generator",
"version": "1.0.0",
"description": "AI Image Generation SPA",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.400.0",
"axios": "^1.6.0",
"canvas": "^3.2.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"multer": "^1.4.5-lts.1",
"sharp": "^0.34.5"
}
}

395
poc-workflow-v2.js Normal file
View File

@@ -0,0 +1,395 @@
/**
* POC Workflow V2: 强化产品一致性
* 核心改进:
* 1. 极致详细的产品描述 (Golden Description)
* 2. 移除AI生成logo后期处理
* 3. 分级策略A级严格复制B级可控创意
* 4. 对比图特殊处理
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// 配置
const API_KEY = 'G9rXx3Ag2Xfa7Gs8zou6t6HqeZ';
const API_BASE = 'https://api.wuyinkeji.com/api';
const OUTPUT_DIR = path.join(__dirname, 'output_poc_v2');
const MATERIAL_DIR = path.join(__dirname, '素材/素材/已有的素材');
// 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(filePath) {
const fileName = `poc-v2-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: filePath.endsWith('.png') ? 'image/png' : 'image/jpeg',
}));
const publicUrl = 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}`;
return publicUrl;
}
// ============================================================
// 🔥 核心改进1: 黄金产品描述 (Golden Product Description)
// ============================================================
const GOLDEN_PRODUCT_DESC = `
EXACT PRODUCT APPEARANCE (MUST MATCH PRECISELY):
- Shape: 8-PETAL FLOWER/FAN shape, C-shaped opening (like Pac-Man), NOT a full circle
- Color: ICE BLUE / Light aqua blue (#C5E8ED approximate)
- Material: Glossy waterproof PU fabric with visible stitching lines between petals
- Edge binding: TEAL/TURQUOISE color binding around the inner neck hole
- Closure: White velcro strap on one petal end
- Logo: "TOUCHDOG®" embroidered in matching blue thread on one petal (small, subtle)
- Texture: Smooth, slightly shiny, NOT matte, NOT cotton fabric
- Structure: Soft but structured, maintains petal shape, NOT floppy
CRITICAL PROHIBITIONS:
- ❌ NO printed patterns or colorful fabric designs
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes
- ❌ NO matte cotton or fleece textures
- ❌ NO random brand logos or text
`;
// ============================================================
// 🔥 核心改进2: 分级Prompt策略
// ============================================================
// A级严格产品复制 (必须和参考图几乎一致)
function createPromptA_Strict(sceneDesc) {
return `
[PRODUCT REPRODUCTION - HIGHEST FIDELITY]
${GOLDEN_PRODUCT_DESC}
SCENE: ${sceneDesc}
INSTRUCTION: Reproduce the EXACT product from reference image.
The product shape, color, and material must be IDENTICAL to reference.
Only change the background/scene as described.
DO NOT add any brand logo overlays or text graphics.
`.trim();
}
// B级可控创意 (产品保持一致,场景可发挥)
function createPromptB_Controlled(sceneDesc, extraInstructions = '') {
return `
[CONTROLLED CREATIVE - PRODUCT CONSISTENCY REQUIRED]
${GOLDEN_PRODUCT_DESC}
SCENE: ${sceneDesc}
${extraInstructions}
INSTRUCTION: Keep product appearance EXACTLY as described above.
Creative freedom allowed ONLY for background, lighting, and composition.
DO NOT modify product shape, color, or material.
DO NOT add any brand logo overlays.
`.trim();
}
// C级对比图专用 (左边严格,右边自由)
function createPromptC_Comparison() {
return `
[COMPARISON IMAGE - SPLIT SCREEN]
LEFT SIDE (OUR PRODUCT - STRICT):
${GOLDEN_PRODUCT_DESC}
- Happy, comfortable cat/dog wearing the ICE BLUE 8-PETAL soft cone
- Bright, colorful, warm lighting
- GREEN CHECKMARK overlay
RIGHT SIDE (GENERIC COMPETITOR - FLEXIBLE):
- Generic transparent HARD PLASTIC cone (the traditional "lamp shade" type)
- Sad, uncomfortable looking cat/dog
- Grayscale / desaturated colors
- RED X-MARK overlay
LAYOUT: Side by side, clear visual contrast
TEXT HEADER: "Soft & Comfortable" vs "Hard & Uncomfortable"
CRITICAL: Left side product must be ICE BLUE 8-PETAL FLOWER shape, NOT any other design!
`.trim();
}
// 生图任务提交
async function submitImageTask(prompt, refImageUrl, aspectRatio = '1:1') {
try {
const payload = {
key: API_KEY,
prompt: prompt,
img_url: refImageUrl, // 强参考图
aspectRatio: aspectRatio,
imageSize: '1K'
};
console.log(` 提交任务...`);
const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload);
const taskId = response.data.data?.id;
if (!taskId) {
console.error('Submit response:', response.data);
throw new Error('No task ID returned');
}
return taskId;
} catch (error) {
console.error('Submit failed:', error.message);
throw error;
}
}
// 轮询图片结果
async function pollImageResult(taskId) {
let attempts = 0;
const maxAttempts = 60;
while (attempts < maxAttempts) {
try {
const response = await axios.get(`${API_BASE}/img/drawDetail`, {
params: { key: API_KEY, id: taskId }
});
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;
console.error('Poll error:', error.message);
throw error;
}
}
throw new Error('Timeout waiting for image');
}
// 生成单张图(含重试)
async function generateSingleImage(item, refUrls) {
console.log(`\n🎨 [${item.level}级] ${item.id}: ${item.name}`);
let retryCount = 0;
const maxRetries = 2;
// 根据类型选择最佳参考图
let refUrl = refUrls.flat; // 默认用平铺图
if (item.useWornRef) {
refUrl = refUrls.worn;
}
while (retryCount <= maxRetries) {
try {
if (retryCount > 0) console.log(` 重试第 ${retryCount} 次...`);
const taskId = await submitImageTask(item.prompt, refUrl, item.aspectRatio);
console.log(` Task ID: ${taskId}`);
const imageUrl = await pollImageResult(taskId);
console.log('\n ✓ 生成成功');
// 下载保存
const imgRes = await axios.get(imageUrl, { responseType: 'arraybuffer' });
fs.writeFileSync(path.join(OUTPUT_DIR, `${item.id}.jpg`), imgRes.data);
console.log(' ✓ 保存本地');
return true;
} catch (error) {
console.error(`\n ✗ 失败: ${error.message}`);
retryCount++;
await new Promise(r => setTimeout(r, 3000));
}
}
console.error(`${item.id} 最终失败`);
return false;
}
// ============================================================
// 主流程
// ============================================================
async function main() {
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
// 1. 上传核心参考图
console.log('📤 上传核心参考图...');
const flatImgPath = path.join(MATERIAL_DIR, 'IMG_5683.png'); // 平铺图(最清晰产品细节)
const wornImgPath = path.join(MATERIAL_DIR, 'IMG_6514.JPG'); // 佩戴图
const refUrls = {
flat: await uploadToR2(flatImgPath),
worn: await uploadToR2(wornImgPath)
};
console.log(' 平铺图:', refUrls.flat);
console.log(' 佩戴图:', refUrls.worn);
// 2. 定义优化后的图片任务
const tasks = [
// ========== A级严格复制 ==========
{
id: 'Main_01_Hero',
name: '场景首图',
level: 'A',
aspectRatio: '1:1',
useWornRef: true,
prompt: createPromptA_Strict(`
Cute white fluffy cat wearing the product.
Cone opens OUTWARD around face (like flower petals spreading out).
Cat looks comfortable and relaxed.
Warm, cozy home interior background (soft focus).
Professional Amazon product photography, 8K quality.
`)
},
{
id: 'Main_02_Flat',
name: '平铺白底图',
level: 'A',
aspectRatio: '1:1',
useWornRef: false,
prompt: createPromptA_Strict(`
Product flat lay on PURE WHITE background.
Shot from directly above (bird's eye view).
Show the 8-petal flower/fan shape clearly.
C-shaped opening (gap between velcro ends) visible.
Clean studio lighting, no shadows.
Product photography style.
`)
},
// ========== B级可控创意 ==========
{
id: 'Main_03_Function',
name: '功能演示',
level: 'B',
aspectRatio: '1:1',
useWornRef: false,
prompt: createPromptB_Controlled(`
Close-up of hands adjusting the white velcro strap.
Show the velcro closure mechanism clearly.
Demonstrate "adjustable fit" feature.
Clean, bright lighting.
`)
},
{
id: 'Main_04_Scenarios',
name: '多场景使用',
level: 'B',
aspectRatio: '1:1',
useWornRef: true,
prompt: createPromptB_Controlled(`
2x2 grid showing different usage scenarios:
- Cat eating from bowl while wearing cone (cone allows eating)
- Cat drinking water comfortably
- Cat sleeping peacefully
- Cat walking around home
All showing the ICE BLUE 8-PETAL cone product.
`)
},
{
id: 'APlus_01_Banner',
name: 'A+横幅',
level: 'B',
aspectRatio: '3:2',
useWornRef: true,
prompt: createPromptB_Controlled(`
Wide banner shot for Amazon A+ content.
Beautiful modern living room scene.
White cat wearing the ICE BLUE 8-petal cone walking on soft rug.
Left side has empty space for text overlay.
Warm, inviting home atmosphere.
Professional lifestyle photography.
`)
},
{
id: 'APlus_03_Detail',
name: '材质细节',
level: 'B',
aspectRatio: '3:2',
useWornRef: false,
prompt: createPromptB_Controlled(`
Extreme close-up macro photography.
Focus on the ICE BLUE glossy PU material texture.
Water droplets beading on the waterproof surface.
Show the neat stitching between petals.
Show the TEAL edge binding around neck hole.
High-end product detail photography.
`)
},
{
id: 'APlus_04_Waterproof',
name: '防水演示',
level: 'B',
aspectRatio: '3:2',
useWornRef: false,
prompt: createPromptB_Controlled(`
Action shot demonstrating waterproof feature.
Water being poured onto the ICE BLUE 8-petal cone.
Water droplets rolling off the glossy PU surface.
Dynamic splash photography style.
Bright lighting, white background.
`)
},
// ========== C级对比图 ==========
{
id: 'APlus_02_Comparison',
name: '对比图',
level: 'C',
aspectRatio: '3:2',
useWornRef: true,
prompt: createPromptC_Comparison()
},
];
// 3. 执行生成
console.log('\n🚀 开始V2优化版生成...\n');
console.log('=' .repeat(50));
let successCount = 0;
for (const task of tasks) {
const success = await generateSingleImage(task, refUrls);
if (success) successCount++;
}
console.log('\n' + '='.repeat(50));
console.log(`✅ 完成! 成功 ${successCount}/${tasks.length}`);
console.log(`📁 输出目录: ${OUTPUT_DIR}`);
// 4. 说明跳过的图
console.log('\n⚠ 以下图片建议后期处理不适合纯AI生成:');
console.log(' - Main_05_Size (尺寸图) → 用设计软件+产品抠图');
console.log(' - Main_06_Brand (品牌图) → 用设计软件+官方logo');
console.log(' - APlus_05_Storage (收纳图) → 需要真实折叠产品素材');
console.log(' - APlus_06_Guide (选购指南) → 用设计软件模板');
}
main().catch(console.error);

482
poc-workflow-v3.js Normal file
View File

@@ -0,0 +1,482 @@
/**
* POC Workflow V3: 通用化工作流
*
* 特性:
* 1. 素材目录作为参数支持一键切换SKU
* 2. Vision自动提取产品特征
* 3. 基于真实交付成品的12张图模板
* 4. 带文字排版的A+/主图
*
* 用法:
* node poc-workflow-v3.js --material-dir="./素材/素材/已有的素材" --sku-name="Touchdog软质伊丽莎白圈"
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { generateAllPrompts } = require('./prompts/image-templates');
// ============================================================
// 配置
// ============================================================
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,
});
// ============================================================
// 命令行参数解析
// ============================================================
function parseArgs() {
const args = process.argv.slice(2);
const config = {
materialDir: path.join(__dirname, '素材/素材/已有的素材'),
skuName: 'Touchdog 软质伊丽莎白圈',
brandName: 'TOUCHDOG',
productName: 'CAT SOFT CONE COLLAR',
outputDir: null, // 自动生成
sellingPoints: [
'CLOUD-LIGHT COMFORT',
'WIDER & CLEARER',
'FOLDABLE & PORTABLE',
'WATERPROOF & EASY CLEAN'
],
competitorWeaknesses: [
'HEAVY & BULKY',
'BLOCKS VISION & MOVEMENT',
'HARD TO STORE'
],
features: [
{ title: 'STURDY AND BREATHABLE', desc: ', DURABLE AND COMFORTABLE' },
{ title: 'EASY TO CLEAN, STYLISH', desc: 'AND ATTRACTIVE' },
{ title: 'REINFORCED STITCHING PROCESS', desc: 'AND DURABLE FABRIC' }
],
sizeChart: {
XS: { neck: '5.6-6.8IN', depth: '3.2IN' },
S: { neck: '7.2-8.4IN', depth: '4IN' },
M: { neck: '8.8-10.4IN', depth: '5IN' },
L: { neck: '10.8-12.4IN', depth: '6IN' },
XL: { neck: '12.8-14.4IN', depth: '7IN' }
}
};
for (const arg of args) {
if (arg.startsWith('--material-dir=')) {
config.materialDir = arg.split('=')[1];
} else if (arg.startsWith('--sku-name=')) {
config.skuName = arg.split('=')[1];
} else if (arg.startsWith('--brand-name=')) {
config.brandName = arg.split('=')[1];
} else if (arg.startsWith('--product-name=')) {
config.productName = arg.split('=')[1];
} else if (arg.startsWith('--output-dir=')) {
config.outputDir = arg.split('=')[1];
}
}
// 自动生成输出目录
if (!config.outputDir) {
const timestamp = new Date().toISOString().slice(0, 10);
const safeName = config.skuName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_').slice(0, 30);
config.outputDir = path.join(__dirname, `output_v3_${safeName}_${timestamp}`);
}
return config;
}
// ============================================================
// 工具函数
// ============================================================
async function uploadToR2(filePath) {
const fileName = `v3-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: filePath.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 scanMaterials(materialDir) {
console.log('📁 扫描素材目录:', materialDir);
const files = fs.readdirSync(materialDir);
const images = files.filter(f => /\.(jpg|jpeg|png)$/i.test(f));
console.log(` 找到 ${images.length} 张图片`);
// 识别图片类型
let flatImage = null; // 平铺图
let wornImage = null; // 佩戴图
for (const img of images) {
const imgPath = path.join(materialDir, img);
// 简单启发式:文件名包含特定关键词
if (img.includes('5683') || img.toLowerCase().includes('flat')) {
flatImage = imgPath;
} else if (img.includes('6514') || img.toLowerCase().includes('worn') || img.toLowerCase().includes('wear')) {
wornImage = imgPath;
}
}
// 如果没找到,用第一张和第二张
if (!flatImage && images.length > 0) {
flatImage = path.join(materialDir, images[0]);
}
if (!wornImage && images.length > 1) {
wornImage = path.join(materialDir, images[1]);
} else if (!wornImage) {
wornImage = flatImage;
}
console.log(' 平铺图:', flatImage ? path.basename(flatImage) : '未找到');
console.log(' 佩戴图:', wornImage ? path.basename(wornImage) : '未找到');
return { flatImage, wornImage, allImages: images.map(i => path.join(materialDir, i)) };
}
// Vision API提取产品特征
const VISION_EXTRACT_PROMPT = `Analyze this pet recovery cone product image in EXTREME detail.
Output ONLY a valid JSON object (no markdown, no explanation):
{
"color": {
"primary": "<hex code>",
"name": "<descriptive name>",
"secondary": "<accent colors>"
},
"shape": {
"type": "<flower/fan/cone>",
"petal_count": <number>,
"opening": "<C-shaped/full-circle>",
"description": "<detailed shape>"
},
"material": {
"type": "<PU/fabric/plastic>",
"finish": "<glossy/matte/satin>",
"texture": "<smooth/quilted>"
},
"edge_binding": {
"color": "<color>",
"material": "<ribbed/fabric>"
},
"closure": {
"type": "<velcro/button>",
"color": "<color>",
"position": "<location>"
},
"logo": {
"text": "<brand>",
"style": "<embroidered/printed>",
"position": "<location>"
},
"unique_features": ["<list>"],
"overall_description": "<2-3 sentence summary>"
}`;
async function extractProductFeatures(imageUrl) {
console.log('\n🔍 Vision分析产品特征...');
try {
const response = await axios.post(`${API_BASE}/chat/index`, {
key: API_KEY,
model: 'gemini-3-pro',
content: VISION_EXTRACT_PROMPT,
image_url: imageUrl
}, { timeout: 90000 });
const content = response.data.data?.choices?.[0]?.message?.content ||
response.data.data?.content;
if (!content) {
throw new Error('Vision响应为空');
}
// 提取JSON
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
console.log(' ✓ 提取成功');
return parsed;
}
throw new Error('无法解析JSON');
} catch (error) {
console.error(' ✗ Vision分析失败:', error.message);
return null;
}
}
// 将Vision结果转换为Golden Description
function buildGoldenDescription(visionResult) {
if (!visionResult) {
return `
PRODUCT: Pet recovery cone collar
- Soft, flexible material
- C-shaped opening design
- Velcro closure
- Comfortable for pets
`;
}
const r = visionResult;
return `
EXACT PRODUCT APPEARANCE (AUTO-EXTRACTED):
- Shape: ${r.shape?.petal_count || '?'}-PETAL ${r.shape?.type?.toUpperCase() || 'FLOWER'} shape, ${r.shape?.opening || 'C-shaped'} opening
- Color: ${r.color?.name?.toUpperCase() || 'UNKNOWN'} (${r.color?.primary || '#???'})
- Material: ${r.material?.finish || 'Soft'} ${r.material?.type || 'fabric'} with ${r.material?.texture || 'smooth'} texture
- Edge binding: ${r.edge_binding?.color || 'Contrasting color'} ${r.edge_binding?.material || 'ribbed elastic'} around inner neck hole
- Closure: ${r.closure?.color || 'White'} ${r.closure?.type || 'velcro'} on ${r.closure?.position || 'one end'}
- Logo: "${r.logo?.text || 'TOUCHDOG'}" ${r.logo?.style || 'embroidered'}
UNIQUE FEATURES:
${(r.unique_features || []).map(f => `- ${f}`).join('\n') || '- Soft, comfortable design'}
CRITICAL PROHIBITIONS:
- ❌ NO printed patterns or colorful fabric designs
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes (must have C-opening)
- ❌ NO random brand logos
`.trim();
}
// 生图任务
async function submitImageTask(prompt, refImageUrl, aspectRatio = '1:1') {
const payload = {
key: API_KEY,
prompt: prompt,
img_url: refImageUrl,
aspectRatio: aspectRatio,
imageSize: '1K'
};
const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload);
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) {
const response = await axios.get(`${API_BASE}/img/drawDetail`, {
params: { key: API_KEY, id: taskId }
});
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++;
}
throw new Error('Timeout');
}
async function generateSingleImage(item, refUrl, outputDir) {
console.log(`\n🎨 [${item.id}] ${item.name}`);
let retryCount = 0;
const maxRetries = 2;
while (retryCount <= maxRetries) {
try {
if (retryCount > 0) console.log(` 重试 ${retryCount}/${maxRetries}...`);
console.log(' 提交任务...');
const taskId = await submitImageTask(item.prompt, refUrl, item.aspectRatio);
console.log(` Task ID: ${taskId}`);
const imageUrl = await pollImageResult(taskId);
console.log('\n ✓ 生成成功');
// 下载保存
const imgRes = await axios.get(imageUrl, { responseType: 'arraybuffer' });
const outputPath = path.join(outputDir, `${item.id}.jpg`);
fs.writeFileSync(outputPath, imgRes.data);
console.log(` ✓ 保存: ${item.id}.jpg`);
return { id: item.id, success: true, path: outputPath };
} catch (error) {
console.error(`\n ✗ 失败: ${error.message}`);
retryCount++;
await new Promise(r => setTimeout(r, 3000));
}
}
return { id: item.id, success: false, error: 'Max retries exceeded' };
}
// ============================================================
// 主流程
// ============================================================
async function main() {
const config = parseArgs();
console.log('='.repeat(70));
console.log('🚀 POC Workflow V3 - 通用化工作流');
console.log('='.repeat(70));
console.log('\n📋 配置:');
console.log(' 素材目录:', config.materialDir);
console.log(' SKU名称:', config.skuName);
console.log(' 品牌:', config.brandName);
console.log(' 输出目录:', config.outputDir);
// 创建输出目录
if (!fs.existsSync(config.outputDir)) {
fs.mkdirSync(config.outputDir, { recursive: true });
}
// 1. 扫描素材
console.log('\n' + '─'.repeat(70));
console.log('📁 阶段1: 扫描素材');
console.log('─'.repeat(70));
const materials = await scanMaterials(config.materialDir);
if (!materials.flatImage) {
console.error('❌ 未找到素材图片,请检查素材目录');
process.exit(1);
}
// 2. 上传素材到R2
console.log('\n' + '─'.repeat(70));
console.log('📤 阶段2: 上传素材');
console.log('─'.repeat(70));
const flatImageUrl = await uploadToR2(materials.flatImage);
console.log(' 平铺图URL:', flatImageUrl);
let wornImageUrl = flatImageUrl;
if (materials.wornImage && materials.wornImage !== materials.flatImage) {
wornImageUrl = await uploadToR2(materials.wornImage);
console.log(' 佩戴图URL:', wornImageUrl);
}
// 3. Vision分析
console.log('\n' + '─'.repeat(70));
console.log('🔍 阶段3: Vision分析产品特征');
console.log('─'.repeat(70));
const visionResult = await extractProductFeatures(flatImageUrl);
const goldenDescription = buildGoldenDescription(visionResult);
console.log('\n生成的Golden Description:');
console.log(goldenDescription.substring(0, 500) + '...');
// 保存Vision结果
fs.writeFileSync(
path.join(config.outputDir, 'vision-analysis.json'),
JSON.stringify(visionResult, null, 2)
);
fs.writeFileSync(
path.join(config.outputDir, 'golden-description.txt'),
goldenDescription
);
// 4. 生成12张图的Prompts
console.log('\n' + '─'.repeat(70));
console.log('📝 阶段4: 生成Prompts');
console.log('─'.repeat(70));
const product = { goldenDescription };
const skuInfo = {
brandName: config.brandName,
productName: config.productName,
sellingPoints: config.sellingPoints,
competitorWeaknesses: config.competitorWeaknesses,
features: config.features,
sizeChart: config.sizeChart
};
const prompts = generateAllPrompts(product, skuInfo);
console.log(` 生成了 ${prompts.length} 个Prompt`);
// 保存prompts供参考
fs.writeFileSync(
path.join(config.outputDir, 'prompts.json'),
JSON.stringify(prompts.map(p => ({ id: p.id, name: p.name, aspectRatio: p.aspectRatio })), null, 2)
);
// 5. 批量生成图片
console.log('\n' + '─'.repeat(70));
console.log('🎨 阶段5: 批量生成图片');
console.log('─'.repeat(70));
const results = [];
for (const promptItem of prompts) {
// 根据图片类型选择参考图
const refUrl = promptItem.id.includes('Main_02') ? flatImageUrl : wornImageUrl;
const result = await generateSingleImage(promptItem, refUrl, config.outputDir);
results.push(result);
}
// 6. 总结
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📁 输出目录: ${config.outputDir}`);
// 保存结果摘要
fs.writeFileSync(
path.join(config.outputDir, 'results.json'),
JSON.stringify({
config,
visionResult,
results,
summary: {
total: results.length,
success: successCount,
failed: results.length - successCount
}
}, null, 2)
);
}
main().catch(console.error);

399
poc-workflow-v4.js Normal file
View File

@@ -0,0 +1,399 @@
/**
* POC Workflow V4: 完整优化版
*
* 改进:
* 1. Vision超时重试机制
* 2. 可配置的套路模板系统
* 3. 基于真实交付成品的Prompt
* 4. 强化产品一致性约束
*
* 用法:
* node poc-workflow-v4.js --material-dir="./素材/素材/已有的素材" --sku-name="Touchdog冰蓝色伊丽莎白圈"
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { extractProductFeatures, buildGoldenDescription } = require('./lib/vision-extractor');
const { generateAllPrompts } = require('./lib/template-config');
// ============================================================
// 配置
// ============================================================
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,
});
// ============================================================
// 命令行参数解析
// ============================================================
function parseArgs() {
const args = process.argv.slice(2);
const config = {
materialDir: path.join(__dirname, '素材/素材/已有的素材'),
skuName: 'Touchdog 软质伊丽莎白圈',
brandName: 'TOUCHDOG',
productName: 'CAT SOFT CONE COLLAR',
petType: 'cat',
outputDir: null
};
for (const arg of args) {
if (arg.startsWith('--material-dir=')) {
config.materialDir = arg.split('=')[1];
} else if (arg.startsWith('--sku-name=')) {
config.skuName = arg.split('=')[1];
} else if (arg.startsWith('--brand-name=')) {
config.brandName = arg.split('=')[1];
} else if (arg.startsWith('--product-name=')) {
config.productName = arg.split('=')[1];
} else if (arg.startsWith('--output-dir=')) {
config.outputDir = arg.split('=')[1];
} else if (arg.startsWith('--pet-type=')) {
config.petType = arg.split('=')[1];
}
}
// 自动生成输出目录
if (!config.outputDir) {
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-');
const safeName = config.skuName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_').slice(0, 20);
config.outputDir = path.join(__dirname, `output_v4_${safeName}_${timestamp}`);
}
return config;
}
// ============================================================
// 工具函数
// ============================================================
async function uploadToR2(filePath) {
const fileName = `v4-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: filePath.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 scanMaterials(materialDir) {
console.log('📁 扫描素材目录:', materialDir);
if (!fs.existsSync(materialDir)) {
throw new Error(`素材目录不存在: ${materialDir}`);
}
const files = fs.readdirSync(materialDir);
const images = files.filter(f => /\.(jpg|jpeg|png)$/i.test(f));
console.log(` 找到 ${images.length} 张图片: ${images.join(', ')}`);
// 识别图片类型
let flatImage = null; // 平铺图
let wornImage = null; // 佩戴图
for (const img of images) {
const imgPath = path.join(materialDir, img);
const imgLower = img.toLowerCase();
// 平铺图识别
if (img.includes('5683') || imgLower.includes('flat') || imgLower.includes('1.png')) {
flatImage = imgPath;
}
// 佩戴图识别
if (img.includes('6514') || imgLower.includes('worn') || imgLower.includes('wear') || imgLower.includes('3.png')) {
wornImage = imgPath;
}
}
// 如果没找到,用启发式规则
if (!flatImage) {
// 优先选png格式通常更清晰
const pngImages = images.filter(i => i.toLowerCase().endsWith('.png'));
flatImage = pngImages.length > 0
? path.join(materialDir, pngImages[0])
: path.join(materialDir, images[0]);
}
if (!wornImage) {
const jpgImages = images.filter(i => i.toLowerCase().endsWith('.jpg') || i.toLowerCase().endsWith('.jpeg'));
wornImage = jpgImages.length > 0
? path.join(materialDir, jpgImages[0])
: flatImage;
}
console.log(' ✓ 平铺图:', path.basename(flatImage));
console.log(' ✓ 佩戴图:', path.basename(wornImage));
return { flatImage, wornImage, allImages: images.map(i => path.join(materialDir, i)) };
}
// 生图任务
async function submitImageTask(prompt, refImageUrl, aspectRatio = '1:1') {
const payload = {
key: API_KEY,
prompt: prompt,
img_url: refImageUrl,
aspectRatio: aspectRatio,
imageSize: '1K'
};
const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload, {
timeout: 30000
});
const taskId = response.data.data?.id;
if (!taskId) {
console.error('Submit response:', response.data);
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 after ' + maxAttempts + ' attempts');
}
async function generateSingleImage(item, refUrl, outputDir) {
console.log(`\n🎨 [${item.id}] ${item.name}`);
let retryCount = 0;
const maxRetries = 2;
while (retryCount <= maxRetries) {
try {
if (retryCount > 0) console.log(` 重试 ${retryCount}/${maxRetries}...`);
console.log(' 提交任务...');
const taskId = await submitImageTask(item.prompt, refUrl, item.aspectRatio);
console.log(` Task ID: ${taskId}`);
const imageUrl = await pollImageResult(taskId);
console.log('\n ✓ 生成成功');
// 下载保存
const imgRes = await axios.get(imageUrl, { responseType: 'arraybuffer', timeout: 30000 });
const outputPath = path.join(outputDir, `${item.id}.jpg`);
fs.writeFileSync(outputPath, imgRes.data);
console.log(` ✓ 保存: ${item.id}.jpg`);
return { id: item.id, success: true, path: outputPath };
} catch (error) {
console.error(`\n ✗ 失败: ${error.message}`);
retryCount++;
if (retryCount <= maxRetries) {
await new Promise(r => setTimeout(r, 5000));
}
}
}
return { id: item.id, success: false, error: 'Max retries exceeded' };
}
// ============================================================
// 主流程
// ============================================================
async function main() {
const config = parseArgs();
console.log('\n' + '═'.repeat(70));
console.log('🚀 POC Workflow V4 - 完整优化版');
console.log('═'.repeat(70));
console.log('\n📋 配置:');
console.log(' 素材目录:', config.materialDir);
console.log(' SKU名称:', config.skuName);
console.log(' 品牌:', config.brandName);
console.log(' 输出目录:', config.outputDir);
// 创建输出目录
if (!fs.existsSync(config.outputDir)) {
fs.mkdirSync(config.outputDir, { recursive: true });
}
// ========== 阶段1: 扫描素材 ==========
console.log('\n' + '─'.repeat(70));
console.log('📁 阶段1: 扫描素材');
console.log('─'.repeat(70));
const materials = await scanMaterials(config.materialDir);
if (!materials.flatImage) {
console.error('❌ 未找到素材图片');
process.exit(1);
}
// ========== 阶段2: 上传素材 ==========
console.log('\n' + '─'.repeat(70));
console.log('📤 阶段2: 上传素材到云存储');
console.log('─'.repeat(70));
const flatImageUrl = await uploadToR2(materials.flatImage);
console.log(' 平铺图:', flatImageUrl);
let wornImageUrl = flatImageUrl;
if (materials.wornImage && materials.wornImage !== materials.flatImage) {
wornImageUrl = await uploadToR2(materials.wornImage);
console.log(' 佩戴图:', wornImageUrl);
}
// ========== 阶段3: Vision分析 ==========
console.log('\n' + '─'.repeat(70));
console.log('🔍 阶段3: Vision分析产品特征 (超时重试机制)');
console.log('─'.repeat(70));
const visionResult = await extractProductFeatures(flatImageUrl, {
maxRetries: 3,
timeout: 150000, // 150秒
retryDelay: 8000,
cacheDir: config.outputDir,
cacheKey: path.basename(materials.flatImage).replace(/\.[^.]+$/, '')
});
const goldenDescription = buildGoldenDescription(visionResult);
console.log('\n📝 Golden Description预览:');
console.log('─'.repeat(50));
console.log(goldenDescription.substring(0, 600) + '...');
// 保存分析结果
fs.writeFileSync(
path.join(config.outputDir, 'vision-analysis.json'),
JSON.stringify(visionResult, null, 2)
);
fs.writeFileSync(
path.join(config.outputDir, 'golden-description.txt'),
goldenDescription
);
// ========== 阶段4: 生成Prompts ==========
console.log('\n' + '─'.repeat(70));
console.log('📝 阶段4: 生成12张图的Prompts (可配置模板系统)');
console.log('─'.repeat(70));
const product = { goldenDescription };
const skuInfo = {
brandName: config.brandName,
productName: config.productName,
petType: config.petType
};
const prompts = generateAllPrompts(product, skuInfo);
console.log(` ✓ 生成了 ${prompts.length} 个Prompt`);
// 保存prompts供参考
fs.writeFileSync(
path.join(config.outputDir, 'all-prompts.json'),
JSON.stringify(prompts.map(p => ({
id: p.id,
name: p.name,
type: p.type,
aspectRatio: p.aspectRatio,
prompt: p.prompt
})), null, 2)
);
// ========== 阶段5: 批量生成 ==========
console.log('\n' + '─'.repeat(70));
console.log('🎨 阶段5: 批量生成图片');
console.log('─'.repeat(70));
const results = [];
for (const promptItem of prompts) {
// Main_02用平铺图参考其他用佩戴图
const refUrl = promptItem.id === 'Main_02' ? flatImageUrl : wornImageUrl;
const result = await generateSingleImage(promptItem, refUrl, config.outputDir);
results.push(result);
}
// ========== 阶段6: 总结 ==========
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📁 输出目录: ${config.outputDir}`);
// 保存结果摘要
fs.writeFileSync(
path.join(config.outputDir, 'results-summary.json'),
JSON.stringify({
config,
visionSuccess: !!visionResult,
results,
summary: {
total: results.length,
success: successCount,
failed: results.length - successCount
},
timestamp: new Date().toISOString()
}, null, 2)
);
console.log('\n💡 提示: 查看 all-prompts.json 了解每张图的Prompt详情');
}
main().catch(console.error);

687
poc-workflow-v5.js Normal file
View File

@@ -0,0 +1,687 @@
/**
* POC Workflow V5: P图合成工作流
*
* 核心策略:
* - 产品部分使用原始素材100%一致)
* - 背景/场景AI生成无产品
* - 文字排版AI生成或代码叠加
* - 最终图片:代码合成各层
*/
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 = `v5-${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 uploadFileToR2(filePath) {
const buffer = fs.readFileSync(filePath);
return await uploadToR2(buffer, path.basename(filePath));
}
// 生图任务
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);
}
// ============================================================
// 12张图的生成逻辑
// ============================================================
/**
* Main_01: 场景首图+卖点
* 策略原图叠加到AI生成的背景上再由AI添加文字
*/
async function generateMain01(materials, config, outputDir) {
console.log('\n🎨 [Main_01] 场景首图+卖点');
// 直接用原佩戴图让AI基于它编辑添加文字和调整背景
const wornBuffer = fs.readFileSync(materials.wornImage);
const wornUrl = await uploadToR2(wornBuffer, 'worn.jpg');
const prompt = `
Edit this image of a cat wearing a pet recovery cone:
1. KEEP the cat and product EXACTLY as shown - DO NOT modify them
2. Enhance the background to be a warm, cozy home interior
3. Add soft, warm lighting effects
4. Add text overlay:
- A curved blue banner across the middle-left with text "DESIGNED FOR COMFORTABLE RECOVERY"
- 3 feature boxes at the bottom:
* "LIGHTER THAN AN EGG" with egg icon
* "WATERPROOF & EASY WIPE" with water droplet icon
* "BREATHABLE COTTON LINING" with cloud icon
5. Add subtle paw print decorations
Style: Professional Amazon product photography with text overlay
Output: 1:1 square image, high quality
`.trim();
const result = await generateAIImage(prompt, wornUrl, '1:1');
await imageProcessor.saveImage(result, path.join(outputDir, 'Main_01.jpg'));
return true;
}
/**
* Main_02: 白底平铺+细节放大
* 策略:使用原平铺图,代码添加标注和放大镜效果
*/
async function generateMain02(materials, config, outputDir) {
console.log('\n🎨 [Main_02] 白底平铺+细节放大');
// 读取原图
const flatBuffer = fs.readFileSync(materials.flatImage);
const metadata = await sharp(flatBuffer).metadata();
// 目标尺寸 1600x1600
const targetSize = 1600;
// 调整原图大小,留出空间给标题和标注
const productSize = Math.floor(targetSize * 0.65);
const productBuffer = await sharp(flatBuffer)
.resize(productSize, productSize, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 1 } })
.toBuffer();
// 创建白色背景
const background = await sharp({
create: {
width: targetSize,
height: targetSize,
channels: 3,
background: { r: 255, g: 255, b: 255 }
}
}).jpeg().toBuffer();
// 创建标题横幅
const bannerHeight = 80;
const bannerSvg = `
<svg width="${targetSize}" height="${bannerHeight}">
<rect width="100%" height="100%" fill="#2D4A3E" rx="0" ry="0"/>
<text x="${targetSize/2}" y="${bannerHeight/2 + 12}" font-size="32"
fill="white" font-weight="bold" font-family="Arial, sans-serif"
text-anchor="middle">"DURABLE WATERPROOF PU LAYER"</text>
</svg>
`;
const bannerBuffer = await sharp(Buffer.from(bannerSvg)).png().toBuffer();
// 创建两个放大镜效果的细节图
const detailSize = 180;
// 从原图裁剪细节区域(左下角材质,右下角内圈)
const origWidth = metadata.width;
const origHeight = metadata.height;
// 细节1左下角区域
const detail1 = await sharp(flatBuffer)
.extract({
left: Math.floor(origWidth * 0.1),
top: Math.floor(origHeight * 0.6),
width: Math.floor(origWidth * 0.25),
height: Math.floor(origHeight * 0.25)
})
.resize(detailSize, detailSize, { fit: 'cover' })
.toBuffer();
// 细节2中心内圈区域
const detail2 = await sharp(flatBuffer)
.extract({
left: Math.floor(origWidth * 0.35),
top: Math.floor(origHeight * 0.35),
width: Math.floor(origWidth * 0.3),
height: Math.floor(origHeight * 0.3)
})
.resize(detailSize, detailSize, { fit: 'cover' })
.toBuffer();
// 创建圆形遮罩
const circleMask = Buffer.from(`
<svg width="${detailSize}" height="${detailSize}">
<circle cx="${detailSize/2}" cy="${detailSize/2}" r="${detailSize/2}" fill="white"/>
</svg>
`);
const detail1Circle = await sharp(detail1)
.composite([{ input: circleMask, blend: 'dest-in' }])
.png()
.toBuffer();
const detail2Circle = await sharp(detail2)
.composite([{ input: circleMask, blend: 'dest-in' }])
.png()
.toBuffer();
// 添加绿色边框
const borderSvg = Buffer.from(`
<svg width="${detailSize}" height="${detailSize}">
<circle cx="${detailSize/2}" cy="${detailSize/2}" r="${detailSize/2 - 2}"
fill="none" stroke="#2D4A3E" stroke-width="4"/>
</svg>
`);
const detail1WithBorder = await sharp(detail1Circle)
.composite([{ input: borderSvg }])
.toBuffer();
const detail2WithBorder = await sharp(detail2Circle)
.composite([{ input: borderSvg }])
.toBuffer();
// 创建标签文字
const label1Svg = Buffer.from(`
<svg width="350" height="40">
<text x="0" y="28" font-size="22" fill="#2D4A3E" font-weight="bold"
font-family="Arial, sans-serif">DURABLE WATERPROOF PU LAYER</text>
</svg>
`);
const label2Svg = Buffer.from(`
<svg width="300" height="40">
<text x="0" y="28" font-size="22" fill="#2D4A3E" font-weight="bold"
font-family="Arial, sans-serif">DOUBLE-LAYER COMFORT</text>
</svg>
`);
// 合成所有图层
const productTop = bannerHeight + 50;
const productLeft = (targetSize - productSize) / 2;
const composite = await sharp(background)
.composite([
{ input: bannerBuffer, top: 0, left: 0 },
{ input: productBuffer, top: productTop, left: productLeft },
{ input: detail1WithBorder, top: targetSize - detailSize - 120, left: 80 },
{ input: await sharp(label1Svg).png().toBuffer(), top: targetSize - 80, left: 80 },
{ input: detail2WithBorder, top: targetSize - detailSize - 120, left: targetSize - detailSize - 80 },
{ input: await sharp(label2Svg).png().toBuffer(), top: targetSize - 80, left: targetSize - 300 - 80 }
])
.jpeg({ quality: 95 })
.toBuffer();
await imageProcessor.saveImage(composite, path.join(outputDir, 'Main_02.jpg'));
return true;
}
/**
* Main_03-06 和 APlus_01-06: 使用原图让AI编辑
* 策略传入原图让AI保持产品不变只做背景和文字编辑
*/
async function generateImageWithOriginal(id, name, materials, prompt, aspectRatio, outputDir) {
console.log(`\n🎨 [${id}] ${name}`);
// 选择合适的参考图
const refImage = id.includes('02') || id.includes('05') || id.includes('06')
? materials.flatImage
: materials.wornImage;
const refBuffer = fs.readFileSync(refImage);
const refUrl = await uploadToR2(refBuffer, `ref-${id}.jpg`);
const result = await generateAIImage(prompt, refUrl, aspectRatio);
await imageProcessor.saveImage(result, path.join(outputDir, `${id}.jpg`));
return true;
}
/**
* APlus_02: 对比图 - 特殊处理
* 左边用原图右边AI生成竞品
*/
async function generateAPlus02(materials, config, outputDir) {
console.log('\n🎨 [APlus_02] 对比图');
// 用原佩戴图作为参考
const wornBuffer = fs.readFileSync(materials.wornImage);
const wornUrl = await uploadToR2(wornBuffer, 'comparison-ref.jpg');
const prompt = `
Create an Amazon A+ comparison image (landscape 3:2 ratio):
LEFT SIDE (colorful, positive):
- Show a happy cat/dog wearing the EXACT same soft cone collar from the reference image
- The cone collar must match the reference EXACTLY in color, shape, and material
- Green checkmark icon
- Label "OUR" on coral/orange background
- 3 selling points in white text:
• CLOUD-LIGHT COMFORT
• WIDER & CLEARER
• FOLDABLE & PORTABLE
RIGHT SIDE (grayscale, negative):
- Show a sad cat wearing a HARD PLASTIC transparent cone (traditional lampshade style)
- Red X icon
- Label "OTHER" on gray background
- Grayscale/desaturated colors
- 3 weaknesses in gray text:
• HEAVY & BULKY
• BLOCKS VISION & MOVEMENT
• HARD TO STORE
CRITICAL:
- LEFT product MUST match the reference image exactly (soft fabric cone)
- RIGHT product should be a generic hard plastic cone (NOT our product)
- NO product image in the center
- Background: warm beige with paw print watermarks
`.trim();
const result = await generateAIImage(prompt, wornUrl, '3:2');
await imageProcessor.saveImage(result, path.join(outputDir, 'APlus_02.jpg'));
return true;
}
// ============================================================
// 主流程
// ============================================================
async function main() {
const args = process.argv.slice(2);
let materialDir = path.join(__dirname, '素材/素材/已有的素材');
let skuName = 'Touchdog冰蓝色伊丽莎白圈';
for (const arg of args) {
if (arg.startsWith('--material-dir=')) {
materialDir = arg.split('=')[1];
} else if (arg.startsWith('--sku-name=')) {
skuName = arg.split('=')[1];
}
}
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-');
const safeName = skuName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_').slice(0, 20);
const outputDir = path.join(__dirname, `output_v5_${safeName}_${timestamp}`);
console.log('\n' + '═'.repeat(70));
console.log('🚀 POC Workflow V5 - P图合成工作流');
console.log('═'.repeat(70));
console.log('\n📋 核心策略: 保持原素材产品不变AI只编辑背景和文字');
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));
// 识别平铺图和佩戴图
let flatImage = images.find(i => i.includes('5683') || i.includes('1.png'));
let wornImage = images.find(i => i.includes('6514') || i.includes('3.png') || i.includes('3.jpg'));
if (!flatImage) flatImage = images.find(i => i.endsWith('.png')) || images[0];
if (!wornImage) wornImage = images.find(i => i.endsWith('.jpg') || i.endsWith('.JPG')) || flatImage;
const materials = {
flatImage: path.join(materialDir, flatImage),
wornImage: path.join(materialDir, wornImage)
};
console.log('\n📁 素材:');
console.log(' 平铺图:', flatImage);
console.log(' 佩戴图:', wornImage);
const config = { brandName: 'TOUCHDOG', productName: 'CAT SOFT CONE COLLAR' };
const results = [];
// ========== 生成12张图 ==========
console.log('\n' + '─'.repeat(70));
console.log('🎨 开始生成12张图');
console.log('─'.repeat(70));
try {
// Main_01: 场景首图
results.push({ id: 'Main_01', success: await generateMain01(materials, config, outputDir) });
} catch (e) {
console.error('Main_01 失败:', e.message);
results.push({ id: 'Main_01', success: false, error: e.message });
}
try {
// Main_02: 白底平铺+细节(代码合成)
results.push({ id: 'Main_02', success: await generateMain02(materials, config, outputDir) });
} catch (e) {
console.error('Main_02 失败:', e.message);
results.push({ id: 'Main_02', success: false, error: e.message });
}
// Main_03: 功能调节展示
try {
const prompt03 = `
Edit this image to create an Amazon product feature showcase:
1. KEEP the pet and product EXACTLY as shown
2. Use a background color that matches the product's main color
3. Add title at top-left: "ADJUSTABLE STRAP FOR A SECURE FIT"
4. Add 2 circular detail callouts on the right side:
- One showing velcro closure detail, label: "SECURE THE ADJUSTABLE STRAP"
- One showing the product from another angle, label: "ADJUST FOR A SNUG FIT"
5. Add paw print decorations
Style: Professional Amazon infographic, 1:1 square
`.trim();
results.push({ id: 'Main_03', success: await generateImageWithOriginal('Main_03', '功能调节展示', materials, prompt03, '1:1', outputDir) });
} catch (e) {
console.error('Main_03 失败:', e.message);
results.push({ id: 'Main_03', success: false, error: e.message });
}
// Main_04: 多场景使用
try {
const prompt04 = `
Create a 2x2 grid Amazon product image showing 4 usage scenarios:
Based on the reference image, show the SAME product (keep it identical) in 4 scenes:
TOP-LEFT: Cat standing, wearing the product
Caption: "• HYGIENIC & EASY TO CLEAN"
TOP-RIGHT: Cat eating from bowl while wearing product
Caption: "• UNRESTRICTED EATING/DRINKING"
BOTTOM-LEFT: Cat playing/stretching with product
Caption: "• REVERSIBLE WEAR"
BOTTOM-RIGHT: Cat sleeping peacefully with product
Caption: "• 360° COMFORT"
CRITICAL: The product in ALL 4 scenes must match the reference image EXACTLY
Background: Warm beige with paw print watermarks
Style: Rounded rectangle frames for each scene
`.trim();
results.push({ id: 'Main_04', success: await generateImageWithOriginal('Main_04', '多场景使用', materials, prompt04, '1:1', outputDir) });
} catch (e) {
console.error('Main_04 失败:', e.message);
results.push({ id: 'Main_04', success: false, error: e.message });
}
// Main_05: 尺寸图(代码生成)
try {
console.log('\n🎨 [Main_05] 尺寸图');
const flatBuffer = fs.readFileSync(materials.flatImage);
const targetSize = 1600;
const productSize = Math.floor(targetSize * 0.5);
const productBuffer = await sharp(flatBuffer)
.resize(productSize, productSize, { fit: 'contain', background: { r: 245, g: 237, b: 228, alpha: 1 } })
.toBuffer();
// 创建米色背景
const background = await sharp({
create: { width: targetSize, height: targetSize, channels: 3, background: { r: 245, g: 237, b: 228 } }
}).jpeg().toBuffer();
// 标题
const titleSvg = Buffer.from(`
<svg width="${targetSize}" height="100">
<text x="${targetSize/2}" y="70" font-size="48" fill="#333" font-weight="bold"
font-family="Arial, sans-serif" text-anchor="middle">PRODUCT SIZE</text>
</svg>
`);
// 尺寸表
const tableSvg = Buffer.from(`
<svg width="800" height="400">
<rect x="0" y="0" width="800" height="50" fill="#E8D5C4" rx="5"/>
<text x="100" y="35" font-size="20" fill="#333" font-weight="bold" font-family="Arial">SIZE</text>
<text x="350" y="35" font-size="20" fill="#333" font-weight="bold" font-family="Arial">NECK</text>
<text x="600" y="35" font-size="20" fill="#333" font-weight="bold" font-family="Arial">DEPTH</text>
<rect x="0" y="55" width="800" height="45" fill="#FFF"/>
<text x="100" y="85" font-size="18" fill="#333" font-family="Arial">XS</text>
<text x="350" y="85" font-size="18" fill="#333" font-family="Arial">5.6-6.8IN</text>
<text x="600" y="85" font-size="18" fill="#333" font-family="Arial">3.2IN</text>
<rect x="0" y="105" width="800" height="45" fill="#F5F5F5"/>
<text x="100" y="135" font-size="18" fill="#333" font-family="Arial">S</text>
<text x="350" y="135" font-size="18" fill="#333" font-family="Arial">7.2-8.4IN</text>
<text x="600" y="135" font-size="18" fill="#333" font-family="Arial">4IN</text>
<rect x="0" y="155" width="800" height="45" fill="#FFF"/>
<text x="100" y="185" font-size="18" fill="#333" font-family="Arial">M</text>
<text x="350" y="185" font-size="18" fill="#333" font-family="Arial">8.8-10.4IN</text>
<text x="600" y="185" font-size="18" fill="#333" font-family="Arial">5IN</text>
<rect x="0" y="205" width="800" height="45" fill="#F5F5F5"/>
<text x="100" y="235" font-size="18" fill="#333" font-family="Arial">L</text>
<text x="350" y="235" font-size="18" fill="#333" font-family="Arial">10.8-12.4IN</text>
<text x="600" y="235" font-size="18" fill="#333" font-family="Arial">6IN</text>
<rect x="0" y="255" width="800" height="45" fill="#FFF"/>
<text x="100" y="285" font-size="18" fill="#333" font-family="Arial">XL</text>
<text x="350" y="285" font-size="18" fill="#333" font-family="Arial">12.8-14.4IN</text>
<text x="600" y="285" font-size="18" fill="#333" font-family="Arial">7IN</text>
<text x="400" y="340" font-size="14" fill="#E8876C" font-family="Arial" text-anchor="middle">
NOTE: ALWAYS MEASURE YOUR PET'S NECK BEFORE SELECTING A SIZE
</text>
</svg>
`);
const composite = await sharp(background)
.composite([
{ input: await sharp(titleSvg).png().toBuffer(), top: 20, left: 0 },
{ input: productBuffer, top: 120, left: (targetSize - productSize) / 2 },
{ input: await sharp(tableSvg).png().toBuffer(), top: targetSize - 450, left: (targetSize - 800) / 2 }
])
.jpeg({ quality: 95 })
.toBuffer();
await imageProcessor.saveImage(composite, path.join(outputDir, 'Main_05.jpg'));
results.push({ id: 'Main_05', success: true });
} catch (e) {
console.error('Main_05 失败:', e.message);
results.push({ id: 'Main_05', success: false, error: e.message });
}
// Main_06: 多角度展示
try {
const prompt06 = `
Edit this image to show multiple angles:
1. Split the image into two parts with a curved decorative divider
2. LEFT: Keep the cat wearing product as shown
3. RIGHT: Show the same cat from a different angle (side view)
4. CRITICAL: The product must be IDENTICAL in both views
5. Warm home interior background
6. NO text overlay
Style: Professional lifestyle photography, 1:1 square
`.trim();
results.push({ id: 'Main_06', success: await generateImageWithOriginal('Main_06', '多角度展示', materials, prompt06, '1:1', outputDir) });
} catch (e) {
console.error('Main_06 失败:', e.message);
results.push({ id: 'Main_06', success: false, error: e.message });
}
// APlus_01: 品牌横幅
try {
const prompt_a01 = `
Create an Amazon A+ brand banner (landscape 3:2):
1. LEFT 40%: Brand text area
- "TOUCHDOG" in playful coral/salmon color (#E8A87C) with paw prints
- "CAT SOFT CONE COLLAR" below in gray
2. RIGHT 60%: Show the cat wearing the product EXACTLY as in reference
3. Warm cozy home background
4. The product must match the reference EXACTLY
`.trim();
results.push({ id: 'APlus_01', success: await generateImageWithOriginal('APlus_01', '品牌横幅', materials, prompt_a01, '3:2', outputDir) });
} catch (e) {
console.error('APlus_01 失败:', e.message);
results.push({ id: 'APlus_01', success: false, error: e.message });
}
// APlus_02: 对比图
try {
results.push({ id: 'APlus_02', success: await generateAPlus02(materials, config, outputDir) });
} catch (e) {
console.error('APlus_02 失败:', e.message);
results.push({ id: 'APlus_02', success: false, error: e.message });
}
// APlus_03-06: 其他A+图
const aplusPrompts = {
'APlus_03': {
name: '功能细节',
prompt: `Create Amazon A+ feature details (3:2):
Title: "ENGINEERED FOR UNCOMPROMISED COMFORT"
Show 3 detail images with captions:
1. Inner lining close-up: "STURDY AND BREATHABLE"
2. Pet wearing with size marker: "EASY TO CLEAN, STYLISH"
3. Stitching detail: "REINFORCED STITCHING"
Product must match reference EXACTLY. Warm beige background.`
},
'APlus_04': {
name: '多场景横版',
prompt: `Create Amazon A+ horizontal 4-scene image (3:2):
4 scenes in a row, each showing the SAME product (match reference):
1. Cat standing: "HYGIENIC & EASY TO CLEAN"
2. Cat eating: "UNRESTRICTED EATING"
3. Cat playing: "REVERSIBLE WEAR"
4. Cat sleeping: "360° COMFORT"
Warm beige background with paw prints.`
},
'APlus_05': {
name: '多角度横版',
prompt: `Create Amazon A+ multiple angles image (3:2):
Show 2 views side by side with curved divider:
LEFT: Front view of cat wearing product
RIGHT: Side view of same product
Product must match reference EXACTLY. NO text. Warm background.`
},
'APlus_06': {
name: '尺寸表横版',
prompt: `Create Amazon A+ size chart (3:2):
LEFT: Product flat lay with dimension arrows (NECK, WIDTH)
RIGHT: Size chart table (XS to XL)
Title: "PRODUCT SIZE"
Product must match reference EXACTLY. Warm beige background.`
}
};
for (const [id, info] of Object.entries(aplusPrompts)) {
try {
results.push({ id, success: await generateImageWithOriginal(id, info.name, materials, info.prompt, '3:2', outputDir) });
} catch (e) {
console.error(`${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}`);
fs.writeFileSync(path.join(outputDir, 'results.json'), JSON.stringify(results, null, 2));
}
main().catch(console.error);

876
poc-workflow-v6.js Normal file
View File

@@ -0,0 +1,876 @@
/**
* 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);

383
poc-workflow.js Normal file
View File

@@ -0,0 +1,383 @@
/**
* POC Workflow: Vision Analysis -> Optimized Generation (Full 12 Images)
* API Provider: wuyinkeji.com
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// 配置
const API_KEY = 'G9rXx3Ag2Xfa7Gs8zou6t6HqeZ';
const API_BASE = 'https://api.wuyinkeji.com/api';
const OUTPUT_DIR = path.join(__dirname, 'output_poc_full');
const MATERIAL_DIR = path.join(__dirname, '素材/素材/已有的素材');
// 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(filePath) {
const fileName = `poc-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: filePath.endsWith('.png') ? 'image/png' : 'image/jpeg',
}));
const publicUrl = 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}`;
return publicUrl;
}
// 核心函数1视觉分析 (Vision Agent)
async function analyzeProductVisuals(refImageUrls) {
console.log('👁️ 正在进行视觉分析 (Vision Agent)...');
const analysisPrompt = `
You are a professional e-commerce product photographer.
Analyze these reference images of a pet recovery cone collar.
Extract visual characteristics:
1. Material & Texture: (e.g. glossy PU, matte cotton)
2. Structure (Flat): (e.g. Open C-shape, Fan shape)
3. Structure (Worn): (e.g. Cone shape flared OUTWARDS/FORWARD)
4. Key Details: (e.g. logo, binding color)
Output JSON: { "material": "...", "structure_flat": "...", "structure_worn": "...", "details": "..." }
`;
try {
// 构造Wuyin Vision请求
// 注意:根据文档截图,/api/chat/index 支持 image_url 参数
// 这里我们只传第一张图作为主要参考或者尝试传多张如果API支持
// 由于接口文档通常 image_url 是字符串,我们先传最具代表性的图(通常是第一张)
// 如果需要多图分析,可能需要多次调用或拼接
// 这里简单起见,我们取一张最清晰的图(假设是第一张或摊平图)进行分析
const mainRefImage = refImageUrls.find(url => url.includes('5683')) || refImageUrls[0];
const response = await axios.post(
`${API_BASE}/chat/index`,
{
key: API_KEY,
model: 'gemini-3-pro', // 明确指定 vision 模型为 gemini-3-pro
content: analysisPrompt,
image_url: mainRefImage
},
{ timeout: 60000 }
);
let content = response.data.data?.content || response.data.choices?.[0]?.message?.content;
if (!content) throw new Error('No content in vision response');
content = content.replace(/```json|```/g, '').trim();
// 尝试解析JSON如果失败则用正则提取或兜底
let analysis;
try {
analysis = JSON.parse(content);
} catch (e) {
console.warn('JSON parse failed, using raw content');
analysis = {
material: "Smooth waterproof PU, slightly glossy",
structure_flat: "Open C-shape/Fan shape",
structure_worn: "Cone flared upwards/outwards",
details: "Embroidered Touchdog logo"
};
}
console.log('✅ 视觉分析完成:', analysis);
return analysis;
} catch (error) {
console.error('❌ 视觉分析失败:', error.message);
// 兜底描述
return {
material: "Smooth waterproof PU fabric with a slight sheen",
structure_flat: "Open C-shape (Fan-like) with petal segments",
structure_worn: "Cone shape flared UPWARDS/FORWARD around head",
details: "Embroidered Touchdog logo on right petal"
};
}
}
// 生图任务提交
async function submitImageTask(prompt, refImageUrls, aspectRatio = '1:1') {
try {
// Wuyin API: /api/img/nanoBanana-pro
// 参数: key, prompt, img_url (参考图), aspectRatio
// 注意img_url 只能传一张。我们传最相关的一张。
// 对于场景图,传佩戴图;对于平铺图,传平铺图。
let refImg = refImageUrls[0];
if (prompt.includes('flat') && refImageUrls.some(u => u.includes('5683'))) {
refImg = refImageUrls.find(u => u.includes('5683')); // 摊平图
} else if (refImageUrls.some(u => u.includes('6514'))) {
refImg = refImageUrls.find(u => u.includes('6514')); // 佩戴图
}
const payload = {
key: API_KEY,
prompt: prompt,
img_url: refImg, // 传单张参考图强约束
aspectRatio: aspectRatio,
imageSize: '1K'
};
console.log(` 提交任务... (Ref: ${path.basename(refImg || '')})`);
const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload);
// 假设返回格式 { code: 200, data: { id: "..." }, msg: "success" }
// 或者直接返回 task_id需根据实际响应调整
// 根据截图: 异步任务通常返回 id
// 这里假设 API 返回包含 task id
const taskId = response.data.data?.id; // 修正: 获取 data.id
if (!taskId) {
console.error('Submit response:', response.data);
throw new Error('No task ID returned');
}
return taskId;
} catch (error) {
console.error('Submit failed:', error.message);
throw error;
}
}
// 轮询图片结果
async function pollImageResult(taskId) {
let attempts = 0;
const maxAttempts = 60; // 60 * 2s = 2 mins
while (attempts < maxAttempts) {
try {
const response = await axios.get(`${API_BASE}/img/drawDetail`, {
params: { key: API_KEY, id: taskId }
});
// 状态判断修正: status 2 是成功
const data = response.data.data;
if (data && data.status === 2 && data.image_url) {
return data.image_url;
} else if (data && (data.status === 3 || data.status === 'failed')) { // 假设 3 是失败,或者保留 failed 字符串判断
throw new Error('Generation failed: ' + (data.fail_reason || 'Unknown'));
}
console.log(` 状态: ${data.status} (等待中...)`);
// Still processing
await new Promise(r => setTimeout(r, 2000));
attempts++;
process.stdout.write('.');
} catch (error) {
console.error('Poll error:', error.message);
throw error; // 直接抛出,触发重试
}
}
throw new Error('Timeout waiting for image');
}
// 生成单张图(含重试)
async function generateSingleImage(item, refImageUrls) {
console.log(`\n🎨 生成 ${item.id} (${item.type})...`);
let retryCount = 0;
const maxRetries = 2;
while (retryCount <= maxRetries) {
try {
if (retryCount > 0) console.log(` 重试第 ${retryCount} 次...`);
const taskId = await submitImageTask(item.prompt, refImageUrls, item.aspectRatio);
console.log(` Task ID: ${taskId}`);
const imageUrl = await pollImageResult(taskId);
console.log(' ✓ 生成成功');
// 下载保存
const imgRes = await axios.get(imageUrl, { responseType: 'arraybuffer' });
fs.writeFileSync(path.join(OUTPUT_DIR, `${item.id}.jpg`), imgRes.data);
console.log(' ✓ 保存本地');
return; // 成功退出
} catch (error) {
console.error(` ✗ 失败: ${error.message}`);
retryCount++;
await new Promise(r => setTimeout(r, 3000));
}
}
console.error(`${item.id} 最终失败`);
}
// 主流程
async function main() {
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
// 1. 上传
console.log('📤 Step 1: 上传素材...');
const files = fs.readdirSync(MATERIAL_DIR).filter(f => /\.(jpg|png)$/i.test(f));
const refImageUrls = [];
files.sort();
for (const file of files) {
if (file.startsWith('.')) continue;
const url = await uploadToR2(path.join(MATERIAL_DIR, file));
refImageUrls.push(url);
}
console.log(`${refImageUrls.length}`);
// 2. 视觉分析
const va = await analyzeProductVisuals(refImageUrls);
// const va = {
// material: "Smooth waterproof PU fabric with a slight glossy sheen",
// structure_flat: "Open C-shape (Fan-like) with petal segments, NOT a closed circle",
// structure_worn: "Cone shape flared UPWARDS/FORWARD around head (megaphone shape)",
// details: "Embroidered Touchdog logo on right petal, teal edge binding"
// };
// 3. 定义12张图的Prompt (结合视觉分析)
const prompts = [
// --- 主图 (1:1) ---
{
id: 'Main_01_Hero',
type: '场景首图',
aspectRatio: '1:1',
prompt: `Amazon main image. Ragdoll cat wearing ${va.material} cone collar.
CRITICAL: Cone opening must face FORWARD/OUTWARDS around the cat's face (like a megaphone), NOT wrapped like a scarf.
Cat is looking comfortable. Warm cozy home background.
Product texture: ${va.material}. Structure: ${va.structure_worn}.
Logo: Touchdog embroidered on right side. High quality, 8k.`
},
{
id: 'Main_02_Flat',
type: '平铺图',
aspectRatio: '1:1',
prompt: `Product flat lay, white background.
Structure: ${va.structure_flat} (Open C-shape/Fan-like, NOT closed circle).
Texture: ${va.material}, glossy PU sheen.
Details: Velcro strap visible, ${va.details}. Clean studio light.`
},
{
id: 'Main_03_Function',
type: '功能演示',
aspectRatio: '1:1',
prompt: `Product feature shot. Close-up of the velcro strap adjustment.
Hands adjusting the strap for secure fit.
Show the ${va.material} texture and stitching quality.
Light blue background with text overlay "ADJUSTABLE FIT" (optional).`
},
{
id: 'Main_04_Scenarios',
type: '场景拼图',
aspectRatio: '1:1',
prompt: `Split screen composition.
Top: Cat eating food easily while wearing the cone (cone facing forward).
Bottom: Cat sleeping comfortably.
Show flexibility and comfort. Soft lighting.`
},
{
id: 'Main_05_Size',
type: '尺寸图',
aspectRatio: '1:1',
prompt: `Product infographic. The ${va.structure_flat} cone laid flat.
Ruler graphics measuring the depth (24.5cm).
Size chart table on the side: XS, S, M, L, XL.
Professional clean design.`
},
{
id: 'Main_06_Brand',
type: '品牌汇总',
aspectRatio: '1:1',
prompt: `Brand image. Large Touchdog logo in center.
Product floating in background.
Icons: Feather (Lightweight), Water drop (Waterproof), Cotton (Soft).
High-end branding style.`
},
// --- A+图 (3:2) ---
{
id: 'APlus_01_Banner',
type: '品牌Banner',
aspectRatio: '3:2',
prompt: `Amazon A+ Banner. Wide shot of modern living room.
White cat wearing the ${va.material} cone walking on rug.
Cone flares outwards correctly.
Text space on left. Warm, inviting atmosphere.`
},
{
id: 'APlus_02_Comparison',
type: '竞品对比',
aspectRatio: '3:2',
prompt: `Comparison image. Split screen.
Left (Touchdog): Colorful, happy cat wearing soft blue cone. Green Checkmark.
Right (Others): Black and white, sad cat wearing hard plastic transparent cone. Red Cross.
Text: "Soft & Comfy" vs "Hard & Heavy".`
},
{
id: 'APlus_03_Detail',
type: '材质细节',
aspectRatio: '3:2',
prompt: `Extreme close-up macro shot of the product material.
Show the waterproof PU texture with water droplets beading off.
Show the neat stitching and soft edge binding.
High tech, quality feel.`
},
{
id: 'APlus_04_Waterproof',
type: '防水测试',
aspectRatio: '3:2',
prompt: `Action shot. Water being poured onto the blue cone collar.
Water repelling instantly, rolling off the surface.
Demonstrates "Easy to Wipe" feature.
Bright lighting.`
},
{
id: 'APlus_05_Storage',
type: '收纳展示',
aspectRatio: '3:2',
prompt: `Lifestyle shot. The cone collar folded/rolled up compactly.
Placed inside a small travel bag or drawer.
Shows "Easy Storage" feature.
Clean composition.`
},
{
id: 'APlus_06_Guide',
type: '选购指南',
aspectRatio: '3:2',
prompt: `Sizing guide banner.
Illustration of how to measure cat's neck circumference.
Touchdog branding elements.
Clean, instructional design.`
}
];
// 4. 执行生成
console.log('\n🚀 开始全量生成 (12张)...');
for (const item of prompts) {
await generateSingleImage(item, refImageUrls);
}
console.log('\n✅ POC 完整验证结束。');
}
main().catch(console.error);

29
product.md Normal file
View File

@@ -0,0 +1,29 @@
# 伊丽莎白圈产品介绍
## 核心卖点
1. **极致轻盈**仅65g约1颗鸡蛋重量云感舒适不束缚宠物活动玩耍、进食饮水无压力。
2. **全面防水易清洁**外层采用防水PU面料污渍一擦即净耐脏防潮保持项圈干燥卫生。
3. **舒适亲肤设计**内层填充PP棉与棉氨纶混纺材质搭配亲肤罗纹包边柔软透气微孔透气结构+加固车线工艺,久戴不闷、揉搓不变形。
4. **实用结构优势**加宽视野设计不遮挡宠物视线可折叠便携轻松卷起收纳于宠物急救包3秒穿脱可调节粘扣确保稳固贴合。
5. **高颜值多选择**:推出幻彩系列、马卡龙色系及优雅花朵设计(含蓝爱五、淡草莓、暮霭粉等颜色),兼具美观与实用性。
6. **强效防护**加宽围度设计24.5cm等规格),术后能有效防止宠物舔咬伤口,适用于多种场景防护。
## 适用场景
术后恢复、驱虫护理、指甲修剪、美容护理、伤口治疗、绝育术后防护、皮肤病管理等。
## 尺寸规格
| 尺寸 | 颈围范围cm/in | 深度cm/in |
|------|-------------------|---------------|
| XS | 14-17/5.5-6.7 | 8/3.1 |
| S | 18-21/7.1-8.3 | 13/5.1 |
| M | 22-26/8.7-10.2 | 12.5/4.9 |
| L | 27-31/10.6-12.2 | 15/5.9 |
| XL | 32-36/12.6-14.2 | 17.5/6.9 |
## 材质工艺
- 外层耐用防水PU面料防污抗皱
- 内层PP棉填充+95%棉+5%氨纶高密罗纹,亲肤吸湿
- 工艺:加固车线、精密缝线,刺绣品牌标识,质感出众
## 品牌优势
Touchdog创立于2004年作为宠物美装品类开创者秉持原创设计理念整合全球设计资源20年斩获100余项设计大奖含PFAAWARDS亚宠2022年度产品设计大奖专注人宠生活美学产品创作。

41
prompt.md Normal file
View File

@@ -0,0 +1,41 @@
# Role: 亚马逊高级电商视觉总监 & AI提示词工程师
# Objective: 根据提供的产品信息和图片需求编写适用于AI绘画工具如NanoBanana/Midjourney/Stable Diffusion的高质量英文提示词Prompts
# Guidelines for Prompt Generation (视觉转化规则):
1. **结构化写作**: 每个Prompt必须包含[主体描述] + [环境/背景] + [光影/氛围] + [构图视角] + [画质修饰词]。
2. **卖点视觉化**: 不要直接在Prompt里写卖点文字如"Waterproof"),而是描述表现该卖点的画面(如"Water droplets beading off the surface, hydrophobic effect")。
3. **一致性控制**:
- 确保所有图片中的产品必须是参考图中的产品。
- 主图Main Images通常需要纯白背景White Background或极简背景。
- A+图Lifestyle Images需要具体的居家或户外场景具有生活感。
4. **尺寸适配**:
- 主图 Prompt 末尾添加参数 `--ar 1:1`
- A+图 Prompt 末尾添加参数 `--ar 3:2` (或接近970:600的比例)
# Output Format (JSON):
请严格按照以下JSON格式返回不要包含Markdown代码块以外的文字
```json
[
{
"id": "Main_01",
"type": "Main Image (1600x1600)",
"purpose": "核心卖点/首图",
"visual_description": "中文描述该图的设计思路(例如:正面特写,展示整体结构)",
"ai_prompt": "English Prompt here, Subject, Action, Context, Lighting, Style, --ar 1:1"
},
{
"id": "APlus_01",
"type": "A+ Image (970x600)",
"purpose": "适用场景/佩戴演示",
"visual_description": "中文描述...",
"ai_prompt": "English Prompt here... --ar 3:2"
}
// ...以此类推
]
```
# Input Data
## 图片设计要求:
[图片设计要求]
## 产品介绍:
[产品介绍]

0
prompts/brain-system.md Normal file
View File

View File

@@ -0,0 +1,145 @@
# 品牌VI约束模板
## 约束级别P1
此约束确保所有图片符合Touchdog品牌视觉识别规范。
---
## Logo体系
### 产品刺绣Logo出现在产品上
- **文字**: TOUCHDOG®大写带®符号
- **位置**: 产品右侧花瓣区域2-3点钟方向
- **样式**: 刺绣效果,凹陷质感
- **颜色**: 与产品主色协调的深色调
### 电商图片Logo出现在图片角落
- **图形**: 红色心形狗狗造型
- **文字**: touchdog®全小写带®符号
- **标语**: wow pretty国际版
- **组合**: 图形 + 文字 + 标语
---
## Logo颜色适配规则
```
=== BRAND LOGO COLOR ADAPTATION ===
Determine background brightness and apply correct logo color:
IF background_brightness > 60% (light background):
→ Use RED logo (#E60012)
→ Red graphic icon + black "touchdog®" text
IF background_brightness < 50% (dark background):
→ Use WHITE logo
→ White graphic icon + white "touchdog®" text
IF background_brightness 50-60% (medium):
→ Evaluate contrast and choose accordingly
→ Prefer the option with better visibility
=== END LOGO COLOR ADAPTATION ===
```
---
## 约束模板(自动注入)
```
=== BRAND VI REQUIREMENTS ===
EMBROIDERED LOGO ON PRODUCT:
- Text: "TOUCHDOG®" (UPPERCASE)
- Position: Right petal segment, 2-3 o'clock position
- Style: Embroidered, stitched into fabric
- Color: Slightly darker shade of product color
E-COMMERCE IMAGE BRAND LOGO:
- Position: {{logo_placement.position}}
- Type: {{logo_placement.type}}
- Color: {{logo_placement.color}}
LOGO SPECIFICATIONS:
- Clear space: Minimum 1/4 of logo height on all sides
- Minimum size: Combined logo ≥ 46px height
- Tagline: "wow pretty" (for international markets)
PROHIBITED LOGO MODIFICATIONS:
- ❌ NO tilting or rotation
- ❌ NO outlines or strokes
- ❌ NO gradient fills
- ❌ NO drop shadows
- ❌ NO proportion changes (stretching/squishing)
- ❌ NO underlines
BRAND TYPOGRAPHY:
- Headlines: OPPOsans-Bold or similar clean sans-serif
- Body text: OPPOsans-Medium or similar
- Style: Clean, modern, professional
BRAND COLOR PALETTE:
- Primary Red: #E60012
- Text Black: #333333
- Background suggestions based on product color:
- Ice Blue product → Light blue/white background
- Iridescent product → Warm beige/cream background
- Solid color product → Complementary light background
=== END BRAND VI REQUIREMENTS ===
```
---
## Logo位置规范
### 主图Logo位置
| 图片类型 | 推荐位置 | 备选位置 |
|----------|----------|----------|
| M1 场景首图 | bottom-right | none产品上已有Logo |
| M2 产品平铺 | none | bottom-right |
| M3 功能演示 | bottom-right | none |
| M4 场景四宫格 | bottom-right | none |
| M5 尺寸规格 | bottom-right | none |
| M6 品牌汇总 | center或prominent | - |
### A+图Logo位置
| 图片类型 | 推荐位置 | 备选位置 |
|----------|----------|----------|
| A1 品牌Banner | prominent/center | top-left |
| A2 竞品对比 | bottom-right我方侧 | none |
| A3 卖点三宫格 | bottom-right | none |
| A4 功能四宫格 | bottom-right | none |
| A5 材质工艺 | bottom-right | none |
| A6 尺寸指南 | bottom-right | none |
---
## 违规案例
以下Logo使用方式是禁止的
1. **倾斜Logo** - 任何角度的旋转都不允许
2. **描边Logo** - 不能给Logo添加外轮廓
3. **渐变Logo** - 不能使用渐变色填充
4. **阴影Logo** - 不能添加投影效果
5. **变形Logo** - 不能拉伸或压缩比例
6. **下划线Logo** - 不能在文字下方添加线条
---
## 验证检查点
生成图片后,检查:
- [ ] 产品上的刺绣Logo是TOUCHDOG®大写
- [ ] 电商图片Logo是touchdog®小写
- [ ] Logo颜色与背景亮度匹配浅底红Logo深底白Logo
- [ ] Logo周围有足够净空间
- [ ] Logo没有倾斜、渐变、阴影等违规处理
- [ ] Logo尺寸不小于46px高度

459
prompts/image-templates.js Normal file
View File

@@ -0,0 +1,459 @@
/**
* 基于真实交付成品的12张图Prompt模板
* 每张图都包含文字排版要求
*/
// ========================================
// 主图6张 (1600x1600px, 1:1)
// ========================================
const MAIN_TEMPLATES = {
// Main_01: 场景首图 + 卖点
// 参考: 幻彩主图冰蓝色 (1).jpg
Main_01: (product, sellingPoints) => `
[AMAZON MAIN IMAGE - LIFESTYLE WITH TEXT OVERLAY]
PRODUCT DESCRIPTION:
${product.goldenDescription}
SCENE:
- Beautiful cat wearing the product
- Warm, cozy home interior background (soft focus)
- Product clearly visible, 60% of frame
- Cat looks comfortable and relaxed
TEXT OVERLAY (MUST INCLUDE):
- CENTER-LEFT AREA: Curved banner in muted blue (#8BB8C4)
- TITLE on banner: "DESIGNED FOR COMFORTABLE RECOVERY" in white, clean sans-serif font
- BOTTOM: 3 feature boxes in rounded rectangles with icons
- Box 1: "LIGHTER THAN AN EGG" with egg icon
- Box 2: "WATERPROOF & EASY WIPE" with water droplet icon
- Box 3: "BREATHABLE COTTON LINING" with cloud icon
STYLE:
- Professional Amazon product photography
- Text is clean, readable, modern sans-serif
- Subtle paw print watermarks in background
- 8K quality, 1:1 aspect ratio
`,
// Main_02: 白底平铺图 (纯产品,无文字)
Main_02: (product) => `
[AMAZON MAIN IMAGE - FLAT LAY WHITE BACKGROUND]
PRODUCT DESCRIPTION:
${product.goldenDescription}
SCENE:
- Product flat lay on PURE WHITE background (#FFFFFF)
- Bird's eye view / top-down angle
- Full C-shaped opening visible
- All petals/segments spread out
- Clean studio lighting, minimal shadows
- Product fills 80% of frame
NO TEXT OVERLAY ON THIS IMAGE.
STYLE:
- Clean Amazon product photography
- 8K quality, 1:1 aspect ratio
`,
// Main_03: 功能特写
Main_03: (product) => `
[AMAZON MAIN IMAGE - FEATURE CLOSE-UP]
PRODUCT DESCRIPTION:
${product.goldenDescription}
SCENE:
- Close-up shot of the product's key feature
- Focus on velcro closure mechanism OR inner lining texture
- Hands may be shown adjusting/demonstrating
STYLE:
- Macro product photography
- Sharp focus on detail area
- Soft blurred background
- 8K quality, 1:1 aspect ratio
`,
// Main_04: 多场景使用 (4宫格)
// 参考: 伊丽莎白A+ (4).jpg
Main_04: (product, sellingPoints) => `
[AMAZON MAIN IMAGE - 4-GRID USAGE SCENARIOS]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT: 2x2 grid of 4 scenes, each with text caption below
SCENE 1 (TOP-LEFT): Cat wearing product, standing
CAPTION: "• HYGIENIC & EASY TO CLEAN"
SUB-TEXT: "${sellingPoints[0] || 'WATERPROOF OUTER LAYER REPELS STAINS'}"
SCENE 2 (TOP-RIGHT): Cat eating from bowl while wearing product
CAPTION: "• UNRESTRICTED EATING/DRINKING"
SUB-TEXT: "${sellingPoints[1] || 'SPECIALLY DESIGNED OPENING ALLOWS NATURAL FEEDING'}"
SCENE 3 (BOTTOM-LEFT): Cat stretching or walking with product
CAPTION: "• REVERSIBLE WEAR (4-IN-1 DESIGN)"
SUB-TEXT: "${sellingPoints[2] || 'FLIP-OVER DESIGN FLEXIBLY ADAPTS TO VARIOUS ACTIVITIES'}"
SCENE 4 (BOTTOM-RIGHT): Cat sleeping comfortably with product
CAPTION: "• 360° COMFORT"
SUB-TEXT: "${sellingPoints[3] || 'FREE PLAY, SLEEP, AND MOVEMENT WITHOUT PRESSURE'}"
STYLE:
- Each scene in rounded rectangle frame
- Warm beige background (#F5EDE4) with paw print watermarks
- Text in dark gray, clean sans-serif
- 8K quality, 1:1 aspect ratio
`,
// Main_05: 尺寸图
// 参考: 伊丽莎白A+ (6).jpg
Main_05: (product, sizeChart) => `
[AMAZON MAIN IMAGE - SIZE GUIDE]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT:
- TOP: Title "PRODUCT SIZE" in bold dark font
- CENTER: Product image with dimension arrows
- Arrow pointing to neck opening: "NECK"
- Arrow pointing to outer width: "WIDTH"
- BOTTOM: Size chart table
SIZE CHART TABLE:
| SIZE | NECK CIRCUMFERENCE | DEPTH |
|------|-------------------|-------|
| XS | ${sizeChart?.XS?.neck || '5.6-6.8IN'} | ${sizeChart?.XS?.depth || '3.2IN'} |
| S | ${sizeChart?.S?.neck || '7.2-8.4IN'} | ${sizeChart?.S?.depth || '4IN'} |
| M | ${sizeChart?.M?.neck || '8.8-10.4IN'} | ${sizeChart?.M?.depth || '5IN'} |
| L | ${sizeChart?.L?.neck || '10.8-12.4IN'} | ${sizeChart?.L?.depth || '6IN'} |
| XL | ${sizeChart?.XL?.neck || '12.8-14.4IN'} | ${sizeChart?.XL?.depth || '7IN'} |
FOOTER TEXT: "NOTE: MEASUREMENTS ARE HAND-CHECKED. ALWAYS MEASURE YOUR PET'S NECK CIRCUMFERENCE BEFORE SELECTING A SIZE."
STYLE:
- Clean infographic style
- Warm beige background with paw prints
- Table with rounded corners
- 8K quality, 1:1 aspect ratio
`,
// Main_06: 多角度展示
// 参考: 伊丽莎白A+ (5).jpg
Main_06: (product) => `
[AMAZON MAIN IMAGE - MULTIPLE ANGLES]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT:
- 2 cats wearing the same product, different angles
- Left: Front view, cat facing camera
- Right: 3/4 side view, cat looking to the side
- Decorative curved line connecting the two images
SCENE:
- Warm home interior background
- Both cats look comfortable
- Product clearly visible on both
STYLE:
- Lifestyle photography
- Soft warm lighting
- No text overlay
- 8K quality, 1:1 aspect ratio
`
};
// ========================================
// A+图6张 (970x600px, 约1.6:1)
// ========================================
const APLUS_TEMPLATES = {
// APlus_01: 品牌横幅
// 参考: 伊丽莎白A+ (1).jpg
APlus_01: (product, brandName, productName) => `
[AMAZON A+ BANNER - BRAND HEADER]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT:
- Wide banner, 970x600px aspect ratio
- LEFT 40%: Empty space with decorative paw prints for text overlay
- RIGHT 60%: Cat wearing product in lifestyle scene
TEXT OVERLAY (LEFT SIDE):
- BRAND NAME: "${brandName || 'TOUCHDOG'}" in playful, slightly curved font (coral/salmon color #E8A87C)
- Decorative paw prints around brand name
- PRODUCT NAME: "${productName || 'CAT SOFT CONE COLLAR'}" below in smaller gray text
SCENE (RIGHT SIDE):
- White cat standing on modern furniture
- Wearing the product, looking comfortable
- Warm cozy interior background
STYLE:
- Professional Amazon A+ content
- Warm, inviting color palette
- 8K quality
`,
// APlus_02: 对比图
// 参考: 伊丽莎白A+ (2).jpg
APlus_02: (product, sellingPoints, competitorWeaknesses) => `
[AMAZON A+ COMPARISON IMAGE]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT: Split screen, left vs right
LEFT SIDE (OUR PRODUCT - COLORFUL):
- GREEN CHECKMARK icon at top-left
- "OUR" label in white on coral/orange background (#E8876C)
- Cat wearing our product, looking HAPPY
- 3 SELLING POINTS in white text:
• "${sellingPoints[0] || 'CLOUD-LIGHT COMFORT'}"
• "${sellingPoints[1] || 'WIDER & CLEARER'}"
• "${sellingPoints[2] || 'FOLDABLE & PORTABLE'}"
RIGHT SIDE (COMPETITOR - GRAYSCALE):
- RED X-MARK icon at top-right
- "OTHER" label in dark text on gray background
- Cat wearing hard plastic cone, looking SAD/UNCOMFORTABLE
- GRAYSCALE/DESATURATED colors
- 3 WEAKNESSES in gray text:
• "${competitorWeaknesses[0] || 'HEAVY & BULKY'}"
• "${competitorWeaknesses[1] || 'BLOCKS VISION & MOVEMENT'}"
• "${competitorWeaknesses[2] || 'HARD TO STORE'}"
CENTER: Product image showing our soft cone
STYLE:
- Clear visual contrast between sides
- Warm beige background with paw prints
- 8K quality, ~1.6:1 aspect ratio
`,
// APlus_03: 功能细节
// 参考: 伊丽莎白A+ (3).jpg
APlus_03: (product, features) => `
[AMAZON A+ FEATURE DETAILS]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT:
- TOP: Large title "ENGINEERED FOR UNCOMPROMISED COMFORT" in bold dark font
- MIDDLE: 3 detail images in rounded rectangles
DETAIL 1 (LEFT):
- Close-up of inner lining/fabric
- CAPTION: "${features[0]?.title || 'STURDY AND BREATHABLE'}"
- SUB-TEXT: "${features[0]?.desc || ', DURABLE AND COMFORTABLE'}"
DETAIL 2 (CENTER):
- Dog/cat wearing product with size annotation line
- Show measurement like "24.5cm"
- CAPTION: "${features[1]?.title || 'EASY TO CLEAN, STYLISH'}"
- SUB-TEXT: "${features[1]?.desc || 'AND ATTRACTIVE'}"
DETAIL 3 (RIGHT):
- Close-up of stitching/material
- CAPTION: "${features[2]?.title || 'REINFORCED STITCHING PROCESS'}"
- SUB-TEXT: "${features[2]?.desc || 'AND DURABLE FABRIC'}"
STYLE:
- Warm beige background (#F5EDE4) with paw print watermarks
- Text in dark gray/brown
- 8K quality, ~1.6:1 aspect ratio
`,
// APlus_04: 多场景 (与Main_04类似但横版)
APlus_04: (product, sellingPoints) => `
[AMAZON A+ MULTI-SCENARIO - HORIZONTAL]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT: 4 scenes in horizontal row
SCENE 1: Cat standing wearing product
CAPTION: "• HYGIENIC & EASY TO CLEAN"
SCENE 2: Cat eating from bowl
CAPTION: "• UNRESTRICTED EATING/DRINKING"
SCENE 3: Cat stretching/playing
CAPTION: "• REVERSIBLE WEAR"
SCENE 4: Cat sleeping peacefully
CAPTION: "• 360° COMFORT"
Each scene in rounded rectangle frame with caption below.
STYLE:
- Warm beige background with paw prints
- Clean modern typography
- 8K quality, ~1.6:1 aspect ratio
`,
// APlus_05: 多角度展示
APlus_05: (product) => `
[AMAZON A+ MULTIPLE ANGLES - HORIZONTAL]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT:
- 2 cats wearing product, side by side
- Left cat: Front view
- Right cat: Side/3/4 view
- Decorative curved divider between them
SCENE:
- Warm interior background
- Both cats comfortable
- Product clearly visible
STYLE:
- No text overlay
- Lifestyle photography
- 8K quality, ~1.6:1 aspect ratio
`,
// APlus_06: 尺寸表
APlus_06: (product, sizeChart) => `
[AMAZON A+ SIZE CHART - HORIZONTAL]
PRODUCT DESCRIPTION:
${product.goldenDescription}
LAYOUT:
- LEFT 40%: Product image with dimension arrows (NECK, WIDTH labels)
- RIGHT 60%: Size chart table
TITLE: "PRODUCT SIZE" at top
SIZE CHART TABLE:
| SIZE | NECK CIRCUMFERENCE | DEPTH |
|------|-------------------|-------|
| XS | ${sizeChart?.XS?.neck || '5.6-6.8IN'} | ${sizeChart?.XS?.depth || '3.2IN'} |
| S | ${sizeChart?.S?.neck || '7.2-8.4IN'} | ${sizeChart?.S?.depth || '4IN'} |
| M | ${sizeChart?.M?.neck || '8.8-10.4IN'} | ${sizeChart?.M?.depth || '5IN'} |
| L | ${sizeChart?.L?.neck || '10.8-12.4IN'} | ${sizeChart?.L?.depth || '6IN'} |
| XL | ${sizeChart?.XL?.neck || '12.8-14.4IN'} | ${sizeChart?.XL?.depth || '7IN'} |
FOOTER: "NOTE: ALWAYS MEASURE YOUR PET'S NECK BEFORE SELECTING A SIZE"
STYLE:
- Warm beige background with paw prints
- Clean infographic style
- 8K quality, ~1.6:1 aspect ratio
`
};
// ========================================
// 导出
// ========================================
module.exports = {
MAIN_TEMPLATES,
APLUS_TEMPLATES,
// 生成所有12张图的Prompts
generateAllPrompts: (product, skuInfo) => {
const prompts = [];
// 主图6张
prompts.push({
id: 'Main_01',
name: '场景首图+卖点',
aspectRatio: '1:1',
prompt: MAIN_TEMPLATES.Main_01(product, skuInfo.sellingPoints || [])
});
prompts.push({
id: 'Main_02',
name: '白底平铺图',
aspectRatio: '1:1',
prompt: MAIN_TEMPLATES.Main_02(product)
});
prompts.push({
id: 'Main_03',
name: '功能特写',
aspectRatio: '1:1',
prompt: MAIN_TEMPLATES.Main_03(product)
});
prompts.push({
id: 'Main_04',
name: '多场景使用',
aspectRatio: '1:1',
prompt: MAIN_TEMPLATES.Main_04(product, skuInfo.sellingPoints || [])
});
prompts.push({
id: 'Main_05',
name: '尺寸图',
aspectRatio: '1:1',
prompt: MAIN_TEMPLATES.Main_05(product, skuInfo.sizeChart || {})
});
prompts.push({
id: 'Main_06',
name: '多角度展示',
aspectRatio: '1:1',
prompt: MAIN_TEMPLATES.Main_06(product)
});
// A+图6张
prompts.push({
id: 'APlus_01',
name: '品牌横幅',
aspectRatio: '3:2',
prompt: APLUS_TEMPLATES.APlus_01(product, skuInfo.brandName, skuInfo.productName)
});
prompts.push({
id: 'APlus_02',
name: '对比图',
aspectRatio: '3:2',
prompt: APLUS_TEMPLATES.APlus_02(
product,
skuInfo.sellingPoints || [],
skuInfo.competitorWeaknesses || []
)
});
prompts.push({
id: 'APlus_03',
name: '功能细节',
aspectRatio: '3:2',
prompt: APLUS_TEMPLATES.APlus_03(product, skuInfo.features || [])
});
prompts.push({
id: 'APlus_04',
name: '多场景横版',
aspectRatio: '3:2',
prompt: APLUS_TEMPLATES.APlus_04(product, skuInfo.sellingPoints || [])
});
prompts.push({
id: 'APlus_05',
name: '多角度横版',
aspectRatio: '3:2',
prompt: APLUS_TEMPLATES.APlus_05(product)
});
prompts.push({
id: 'APlus_06',
name: '尺寸表横版',
aspectRatio: '3:2',
prompt: APLUS_TEMPLATES.APlus_06(product, skuInfo.sizeChart || {})
});
return prompts;
}
};

486
prompts/v6-optimized.md Normal file
View File

@@ -0,0 +1,486 @@
# V6 Optimized Prompts for Amazon Listing
## Strategy Overview
- **Strategy**: Image Editing (NOT Generation)
- **Core Principle**: Preserve specific elements (product/pet) while modifying background/layout
- **Prompt Structure**:
1. Reference Analysis
2. Critical Preservation Requirements
3. Editing Tasks
4. Layout Specification
5. Style Guide
6. Final Check
## Main Images (1:1 Square)
### Main_01: Hero Scene
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: 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.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The cat MUST remain a Ragdoll with blue eyes, white/cream fur, brown ear markings - EXACTLY as shown
2. The cone collar MUST remain ice-blue (#B5E5E8), flower/petal shaped, with "TOUCHDOG" branding
3. The cat's pose, expression, and the way the collar sits on the cat
4. The product's texture, stitching pattern, and velcro closure detail
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Enhance background to warm, cozy home interior (fireplace, blanket, wooden furniture)
2. Add soft warm lighting with gentle shadows
3. Add a curved blue banner (#4A7C9B) across middle area with text: "DESIGNED FOR COMFORTABLE RECOVERY"
4. 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"
5. Add subtle paw print watermarks in light blue on the banner area
LAYOUT SPECIFICATION:
- Top 60%: Cat wearing product in enhanced home scene
- Middle: Curved banner with main tagline
- Bottom 25%: Three feature callout boxes in a row
STYLE GUIDE:
Professional Amazon main image style. Warm color palette. Text should be crisp and readable. The cat and product should be the clear focal point.
OUTPUT: 1:1 square 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
```
### Main_02: Flat Lay Detail
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: 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.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The product MUST remain EXACTLY as shown: ice-blue color (#B5E5E8), flower shape, 8 petal segments
2. The TOUCHDOG branding text on the product
3. The velcro strap detail and green/teal inner rim
4. The product's proportions and stitching pattern
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Change background from black to clean white
2. Add a dark green (#2D4A3E) banner at top with text: "DURABLE WATERPROOF PU LAYER"
3. 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"
4. Add thin green borders around detail circles
5. Keep layout clean and professional
LAYOUT SPECIFICATION:
- Top: Dark green banner with title
- Center: Main product image on white background
- Bottom: Two circular detail magnifications with labels
STYLE GUIDE:
Clean white background product photography. Professional Amazon main image style. High contrast, sharp details.
OUTPUT: 1:1 square 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
```
### Main_03: Feature Showcase
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: 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.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The product MUST remain EXACTLY as shown: ice-blue color (#B5E5E8), flower shape, TOUCHDOG branding
2. The velcro strap detail on the left side
3. The green/teal inner rim around the neck hole
4. The 8-petal segmented design with visible stitching
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Place the product on a soft blue background (#6B9AC4) that complements the product color
2. Add title text at top-left: "ADJUSTABLE STRAP FOR A SECURE FIT" in white, bold
3. 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"
4. Add decorative white paw prints scattered in the background
5. Add thin white circular borders around the detail callouts
LAYOUT SPECIFICATION:
- 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
STYLE GUIDE:
Clean infographic style. Blue color scheme matching the product. Professional Amazon A+ content quality. High contrast text for readability.
OUTPUT: 1:1 square 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
```
### Main_04: 4-Scenario Grid
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: 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.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. ALL 4 SCENES MUST SHOW THE EXACT SAME CAT: Ragdoll breed, blue eyes, white/cream fur, brown ear markings
2. ALL 4 SCENES MUST SHOW THE EXACT SAME PRODUCT: Ice-blue (#B5E5E8) flower-shaped soft cone, TOUCHDOG brand
3. The cat's fur texture and coloring must be consistent across all 4 images
4. The product's color, shape, and branding must be identical in all 4 scenes
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create a 2x2 grid layout with 4 scenes, each in a rounded rectangle frame
2. TOP-LEFT scene: Cat standing alert, wearing the cone - Caption: "• HYGIENIC & EASY TO CLEAN"
3. TOP-RIGHT scene: Cat eating/drinking from a bowl while wearing cone - Caption: "• UNRESTRICTED EATING/DRINKING"
4. BOTTOM-LEFT scene: Cat stretching or playing while wearing cone - Caption: "• REVERSIBLE WEAR"
5. BOTTOM-RIGHT scene: Cat sleeping peacefully curled up with cone - Caption: "• 360° COMFORT"
6. Add light paw print watermarks in corners of the overall image
LAYOUT SPECIFICATION:
- 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
STYLE GUIDE:
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.
OUTPUT: 1:1 square 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
```
### Main_05: Size Chart
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: An ice-blue TOUCHDOG soft pet recovery cone collar in flat lay position, showing its flower/petal shape design.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The product MUST remain EXACTLY as shown: ice-blue color, flower shape, TOUCHDOG branding
2. The product's proportions and shape
3. The velcro closure and inner rim details
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Place product on warm beige background (#F5EDE4)
2. Add title "PRODUCT SIZE" at top center in dark text
3. Add a size chart table below the product with columns: SIZE | NECK | DEPTH
4. 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
5. Add note in coral/salmon color: "NOTE: ALWAYS MEASURE YOUR PET'S NECK BEFORE SELECTING A SIZE"
6. Add subtle paw print decorations in beige tones
LAYOUT SPECIFICATION:
- Top: Title
- Center: Product image (main focus)
- Bottom: Size chart table with clean design
STYLE GUIDE:
Clean, informative infographic style. Easy to read table. Warm neutral background. Professional Amazon listing quality.
OUTPUT: 1:1 square 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
```
### Main_06: Multi-Angle
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: A Ragdoll cat (white/cream fur, brown ear markings, blue eyes) wearing an ice-blue TOUCHDOG soft recovery cone collar.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The cat MUST remain a Ragdoll: blue eyes, white/cream fur, brown markings
2. The product MUST remain ice-blue (#B5E5E8), flower-shaped, TOUCHDOG branded
3. Both views must show the EXACT SAME cat and product
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create a split image with curved decorative divider in the middle
2. LEFT SIDE: Front view of cat wearing the cone (from reference)
3. RIGHT SIDE: Side/profile view of the same cat wearing the same cone
4. Warm home interior background in both halves
5. NO text overlays - pure lifestyle imagery
LAYOUT SPECIFICATION:
- Split layout with elegant curved divider
- Left: Front view
- Right: Side view
- Consistent warm lighting across both
STYLE GUIDE:
Lifestyle photography. Warm, cozy atmosphere. The cat should look comfortable. NO text.
OUTPUT: 1:1 square 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
```
## A+ Images (3:2 Landscape)
### APlus_01: Brand Banner
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar in a home setting.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The cat MUST remain a Ragdoll with blue eyes and white/cream fur
2. The product MUST remain ice-blue, flower-shaped, TOUCHDOG branded
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create a horizontal banner layout (3:2 ratio)
2. 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
3. RIGHT 60%: The cat wearing the product in a warm home setting
4. Soft, warm color palette throughout
LAYOUT SPECIFICATION:
- Left side: Brand name and product title
- Right side: Hero image of cat with product
- Warm, cohesive color scheme
STYLE GUIDE:
Amazon A+ brand story style. Warm and inviting. Professional yet friendly.
OUTPUT: 3:2 landscape 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
```
### APlus_02: Comparison Chart
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. LEFT SIDE: The cat and product MUST match the reference - Ragdoll cat, ice-blue soft cone
2. The product color (#B5E5E8), flower shape, and TOUCHDOG branding on left side
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create a side-by-side comparison layout
2. 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"
3. 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"
4. Warm beige background with paw print watermarks
LAYOUT SPECIFICATION:
- Split layout with curved divider
- Left side larger, full color, positive mood
- Right side smaller, desaturated, negative mood
- Text overlays on each side
STYLE GUIDE:
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.
OUTPUT: 3:2 landscape 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
```
### APlus_03: Feature Details
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The cat MUST remain a Ragdoll with blue eyes
2. The product MUST remain ice-blue, flower-shaped, TOUCHDOG branded
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create horizontal layout (3:2) with title and 3 detail panels
2. Title at top: "ENGINEERED FOR UNCOMPROMISED COMFORT" in dark text
3. 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"
4. Warm beige background (#F5EBE0) with subtle paw prints
LAYOUT SPECIFICATION:
- Top: Title banner
- Bottom: Three equal-width panels with captions
STYLE GUIDE:
Amazon A+ feature module style. Clean, informative. Warm color palette.
OUTPUT: 3:2 landscape 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
```
### APlus_04: 4-Scene Horizontal
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: A Ragdoll cat (white/cream fur, blue eyes, brown ear markings) wearing an ice-blue TOUCHDOG soft recovery cone collar.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. ALL 4 SCENES MUST SHOW THE EXACT SAME CAT: Ragdoll breed, blue eyes, white/cream fur
2. ALL 4 SCENES MUST SHOW THE EXACT SAME PRODUCT: Ice-blue flower-shaped soft cone
3. Consistent cat appearance and product across all panels
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create horizontal layout (3:2) with 4 scenes in a row
2. Scene 1: Cat standing - "HYGIENIC & EASY TO CLEAN"
3. Scene 2: Cat eating from bowl - "UNRESTRICTED EATING"
4. Scene 3: Cat playing/stretching - "REVERSIBLE WEAR"
5. Scene 4: Cat sleeping curled up - "360° COMFORT"
6. Each scene in a rounded rectangle frame
7. Warm beige background with paw print decorations
LAYOUT SPECIFICATION:
- 4 equal panels arranged horizontally
- Each panel has image + caption below
- Consistent warm lighting across all
STYLE GUIDE:
Lifestyle photography. Same cat in all scenes. Warm, cozy feel.
OUTPUT: 3:2 landscape 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
```
### APlus_05: Multi-Angle Horizontal
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: A Ragdoll cat wearing an ice-blue TOUCHDOG soft recovery cone collar.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. Both views must show the SAME Ragdoll cat with blue eyes
2. Both views must show the SAME ice-blue TOUCHDOG cone
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Create horizontal split layout (3:2)
2. LEFT: Front view of cat wearing cone
3. RIGHT: Side/profile view of same cat with cone
4. Elegant curved divider between the two views
5. Warm home background in both
6. NO text - pure visual showcase
LAYOUT SPECIFICATION:
- Two equal halves with curved divider
- Left: Front angle
- Right: Side angle
- Warm consistent lighting
STYLE GUIDE:
High-end lifestyle photography. No text. Warm atmosphere.
OUTPUT: 3:2 landscape 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
```
### APlus_06: Size Chart Horizontal (Corrected)
```text
[IMAGE EDITING TASK - NOT GENERATION]
REFERENCE IMAGE ANALYSIS:
The reference image shows: 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.
CRITICAL PRESERVATION REQUIREMENTS (DO NOT MODIFY):
1. The product MUST keep its C-SHAPED OPENING - DO NOT close the gap into a full circle
2. The velcro strap visible on the left side of the opening
3. Ice-blue color (#B5E5E8), flower/petal shape with 8 segments
4. TOUCHDOG brand text on the product
5. Green/teal inner rim around the neck hole
EDITING TASKS (ONLY THESE CHANGES ARE ALLOWED):
1. Change background from black to warm beige (#F5EDE4)
2. Add title "PRODUCT SIZE" at top center in dark text
3. Add dimension labels: "NECK" pointing to inner circle, "DEPTH" pointing outward
4. 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
5. Add note in coral: "ALWAYS MEASURE YOUR PET'S NECK BEFORE SELECTING A SIZE"
6. Add subtle paw print watermarks
LAYOUT SPECIFICATION:
- Left 45%: Product image maintaining C-shape opening
- Right 55%: Size chart table
- Top: Title
- Bottom: Note text
STYLE GUIDE:
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.
OUTPUT: 3:2 landscape 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
```

2018
public/css/bootstrap-icons.css vendored Normal file

File diff suppressed because it is too large Load Diff

6
public/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

845
public/index.html Normal file
View File

@@ -0,0 +1,845 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Touchdog AI 智能电商出图系统</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--primary-color: #E60012;
--secondary-color: #c5000f;
--accent-color: #FF6B6B;
--bg-light: #fafafa;
--text-dark: #333;
}
body {
background: linear-gradient(135deg, #fff5f5 0%, #fafafa 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
}
.main-container { max-width: 1400px; margin: 0 auto; padding: 2rem; }
.brand-header {
background: linear-gradient(135deg, var(--primary-color) 0%, #ff4444 100%);
color: white;
padding: 2rem;
border-radius: 16px;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(230, 0, 18, 0.2);
}
.brand-logo { font-size: 2rem; font-weight: bold; }
.card {
border: none;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 1.5rem;
transition: all 0.3s;
overflow: hidden;
}
.card:hover {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
.card-header-custom {
background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
border-bottom: 2px solid var(--primary-color);
padding: 1rem 1.5rem;
}
.step-badge {
background: var(--primary-color);
color: white;
width: 28px;
height: 28px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
margin-right: 0.75rem;
}
.btn-primary {
background: var(--primary-color);
border-color: var(--primary-color);
border-radius: 8px;
padding: 0.6rem 1.5rem;
font-weight: 500;
}
.btn-primary:hover {
background: var(--secondary-color);
border-color: var(--secondary-color);
}
.btn-outline-primary {
color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-outline-primary:hover {
background: var(--primary-color);
border-color: var(--primary-color);
}
.json-editor {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: #1e1e1e;
color: #d4d4d4;
border-radius: 8px;
border: none;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
.image-card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: all 0.3s;
}
.image-card:hover {
transform: scale(1.02);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.image-card img {
width: 100%;
height: 200px;
object-fit: cover;
cursor: pointer;
}
.image-card .card-body {
padding: 1rem;
}
.image-type-badge {
font-size: 11px;
padding: 4px 8px;
border-radius: 4px;
}
.badge-main { background: #4CAF50; }
.badge-aplus { background: #2196F3; }
.progress-ring {
width: 120px;
height: 120px;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.95);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}
.tab-content { padding-top: 1.5rem; }
.nav-tabs .nav-link {
color: var(--text-dark);
border: none;
padding: 0.75rem 1.5rem;
font-weight: 500;
}
.nav-tabs .nav-link.active {
color: var(--primary-color);
border-bottom: 3px solid var(--primary-color);
background: transparent;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
}
.status-success { background: #4CAF50; }
.status-failed { background: #f44336; }
.status-pending { background: #ff9800; }
.toast-container { position: fixed; top: 20px; right: 20px; z-index: 1050; }
.ref-image-preview {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
border: 2px solid #ddd;
}
.ref-image-preview:hover {
border-color: var(--primary-color);
}
</style>
</head>
<body>
<div class="main-container">
<!-- Brand Header -->
<div class="brand-header">
<div class="d-flex align-items-center justify-content-between">
<div>
<div class="brand-logo">
<i class="bi bi-stars"></i> Touchdog AI 智能电商出图系统
</div>
<p class="mb-0 mt-2 opacity-75">输入SKU数据 → Brain智能规划 → 自动生成12张专业电商图</p>
</div>
<div class="text-end">
<div class="badge bg-light text-dark px-3 py-2">
<i class="bi bi-cpu"></i> Gemini 3 Pro Image
</div>
</div>
</div>
</div>
<!-- Main Tabs -->
<ul class="nav nav-tabs" id="mainTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#skuTab">
<i class="bi bi-box-seam"></i> SKU智能出图
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#legacyTab">
<i class="bi bi-image"></i> 传统模式
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#historyTab">
<i class="bi bi-clock-history"></i> 历史记录
</button>
</li>
</ul>
<div class="tab-content">
<!-- SKU智能出图 Tab -->
<div class="tab-pane fade show active" id="skuTab">
<div class="row">
<!-- 左侧SKU输入 -->
<div class="col-lg-5">
<div class="card">
<div class="card-header-custom">
<span class="step-badge">1</span>
<strong>输入SKU数据</strong>
</div>
<div class="card-body">
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label mb-0 fw-bold">SKU JSON数据</label>
<button class="btn btn-sm btn-outline-secondary" onclick="loadSampleSKU()">
<i class="bi bi-file-earmark-code"></i> 加载示例
</button>
</div>
<textarea class="form-control json-editor" id="skuInput" rows="18"
placeholder='{"sku_id": "...", "product_name": "...", ...}'></textarea>
</div>
<div class="mb-3">
<label class="form-label fw-bold">参考图片URLs</label>
<div id="refImagesPreview" class="d-flex gap-2 flex-wrap mb-2"></div>
<small class="text-muted">在SKU JSON中设置 ref_images 字段</small>
</div>
<button class="btn btn-primary w-100" id="generateSkuBtn" onclick="generateSKU()">
<i class="bi bi-magic"></i> 开始智能生图
</button>
</div>
</div>
<!-- Brain分析结果 -->
<div class="card" id="analysisCard" style="display:none;">
<div class="card-header-custom">
<span class="step-badge">2</span>
<strong>Brain分析结果</strong>
</div>
<div class="card-body">
<div id="analysisContent"></div>
</div>
</div>
</div>
<!-- 右侧:生成结果 -->
<div class="col-lg-7">
<div class="card">
<div class="card-header-custom d-flex justify-content-between align-items-center">
<div>
<span class="step-badge">3</span>
<strong>生成结果</strong>
<span class="badge bg-secondary ms-2" id="resultCount">0/12</span>
</div>
<button class="btn btn-sm btn-outline-primary" onclick="downloadAllImages()" id="downloadAllBtn" disabled>
<i class="bi bi-download"></i> 批量下载
</button>
</div>
<div class="card-body">
<div id="generationProgress" style="display:none;" class="mb-4">
<div class="progress" style="height: 8px;">
<div class="progress-bar bg-danger" id="progressBar" style="width: 0%"></div>
</div>
<div class="d-flex justify-content-between mt-2">
<small class="text-muted" id="progressText">准备中...</small>
<small class="text-muted" id="progressStats"></small>
</div>
</div>
<div id="imageResults" class="image-grid">
<div class="text-center text-muted py-5">
<i class="bi bi-images" style="font-size: 3rem; opacity: 0.3;"></i>
<p class="mt-3">输入SKU数据后点击"开始智能生图"</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 传统模式 Tab -->
<div class="tab-pane fade" id="legacyTab">
<div class="card p-4">
<div class="card-header-custom mb-3">
<span class="step-badge">1</span>
<strong>素材与需求录入</strong>
</div>
<div class="row g-4">
<div class="col-md-4">
<label class="form-label fw-bold">参考图片上传 (R2存储)</label>
<div class="border rounded p-3 bg-light text-center" style="border-style: dashed !important; cursor: pointer;" onclick="document.getElementById('imageUpload').click()">
<i class="bi bi-cloud-upload fs-1 text-muted"></i>
<p class="text-muted mb-0">点击或拖拽上传图片</p>
<input type="file" id="imageUpload" multiple accept="image/*" class="d-none" onchange="handleFileSelect(this)">
</div>
<div id="uploadPreview" class="d-flex flex-wrap gap-2 mt-2"></div>
<button class="btn btn-outline-primary w-100 mt-3" id="uploadBtn" onclick="uploadImages()">
<i class="bi bi-upload"></i> 开始上传
</button>
</div>
<div class="col-md-8">
<div class="mb-3">
<label class="form-label fw-bold">产品介绍</label>
<textarea class="form-control" id="productIntro" rows="3" placeholder="例如:这是一款超轻伊丽莎白圈,防水易清洁..."></textarea>
</div>
<div class="mb-3">
<label class="form-label fw-bold">图片设计要求</label>
<textarea class="form-control" id="designReqs" rows="3" placeholder="例如需要4张主图纯白背景展示细节..."></textarea>
</div>
</div>
</div>
<hr class="my-4">
<div class="card-header-custom mb-3">
<span class="step-badge">2</span>
<strong>生成提示词</strong>
</div>
<button class="btn btn-primary mb-3" id="genPromptsBtn" onclick="generatePrompts()">
<i class="bi bi-chat-right-quote"></i> 调用大模型生成提示词
</button>
<textarea class="form-control json-editor" id="promptsJson" rows="6" readonly></textarea>
<hr class="my-4">
<div class="card-header-custom mb-3">
<span class="step-badge">3</span>
<strong>生成图片</strong>
</div>
<div class="d-flex gap-2 mb-3">
<button class="btn btn-success flex-grow-1" id="genImagesBtn" onclick="generateImages()">
<i class="bi bi-palette"></i> 开始批量生图
</button>
<button class="btn btn-secondary" onclick="downloadAllLegacy()">
<i class="bi bi-download"></i> 批量下载
</button>
</div>
<div id="legacyImageResults" class="image-grid"></div>
</div>
</div>
<!-- 历史记录 Tab -->
<div class="tab-pane fade" id="historyTab">
<div class="card">
<div class="card-header-custom d-flex justify-content-between">
<strong><i class="bi bi-clock-history"></i> 生成历史</strong>
<button class="btn btn-sm btn-outline-danger" onclick="clearHistory()">
<i class="bi bi-trash"></i> 清空历史
</button>
</div>
<div class="card-body">
<div id="historyList" class="list-group list-group-flush"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay" style="display:none;">
<div class="spinner-border text-danger" style="width: 4rem; height: 4rem;" role="status"></div>
<h4 class="mt-4" id="loadingTitle">正在生成中...</h4>
<p class="text-muted" id="loadingSubtitle">Brain正在分析产品并规划图片策略</p>
<div class="progress mt-3" style="width: 300px; height: 6px;">
<div class="progress-bar bg-danger" id="loadingProgress" style="width: 0%"></div>
</div>
<small class="text-muted mt-2" id="loadingStatus">初始化...</small>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/axios.min.js"></script>
<script>
// ============================================
// 状态管理
// ============================================
let state = {
currentSKU: null,
currentPlan: null,
generatedImages: [],
uploadedUrls: [],
history: []
};
// ============================================
// 工具函数
// ============================================
function showToast(message, type = 'success') {
const container = document.getElementById('toastContainer');
const bgClass = type === 'error' ? 'text-bg-danger' : type === 'warning' ? 'text-bg-warning' : 'text-bg-success';
const toastEl = document.createElement('div');
toastEl.className = `toast align-items-center ${bgClass} border-0`;
toastEl.innerHTML = `
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
container.appendChild(toastEl);
new bootstrap.Toast(toastEl).show();
setTimeout(() => toastEl.remove(), 4000);
}
function setLoading(show, title = '', subtitle = '', status = '') {
const overlay = document.getElementById('loadingOverlay');
overlay.style.display = show ? 'flex' : 'none';
if (title) document.getElementById('loadingTitle').textContent = title;
if (subtitle) document.getElementById('loadingSubtitle').textContent = subtitle;
if (status) document.getElementById('loadingStatus').textContent = status;
}
function updateLoadingProgress(percent, status) {
document.getElementById('loadingProgress').style.width = percent + '%';
if (status) document.getElementById('loadingStatus').textContent = status;
}
// ============================================
// SKU智能出图功能
// ============================================
async function loadSampleSKU() {
try {
const res = await axios.get('/api/sample-sku');
document.getElementById('skuInput').value = JSON.stringify(res.data, null, 2);
updateRefImagesPreview(res.data.ref_images || []);
showToast('示例SKU已加载');
} catch (e) {
showToast('加载示例失败', 'error');
}
}
function updateRefImagesPreview(urls) {
const container = document.getElementById('refImagesPreview');
container.innerHTML = urls.map(url =>
`<img src="${url}" class="ref-image-preview" onclick="window.open('${url}')" alt="参考图">`
).join('');
}
async function generateSKU() {
const skuInput = document.getElementById('skuInput').value.trim();
if (!skuInput) {
showToast('请输入SKU数据', 'error');
return;
}
let sku;
try {
sku = JSON.parse(skuInput);
} catch (e) {
showToast('SKU数据JSON格式错误', 'error');
return;
}
if (!sku.sku_id) {
showToast('SKU数据缺少sku_id字段', 'error');
return;
}
state.currentSKU = sku;
state.generatedImages = [];
// 显示加载状态
setLoading(true, '正在生成中...', 'Brain正在分析产品并规划图片策略', '初始化...');
document.getElementById('generateSkuBtn').disabled = true;
document.getElementById('generationProgress').style.display = 'block';
document.getElementById('imageResults').innerHTML = '';
document.getElementById('analysisCard').style.display = 'none';
try {
// 调用生成API
updateLoadingProgress(5, 'Stage 1: Brain正在规划...');
const response = await axios.post('/api/generate-sku', { sku }, {
timeout: 600000 // 10分钟超时
});
if (response.data.success) {
state.currentPlan = response.data.plan;
state.generatedImages = response.data.results;
// 显示分析结果
showAnalysis(response.data.plan.analysis);
// 显示生成的图片
renderGeneratedImages(response.data.results);
// 更新统计
const successCount = response.data.summary.success;
document.getElementById('resultCount').textContent = `${successCount}/12`;
document.getElementById('downloadAllBtn').disabled = successCount === 0;
// 保存到历史
saveToHistory(sku, response.data.results);
showToast(`生成完成!成功 ${successCount}/12 张图片`);
} else {
throw new Error(response.data.error || '生成失败');
}
} catch (error) {
console.error('Generation error:', error);
showToast('生成失败: ' + (error.response?.data?.error || error.message), 'error');
} finally {
setLoading(false);
document.getElementById('generateSkuBtn').disabled = false;
document.getElementById('generationProgress').style.display = 'none';
}
}
function showAnalysis(analysis) {
const card = document.getElementById('analysisCard');
const content = document.getElementById('analysisContent');
content.innerHTML = `
<div class="row g-3">
<div class="col-6">
<small class="text-muted">产品品类</small>
<div class="fw-bold">${analysis.product_category || '-'}</div>
</div>
<div class="col-6">
<small class="text-muted">背景策略</small>
<div class="fw-bold">${analysis.background_strategy === 'light' ? '浅色背景' : '深色背景'}</div>
</div>
<div class="col-6">
<small class="text-muted">Logo颜色</small>
<div class="fw-bold" style="color: ${analysis.logo_color === 'red' ? '#E60012' : '#333'}">
${analysis.logo_color === 'red' ? '红色' : '白色'}
</div>
</div>
<div class="col-6">
<small class="text-muted">核心卖点</small>
<div class="fw-bold">${(analysis.core_selling_points || []).join(', ')}</div>
</div>
<div class="col-12">
<small class="text-muted">视觉风格</small>
<div>${analysis.visual_style || '-'}</div>
</div>
</div>
`;
card.style.display = 'block';
}
function renderGeneratedImages(results) {
const container = document.getElementById('imageResults');
container.innerHTML = results.map(img => {
const isMain = img.id.startsWith('Main_');
const badgeClass = isMain ? 'badge-main' : 'badge-aplus';
const statusClass = img.status === 'success' ? 'status-success' : 'status-failed';
return `
<div class="image-card">
${img.url ?
`<img src="${img.url}" onclick="window.open('${img.url}')" alt="${img.id}">` :
`<div class="d-flex align-items-center justify-content-center bg-light" style="height:200px">
<i class="bi bi-exclamation-triangle text-danger fs-1"></i>
</div>`
}
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="badge image-type-badge ${badgeClass}">${img.id}</span>
<span><span class="status-dot ${statusClass}"></span>${img.status === 'success' ? '成功' : '失败'}</span>
</div>
<div class="small text-muted">${img.type || ''}</div>
${img.purpose ? `<div class="small mt-1">${img.purpose}</div>` : ''}
</div>
</div>
`;
}).join('');
}
function downloadAllImages() {
const successImages = state.generatedImages.filter(img => img.status === 'success' && img.url);
if (successImages.length === 0) {
showToast('没有可下载的图片', 'warning');
return;
}
successImages.forEach((img, idx) => {
const link = document.createElement('a');
link.href = img.url;
link.download = `${state.currentSKU?.sku_id || 'image'}-${img.id}.png`;
link.target = '_blank';
document.body.appendChild(link);
setTimeout(() => {
link.click();
document.body.removeChild(link);
}, idx * 500);
});
showToast(`开始下载 ${successImages.length} 张图片`);
}
// ============================================
// 历史记录
// ============================================
function saveToHistory(sku, results) {
const entry = {
id: Date.now(),
date: new Date().toLocaleString(),
sku_id: sku.sku_id,
product_name: sku.product_name,
results: results
};
let history = JSON.parse(localStorage.getItem('skuHistory') || '[]');
history.unshift(entry);
if (history.length > 50) history = history.slice(0, 50);
localStorage.setItem('skuHistory', JSON.stringify(history));
renderHistory();
}
function renderHistory() {
const history = JSON.parse(localStorage.getItem('skuHistory') || '[]');
const container = document.getElementById('historyList');
if (history.length === 0) {
container.innerHTML = '<div class="text-center text-muted py-4">暂无历史记录</div>';
return;
}
container.innerHTML = history.map(entry => {
const successCount = entry.results?.filter(r => r.status === 'success').length || 0;
return `
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>${entry.sku_id}</strong>
<small class="text-muted ms-2">${entry.product_name || ''}</small>
<div class="small text-muted">${entry.date}</div>
</div>
<div>
<span class="badge bg-success">${successCount}/12</span>
<button class="btn btn-sm btn-outline-primary ms-2" onclick="loadHistoryEntry(${entry.id})">
<i class="bi bi-eye"></i>
</button>
</div>
</div>
`;
}).join('');
}
function loadHistoryEntry(id) {
const history = JSON.parse(localStorage.getItem('skuHistory') || '[]');
const entry = history.find(h => h.id === id);
if (entry) {
state.generatedImages = entry.results;
renderGeneratedImages(entry.results);
const successCount = entry.results.filter(r => r.status === 'success').length;
document.getElementById('resultCount').textContent = `${successCount}/12`;
document.getElementById('downloadAllBtn').disabled = successCount === 0;
// 切换到SKU标签页
document.querySelector('[data-bs-target="#skuTab"]').click();
showToast('已加载历史记录');
}
}
function clearHistory() {
if (confirm('确定要清空所有历史记录吗?')) {
localStorage.removeItem('skuHistory');
renderHistory();
showToast('历史记录已清空');
}
}
// ============================================
// 传统模式功能(保留原有功能)
// ============================================
function handleFileSelect(input) {
const container = document.getElementById('uploadPreview');
container.innerHTML = '';
if (input.files) {
Array.from(input.files).forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement('img');
img.src = e.target.result;
img.className = 'ref-image-preview';
container.appendChild(img);
};
reader.readAsDataURL(file);
});
}
}
async function uploadImages() {
const fileInput = document.getElementById('imageUpload');
const files = fileInput.files;
if (!files.length) {
showToast('请先选择文件', 'error');
return;
}
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]);
}
try {
document.getElementById('uploadBtn').disabled = true;
const res = await axios.post('/api/upload-r2', formData);
state.uploadedUrls = res.data.urls;
showToast(`成功上传 ${res.data.urls.length} 张图片`);
} catch (e) {
showToast('上传失败', 'error');
} finally {
document.getElementById('uploadBtn').disabled = false;
}
}
async function generatePrompts() {
const intro = document.getElementById('productIntro').value;
const reqs = document.getElementById('designReqs').value;
if (!intro || !reqs) {
showToast('请填写产品介绍和设计要求', 'error');
return;
}
try {
document.getElementById('genPromptsBtn').disabled = true;
const templateRes = await axios.get('/api/prompt-template');
let prompt = templateRes.data.content
.replace('[产品介绍]', intro)
.replace('[图片设计要求]', reqs);
const res = await axios.post('/api/generate-prompts', { prompt });
let content = res.data.data?.choices?.[0]?.message?.content || '';
// 解析JSON
content = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
const jsonMatch = content.match(/```json\s*([\s\S]*?)```/) || content.match(/\[[\s\S]*\]/);
if (jsonMatch) {
const prompts = JSON.parse(jsonMatch[1] || jsonMatch[0]);
document.getElementById('promptsJson').value = JSON.stringify(prompts, null, 2);
state.legacyPrompts = prompts;
showToast('提示词生成成功');
}
} catch (e) {
showToast('生成失败', 'error');
} finally {
document.getElementById('genPromptsBtn').disabled = false;
}
}
async function generateImages() {
if (!state.legacyPrompts?.length) {
showToast('请先生成提示词', 'error');
return;
}
const container = document.getElementById('legacyImageResults');
container.innerHTML = '';
state.legacyImages = [];
document.getElementById('genImagesBtn').disabled = true;
for (const item of state.legacyPrompts) {
const promptText = item.ai_prompt || item.prompt;
if (!promptText) continue;
const card = document.createElement('div');
card.className = 'image-card';
card.innerHTML = `
<div class="d-flex align-items-center justify-content-center bg-light" style="height:200px">
<div class="spinner-border text-secondary"></div>
</div>
<div class="card-body">
<div class="small text-muted">${item.type || item.id}</div>
</div>
`;
container.appendChild(card);
try {
const res = await axios.post('/api/generate-image', {
prompt: promptText,
refImages: state.uploadedUrls
});
const imgUrl = res.data.data?.[0]?.url;
if (imgUrl) {
card.querySelector('.bg-light').innerHTML = `<img src="${imgUrl}" onclick="window.open('${imgUrl}')" style="width:100%;height:200px;object-fit:cover;cursor:pointer">`;
state.legacyImages.push({ prompt: promptText, url: imgUrl });
}
} catch (e) {
card.querySelector('.bg-light').innerHTML = `<i class="bi bi-exclamation-triangle text-danger fs-1"></i>`;
}
}
document.getElementById('genImagesBtn').disabled = false;
showToast('图片生成完成');
}
function downloadAllLegacy() {
if (!state.legacyImages?.length) {
showToast('没有可下载的图片', 'warning');
return;
}
state.legacyImages.forEach((img, idx) => {
const link = document.createElement('a');
link.href = img.url;
link.download = `generated-${idx}.png`;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
// ============================================
// 初始化
// ============================================
window.onload = () => {
renderHistory();
// 监听SKU输入变化更新参考图预览
document.getElementById('skuInput').addEventListener('input', (e) => {
try {
const sku = JSON.parse(e.target.value);
if (sku.ref_images) {
updateRefImagesPreview(sku.ref_images);
}
} catch {}
});
};
</script>
</body>
</html>

3
public/js/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
public/js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,119 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "touchdog-brain-output",
"title": "Touchdog Brain Output Schema",
"description": "Brain决策层输出的图片规划数据结构",
"type": "object",
"required": ["analysis", "images"],
"properties": {
"analysis": {
"type": "object",
"description": "产品分析结果",
"required": ["product_category", "core_selling_points", "background_strategy", "logo_color"],
"properties": {
"product_category": {
"type": "string",
"description": "产品品类英文名",
"examples": ["Pet Recovery Cone", "Dog Harness", "Pet Bed"]
},
"core_selling_points": {
"type": "array",
"description": "选中的核心卖点key列表3-4个",
"items": { "type": "string" },
"minItems": 3,
"maxItems": 4,
"examples": [["lightweight", "waterproof", "breathable"]]
},
"background_strategy": {
"type": "string",
"description": "背景亮度策略",
"enum": ["light", "dark", "mixed"]
},
"logo_color": {
"type": "string",
"description": "Logo颜色选择",
"enum": ["red", "white"]
},
"visual_style": {
"type": "string",
"description": "整体视觉风格描述",
"examples": ["Warm, caring home environment emphasizing comfort and ease"]
}
}
},
"images": {
"type": "array",
"description": "12张图片的规划",
"minItems": 12,
"maxItems": 12,
"items": {
"type": "object",
"required": ["id", "type", "purpose", "layout_description", "ai_prompt", "logo_placement"],
"properties": {
"id": {
"type": "string",
"description": "图片ID",
"pattern": "^(Main_0[1-6]|APlus_0[1-6])$",
"examples": ["Main_01", "APlus_01"]
},
"type": {
"type": "string",
"description": "图片类型",
"examples": ["Hero Scene + Key Benefits", "Product Detail + Craftsmanship", "Competitor Comparison"]
},
"purpose": {
"type": "string",
"description": "这张图的营销目的(中文)",
"examples": ["首图决定点击率,展示产品使用状态+核心卖点"]
},
"layout_description": {
"type": "string",
"description": "布局描述(中文,给人审核用)",
"examples": ["上半部分布偶猫佩戴冰蓝色伊丽莎白圈在温馨的木质家居架子上。下半部分浅蓝色圆弧Banner..."]
},
"ai_prompt": {
"type": "string",
"description": "完整的英文AI生图Prompt",
"minLength": 100
},
"logo_placement": {
"type": "object",
"description": "Logo放置信息",
"required": ["position", "type", "color"],
"properties": {
"position": {
"type": "string",
"description": "Logo位置",
"enum": ["bottom-right", "bottom-left", "top-right", "top-left", "center", "none"]
},
"type": {
"type": "string",
"description": "Logo类型",
"enum": ["combined", "graphic_only", "text_only", "none"]
},
"color": {
"type": "string",
"description": "Logo颜色",
"enum": ["red", "white", "auto"]
}
}
},
"aspect_ratio": {
"type": "string",
"description": "图片宽高比",
"enum": ["1:1", "3:2"],
"default": "1:1"
},
"is_comparison": {
"type": "boolean",
"description": "是否是竞品对比图",
"default": false
}
}
}
}
}
}

View File

@@ -0,0 +1,219 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "touchdog-sku-input",
"title": "Touchdog SKU Input Schema",
"description": "用于AI电商图片生成的SKU输入数据结构",
"type": "object",
"required": ["sku_id", "product_name", "brand", "color", "selling_points", "specs", "ref_images"],
"properties": {
"sku_id": {
"type": "string",
"description": "SKU唯一标识符",
"examples": ["TD-EC-001-IBLUE"]
},
"product_name": {
"type": "string",
"description": "产品英文名称",
"examples": ["Cat Soft Cone Collar"]
},
"product_name_cn": {
"type": "string",
"description": "产品中文名称",
"examples": ["伊丽莎白圈"]
},
"brand": {
"type": "string",
"description": "品牌名称",
"const": "Touchdog"
},
"color": {
"type": "object",
"description": "颜色信息",
"required": ["name", "hex"],
"properties": {
"name": {
"type": "string",
"description": "颜色英文名",
"examples": ["Ice Blue", "Mint Green", "Coral Pink"]
},
"name_cn": {
"type": "string",
"description": "颜色中文名",
"examples": ["冰蓝色", "薄荷绿", "珊瑚粉"]
},
"series": {
"type": "string",
"description": "色系系列",
"enum": ["Iridescent", "Solid", "Macaron", "Floral"],
"examples": ["Iridescent"]
},
"hex": {
"type": "string",
"description": "主色HEX值",
"pattern": "^#[0-9A-Fa-f]{6}$",
"examples": ["#B0E0E6"]
},
"edge_color": {
"type": "string",
"description": "边缘包边颜色HEX值",
"pattern": "^#[0-9A-Fa-f]{6}$",
"examples": ["#7FDBDB"]
}
}
},
"selling_points": {
"type": "array",
"description": "产品卖点列表",
"minItems": 1,
"items": {
"type": "object",
"required": ["key", "title_en"],
"properties": {
"key": {
"type": "string",
"description": "卖点唯一标识",
"examples": ["lightweight", "waterproof", "breathable", "adjustable", "wide_view", "foldable"]
},
"title_en": {
"type": "string",
"description": "卖点英文标题(用于图片显示)",
"examples": ["LIGHTER THAN AN EGG", "WATERPROOF & EASY WIPE"]
},
"title_cn": {
"type": "string",
"description": "卖点中文标题",
"examples": ["极致轻盈", "防水易清洁"]
},
"value": {
"type": "string",
"description": "具体数值(如有)",
"examples": ["65g", "24.5cm"]
},
"description_en": {
"type": "string",
"description": "卖点英文描述",
"examples": ["Cloud-light comfort, won't restrict pet activity"]
},
"description_cn": {
"type": "string",
"description": "卖点中文描述",
"examples": ["云感舒适,不束缚宠物活动"]
},
"visual_prompt": {
"type": "string",
"description": "视觉化表达提示用于AI生图",
"examples": ["product shown next to an egg for size comparison", "water droplets beading on the surface"]
}
}
}
},
"specs": {
"type": "object",
"description": "产品规格",
"required": ["weight", "sizes"],
"properties": {
"weight": {
"type": "string",
"description": "产品重量",
"examples": ["65g"]
},
"depth_cm": {
"type": "string",
"description": "圈深度(厘米)",
"examples": ["24.5"]
},
"sizes": {
"type": "array",
"description": "可用尺码",
"items": {
"type": "string",
"enum": ["XS", "S", "M", "L", "XL"]
},
"examples": [["XS", "S", "M", "L", "XL"]]
},
"size_chart": {
"type": "array",
"description": "尺码对照表",
"items": {
"type": "object",
"properties": {
"size": { "type": "string" },
"neck_range_cm": { "type": "string" },
"neck_range_in": { "type": "string" },
"depth_cm": { "type": "string" },
"depth_in": { "type": "string" }
}
}
},
"materials": {
"type": "object",
"description": "材质信息",
"properties": {
"outer": {
"type": "string",
"description": "外层材质",
"examples": ["Durable Waterproof PU"]
},
"inner": {
"type": "string",
"description": "内层材质",
"examples": ["95% Cotton + 5% Spandex"]
}
}
}
}
},
"use_cases": {
"type": "array",
"description": "使用场景列表",
"items": {
"type": "object",
"required": ["name_en"],
"properties": {
"name_en": {
"type": "string",
"description": "场景英文名",
"examples": ["Postoperative Care", "Eye Drop Application", "Nail Trimming", "Grooming"]
},
"name_cn": {
"type": "string",
"description": "场景中文名",
"examples": ["术后恢复", "驱虫护理", "指甲修剪", "美容护理"]
},
"visual_prompt": {
"type": "string",
"description": "场景视觉化提示",
"examples": ["cat resting comfortably after surgery wearing the cone"]
}
}
}
},
"ref_images": {
"type": "array",
"description": "参考图片URLs用于产品一致性约束",
"minItems": 1,
"items": {
"type": "string",
"format": "uri"
},
"examples": [
["https://r2.example.com/td-ec-001-flat.jpg", "https://r2.example.com/td-ec-001-worn.jpg"]
]
},
"pet_type": {
"type": "string",
"description": "适用宠物类型",
"enum": ["cat", "dog", "both"],
"default": "cat"
},
"target_market": {
"type": "string",
"description": "目标市场",
"enum": ["us", "eu", "global"],
"default": "global"
}
}
}

624
server.js Normal file
View File

@@ -0,0 +1,624 @@
require('dotenv').config();
const express = require('express');
const multer = require('multer');
const { S3Client, PutObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const cors = require('cors');
// 导入新模块
const { planImages } = require('./lib/brain');
const { injectConstraints, getAspectRatio } = require('./lib/constraint-injector');
const app = express();
const port = process.env.PORT || 3000;
app.use(cors());
app.use(express.json({ limit: '50mb' }));
app.use(express.static('public'));
// Configure R2 Client
const bucketName = process.env.R2_BUCKET_NAME || 'ai-flow';
console.log('Using R2 Bucket:', bucketName);
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,
});
// Multer for memory storage (upload directly to R2)
const upload = multer({ storage: multer.memoryStorage() });
// ============================================
// 原有API端点
// ============================================
// Endpoint to get prompt template
app.get('/api/prompt-template', (req, res) => {
try {
const promptPath = path.join(__dirname, 'prompt.md');
const content = fs.readFileSync(promptPath, 'utf-8');
res.json({ content });
} catch (error) {
res.status(500).json({ error: 'Failed to read prompt template' });
}
});
// Endpoint to upload to R2
app.post('/api/upload-r2', upload.array('files'), async (req, res) => {
try {
const files = req.files;
if (!files || files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' });
}
const uploadedUrls = [];
for (const file of files) {
const fileName = `${Date.now()}-${file.originalname}`;
const command = new PutObjectCommand({
Bucket: bucketName,
Key: fileName,
Body: file.buffer,
ContentType: file.mimetype,
});
await r2Client.send(command);
const publicUrl = process.env.R2_PUBLIC_DOMAIN
? `${process.env.R2_PUBLIC_DOMAIN}/${fileName}`
: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${bucketName}/${fileName}`;
uploadedUrls.push(publicUrl);
}
res.json({ urls: uploadedUrls });
} catch (error) {
console.error('R2 Upload Error:', error);
res.status(500).json({ error: 'Upload failed: ' + error.message });
}
});
// Endpoint to delete from R2
app.delete('/api/delete-r2', async (req, res) => {
try {
const { url } = req.body;
if (!url) return res.status(400).json({ error: 'URL is required' });
const urlObj = new URL(url);
const key = decodeURIComponent(urlObj.pathname.substring(1));
const bucketName = process.env.R2_BUCKET_NAME || 'ai-flow';
await r2Client.send(new DeleteObjectCommand({
Bucket: bucketName,
Key: key,
}));
res.json({ success: true });
} catch (error) {
console.error('R2 Delete Error:', error);
res.status(500).json({ error: 'Delete failed: ' + error.message });
}
});
// Endpoint to generate prompts (LLM) - 保留原有功能
app.post('/api/generate-prompts', async (req, res) => {
try {
const { prompt } = req.body;
const response = await axios.post('https://api2img.shubiaobiao.com/v1/chat/completions', {
model: 'gemini-3-pro-preview',
messages: [
{ role: 'user', content: prompt }
],
stream: false
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
}
});
res.json({ data: response.data });
} catch (error) {
console.error('LLM API Error:', error.response?.data || error.message);
res.status(500).json({ error: 'Prompt generation failed' });
}
});
// Endpoint to generate image (Sync) - 保留原有功能
app.post('/api/generate-image', async (req, res) => {
try {
const { prompt, aspectRatio, refImages } = req.body;
console.log('Generate Image Request:', { promptLength: prompt?.length, refImagesCount: refImages?.length });
let finalPrompt = prompt;
let ar = aspectRatio || '1:1';
const arMatch = prompt.match(/--ar\s+(\d+:\d+)/i);
if (arMatch) {
ar = arMatch[1];
}
const payload = {
model: 'gemini-3-pro-image-preview',
prompt: finalPrompt,
n: 1,
size: "1K",
aspect_ratio: ar
};
if (refImages && Array.isArray(refImages) && refImages.length > 0) {
payload.image_urls = refImages;
}
const response = await axios.post('https://api2img.shubiaobiao.com/v1/images/generations', payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
timeout: 120000
});
res.json(response.data);
} catch (error) {
console.error('Image Gen API Error:', error.response?.data || error.message);
res.status(500).json({ error: 'Image generation failed' });
}
});
// Endpoint to get image result
app.post('/api/get-image-result', async (req, res) => {
try {
const { id } = req.body;
if (!id) return res.status(400).json({ error: 'Task ID is required' });
const queryUrl = process.env.IMAGE_QUERY_API_URL || 'https://api.wuyinkeji.com/api/img/drawDetail';
const response = await axios.get(queryUrl, {
params: {
key: process.env.API_KEY,
id: id
},
headers: { 'Content-Type': 'application/json' }
});
res.json(response.data);
} catch (error) {
console.error('Image Query API Error:', error.response?.data || error.message);
res.status(500).json({ error: 'Failed to query image result' });
}
});
// ============================================
// 新增API端点 - SKU智能生图
// ============================================
/**
* 获取Brain System Prompt
*/
app.get('/api/brain-prompt', (req, res) => {
try {
const promptPath = path.join(__dirname, 'prompts/brain-system.md');
const content = fs.readFileSync(promptPath, 'utf-8');
res.json({ content });
} catch (error) {
res.status(500).json({ error: 'Failed to read brain prompt' });
}
});
/**
* 获取示例SKU数据
*/
app.get('/api/sample-sku', (req, res) => {
try {
const skuPath = path.join(__dirname, 'data/sample-sku.json');
const content = fs.readFileSync(skuPath, 'utf-8');
res.json(JSON.parse(content));
} catch (error) {
res.status(500).json({ error: 'Failed to read sample SKU' });
}
});
/**
* Stage 1: Brain决策 - 生成图片规划
* POST /api/plan-images
* Body: { sku: SKU数据对象 }
* Returns: { plan: Brain输出的图片规划 }
*/
app.post('/api/plan-images', async (req, res) => {
try {
const { sku } = req.body;
if (!sku || !sku.sku_id) {
return res.status(400).json({ error: 'SKU data is required' });
}
console.log(`Planning images for SKU: ${sku.sku_id}`);
const plan = await planImages(sku, {
apiKey: process.env.API_KEY
});
res.json({
success: true,
sku_id: sku.sku_id,
plan
});
} catch (error) {
console.error('Plan Images Error:', error.message);
res.status(500).json({ error: 'Failed to plan images: ' + error.message });
}
});
/**
* Stage 2: 生成单张图片(带约束注入)
* POST /api/generate-image-with-constraints
* Body: { sku, imageSpec, plan }
* Returns: { imageUrl }
*/
app.post('/api/generate-image-with-constraints', async (req, res) => {
try {
const { sku, imageSpec } = req.body;
if (!sku || !imageSpec) {
return res.status(400).json({ error: 'SKU and imageSpec are required' });
}
// 注入约束
const isComparison = imageSpec.is_comparison || imageSpec.id === 'APlus_02';
const fullPrompt = injectConstraints(
imageSpec.ai_prompt,
sku,
{
isComparison,
logoPlacement: imageSpec.logo_placement || {}
}
);
// 确定宽高比
const aspectRatio = getAspectRatio(imageSpec.id);
console.log(`Generating image ${imageSpec.id} for SKU ${sku.sku_id}`);
// 调用图像生成API
const payload = {
model: 'gemini-3-pro-image-preview',
prompt: fullPrompt,
n: 1,
size: "1K",
aspect_ratio: aspectRatio
};
// 添加参考图
if (sku.ref_images && sku.ref_images.length > 0) {
payload.image_urls = sku.ref_images;
}
const response = await axios.post('https://api2img.shubiaobiao.com/v1/images/generations', payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
timeout: 180000 // 3分钟超时
});
// 提取图片URL
let imageUrl = null;
if (response.data.data && Array.isArray(response.data.data) && response.data.data.length > 0) {
imageUrl = response.data.data[0].url;
}
res.json({
success: true,
image_id: imageSpec.id,
image_url: imageUrl,
aspect_ratio: aspectRatio
});
} catch (error) {
console.error('Generate Image With Constraints Error:', error.response?.data || error.message);
res.status(500).json({ error: 'Image generation failed: ' + error.message });
}
});
/**
* 完整流程为单个SKU生成全部12张图
* POST /api/generate-sku
* Body: { sku: SKU数据对象 }
* Returns: { results: [{ id, url, status }] }
*/
app.post('/api/generate-sku', async (req, res) => {
try {
const { sku } = req.body;
if (!sku || !sku.sku_id) {
return res.status(400).json({ error: 'SKU data is required' });
}
console.log(`\n========== Starting SKU Generation: ${sku.sku_id} ==========`);
// Stage 1: Brain规划
console.log('Stage 1: Planning images...');
const plan = await planImages(sku, { apiKey: process.env.API_KEY });
console.log(`Plan generated with ${plan.images.length} images`);
// Stage 2: 逐张生成图片
console.log('Stage 2: Generating images...');
const results = [];
for (const imageSpec of plan.images) {
try {
console.log(` Generating ${imageSpec.id}...`);
// 注入约束
const isComparison = imageSpec.is_comparison || imageSpec.id === 'APlus_02';
const fullPrompt = injectConstraints(
imageSpec.ai_prompt,
sku,
{
isComparison,
logoPlacement: imageSpec.logo_placement || {}
}
);
const aspectRatio = getAspectRatio(imageSpec.id);
const payload = {
model: 'gemini-3-pro-image-preview',
prompt: fullPrompt,
n: 1,
size: "1K",
aspect_ratio: aspectRatio
};
if (sku.ref_images && sku.ref_images.length > 0) {
payload.image_urls = sku.ref_images;
}
const response = await axios.post('https://api2img.shubiaobiao.com/v1/images/generations', payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
timeout: 180000
});
let imageUrl = null;
if (response.data.data && Array.isArray(response.data.data) && response.data.data.length > 0) {
imageUrl = response.data.data[0].url;
}
results.push({
id: imageSpec.id,
type: imageSpec.type,
purpose: imageSpec.purpose,
url: imageUrl,
status: imageUrl ? 'success' : 'failed',
aspect_ratio: aspectRatio
});
console.log(`${imageSpec.id} generated`);
// 添加小延迟避免API限流
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (imgError) {
console.error(`${imageSpec.id} failed:`, imgError.message);
results.push({
id: imageSpec.id,
type: imageSpec.type,
purpose: imageSpec.purpose,
url: null,
status: 'failed',
error: imgError.message
});
}
}
const successCount = results.filter(r => r.status === 'success').length;
console.log(`\n========== SKU Generation Complete: ${successCount}/${results.length} ==========\n`);
res.json({
success: true,
sku_id: sku.sku_id,
plan: {
analysis: plan.analysis,
image_count: plan.images.length
},
results,
summary: {
total: results.length,
success: successCount,
failed: results.length - successCount
}
});
} catch (error) {
console.error('Generate SKU Error:', error.message);
res.status(500).json({ error: 'SKU generation failed: ' + error.message });
}
});
/**
* 批量生成为多个SKU生成图片
* POST /api/batch-generate
* Body: { skus: [SKU数据数组], concurrency: 并发数 }
* Returns: { results: [{ sku_id, status, images }] }
*/
app.post('/api/batch-generate', async (req, res) => {
try {
const { skus, concurrency = 1 } = req.body;
if (!skus || !Array.isArray(skus) || skus.length === 0) {
return res.status(400).json({ error: 'SKUs array is required' });
}
console.log(`\n========== Batch Generation Started: ${skus.length} SKUs ==========`);
const allResults = [];
// 简单的串行处理避免API限流
for (let i = 0; i < skus.length; i++) {
const sku = skus[i];
console.log(`\nProcessing SKU ${i + 1}/${skus.length}: ${sku.sku_id}`);
try {
// 调用单个SKU生成
const plan = await planImages(sku, { apiKey: process.env.API_KEY });
const skuResults = {
sku_id: sku.sku_id,
status: 'processing',
images: [],
plan_analysis: plan.analysis
};
for (const imageSpec of plan.images) {
try {
const isComparison = imageSpec.is_comparison || imageSpec.id === 'APlus_02';
const fullPrompt = injectConstraints(
imageSpec.ai_prompt,
sku,
{ isComparison, logoPlacement: imageSpec.logo_placement || {} }
);
const aspectRatio = getAspectRatio(imageSpec.id);
const payload = {
model: 'gemini-3-pro-image-preview',
prompt: fullPrompt,
n: 1,
size: "1K",
aspect_ratio: aspectRatio
};
if (sku.ref_images && sku.ref_images.length > 0) {
payload.image_urls = sku.ref_images;
}
const response = await axios.post('https://api2img.shubiaobiao.com/v1/images/generations', payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
timeout: 180000
});
let imageUrl = null;
if (response.data.data && Array.isArray(response.data.data) && response.data.data.length > 0) {
imageUrl = response.data.data[0].url;
}
skuResults.images.push({
id: imageSpec.id,
url: imageUrl,
status: imageUrl ? 'success' : 'failed'
});
// 延迟避免限流
await new Promise(resolve => setTimeout(resolve, 1500));
} catch (imgErr) {
skuResults.images.push({
id: imageSpec.id,
url: null,
status: 'failed',
error: imgErr.message
});
}
}
const successCount = skuResults.images.filter(img => img.status === 'success').length;
skuResults.status = successCount === skuResults.images.length ? 'complete' : 'partial';
skuResults.summary = {
total: skuResults.images.length,
success: successCount,
failed: skuResults.images.length - successCount
};
allResults.push(skuResults);
console.log(` SKU ${sku.sku_id} complete: ${successCount}/${skuResults.images.length} images`);
} catch (skuErr) {
console.error(` SKU ${sku.sku_id} failed:`, skuErr.message);
allResults.push({
sku_id: sku.sku_id,
status: 'failed',
error: skuErr.message,
images: []
});
}
}
const completedCount = allResults.filter(r => r.status === 'complete').length;
console.log(`\n========== Batch Complete: ${completedCount}/${skus.length} SKUs ==========\n`);
res.json({
success: true,
total_skus: skus.length,
completed: completedCount,
results: allResults
});
} catch (error) {
console.error('Batch Generate Error:', error.message);
res.status(500).json({ error: 'Batch generation failed: ' + error.message });
}
});
/**
* 获取约束预览(调试用)
* POST /api/preview-constraints
*/
app.post('/api/preview-constraints', (req, res) => {
try {
const { sku, imageSpec } = req.body;
if (!sku) {
return res.status(400).json({ error: 'SKU data is required' });
}
const isComparison = imageSpec?.is_comparison || imageSpec?.id === 'APlus_02';
const fullPrompt = injectConstraints(
imageSpec?.ai_prompt || 'Sample prompt for preview',
sku,
{
isComparison,
logoPlacement: imageSpec?.logo_placement || {}
}
);
res.json({
original_prompt: imageSpec?.ai_prompt || 'Sample prompt for preview',
full_prompt_with_constraints: fullPrompt,
prompt_length: fullPrompt.length
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============================================
// 启动服务器
// ============================================
app.listen(port, () => {
console.log(`\n🚀 Server running at http://localhost:${port}`);
console.log('📁 Available endpoints:');
console.log(' GET /api/prompt-template - Get prompt template');
console.log(' GET /api/brain-prompt - Get brain system prompt');
console.log(' GET /api/sample-sku - Get sample SKU data');
console.log(' POST /api/plan-images - Stage 1: Brain planning');
console.log(' POST /api/generate-sku - Full SKU generation (12 images)');
console.log(' POST /api/batch-generate - Batch SKU generation');
console.log(' POST /api/preview-constraints - Preview constraints injection');
console.log('');
});

359
test-generate.js Normal file
View File

@@ -0,0 +1,359 @@
/**
* 测试脚本:使用素材图片生成电商图片
* 运行方式: node test-generate.js
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// 配置
const SERVER_URL = 'http://localhost:3000';
const OUTPUT_DIR = path.join(__dirname, 'output');
const MATERIAL_DIR = path.join(__dirname, '素材/素材/已有的素材');
// 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,
});
/**
* 上传图片到R2
*/
async function uploadToR2(filePath) {
const fileName = `test-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
const contentType = filePath.endsWith('.png') ? 'image/png' : 'image/jpeg';
const command = new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: contentType,
});
await r2Client.send(command);
const publicUrl = process.env.R2_PUBLIC_DOMAIN
? `${process.env.R2_PUBLIC_DOMAIN}/${fileName}`
: `https://pub-${process.env.R2_ACCOUNT_ID}.r2.dev/${fileName}`;
console.log(` 上传成功: ${fileName}`);
return publicUrl;
}
/**
* 下载图片到本地
*/
async function downloadImage(url, outputPath) {
try {
const response = await axios.get(url, { responseType: 'arraybuffer', timeout: 60000 });
fs.writeFileSync(outputPath, response.data);
console.log(` 下载成功: ${path.basename(outputPath)}`);
return true;
} catch (error) {
console.error(` 下载失败: ${error.message}`);
return false;
}
}
/**
* 主函数
*/
async function main() {
console.log('\n========================================');
console.log('Touchdog 电商图片生成测试');
console.log('========================================\n');
// 确保输出目录存在
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
// 1. 获取素材图片
console.log('📁 Step 1: 读取素材图片...');
const materialFiles = fs.readdirSync(MATERIAL_DIR)
.filter(f => /\.(jpg|jpeg|png)$/i.test(f))
.slice(0, 3); // 取前3张作为参考
console.log(` 找到 ${materialFiles.length} 张素材图片`);
// 2. 上传素材到R2
console.log('\n📤 Step 2: 上传素材到R2...');
const refImageUrls = [];
for (const file of materialFiles) {
try {
const filePath = path.join(MATERIAL_DIR, file);
const url = await uploadToR2(filePath);
refImageUrls.push(url);
} catch (error) {
console.error(` 上传失败 ${file}: ${error.message}`);
}
}
if (refImageUrls.length === 0) {
console.error('❌ 没有成功上传任何参考图片,退出');
process.exit(1);
}
console.log(` 成功上传 ${refImageUrls.length} 张参考图`);
// 3. 构建SKU数据
console.log('\n📝 Step 3: 构建SKU数据...');
const sku = {
sku_id: 'TD-EC-TEST-001',
product_name: 'Cat Soft Cone Collar',
product_name_cn: '伊丽莎白圈',
brand: 'Touchdog',
color: {
name: 'Ice Blue',
name_cn: '冰蓝色',
series: 'Iridescent',
hex: '#B0E0E6',
edge_color: '#7FDBDB'
},
selling_points: [
{
key: 'lightweight',
title_en: 'LIGHTER THAN AN EGG',
title_cn: '极致轻盈',
value: '65g',
description_en: 'Cloud-light comfort at only 65g',
visual_prompt: 'product shown next to an egg for size comparison'
},
{
key: 'waterproof',
title_en: 'WATERPROOF & EASY WIPE',
title_cn: '防水易清洁',
description_en: 'Waterproof PU fabric, stains wipe clean instantly',
visual_prompt: 'water droplets beading on the surface'
},
{
key: 'breathable',
title_en: 'BREATHABLE COTTON LINING',
title_cn: '舒适亲肤',
description_en: 'Soft cotton inner layer, comfortable for extended wear',
visual_prompt: 'close-up of soft cotton inner lining'
},
{
key: 'adjustable',
title_en: 'ADJUSTABLE STRAP',
title_cn: '可调节',
description_en: '3-second on/off, adjustable velcro',
visual_prompt: 'velcro strap being adjusted'
}
],
specs: {
weight: '65g',
depth_cm: '24.5',
sizes: ['XS', 'S', 'M', 'L', 'XL'],
materials: {
outer: 'Durable Waterproof PU',
inner: '95% Cotton + 5% Spandex'
}
},
use_cases: [
{ name_en: 'Postoperative Care', name_cn: '术后恢复' },
{ name_en: 'Eye Drop Application', name_cn: '驱虫护理' },
{ name_en: 'Nail Trimming', name_cn: '指甲修剪' },
{ name_en: 'Grooming', name_cn: '美容护理' }
],
ref_images: refImageUrls
};
console.log(' SKU数据准备完成');
console.log(` 参考图: ${refImageUrls.length}`);
// 4. 调用生成API (只生成前3张测试)
console.log('\n🎨 Step 4: 调用AI生图API...');
console.log(' (为节省时间仅生成3张测试图)\n');
// 直接调用图像生成API进行测试
const testPrompts = [
{
id: 'Main_01',
type: '场景首图+卖点摘要',
prompt: `Professional Amazon main image for Touchdog Cat Soft Cone Collar, 1:1 square format.
COMPOSITION:
- TOP 60%: A beautiful Ragdoll cat wearing the Ice Blue (#B0E0E6) soft cone collar in a cozy modern home interior with warm wood shelving. Natural soft lighting from window. Cat looks comfortable and relaxed. The cone collar is soft fabric with petal-like segments.
- BOTTOM 40%: Light blue (#B0E0E6) rounded banner with title "DESIGNED FOR COMFORTABLE RECOVERY" in navy blue text. Below: three small circular images showing:
1. Product next to an egg showing 65g weight - "LIGHTER THAN AN EGG"
2. Water droplets on surface - "WATERPROOF & EASY WIPE"
3. Soft cotton lining detail - "BREATHABLE COTTON LINING"
PRODUCT REQUIREMENTS:
- Soft cone collar with petal-like fabric segments, NOT rigid plastic
- Color: Ice Blue (#B0E0E6) with teal edge binding (#7FDBDB)
- Embroidered "TOUCHDOG®" logo visible on right petal (2-3 o'clock position)
- Waterproof PU outer, soft cotton inner visible at edges
STYLE: Clean, professional Amazon listing photo. Warm home aesthetic. High-end pet product photography.
--ar 1:1`
},
{
id: 'Main_02',
type: '产品平铺+细节放大',
prompt: `Professional Amazon product detail image for Touchdog Cat Soft Cone Collar, 1:1 square format.
COMPOSITION:
- CENTER: Ice Blue (#B0E0E6) soft cone collar displayed flat from above on clean white background. Shows full petal-like segment structure.
- Two circular detail callout bubbles with thin lines pointing to product:
1. Left callout: "DURABLE WATERPROOF PU LAYER" - showing the smooth outer texture
2. Right callout: "DOUBLE-LAYER COMFORT" - showing the soft ribbed edge binding in teal (#7FDBDB)
PRODUCT REQUIREMENTS:
- Soft fabric cone with 8-10 petal segments radiating from center
- Visible velcro closure strap
- "TOUCHDOG®" embroidered logo on right side
- Color: Ice Blue (#B0E0E6) main, teal binding
STYLE: Clean white background product photography. Professional Amazon listing style. Even studio lighting.
--ar 1:1`
},
{
id: 'APlus_01',
type: '品牌Banner',
prompt: `Professional Amazon A+ brand banner image for Touchdog Cat Soft Cone Collar, landscape 970x600 format.
COMPOSITION:
- FULL WIDTH: Warm modern living room scene with a beautiful white cat wearing the Ice Blue soft cone collar, standing gracefully on a light wood surface.
- LEFT SIDE: Large stylized "TOUCHDOG" brand text in coral/pink color with playful font, paw print decorations
- BELOW: "CAT SOFT CONE COLLAR" product name text
PRODUCT REQUIREMENTS:
- Cat wearing Ice Blue (#B0E0E6) soft cone collar
- Petal-like fabric segments visible
- Collar looks soft and comfortable, not rigid
ENVIRONMENT:
- Cozy modern home interior
- Soft natural lighting
- Warm neutral tones (beige, cream, light wood)
- Professional lifestyle photography feel
BRAND ELEMENTS:
- Touchdog logo with heart-shaped dog icon in red
- "wow pretty" tagline
--ar 3:2`
}
];
const results = [];
for (const testPrompt of testPrompts) {
console.log(` 生成 ${testPrompt.id}: ${testPrompt.type}...`);
try {
const payload = {
model: 'gemini-3-pro-image-preview',
prompt: testPrompt.prompt,
n: 1,
size: '1K',
aspect_ratio: testPrompt.id.startsWith('APlus') ? '3:2' : '1:1',
image_urls: refImageUrls
};
const response = await axios.post(
'https://api2img.shubiaobiao.com/v1/images/generations',
payload,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
timeout: 180000
}
);
// 处理响应 - 可能是URL或base64
const imageData = response.data.data?.[0];
const outputPath = path.join(OUTPUT_DIR, `${sku.sku_id}-${testPrompt.id}.jpg`);
if (imageData?.b64_json) {
// Base64格式 - 直接保存
const imageBuffer = Buffer.from(imageData.b64_json, 'base64');
fs.writeFileSync(outputPath, imageBuffer);
console.log(` 保存成功: ${path.basename(outputPath)}`);
results.push({
id: testPrompt.id,
type: testPrompt.type,
localPath: outputPath,
status: 'success'
});
console.log(`${testPrompt.id} 生成成功`);
} else if (imageData?.url) {
// URL格式 - 下载保存
await downloadImage(imageData.url, outputPath);
results.push({
id: testPrompt.id,
type: testPrompt.type,
url: imageData.url,
localPath: outputPath,
status: 'success'
});
console.log(`${testPrompt.id} 生成成功`);
} else {
throw new Error('No image data in response');
}
// 延迟避免限流
await new Promise(r => setTimeout(r, 2000));
} catch (error) {
console.error(`${testPrompt.id} 生成失败: ${error.message}`);
results.push({
id: testPrompt.id,
type: testPrompt.type,
status: 'failed',
error: error.message
});
}
}
// 5. 输出结果
console.log('\n========================================');
console.log('生成结果汇总');
console.log('========================================');
const successCount = results.filter(r => r.status === 'success').length;
console.log(`\n成功: ${successCount}/${results.length}`);
results.forEach(r => {
const status = r.status === 'success' ? '✓' : '✗';
console.log(` ${status} ${r.id} - ${r.type}`);
if (r.localPath) {
console.log(` 保存位置: ${r.localPath}`);
}
});
console.log(`\n📁 输出目录: ${OUTPUT_DIR}`);
console.log('\n========================================\n');
// 保存结果JSON
fs.writeFileSync(
path.join(OUTPUT_DIR, 'generation-results.json'),
JSON.stringify({ sku, results }, null, 2)
);
}
// 运行
main().catch(console.error);

209
test-vision-extract.js Normal file
View File

@@ -0,0 +1,209 @@
/**
* 测试Vision自动提取产品特征
* 目标验证Vision能否从素材图中自动提取"黄金产品描述"
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
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(filePath) {
const fileName = `vision-test-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: filePath.endsWith('.png') ? 'image/png' : 'image/jpeg',
}));
const publicUrl = 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}`;
return publicUrl;
}
// Vision提取产品特征的Prompt
const VISION_EXTRACT_PROMPT = `Analyze this pet recovery cone product image in EXTREME detail.
You MUST extract and output the following information as structured JSON:
{
"color": {
"primary": "<hex code like #C5E8ED>",
"name": "<descriptive name like 'ice blue', 'mint green'>",
"secondary": "<any accent colors>"
},
"shape": {
"type": "<flower/fan/cone/other>",
"petal_count": <number of segments/petals>,
"opening": "<C-shaped/full-circle/other>",
"description": "<detailed shape description>"
},
"material": {
"type": "<PU/fabric/plastic/other>",
"finish": "<glossy/matte/satin>",
"texture": "<smooth/quilted/ribbed>"
},
"edge_binding": {
"color": "<color of inner neck edge>",
"material": "<ribbed elastic/fabric/other>"
},
"closure": {
"type": "<velcro/button/snap/other>",
"color": "<white/matching/other>",
"position": "<which petal or location>"
},
"logo": {
"text": "<brand name if visible>",
"style": "<embroidered/printed/tag>",
"position": "<location on product>"
},
"unique_features": [
"<list any distinctive features>"
],
"overall_description": "<2-3 sentence summary for image generation prompt>"
}
Be EXTREMELY precise about colors and structural details. This will be used to generate consistent product images.`;
async function testVisionExtract(imagePath) {
console.log('📤 上传图片到R2...');
const imageUrl = await uploadToR2(imagePath);
console.log(' URL:', imageUrl);
console.log('\n🔍 调用Vision API分析图片...');
try {
const response = await axios.post(`${API_BASE}/chat/index`, {
key: API_KEY,
model: 'gemini-3-pro',
content: VISION_EXTRACT_PROMPT,
image_url: imageUrl
}, {
timeout: 60000
});
const content = response.data.data?.choices?.[0]?.message?.content ||
response.data.data?.content ||
response.data.choices?.[0]?.message?.content;
if (!content) {
console.error('Vision响应为空');
console.log('完整响应:', JSON.stringify(response.data, null, 2));
return null;
}
console.log('\n📝 Vision原始输出:\n');
console.log(content);
// 尝试提取JSON
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
const parsed = JSON.parse(jsonMatch[0]);
console.log('\n✅ 解析后的JSON:\n');
console.log(JSON.stringify(parsed, null, 2));
return parsed;
} catch (e) {
console.log('\n⚠ JSON解析失败但有输出内容');
return { raw: content };
}
}
return { raw: content };
} catch (error) {
console.error('Vision API错误:', error.message);
if (error.response) {
console.error('响应数据:', error.response.data);
}
return null;
}
}
async function main() {
const MATERIAL_DIR = path.join(__dirname, '素材/素材/已有的素材');
const flatImagePath = path.join(MATERIAL_DIR, 'IMG_5683.png');
console.log('='.repeat(60));
console.log('🧪 Vision产品特征提取测试');
console.log('='.repeat(60));
console.log('\n测试图片:', flatImagePath);
const result = await testVisionExtract(flatImagePath);
if (result) {
// 保存结果
const outputPath = path.join(__dirname, 'vision-extract-result.json');
fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
console.log('\n💾 结果已保存到:', outputPath);
// 如果成功解析生成Golden Description
if (result.overall_description || result.shape) {
console.log('\n' + '='.repeat(60));
console.log('🌟 生成的Golden Product Description:');
console.log('='.repeat(60));
const golden = generateGoldenDescription(result);
console.log(golden);
fs.writeFileSync(
path.join(__dirname, 'golden-description.txt'),
golden
);
}
}
}
function generateGoldenDescription(visionResult) {
if (visionResult.raw) {
return `[需要手动整理Vision输出]\n\n${visionResult.raw}`;
}
const r = visionResult;
return `
EXACT PRODUCT APPEARANCE (AUTO-EXTRACTED BY VISION):
- Shape: ${r.shape?.petal_count || '?'}-PETAL ${r.shape?.type?.toUpperCase() || 'FLOWER'} shape, ${r.shape?.opening || 'C-shaped'} opening
- Color: ${r.color?.name?.toUpperCase() || 'UNKNOWN'} (${r.color?.primary || '#???'})
- Material: ${r.material?.finish || 'Glossy'} ${r.material?.type || 'PU'} fabric with ${r.material?.texture || 'smooth'} texture
- Edge binding: ${r.edge_binding?.color || 'TEAL'} ${r.edge_binding?.material || 'ribbed elastic'} around inner neck hole
- Closure: ${r.closure?.color || 'White'} ${r.closure?.type || 'velcro'} on ${r.closure?.position || 'one petal end'}
- Logo: "${r.logo?.text || 'TOUCHDOG'}" ${r.logo?.style || 'embroidered'} on ${r.logo?.position || 'one petal'}
UNIQUE FEATURES:
${(r.unique_features || []).map(f => `- ${f}`).join('\n') || '- Visible stitching between petals\n- Soft but structured'}
SUMMARY FOR PROMPTS:
${r.overall_description || 'Pet recovery cone with flower-petal design, soft waterproof material.'}
CRITICAL PROHIBITIONS:
- ❌ NO printed patterns or colorful fabric designs
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes (must have C-opening)
- ❌ NO random brand logos or text
`.trim();
}
main().catch(console.error);

240
test-vision-vs-manual.js Normal file
View File

@@ -0,0 +1,240 @@
/**
* 对比测试Vision自动提取 vs 手写描述
* 生成Main_02平铺图对比两者效果
*/
require('dotenv').config();
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const API_KEY = 'G9rXx3Ag2Xfa7Gs8zou6t6HqeZ';
const API_BASE = 'https://api.wuyinkeji.com/api';
const OUTPUT_DIR = path.join(__dirname, 'output_comparison');
const MATERIAL_DIR = path.join(__dirname, '素材/素材/已有的素材');
// 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(filePath) {
const fileName = `compare-${Date.now()}-${path.basename(filePath)}`;
const fileBuffer = fs.readFileSync(filePath);
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME || 'ai-flow',
Key: fileName,
Body: fileBuffer,
ContentType: filePath.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}`;
}
// ========================================
// 方案A: 手写的Golden Description (V2版)
// ========================================
const MANUAL_GOLDEN_DESC = `
EXACT PRODUCT APPEARANCE (MUST MATCH PRECISELY):
- Shape: 8-PETAL FLOWER/FAN shape, C-shaped opening (like Pac-Man), NOT a full circle
- Color: ICE BLUE / Light aqua blue (#C5E8ED approximate)
- Material: Glossy waterproof PU fabric with visible stitching lines between petals
- Edge binding: TEAL/TURQUOISE color binding around the inner neck hole
- Closure: White velcro strap on one petal end
- Logo: "TOUCHDOG®" embroidered in matching blue thread on one petal (small, subtle)
- Texture: Smooth, slightly shiny, NOT matte, NOT cotton fabric
- Structure: Soft but structured, maintains petal shape, NOT floppy
CRITICAL PROHIBITIONS:
- ❌ NO printed patterns or colorful fabric designs
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes
- ❌ NO matte cotton or fleece textures
- ❌ NO random brand logos or text
`;
// ========================================
// 方案B: Vision自动提取的Golden Description
// ========================================
const VISION_GOLDEN_DESC = `
EXACT PRODUCT APPEARANCE (AUTO-EXTRACTED BY VISION):
- Shape: 7-PETAL FLOWER/FAN shape, C-shaped opening
- Color: PASTEL ICE BLUE (#C3E6E8)
- Material: soft matte/satin synthetic fabric (likely water-resistant polyester/nylon) with smooth, padded/quilted panels texture
- Edge binding: Mint Green (slightly more saturated than body) ribbed knit elastic fabric around inner neck hole
- Closure: white velcro (hook and loop) on large rectangular strip covering the entire bottom-left terminal segment
- Logo: "TOUCHDOG®" embroidered on centered on the middle-right segment
UNIQUE FEATURES:
- Scalloped outer edge resembling flower petals
- Soft ribbed knit neckline for comfort
- Radial stitching lines creating distinct padded zones
- Large surface area velcro for adjustable sizing
CRITICAL PROHIBITIONS:
- ❌ NO printed patterns or colorful fabric designs
- ❌ NO hard plastic transparent cones
- ❌ NO fully circular/closed shapes (must have C-opening)
- ❌ NO random brand logos or text
`;
// Main_02 平铺图 Prompt模板
function createMain02Prompt(goldenDesc) {
return `
[PRODUCT FLAT LAY PHOTO - WHITE BACKGROUND]
${goldenDesc}
SCENE REQUIREMENTS:
- Product flat lay on PURE WHITE background (#FFFFFF)
- Shot from directly above (bird's eye view / top-down angle)
- Show the full C-shaped opening clearly (gap between velcro ends visible)
- All petals/segments fully visible and spread out
- Clean studio lighting, MINIMAL shadows
- Product fills 70-80% of frame
- Professional Amazon product photography style
OUTPUT: High quality 1:1 aspect ratio product photo, 8K resolution
`.trim();
}
// 生图任务提交
async function submitImageTask(prompt, refImageUrl) {
const payload = {
key: API_KEY,
prompt: prompt,
img_url: refImageUrl,
aspectRatio: '1:1',
imageSize: '1K'
};
const response = await axios.post(`${API_BASE}/img/nanoBanana-pro`, payload);
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 = 60;
while (attempts < maxAttempts) {
const response = await axios.get(`${API_BASE}/img/drawDetail`, {
params: { key: API_KEY, id: taskId }
});
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++;
}
throw new Error('Timeout waiting for image');
}
// 生成单张图
async function generateImage(name, prompt, refUrl) {
console.log(`\n🎨 生成: ${name}`);
console.log(' 提交任务...');
const taskId = await submitImageTask(prompt, refUrl);
console.log(` Task ID: ${taskId}`);
const imageUrl = await pollImageResult(taskId);
console.log('\n ✓ 生成成功');
// 下载保存
const imgRes = await axios.get(imageUrl, { responseType: 'arraybuffer' });
const outputPath = path.join(OUTPUT_DIR, `${name}.jpg`);
fs.writeFileSync(outputPath, imgRes.data);
console.log(` ✓ 保存: ${outputPath}`);
return outputPath;
}
async function main() {
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
console.log('='.repeat(60));
console.log('🔬 Vision提取 vs 手写描述 对比测试');
console.log('='.repeat(60));
// 上传参考图
console.log('\n📤 上传参考图...');
const flatImgPath = path.join(MATERIAL_DIR, 'IMG_5683.png');
const refUrl = await uploadToR2(flatImgPath);
console.log(' 参考图URL:', refUrl);
// 生成两个版本的Main_02
const results = [];
// 版本A: 手写描述
console.log('\n' + '='.repeat(60));
console.log('📝 版本A: 手写Golden Description');
console.log('='.repeat(60));
const promptA = createMain02Prompt(MANUAL_GOLDEN_DESC);
console.log('Prompt预览:\n', promptA.substring(0, 500) + '...');
try {
const pathA = await generateImage('Main_02_Manual', promptA, refUrl);
results.push({ name: 'Manual', path: pathA, success: true });
} catch (e) {
console.error('版本A失败:', e.message);
results.push({ name: 'Manual', success: false, error: e.message });
}
// 版本B: Vision提取
console.log('\n' + '='.repeat(60));
console.log('🤖 版本B: Vision自动提取Golden Description');
console.log('='.repeat(60));
const promptB = createMain02Prompt(VISION_GOLDEN_DESC);
console.log('Prompt预览:\n', promptB.substring(0, 500) + '...');
try {
const pathB = await generateImage('Main_02_Vision', promptB, refUrl);
results.push({ name: 'Vision', path: pathB, success: true });
} catch (e) {
console.error('版本B失败:', e.message);
results.push({ name: 'Vision', success: false, error: e.message });
}
// 总结
console.log('\n' + '='.repeat(60));
console.log('📊 对比结果');
console.log('='.repeat(60));
console.log(`输出目录: ${OUTPUT_DIR}`);
console.log('\n生成结果:');
results.forEach(r => {
if (r.success) {
console.log(`${r.name}: ${r.path}`);
} else {
console.log(`${r.name}: ${r.error}`);
}
});
console.log('\n💡 请手动对比两张图片评估Vision自动提取的效果是否达标');
}
main().catch(console.error);

31
test_upload.js Normal file
View File

@@ -0,0 +1,31 @@
require('dotenv').config();
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const client = 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 () => {
try {
console.log('Attempting upload to bucket:', process.env.R2_BUCKET_NAME);
const command = new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME,
Key: 'test-file.txt',
Body: 'Hello R2',
ContentType: 'text/plain',
});
await client.send(command);
console.log('Upload successful!');
} catch (err) {
console.error('Upload Error Details:');
console.error('Code:', err.Code);
console.error('Message:', err.message);
console.error('Full Error:', err);
}
})();

View File

@@ -0,0 +1,39 @@
{
"color": {
"primary": "#C3E6E8",
"name": "Pastel Ice Blue",
"secondary": "#FFFFFF (Velcro), #98DBCF (Neck Ribbing)"
},
"shape": {
"type": "flower/fan",
"petal_count": 7,
"opening": "C-shaped",
"description": "A flat, semi-circular fan shape composed of padded segments with a scalloped outer edge, designed to wrap into a cone."
},
"material": {
"type": "synthetic fabric (likely water-resistant polyester/nylon)",
"finish": "soft matte/satin",
"texture": "smooth, padded/quilted panels"
},
"edge_binding": {
"color": "Mint Green (slightly more saturated than body)",
"material": "ribbed knit elastic fabric"
},
"closure": {
"type": "velcro (hook and loop)",
"color": "white",
"position": "large rectangular strip covering the entire bottom-left terminal segment"
},
"logo": {
"text": "TOUCHDOG®",
"style": "embroidered",
"position": "centered on the middle-right segment"
},
"unique_features": [
"Scalloped outer edge resembling flower petals",
"Soft ribbed knit neckline for comfort",
"Radial stitching lines creating distinct padded zones",
"Large surface area velcro for adjustable sizing"
],
"overall_description": "A soft, padded pet recovery collar in pastel ice blue with a scalloped, flower-like shape. It features a comfortable mint-green ribbed knit neck opening, sectioned padding for flexibility, and a prominent white velcro closure strip."
}