119 lines
5.9 KiB
JavaScript
119 lines
5.9 KiB
JavaScript
/**
|
|
* 🤖 300 个 bottts 机器人头像候选预览页(资源库 DiceBear bottts 源直选,无需渲染)
|
|
* 用确定性 seed 生成 300 个不同机器人(过滤负面表情变体),内联 SVG 预览,
|
|
* 支持勾选,每张可下载白底 PNG,底部可复制已选编号。
|
|
*
|
|
* 用法:node scripts/gen-300-bot-avatars.js
|
|
*/
|
|
'use strict';
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
const { generateSeeds } = require('./bot-common');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const OUT_DIR = path.join(ROOT, 'output', 'agent-avatars-png');
|
|
const COUNT = 300;
|
|
|
|
// —— 1. 加载 bottts 风格定义 + DiceBear 渲染引擎 ——
|
|
global.window = global;
|
|
const styleFile = path.join(ROOT, 'js', 'data', 'sources', 'dicebear-bottts.js');
|
|
vm.runInThisContext(fs.readFileSync(styleFile, 'utf8'), { filename: styleFile });
|
|
vm.runInThisContext(fs.readFileSync(path.join(ROOT, 'js', 'core', 'dicebear.js'), 'utf8'), { filename: 'js/core/dicebear.js' });
|
|
|
|
// —— 2. 生成 300 个不同 seed(共享逻辑:过滤负面表情)——
|
|
const cards = generateSeeds(seed => HGB_Dicebear.renderAvatar('bottts', seed), COUNT);
|
|
|
|
// —— 3. 预览页(内联 SVG + 勾选交互 + PNG 下载)——
|
|
let cardHtml = '';
|
|
for (let i = 0; i < cards.length; i++) {
|
|
const c = cards[i];
|
|
const n = String(i + 1).padStart(2, '0');
|
|
cardHtml += `<label class="card" data-idx="${i + 1}">
|
|
<input type="checkbox" class="cb" value="${i + 1}" data-name="${c.seed}">
|
|
${c.svg}
|
|
<div class="no">#${n}</div>
|
|
<div class="name">${c.seed}</div>
|
|
<a class="dl" href="bot-png/agent-${n}.png" download="agent-${n}.png" onclick="event.stopPropagation()">下载 PNG</a>
|
|
</label>\n`;
|
|
}
|
|
|
|
const html = `<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>🤖 300 个 bottts 机器人头像候选 · 勾选后批量渲染</title>
|
|
<style>
|
|
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background:#0f1115; color:#e8eaed; margin:0; padding:24px; }
|
|
h1 { font-size:20px; margin:0 0 4px; }
|
|
.sub { color:#9aa0a6; font-size:13px; margin-bottom:16px; }
|
|
.toolbar { position:sticky; top:0; background:#0f1115ee; padding:10px 0; z-index:9; border-bottom:1px solid #2a2f3a; margin-bottom:16px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
|
|
.toolbar button { background:#2563eb; color:#fff; border:none; border-radius:6px; padding:6px 14px; font-size:13px; cursor:pointer; }
|
|
.toolbar .count { color:#8ab4f8; font-size:13px; }
|
|
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:12px; }
|
|
.card { background:#1b1e26; border:1px solid #2a2f3a; border-radius:12px; padding:12px 8px 10px; text-align:center; cursor:pointer; position:relative; transition:border-color .15s; }
|
|
.card:hover { border-color:#3b82f6; }
|
|
.card.sel { border-color:#22c55e; background:#1a2b1f; }
|
|
.card svg { width:96px; height:96px; border-radius:50%; background:#fff; display:block; margin:0 auto; }
|
|
.card .no { font-size:11px; color:#80868b; margin-top:8px; }
|
|
.card .name { font-size:11px; color:#bdc1c6; }
|
|
.card .cb { position:absolute; top:8px; left:8px; width:16px; height:16px; accent-color:#22c55e; }
|
|
a.dl { display:block; margin-top:6px; font-size:11px; color:#8ab4f8; text-decoration:none; }
|
|
a.dl:hover { text-decoration:underline; }
|
|
.out { margin-top:16px; }
|
|
.out textarea { width:100%; height:56px; background:#1b1e26; color:#8ab4f8; border:1px solid #2a2f3a; border-radius:8px; padding:8px; font-size:12px; box-sizing:border-box; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🤖 300 个 bottts 机器人头像候选</h1>
|
|
<div class="sub">资源库 DiceBear bottts 源 · 已过滤负面表情(眩晕/龇牙)· 点击卡片勾选(默认全选)· 每张可直接下载白底 PNG(bot-png/)· 可复制编号批量渲染</div>
|
|
<div class="toolbar">
|
|
<button onclick="sel(true)">全选</button>
|
|
<button onclick="sel(false)">清空</button>
|
|
<span class="count" id="count">已选 0/${COUNT}</span>
|
|
<button onclick="copySel()">复制已选编号</button>
|
|
</div>
|
|
<div class="grid">${cardHtml}</div>
|
|
<div class="out">
|
|
<textarea id="selText" placeholder="已选编号将显示在这里…" readonly></textarea>
|
|
</div>
|
|
<script>
|
|
const cards = document.querySelectorAll('.card');
|
|
const countEl = document.getElementById('count');
|
|
const selText = document.getElementById('selText');
|
|
function update() {
|
|
const sel = [...cards].filter(c => c.querySelector('.cb').checked);
|
|
cards.forEach(c => c.classList.toggle('sel', c.querySelector('.cb').checked));
|
|
countEl.textContent = '已选 ' + sel.length + '/' + cards.length;
|
|
selText.value = sel.map(c => c.dataset.idx).join(',');
|
|
}
|
|
function sel(v) { cards.forEach(c => c.querySelector('.cb').checked = v); update(); }
|
|
function copySel() {
|
|
if (!selText.value) return alert('请先勾选头像');
|
|
navigator.clipboard.writeText(selText.value).then(() => alert('已复制编号:' + selText.value)).catch(() => alert(selText.value));
|
|
}
|
|
cards.forEach(c => c.addEventListener('click', e => {
|
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'A') return;
|
|
const cb = c.querySelector('.cb'); cb.checked = !cb.checked; update();
|
|
}));
|
|
sel(true);
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
fs.writeFileSync(path.join(OUT_DIR, 'preview-300.html'), html, 'utf8');
|
|
console.log('🎉 已生成 ' + COUNT + ' 个 bottts 候选(负面表情已过滤)→ output/agent-avatars-png/preview-300.html');
|
|
|
|
// 校验唯一性
|
|
const { createHash } = require('crypto');
|
|
const hashes = new Set();
|
|
let dup = 0;
|
|
for (const c of cards) {
|
|
const h = createHash('md5').update(c.svg).digest('hex');
|
|
if (hashes.has(h)) dup++;
|
|
hashes.add(h);
|
|
}
|
|
console.log('头像唯一性:' + (cards.length - dup) + '/' + cards.length + (dup ? '(⚠️ ' + dup + ' 个重复)' : ' ✅'));
|