feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能

This commit is contained in:
sgp
2026-10-03 21:44:18 +08:00
commit 9e222877ea
497 changed files with 43611 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
/**
* 画个标 · 核心:头像生成(Avatar)
* 头像 = 复用 Mark 纯图形引擎 + 头像专属约束 + 平台规格导出
* - 仅纯图形:过滤带文字布局(monogram 首字 / ribbon 绶带区),保证小尺寸清晰
* - 圆形构图优先:头像布局以圆形/圆角/对称构图为倾向,缩小不变形
* - 圆形裁切预览与导出:社交平台头像均以圆形展示
* - 平台规格一键导出:微信 / QQ / 抖音 / YouTube / App 图标 / Favicon + 自定义
*/
const HGB_AvatarGen = (function () {
'use strict';
/** 适合头像的纯图形布局(Mark 层,全部无文字) */
const AVATAR_LAYOUTS = [
'orb', // 圆标容器(径向渐变圆)
'badge', // 双环徽章
'seal', // 双环印章
'burst', // 光芒徽章(射线环)
'duo', // 双图标对称
'emblem', // 渐变盾徽
'hexagon', // 六边形徽章
'diamond', // 菱形徽章
'arch', // 拱门徽章
'geo', // 几何构形
'techcorner', // 科技角标
'fullbleed', // 头像专属:全出血大图标(无容器)
'concentric', // 头像专属:同心圆环
'centroid' // 头像专属:中心单环
];
/** 平台头像导出规格预设 */
const AVATAR_SIZES = [
{ id: 'wechat', name: '微信头像', size: 300, note: '微信/微博 300' },
{ id: 'qq', name: 'QQ 头像', size: 640, note: 'QQ 640' },
{ id: 'douyin', name: '抖音头像', size: 400, note: '抖音/快手 400' },
{ id: 'youtube', name: 'YouTube 头像', size: 800, note: '频道头像 800' },
{ id: 'app', name: 'App 图标', size: 1024, note: 'iOS/安卓 1024' },
{ id: 'favicon', name: '网站 Favicon', size: 64, note: '浏览器标签 64' }
];
/** 头像词根扩充的主力单色源(懒加载优先预载;多色 emoji/品牌源不参与词根搜索) */
const AVATAR_CORE_SOURCES = [
'lucide', 'tabler', 'feather', 'mdi', 'fluent', 'solar', 'msymbols',
'iconpark', 'hugeicons', 'mingcute', 'carbon', 'majesticons', 'pixelarticons', 'akar-icons'
];
/** 布局 id 是否适合头像(在头像白名单内) */
function isAvatarLayout(layoutId) {
return AVATAR_LAYOUTS.includes(layoutId);
}
/** 洗牌(保证多套头像图标各异) */
function shuffle(arr) {
const a = arr.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
/** 内置库关键词搜索:图标名包含任一英文词根即命中(返回带库前缀 id 的图标数组;懒加载适配:先预载核心源再搜已加载源) */
async function searchBuiltin(roots, limit) {
// 词根搜索依赖已加载源 → 预载头像核心源(容忍单个失败)
await HGB_IconLib.preload(AVATAR_CORE_SOURCES);
const out = [];
const seen = new Set();
const kws = roots.filter(k => k.length >= 3);
if (!kws.length) return out;
for (const kw of kws) {
for (const ic of HGB_IconLib.searchLoaded(kw, limit)) {
const fullId = ic.id;
if (seen.has(fullId)) continue;
seen.add(fullId);
out.push({ id: fullId, name: ic.name, svg: ic.svg, source: ic.source, type: ic.type });
if (out.length >= limit) return out;
}
}
return out;
}
/** 头像素材池:用户选中 + 行业图标优先,内置库按行业图标词根扩充(解决"素材不够/重复") */
async function avatarIconPool(ind, userIcons) {
const picked = (userIcons || []).filter(Boolean).slice(0, 5);
const indIcons = (ind.icons || []);
const poolMap = new Map();
// 本地段:用户选中 + 行业图标(保持插入顺序)
for (const ic of picked) poolMap.set(ic.id, ic);
for (const ic of indIcons) if (!poolMap.has(ic.id)) poolMap.set(ic.id, ic);
const localCount = poolMap.size;
// 词根:行业图标 id 按 '-' 拆分(英文语义词根,如 chef-hat → chef/hat)
const roots = [];
for (const ic of indIcons) {
for (const part of String(ic.id || '').split('-')) {
const k = part.toLowerCase();
if (k.length >= 3 && !roots.includes(k)) roots.push(k);
}
}
if (roots.length) {
for (const ic of await searchBuiltin(roots, 60)) {
if (!poolMap.has(ic.id)) poolMap.set(ic.id, ic);
}
}
const all = Array.from(poolMap.values());
// 分段洗牌:行业/用户图标在前(高度相关),内置库扩充在后;各自洗牌保证多套各异
return shuffle(all.slice(0, localCount)).concat(shuffle(all.slice(localCount)));
}
/** 批量生成头像(复用 Mark 生成引擎 + 头像布局白名单 + 内置库素材扩充) */
async function generateAvatars(opt) {
const ind = opt.industry || {};
const count = Math.min(Math.max(opt.count || 5, 1), 20);
// 1) 基础批量生成(风格全覆盖 + 头像布局白名单)
const base = HGB_Generator.generatePlans({ ...opt, count, layoutWhitelist: AVATAR_LAYOUTS });
// 2) 头像素材池扩充(行业/用户图标在前 + 内置库词根扩充;懒加载预载核心源)
const pool = await avatarIconPool(ind, opt.icons);
if (pool.length < 2) return base;
// 3) 逐套替换为池中不同图标(保持风格/布局/配色不变,图标素材多样)
return base.map((p, i) => {
const ic1 = pool[i % pool.length];
const ic2 = pool[(i + Math.ceil(pool.length / 2)) % pool.length];
if (!ic1) return p;
const v = HGB_Generator.generateVariant({
...opt,
layoutWhitelist: AVATAR_LAYOUTS,
layoutId: p.layoutId,
styleId: p.styleId,
icons: ic2 && ic2.id !== ic1.id ? [ic1, ic2] : [ic1],
lockup: p.lockup
});
return Object.assign({}, v, {
id: p.id, name: p.name, concept: p.concept, aiIcon: ic1.id
});
});
}
/** 生成单个头像变体(预览弹窗微调) */
function generateVariant(opt) {
return HGB_Generator.generateVariant({
...opt,
layoutWhitelist: AVATAR_LAYOUTS
});
}
return {
AVATAR_LAYOUTS,
AVATAR_SIZES,
isAvatarLayout,
generateAvatars,
generateVariant
};
})();