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