/** * 🤖 bottts 机器人头像生成共享逻辑(预览页与 PNG 渲染共用,保证 seed 序列一致) * - 过滤负面表情变体:eyes.dizzy(眩晕)、mouth.bite(龇牙) * - 确定性 seed(agent-N),负面/重复时自动追加后缀重试 */ 'use strict'; /** 负面变体特征字符串(来自 dicebear-bottts.js 定义,命中即视为负面表情) */ const NEG_MARKERS = [ 'M30.5 15.3c.4-.4', // eyes.dizzy(眩晕旋转眼) '6-8H10z' // mouth.bite(咬合龇牙) ]; function isNegative(svg) { return NEG_MARKERS.some(m => svg.includes(m)); } /** * 生成 count 个不重复、非负面表情的头像 * @param {(seed:string)=>{svg:string}} render 渲染函数(同 seed 同输出) * @param {number} count 数量 * @returns {{seed:string, svg:string}[]} */ function generateSeeds(render, count) { const out = []; let n = 1; let guard = 0; const MAX_GUARD = count * 120 + 5000; while (out.length < count) { if (++guard > MAX_GUARD) { throw new Error(`[bot-common] 生成 ${count} 个唯一头像失败:该风格可能渲染无变化(n=${n}, 已有 ${out.length})`); } let seed = 'agent-' + n; let svg = render(seed).svg; if (!svg) { n++; continue; } // 空渲染直接跳过 let attempt = 0; while (isNegative(svg) || out.some(o => o.svg === svg)) { attempt++; if (attempt > 60) break; // 保护 seed = 'agent-' + n + '-' + attempt; svg = render(seed).svg; if (!svg) { n++; break; } } if (!isNegative(svg) && !out.some(o => o.svg === svg)) { out.push({ seed, svg }); } n++; } return out; } module.exports = { generateSeeds, isNegative };