Files
drawlogo/scripts/avatar-ai-smoke.js
T

140 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 冒烟:头像模式 AI 路径(aiDesigner buildPrompt/validate/mapToPlan)
* 验证:
* 1) buildPrompt 头像版布局白名单 = 头像布局、不含 lockup 白名单
* 2) validate + normalizeLayoutDecisions 把 AI 布局修正到头像白名单内
* 3) mapToPlan 产物 layoutId 在头像白名单、lockup 为空、无文字元素
*/
'use strict';
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const ROOT = path.resolve(__dirname, '..');
// 加载顺序:数据 → 核心(依赖全局常量 HGB_*)
const files = [
'js/data/icons.js',
'js/data/styles.js',
'js/data/industries.js',
'js/data/builtin-icons.js',
'js/data/decorations.js',
'js/core/generator.js',
'js/core/avatarGen.js',
'js/skills/skills.js',
'js/core/ai.js',
'js/core/aiDesigner.js'
];
for (const f of files) {
const src = fs.readFileSync(path.join(ROOT, f), 'utf8');
// vm 在全局作用域执行,使 HGB_* 成为全局词法绑定
vm.runInThisContext(src, { filename: f });
}
const ind = HGB_Industries.find('food');
const opt = {
industry: ind,
brand: '川味小馆',
sub: '',
desc: '川菜馆,麻辣鲜香',
icons: [],
styleIds: [],
skills: ['composition', 'contrast-check'],
count: 5,
avatar: true
};
let pass = 0, fail = 0;
function check(name, ok, detail) {
if (ok) { pass++; console.log(' ✅ ' + name + (detail ? ' — ' + detail : '')); }
else { fail++; console.log(' ❌ ' + name + (detail ? ' — ' + detail : '')); }
}
// 1) Prompt 头像版
const msgs = HGB_AIDesigner.buildPrompt(opt);
const sys = msgs[0].content, user = msgs[1].content;
const avatarLayoutIds = HGB_AvatarGen.AVATAR_LAYOUTS;
const fullLayoutIds = HGB_Generator.LAYOUTS.map(l => l.id);
console.log('== 1) buildPrompt 头像版 ==');
check('系统提示为头像设计师', /头像设计师/.test(sys));
// 布局白名单:LOGO 独有的布局(monogram 等)不应出现在头像 prompt
const logoOnly = fullLayoutIds.filter(id => !avatarLayoutIds.includes(id));
check('头像 prompt 不含 LOGO 专属布局', !logoOnly.some(id => new RegExp('\\b' + id + '\\b').test(user)), '排除: ' + logoOnly.join('/'));
check('头像 prompt 含头像布局', avatarLayoutIds.every(id => new RegExp('\\b' + id + '\\b').test(user)) || user.includes('orb'));
check('头像 prompt 不含 lockup 白名单', !user.includes('lockup') && !user.includes('品牌组合位置'));
check('头像 prompt 强调无文字/纯图形', /纯图形|不含任何文字/.test(sys));
// 2) validate:给一批非法布局(含 LOGO 专属 monogram/ribbon),验证全部修正到头像白名单
console.log('== 2) validate 布局修正 ==');
const fake = {
overallConcept: 'x',
plans: [
{ name: 'A', concept: 'c', icon: 'bad', layout: 'monogram', styleId: 'minimal' },
{ name: 'B', concept: 'c', icon: 'bad', layout: 'ribbon', styleId: 'artdeco' },
{ name: 'C', concept: 'c', icon: 'bad', layout: 'badge', styleId: 'neon' },
{ name: 'D', concept: 'c', icon: 'bad', layout: 'seal', styleId: 'minimal' },
{ name: 'E', concept: 'c', icon: 'bad', layout: 'orb', styleId: 'minimal' }
]
};
const data = HGB_AIDesigner.validate(fake, opt);
const layouts = data.plans.map(p => p.layout);
const uniq = new Set(layouts);
check('全部布局在头像白名单', layouts.every(l => avatarLayoutIds.includes(l)), layouts.join('/'));
check('非法布局被修正并标记', layouts.includes('orb') || data.plans.some(p => p._layoutInvalid));
check('布局多样性 ≥3', uniq.size >= 3, '种类 ' + uniq.size);
const round = layouts.filter(l => ['badge', 'seal', 'orb', 'duo', 'burst'].includes(l)).length;
check('头像圆形构图 ≤4', round <= 4, '圆形 ' + round);
// 3) mapToPlan 产物结构(icon 非法 → 回退行业图标)
console.log('== 3) mapToPlan 产物 ==');
const plans = data.plans.map((p, i) => HGB_AIDesigner.mapToPlan(p, opt, i));
for (let i = 0; i < plans.length; i++) {
const p = plans[i];
check('方案' + (i + 1) + ' layoutId 在头像白名单', avatarLayoutIds.includes(p.layoutId), p.layoutId);
const hasText = (p.markElements || []).some(el => el.type === 'text');
check('方案' + (i + 1) + ' Mark 无文字元素', !hasText);
check('方案' + (i + 1) + ' 有 ai 标记与概念', !!p.ai && !!p.concept);
check('方案' + (i + 1) + ' 配色跟随风格', !!p.paletteName && !!p.bg);
}
// 图标差异:AI 图标非法时按序号轮换行业图标,不应全套同一
const iconSet = new Set(plans.map(p => (p.aiIcon || '') + '|' + (p.markElements || []).filter(e => e.type === 'icon').map(e => e.iconId).join(',')));
check('图标有差异(>1 种)', iconSet.size > 1, '种类 ' + iconSet.size);
// 4) 头像专有布局(avatarOnly)差异化:LOGO 全局池排除、头像白名单包含
console.log('== 4) 头像专有布局差异化 ==');
const avatarOnlyIds = HGB_Generator.LAYOUTS.filter(l => l.avatarOnly).map(l => l.id);
check('存在 3 个头像专有布局', avatarOnlyIds.length === 3 && JSON.stringify(avatarOnlyIds) === JSON.stringify(['fullbleed', 'concentric', 'centroid']), avatarOnlyIds.join('/'));
// LOGO 模式 generatePlans(无白名单)不得出现头像专有布局
const logoPlans = HGB_Generator.generatePlans({ industry: ind, brand: '川味小馆', count: 12 });
check('LOGO 模式 12 套全部布局合法', logoPlans.every(p => !avatarOnlyIds.includes(p.layoutId)));
check('LOGO 模式布局多样', new Set(logoPlans.map(p => p.layoutId)).size >= 5);
// 头像模式 generateAvatars 白名单包含头像专有布局
check('头像白名单含头像专有布局', avatarOnlyIds.every(id => HGB_AvatarGen.AVATAR_LAYOUTS.includes(id)));
const avatarPlans = HGB_AvatarGen.generateAvatars({ industry: ind, brand: '川味小馆', count: 20 });
const avLayouts = new Set(avatarPlans.map(p => p.layoutId));
check('头像 20 套产出头像专有布局', avatarOnlyIds.some(id => avLayouts.has(id)), '头像布局 ' + avLayouts.size + ' 种: ' + [...avLayouts].slice(0, 8).join('/') + '…');
check('头像 20 套全部在白名单内', avatarPlans.every(p => HGB_AvatarGen.isAvatarLayout(p.layoutId)));
// AI 校验:头像 validate 允许头像专有布局;LOGO validate 标记头像专有布局非法
const avFake = { overallConcept: 'x', plans: [
{ name: 'A', concept: 'c', icon: 'bad', layout: 'fullbleed', styleId: 'minimal' },
{ name: 'B', concept: 'c', icon: 'bad', layout: 'concentric', styleId: 'neon' },
{ name: 'C', concept: 'c', icon: 'bad', layout: 'centroid', styleId: 'minimal' },
{ name: 'D', concept: 'c', icon: 'bad', layout: 'orb', styleId: 'minimal' },
{ name: 'E', concept: 'c', icon: 'bad', layout: 'seal', styleId: 'artdeco' }
]};
const avData = HGB_AIDesigner.validate(avFake, opt);
check('AI 头像接受头像专有布局', avData.plans.every(p => avatarOnlyIds.includes(p.layout) || HGB_AvatarGen.isAvatarLayout(p.layout)));
const logoOpt = { ...opt, avatar: false };
const lgFake = { overallConcept: 'x', plans: [
{ name: 'A', concept: 'c', icon: 'bad', layout: 'fullbleed', styleId: 'minimal' },
{ name: 'B', concept: 'c', icon: 'bad', layout: 'badge', styleId: 'minimal' },
{ name: 'C', concept: 'c', icon: 'bad', layout: 'seal', styleId: 'minimal' },
{ name: 'D', concept: 'c', icon: 'bad', layout: 'orb', styleId: 'minimal' },
{ name: 'E', concept: 'c', icon: 'bad', layout: 'monogram', styleId: 'minimal' }
]};
const lgData = HGB_AIDesigner.validate(lgFake, logoOpt);
const lgLayouts = lgData.plans.map(p => p.layout);
check('AI LOGO 排除头像专有布局', lgLayouts.every(l => !avatarOnlyIds.includes(l)), lgLayouts.join('/'));
console.log('\n结果: ' + pass + ' 通过, ' + fail + ' 失败');
process.exit(fail ? 1 : 0);