Upload latest code and optimized prompts (v6)
This commit is contained in:
240
test-vision-vs-manual.js
Normal file
240
test-vision-vs-manual.js
Normal 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user