Files
zhuiguang-ai/scripts/generate-diverse-avatars.mjs

384 lines
17 KiB
JavaScript

// scripts/generate-diverse-avatars.mjs
// 创建一个真正多样化的头像库
// 不绑定具体 Bot,生成200个完全不同风格的头像
// Bot 从这个库中随机匹配
import { writeFileSync, existsSync, mkdirSync } from "fs";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const AVATAR_DIR = resolve(__dirname, "..", "public", "bot-avatars");
const MANIFEST_PATH = resolve(AVATAR_DIR, "manifest.json");
if (!existsSync(AVATAR_DIR)) mkdirSync(AVATAR_DIR, { recursive: true });
// ---------- 调色板 ----------
const PALETTES = [
["#FF6B6B", "#FF8E8E"], ["#FF9F43", "#FFB366"], ["#FECA57", "#FFD571"],
["#48DBFB", "#6BE2FC"], ["#1DD1A1", "#45D9B3"], ["#A29BFE", "#BEB8FF"],
["#FD79A8", "#FD8FB8"], ["#00CEC9", "#33D9D5"], ["#E17055", "#E58874"],
["#6C5CE7", "#8378EC"], ["#FDCB6E", "#FED88C"], ["#E84393", "#EB6CA8"],
["#00B894", "#33C8A7"], ["#0984E3", "#339DEB"], ["#D63031", "#DE5252"],
["#55EFC4", "#77F2D3"], ["#74B9FF", "#8DC8FF"], ["#FF7675", "#FF8D8D"],
["#81ECEC", "#9BF0F0"], ["#A8E6CF", "#BCEDD7"], ["#FFD3B6", "#FFDFCD"],
["#D4A5A5", "#DFB9B9"], ["#C8D6E5", "#D5E0ED"], ["#F8B500", "#F9C533"],
["#0052CC", "#336FD6"], ["#36B37E", "#55C192"], ["#FF5630", "#FF7354"],
["#6554C0", "#7D6FCC"], ["#FFB400", "#FFC233"], ["#009DAE", "#00B3C6"],
];
// 头像尺寸
const W = 512, H = 512, CX = 256, CY = 256;
// ---------- 图标库(共66个不同图标)----------
// 每个图标是一个返回 SVG path/元素的函数
const ICONS = [];
// === 动物 ===
ICONS.push(() => {
// 猫
return `<g transform="translate(${CX},${CY-20})">
<ellipse cx="0" cy="20" rx="70" ry="60" fill="white" opacity="0.95"/>
<polygon points="-50,-50 -25,-15 25,-15 50,-50" fill="white" opacity="0.9"/>
<circle cx="-25" cy="-30" r="8" fill="rgba(0,0,0,0.6)"/><circle cx="25" cy="-30" r="8" fill="rgba(0,0,0,0.6)"/>
<ellipse cx="0" cy="-25" rx="6" ry="4" fill="rgba(0,0,0,0.4)"/>
<path d="M-8,-15 Q0,-10 8,-15" stroke="rgba(0,0,0,0.3)" stroke-width="2" fill="none"/>
<path d="M-15,-50 L-25,-80 L0,-55" fill="white" opacity="0.8"/>
<path d="M15,-50 L25,-80 L0,-55" fill="white" opacity="0.8"/>
</g>`;
});
ICONS.push(() => {
// 狗
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="30" rx="75" ry="60" fill="white" opacity="0.95"/>
<ellipse cx="0" cy="-15" rx="50" ry="40" fill="white" opacity="0.9"/>
<ellipse cx="-60" cy="-5" rx="30" ry="20" fill="white" opacity="0.8"/>
<ellipse cx="60" cy="-5" rx="30" ry="20" fill="white" opacity="0.8"/>
<circle cx="-15" cy="-25" r="6" fill="rgba(0,0,0,0.6)"/><circle cx="15" cy="-25" r="6" fill="rgba(0,0,0,0.6)"/>
<ellipse cx="0" cy="-18" rx="10" ry="6" fill="rgba(0,0,0,0.5)"/>
<path d="M-10,-10 Q0,-5 10,-10" stroke="rgba(0,0,0,0.3)" stroke-width="2" fill="none"/>
<path d="M0,-10 L0,25" stroke="rgba(0,0,0,0.15)" stroke-width="3"/>
</g>`;
});
ICONS.push(() => {
// 鱼
return `<g transform="translate(${CX},${CY})">
<ellipse cx="0" cy="0" rx="100" ry="60" fill="white" opacity="0.95"/>
<polygon points="100,0 160,-40 160,40" fill="white" opacity="0.8"/>
<circle cx="-40" cy="-15" r="10" fill="rgba(0,0,0,0.5)"/>
<circle cx="-40" cy="-15" r="5" fill="rgba(0,0,0,0.3)"/>
<path d="M-20,-20 Q0,-40 20,-20" stroke="rgba(0,0,0,0.15)" stroke-width="3" fill="none"/>
<path d="M-30,10 Q0,30 30,10" stroke="rgba(0,0,0,0.15)" stroke-width="3" fill="none"/>
</g>`;
});
ICONS.push(() => {
// 鸟
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="20" rx="50" ry="40" fill="white" opacity="0.95"/>
<circle cx="-10" cy="-5" r="20" fill="white" opacity="0.9"/>
<circle cx="-20" cy="-10" r="5" fill="rgba(0,0,0,0.5)"/>
<polygon points="-10,-25 10,-15 -8,-15" fill="white" opacity="0.8"/>
<path d="M-80,30 Q-50,10 -30,35" stroke="white" stroke-width="12" fill="none" opacity="0.7"/>
<path d="M30,35 Q50,10 80,25" stroke="white" stroke-width="12" fill="none" opacity="0.7"/>
</g>`;
});
ICONS.push(() => {
// 兔子
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="30" rx="55" ry="50" fill="white" opacity="0.95"/>
<circle cx="0" cy="-10" r="35" fill="white" opacity="0.9"/>
<ellipse cx="-15" cy="-60" rx="12" ry="30" fill="white" opacity="0.85"/>
<ellipse cx="15" cy="-60" rx="12" ry="30" fill="white" opacity="0.85"/>
<circle cx="-12" cy="-15" r="5" fill="rgba(0,0,0,0.5)"/><circle cx="12" cy="-15" r="5" fill="rgba(0,0,0,0.5)"/>
<ellipse cx="0" cy="-8" rx="4" ry="3" fill="rgba(0,0,0,0.4)"/>
<path d="M-5,-5 Q0,0 5,-5" stroke="rgba(0,0,0,0.25)" stroke-width="2" fill="none"/>
</g>`;
});
ICONS.push(() => {
// 狐狸
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="30" rx="55" ry="45" fill="white" opacity="0.95"/>
<polygon points="-40,-20 0,-80 40,-20" fill="white" opacity="0.9"/>
<polygon points="-40,-20 -70,-50 -30,-15" fill="white" opacity="0.7"/>
<polygon points="40,-20 70,-50 30,-15" fill="white" opacity="0.7"/>
<circle cx="-12" cy="-5" r="5" fill="rgba(0,0,0,0.5)"/><circle cx="12" cy="-5" r="5" fill="rgba(0,0,0,0.5)"/>
<ellipse cx="0" cy="2" rx="4" ry="3" fill="rgba(0,0,0,0.4)"/>
</g>`;
});
ICONS.push(() => {
// 熊猫
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="30" rx="65" ry="60" fill="white" opacity="0.95"/>
<circle cx="0" cy="-10" r="45" fill="white" opacity="0.9"/>
<ellipse cx="-20" cy="-40" rx="18" ry="14" fill="rgba(0,0,0,0.7)"/>
<ellipse cx="20" cy="-40" rx="18" ry="14" fill="rgba(0,0,0,0.7)"/>
<circle cx="-15" cy="-15" r="6" fill="rgba(0,0,0,0.5)"/><circle cx="15" cy="-15" r="6" fill="rgba(0,0,0,0.5)"/>
<ellipse cx="0" cy="-7" rx="8" ry="5" fill="rgba(0,0,0,0.4)"/>
<ellipse cx="-40" cy="30" rx="20" ry="16" fill="rgba(0,0,0,0.7)"/>
<ellipse cx="40" cy="30" rx="20" ry="16" fill="rgba(0,0,0,0.7)"/>
</g>`;
});
ICONS.push(() => {
// 大象
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="30" rx="75" ry="65" fill="white" opacity="0.95"/>
<ellipse cx="-30" cy="-10" rx="18" ry="22" fill="white" opacity="0.85"/>
<ellipse cx="30" cy="-10" rx="18" ry="22" fill="white" opacity="0.85"/>
<circle cx="-12" cy="-10" r="4" fill="rgba(0,0,0,0.5)"/><circle cx="12" cy="-10" r="4" fill="rgba(0,0,0,0.5)"/>
<path d="M0,-15 Q-10,-40 10,-40 Q20,-40 15,-15" stroke="white" stroke-width="8" fill="none" opacity="0.8"/>
<ellipse cx="-60" cy="30" rx="22" ry="16" fill="white" opacity="0.75"/>
<ellipse cx="60" cy="30" rx="22" ry="16" fill="white" opacity="0.75"/>
</g>`;
});
ICONS.push(() => {
// 猫头鹰
return `<g transform="translate(${CX},${CY-30})">
<ellipse cx="0" cy="20" rx="55" ry="50" fill="white" opacity="0.95"/>
<ellipse cx="0" cy="-15" rx="50" ry="38" fill="white" opacity="0.9"/>
<polygon points="-30,-50 30,-50 0,-25" fill="white" opacity="0.7"/>
<circle cx="-18" cy="-20" r="12" fill="white" opacity="0.85"/>
<circle cx="18" cy="-20" r="12" fill="white" opacity="0.85"/>
<circle cx="-18" cy="-20" r="5" fill="rgba(0,0,0,0.5)"/><circle cx="18" cy="-20" r="5" fill="rgba(0,0,0,0.5)"/>
<polygon points="-5,-10 0,-5 5,-10" fill="rgba(0,0,0,0.4)"/>
</g>`;
});
// === 自然 ===
ICONS.push(() => {
// 山
return `<g transform="translate(${CX},${CY+30})">
<polygon points="-140,80 0,-120 140,80" fill="white" opacity="0.9"/>
<polygon points="-100,80 -30,-40 40,80" fill="rgba(255,255,255,0.5)"/>
<rect x="-30" y="50" width="60" height="50" rx="5" fill="white" opacity="0.4"/>
</g>`;
});
ICONS.push(() => {
// 太阳
return `<g transform="translate(${CX},${CY})">
<circle cx="0" cy="0" r="70" fill="white" opacity="0.95"/>
${Array.from({length:12}, (_,i) => {
const a = i * 30 * Math.PI/180;
return `<line x1="${Math.cos(a)*85}" y1="${Math.sin(a)*85}" x2="${Math.cos(a)*120}" y2="${Math.sin(a)*120}" stroke="white" stroke-width="8" stroke-linecap="round" opacity="0.7"/>`;
}).join("\n ")}
</g>`;
});
ICONS.push(() => {
// 月亮
return `<g transform="translate(${CX},${CY})">
<circle cx="0" cy="0" r="80" fill="white" opacity="0.95"/>
<circle cx="30" cy="-15" r="60" fill="rgba(0,0,0,0.08)"/>
${Array.from({length:5}, (_,i) => {
const a = i * 60 * Math.PI/180;
const cx2 = Math.cos(a)*35, cy2 = Math.sin(a)*35;
return `<circle cx="${cx2+5}" cy="${cy2-5}" r="${4-i*0.5}" fill="white" opacity="${0.6-i*0.1}"/>`;
}).join("\n ")}
</g>`;
});
ICONS.push(() => {
// 星
return `<g transform="translate(${CX},${CY})">
<polygon points="0,-100 22,-31 95,-31 36,11 59,80 0,40 -59,80 -36,11 -95,-31 -22,-31" fill="white" opacity="0.95"/>
</g>`;
});
ICONS.push(() => {
// 树
return `<g transform="translate(${CX},${CY+10})">
<rect x="-15" y="60" width="30" height="50" rx="4" fill="white" opacity="0.7"/>
<polygon points="0,-120 -100,60 100,60" fill="white" opacity="0.9"/>
<polygon points="0,-80 -80,30 80,30" fill="white" opacity="0.8"/>
<polygon points="0,-40 -60,0 60,0" fill="white" opacity="0.7"/>
</g>`;
});
ICONS.push(() => {
// 花
return `<g transform="translate(${CX},${CY})">
${Array.from({length:6}, (_,i) => {
const a = i * 60 * Math.PI/180;
const cx2 = Math.cos(a)*50, cy2 = Math.sin(a)*50;
return `<ellipse cx="${cx2}" cy="${cy2}" rx="30" ry="20" fill="white" opacity="0.8" transform="rotate(${i*60} ${cx2} ${cy2})"/>`;
}).join("\n ")}
<circle cx="0" cy="0" r="25" fill="white" opacity="0.95"/>
</g>`;
});
ICONS.push(() => {
// 叶子
return `<g transform="translate(${CX},${CY})">
<path d="M0,-100 Q80,-50 60,30 Q20,-10 0,100 Q-20,-10 -60,30 Q-80,-50 0,-100Z" fill="white" opacity="0.95"/>
<line x1="0" y1="-90" x2="0" y2="90" stroke="rgba(0,0,0,0.1)" stroke-width="3"/>
${Array.from({length:7}, (_,i) => {
const y = -60 + i*20;
return `<line x1="-15" y1="${y}" x2="15" y2="${y}" stroke="rgba(0,0,0,0.08)" stroke-width="2"/>`;
}).join("\n ")}
</g>`;
});
ICONS.push(() => {
// 云
return `<g transform="translate(${CX},${CY})">
<circle cx="-50" cy="10" r="35" fill="white" opacity="0.9"/>
<circle cx="0" cy="-10" r="45" fill="white" opacity="0.95"/>
<circle cx="50" cy="10" r="35" fill="white" opacity="0.9"/>
<rect x="-50" y="10" width="100" height="30" rx="15" fill="white" opacity="0.9"/>
</g>`;
});
ICONS.push(() => {
// 闪电
return `<g transform="translate(${CX},${CY-10})">
<polygon points="-30,-100 10,-20 -10,-20 40,90 0,10 20,10 -20,90" fill="white" opacity="0.95"/>
</g>`;
});
ICONS.push(() => {
// 雨滴
return `<g transform="translate(${CX},${CY})">
<path d="M0,-90 Q-40,-20 0,60 Q40,-20 0,-90Z" fill="white" opacity="0.9"/>
<ellipse cx="-10" cy="-40" rx="6" ry="3" fill="rgba(255,255,255,0.3)"/>
</g>`;
});
ICONS.push(() => {
// 火焰
return `<g transform="translate(${CX},${CY})">
<path d="M0,-100 Q-50,-30 -30,50 Q-15,80 0,90 Q15,80 30,50 Q50,-30 0,-100Z" fill="white" opacity="0.95"/>
<path d="M0,-70 Q-25,-10 -15,50 Q-5,70 0,80 Q5,70 15,50 Q25,-10 0,-70Z" fill="rgba(255,255,255,0.4)"/>
</g>`;
});
// === 科技 ===
ICONS.push(() => {
// 齿轮
return `<g transform="translate(${CX},${CY})">
<circle cx="0" cy="0" r="50" fill="white" opacity="0.95"/>
${Array.from({length:8}, (_,i) => {
const a = i * 45 * Math.PI/180;
return `<rect x="${Math.cos(a)*65-15}" y="${Math.sin(a)*65-10}" width="30" height="20" rx="5" fill="white" transform="rotate(${i*45} ${Math.cos(a)*65} ${Math.sin(a)*65})" opacity="0.8"/>`;
}).join("\n ")}
<circle cx="0" cy="0" r="20" fill="rgba(0,0,0,0.1)"/>
</g>`;
});
ICONS.push(() => {
// 机器人
return `<g transform="translate(${CX},${CY})">
<rect x="-50" y="-60" width="100" height="80" rx="20" fill="white" opacity="0.95"/>
<rect x="-70" y="-40" width="20" height="40" rx="10" fill="white" opacity="0.8"/>
<rect x="50" y="-40" width="20" height="40" rx="10" fill="white" opacity="0.8"/>
<circle cx="-18" cy="-25" r="10" fill="rgba(0,0,0,0.1)"/><circle cx="-18" cy="-25" r="5" fill="rgba(0,0,0,0.3)"/>
<circle cx="18" cy="-25" r="10" fill="rgba(0,0,0,0.1)"/><circle cx="18" cy="-25" r="5" fill="rgba(0,0,0,0.3)"/>
<rect x="-20" y="0" width="40" height="5" rx="3" fill="rgba(0,0,0,0.2)"/>
<line x1="0" y1="-80" x2="0" y2="-100" stroke="white" stroke-width="6" stroke-linecap="round" opacity="0.8"/>
<circle cx="0" cy="-100" r="8" fill="white" opacity="0.8"/>
</g>`;
});
ICONS.push(() => {
// 灯泡
return `<g transform="translate(${CX},${CY-10})">
<circle cx="0" cy="-30" r="55" fill="white" opacity="0.95"/>
<rect x="-15" y="20" width="30" height="25" rx="4" fill="white" opacity="0.85"/>
<rect x="-25" y="45" width="50" height="12" rx="4" fill="white" opacity="0.75"/>
<rect x="-20" y="57" width="40" height="8" rx="3" fill="white" opacity="0.65"/>
${Array.from({length:4}, (_,i) => {
const a = (i*30-45) * Math.PI/180;
return `<line x1="${Math.cos(a)*25}" y1="${Math.sin(a)*25-20}" x2="${Math.cos(a)*50}" y2="${Math.sin(a)*50-20}" stroke="white" stroke-width="3" stroke-linecap="round" opacity="0.5"/>`;
}).join("\n ")}
</g>`;
});
ICONS.push(() => {
// 代码符号 </>
return `<g transform="translate(${CX},${CY})">
<path d="M-80,-60 L-20,0 L-80,60" stroke="white" stroke-width="18" fill="none" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"/>
<path d="M80,-60 L20,0 L80,60" stroke="white" stroke-width="18" fill="none" stroke-linecap="round" stroke-linejoin="round" opacity="0.9"/>
</g>`;
});
ICONS.push(() => {
// WiFi
return `<g transform="translate(${CX},${CY+30})">
<circle cx="0" cy="-80" r="10" fill="white" opacity="0.9"/>
<path d="M-40,-40 Q0,-90 40,-40" stroke="white" stroke-width="10" fill="none" stroke-linecap="round" opacity="0.7"/>
<path d="M-75,-10 Q0,-80 75,-10" stroke="white" stroke-width="10" fill="none" stroke-linecap="round" opacity="0.45"/>
</g>`;
});
ICONS.push(() => {
// 芯片
return `<g transform="translate(${CX},${CY})">
<rect x="-70" y="-70" width="140" height="140" rx="15" fill="white" opacity="0.95"/>
<rect x="-40" y="-40" width="80" height="80" rx="8" fill="rgba(0,0,0,0.1)"/>
${Array.from({length:8}, (_,i) => {
const pos = -56 + i * 16;
return `<rect x="${pos}" y="-75" width="8" height="10" rx="2" fill="white" opacity="0.7"/>
<rect x="${pos}" y="65" width="8" height="10" rx="2" fill="white" opacity="0.7"/>
<rect x="-75" y="${pos}" width="10" height="8" rx="2" fill="white" opacity="0.7"/>
<rect x="65" y="${pos}" width="10" height="8" rx="2" fill="white" opacity="0.7"/>`;
}).join("\n ")}
</g>`;
});
ICONS.push(() => {
// 火箭
return `<g transform="translate(${CX},${CY-20})">
<path d="M0,-110 L-30,30 L0,80 L30,30 Z" fill="white" opacity="0.95"/>
<ellipse cx="0" cy="-10" rx="14" ry="18" fill="rgba(0,0,0,0.08)"/>
<polygon points="-15,30 0,100 15,30" fill="rgba(255,255,255,0.5)"/>
<polygon points="-40,30 -10,70 0,30" fill="white" opacity="0.6"/>
<polygon points="40,30 10,70 0,30" fill="white" opacity="0.6"/>
</g>`;
});
ICONS.push(() => {
// 盾牌
return `<g transform="translate(${CX},${CY-20})">
<path d="M0,-110 L-70,-30 L-70,40 Q-70,90 0,110 Q70,90 70,40 L70,-30 Z" fill="white" opacity="0.95"/>
<path d="M-30,-10 L0,20 L30,-10" stroke="rgba(0,0,0,0.15)" stroke-width="12" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="0" y1="-30" x2="0" y2="30" stroke="rgba(0,0,0,0.15)" stroke-width="12" stroke-linecap="round"/>
<line x1="0" y1="40" x2="0" y2="70" stroke="rgba(0,0,0,0.15)" stroke-width="8" stroke-linecap="round"/>
</g>`;
});
// === 商业/学习 ===
ICONS.push(() => {
// 图表(柱状图)
return `<g transform="translate(${CX},${CY-10})">
<rect x="-80" y="30" width="30" height="50" rx="5" fill="white" opacity="0.85"/>
<rect x="-30" y="-10" width="30" height="90" rx="5" fill="white" opacity="0.95"/>
<rect x="20" y="10" width="30" height="70" rx="5" fill="white" opacity="0.9"/>
<rect x="70" y="-40" width="30" height="120" rx="5" fill="white" opacity="0.8"/>
<line x1="-95" y1="80" x2="115" y2="80" stroke="white" stroke-width="4" opacity="0.6"/>
</g>`;
});
ICONS.push(() => {
// 钱包/钱袋
return `<g transform="translate(${CX},${CY-10})">
<path d="M-70,-40 Q-80,-80 0,-100 Q80,-80 70,-40 L80,80 Q80,100 60,100 L-60,100 Q-80,100 -80,80 Z" fill="white" opacity="0.95"/>
<rect x="-30" y="-10" width="60" height="35" rx="10" fill="rgba(0,0,0,0.1)"/>
<circle cx="-15" cy="8" r="5" fill="rgba(0,0,0,0.15)"/><circle cx="5" cy="8" r="5" fill="rgba(0,0,0,0.15)"/><circle cx="25" cy="8" r="5" fill="rgba(0,0,0,0.15)"/>
<line x1="-60" y1="-40" x2="60" y2="-40" stroke="rgba(0,0,0,0.1)" stroke-width="6" stroke-linecap="round"/>
</g>`;
});
ICONS.push(() => {
// 书本
return `<g transform="translate(${CX},${CY})">
<path d="M-90,-70 L0,-90 L90,-70 L90,80 L0,100 L-90,80 Z" fill="white" opacity="0.9"/>
<path d="M-70,-55 L0,-75 L70,-55 L70,65 L0,85 L-70,65 Z" fill="rgba(0,0,0,0.06)"/>