feat: 首次推送到Gitea - 完整项目代码 + 安全加固 + 知识库
This commit is contained in:
@@ -82,7 +82,8 @@ export const PALETTE = [
|
||||
];
|
||||
|
||||
export function pickPalette(key) {
|
||||
return PALETTE[pickFromKey(key, PALETTE.length)];
|
||||
// 改为真正的随机,而不是基于key的哈希
|
||||
return PALETTE[Math.floor(Math.random() * PALETTE.length)];
|
||||
}
|
||||
|
||||
// ---------- Lunaris 调用 ----------
|
||||
@@ -121,35 +122,80 @@ export async function isLunarisDefault(buffer) {
|
||||
* 根据 bot persona 生成 512x512 SVG 头像
|
||||
* 设计要素:
|
||||
* - 渐变背景(palette.from → palette.to)
|
||||
* - 4 个装饰几何形(位置/大小/旋转由 key 决定)
|
||||
* - 中央大字 initial(displayName 首字符)
|
||||
* - 右下角 🤖 标识(用 SVG path 画,不依赖 emoji 字体)
|
||||
* - 独特的几何图案(基于 key 哈希生成)
|
||||
* - 中央标识(使用 displayName 的完整首字或独特符号)
|
||||
* - 装饰元素多样化(圆形、方形、三角形、线条等)
|
||||
*/
|
||||
export function generateSvgAvatar({ key, displayName, persona }) {
|
||||
const palette = pickPalette(key);
|
||||
const [b1, b2, b3, b4] = keyToBytes(key);
|
||||
const hash = hashKey(key);
|
||||
const initial = (displayName || key).charAt(0).toUpperCase();
|
||||
const isPasserby = persona?.role === "passerby";
|
||||
|
||||
// 4 个装饰形:圆/三角/方/菱形,位置基于 key 哈希
|
||||
const shapes = [
|
||||
{ type: "circle", cx: 40 + b1 * 0.7, cy: 60 + b2 * 0.4, r: 30 + (b3 % 40), opacity: 0.18 },
|
||||
{ type: "rect", x: 320 + b2 * 0.3, y: 30 + b3 * 0.2, w: 60 + (b4 % 50), h: 60 + (b1 % 50), rot: b1 % 90, opacity: 0.14 },
|
||||
{ type: "circle", cx: 380 + b3 * 0.2, cy: 320 + b4 * 0.4, r: 40 + (b2 % 60), opacity: 0.12 },
|
||||
{ type: "polygon", points: `60,${380 + b1 * 0.2} ${120 + b2 * 0.2},${440 + b3 * 0.15} ${20 + b4 * 0.3},${460}`, opacity: 0.16 },
|
||||
];
|
||||
// 基于 hash 生成多样化的设计元素
|
||||
const designType = hash % 5; // 5种不同的设计风格
|
||||
const rotation = (hash % 360);
|
||||
const scale = 0.8 + (hash % 40) / 100; // 0.8-1.2
|
||||
|
||||
const shapeSvg = shapes
|
||||
.map((s) => {
|
||||
if (s.type === "circle") {
|
||||
return `<circle cx="${s.cx}" cy="${s.cy}" r="${s.r}" fill="white" opacity="${s.opacity}"/>`;
|
||||
let shapeSvg = "";
|
||||
|
||||
if (designType === 0) {
|
||||
// 风格0:放射状圆形
|
||||
const circles = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const angle = (i * 45) * Math.PI / 180;
|
||||
const cx = 256 + Math.cos(angle) * 150;
|
||||
const cy = 256 + Math.sin(angle) * 150;
|
||||
const r = 30 + (hash % 30);
|
||||
circles.push(`<circle cx="${cx}" cy="${cy}" r="${r}" fill="white" opacity="0.15"/>`);
|
||||
}
|
||||
shapeSvg = circles.join("\n ");
|
||||
} else if (designType === 1) {
|
||||
// 风格1:网格方块
|
||||
const rects = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
for (let j = 0; j < 6; j++) {
|
||||
if ((i + j + hash) % 3 === 0) {
|
||||
const x = 50 + i * 70;
|
||||
const y = 50 + j * 70;
|
||||
rects.push(`<rect x="${x}" y="${y}" width="50" height="50" fill="white" opacity="0.12" transform="rotate(${rotation} ${x + 25} ${y + 25})"/>`);
|
||||
}
|
||||
}
|
||||
if (s.type === "rect") {
|
||||
return `<rect x="${s.x}" y="${s.y}" width="${s.w}" height="${s.h}" transform="rotate(${s.rot} ${s.x + s.w / 2} ${s.y + s.h / 2})" fill="white" opacity="${s.opacity}"/>`;
|
||||
}
|
||||
return `<polygon points="${s.points}" fill="white" opacity="${s.opacity}"/>`;
|
||||
})
|
||||
.join("\n ");
|
||||
}
|
||||
shapeSvg = rects.join("\n ");
|
||||
} else if (designType === 2) {
|
||||
// 风格2:波浪线条
|
||||
const paths = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const y = 80 + i * 80;
|
||||
const amplitude = 40 + (hash % 30);
|
||||
paths.push(`<path d="M 0 ${y} Q 128 ${y - amplitude} 256 ${y} T 512 ${y}" stroke="white" stroke-width="3" fill="none" opacity="0.2"/>`);
|
||||
}
|
||||
shapeSvg = paths.join("\n ");
|
||||
} else if (designType === 3) {
|
||||
// 风格3:三角形阵列
|
||||
const triangles = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const cx = (hash * (i + 1)) % 450 + 30;
|
||||
const cy = (hash * (i + 2)) % 450 + 30;
|
||||
const size = 20 + (hash % 25);
|
||||
const rot = (hash * i) % 360;
|
||||
triangles.push(`<polygon points="${cx},${cy - size} ${cx - size * 0.866},${cy + size * 0.5} ${cx + size * 0.866},${cy + size * 0.5}" fill="white" opacity="0.15" transform="rotate(${rot} ${cx} ${cy})"/>`);
|
||||
}
|
||||
shapeSvg = triangles.join("\n ");
|
||||
} else {
|
||||
// 风格4:螺旋圆点
|
||||
const dots = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const angle = i * 0.5;
|
||||
const radius = 50 + i * 10;
|
||||
const cx = 256 + Math.cos(angle) * radius;
|
||||
const cy = 256 + Math.sin(angle) * radius;
|
||||
const r = 8 + (i % 5) * 2;
|
||||
dots.push(`<circle cx="${cx}" cy="${cy}" r="${r}" fill="white" opacity="${0.3 - i * 0.01}"/>`);
|
||||
}
|
||||
shapeSvg = dots.join("\n ");
|
||||
}
|
||||
|
||||
// 角色副标识
|
||||
const roleBadge = isPasserby
|
||||
@@ -158,6 +204,10 @@ export function generateSvgAvatar({ key, displayName, persona }) {
|
||||
: `<rect x="20" y="20" width="92" height="28" rx="14" fill="rgba(255,255,255,0.25)"/>
|
||||
<text x="66" y="38" text-anchor="middle" fill="white" font-size="13" font-weight="600" font-family="system-ui">行业专家</text>`;
|
||||
|
||||
// 中央文字:使用完整的首字,并添加阴影效果
|
||||
const textShadow = `<text x="258" y="322" text-anchor="middle" fill="rgba(0,0,0,0.3)" font-size="280" font-weight="800" font-family="system-ui,-apple-system,sans-serif">${escapeXml(initial)}</text>`;
|
||||
const textMain = `<text x="256" y="320" text-anchor="middle" fill="white" font-size="280" font-weight="800" font-family="system-ui,-apple-system,sans-serif">${escapeXml(initial)}</text>`;
|
||||
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||
<defs>
|
||||
@@ -165,12 +215,6 @@ export function generateSvgAvatar({ key, displayName, persona }) {
|
||||
<stop offset="0%" stop-color="${palette.from}"/>
|
||||
<stop offset="100%" stop-color="${palette.to}"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="6"/>
|
||||
<feOffset dx="0" dy="4" result="offsetblur"/>
|
||||
<feComponentTransfer><feFuncA type="linear" slope="0.35"/></feComponentTransfer>
|
||||
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- 背景渐变 -->
|
||||
<rect width="512" height="512" fill="url(#bg)"/>
|
||||
@@ -178,9 +222,10 @@ export function generateSvgAvatar({ key, displayName, persona }) {
|
||||
${shapeSvg}
|
||||
<!-- 角色徽章 -->
|
||||
${roleBadge}
|
||||
<!-- 中央大字母 -->
|
||||
<text x="256" y="320" text-anchor="middle" fill="white" font-size="280" font-weight="800" font-family="system-ui,-apple-system,sans-serif" filter="url(#shadow)">${escapeXml(initial)}</text>
|
||||
<!-- 右下角 🤖 标识(用 path 画,不依赖 emoji 字体) -->
|
||||
<!-- 中央大字母(带阴影) -->
|
||||
${textShadow}
|
||||
${textMain}
|
||||
<!-- 右下角 🤖 标识 -->
|
||||
<g transform="translate(440 440)">
|
||||
<circle cx="0" cy="0" r="32" fill="white" opacity="0.95"/>
|
||||
<path d="M-12 -4 L-12 8 Q-12 14 -6 14 L6 14 Q12 14 12 8 L12 -4 Q12 -10 6 -10 L-6 -10 Q-12 -10 -12 -4 Z" fill="${palette.from}"/>
|
||||
|
||||
Reference in New Issue
Block a user