370 lines
11 KiB
JavaScript
370 lines
11 KiB
JavaScript
/**
|
|
* 约束注入器
|
|
* 自动将各类约束追加到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
|
|
};
|
|
|
|
|
|
|