Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -0,0 +1,296 @@
const { createCanvas, loadImage, registerFont } = require('canvas');
const fs = require('fs');
const path = require('path');
// --- Configuration ---
const WIDTH = 1080;
const HEIGHT = 1440;
const PADDING = 80;
// Colors (Systemic Flux Palette)
const COLORS = {
bg: '#0D0E12', // Deep Charcoal/Black
textPrimary: '#FFFFFF',
textSecondary: '#A0A0A0',
accent1: '#00FF94', // Neon Green (Success/Active)
accent2: '#5E5CE6', // Indigo (Processing)
grid: '#2A2A2A',
surface: '#1A1B20',
};
// Font Paths - Try multiple locations with fallback
// Priority: 1) NOMIFUN_FONTS_DIR env var, 2) skills/canvas-design relative path, 3) system fonts
function getFontDir() {
const candidates = [
process.env.NOMIFUN_FONTS_DIR,
path.join(__dirname, '../../canvas-design/canvas-fonts'),
path.join(process.env.HOME || '', 'Library/Application Support/Nomi/config/skills/canvas-design/canvas-fonts'),
path.join(process.env.APPDATA || '', 'Nomi/config/skills/canvas-design/canvas-fonts'),
].filter(Boolean);
for (const dir of candidates) {
if (fs.existsSync(dir)) return dir;
}
return null;
}
const FONT_DIR = getFontDir();
const FONTS = FONT_DIR
? {
monoBold: path.join(FONT_DIR, 'JetBrainsMono-Bold.ttf'),
monoReg: path.join(FONT_DIR, 'JetBrainsMono-Regular.ttf'),
sansReg: path.join(FONT_DIR, 'InstrumentSans-Regular.ttf'),
sansBold: path.join(FONT_DIR, 'InstrumentSans-Bold.ttf'),
}
: null;
// Register Fonts (skip if fonts not found - will use system defaults)
if (FONTS) {
try {
registerFont(FONTS.monoBold, { family: 'Mono', weight: 'bold' });
registerFont(FONTS.monoReg, { family: 'Mono', weight: 'normal' });
registerFont(FONTS.sansReg, { family: 'Sans', weight: 'normal' });
registerFont(FONTS.sansBold, { family: 'Sans', weight: 'bold' });
} catch (e) {
console.warn('Custom fonts not available, using system defaults:', e.message);
}
} else {
console.warn('Font directory not found, using system default fonts');
}
// --- Helpers ---
function drawGrid(ctx, w, h, step = 60) {
ctx.strokeStyle = COLORS.grid;
ctx.lineWidth = 1;
ctx.beginPath();
for (let x = 0; x <= w; x += step) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
}
for (let y = 0; y <= h; y += step) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
ctx.stroke();
// Add some "data points" - crosses at intersections
ctx.fillStyle = COLORS.textSecondary;
for (let x = step; x < w; x += step * 4) {
for (let y = step; y < h; y += step * 4) {
ctx.fillRect(x - 2, y - 1, 4, 2);
ctx.fillRect(x - 1, y - 2, 2, 4);
}
}
}
function drawParagraph(ctx, text, x, y, maxWidth, lineHeight) {
const chars = text.split('');
let line = '';
let currentY = y;
for (let i = 0; i < chars.length; i++) {
let char = chars[i];
if (/[a-zA-Z0-9@]/.test(char)) {
let word = char;
while (i + 1 < chars.length && /[a-zA-Z0-9@.]/.test(chars[i + 1])) {
word += chars[++i];
}
char = word;
}
const testLine = line + char;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && line !== '') {
ctx.fillText(line, x, currentY);
line = char;
currentY += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, x, currentY);
return currentY + lineHeight;
}
function drawTechDecoration(ctx) {
ctx.fillStyle = COLORS.surface;
ctx.strokeStyle = COLORS.textSecondary;
ctx.lineWidth = 1;
ctx.strokeRect(WIDTH - 250, 40, 210, 60);
ctx.font = 'bold 16px Mono';
ctx.fillStyle = COLORS.accent1;
ctx.fillText('STATUS: ONLINE', WIDTH - 230, 75);
ctx.fillStyle = COLORS.accent1;
ctx.beginPath();
ctx.arc(WIDTH - 60, 70, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = COLORS.accent2;
ctx.fillRect(40, HEIGHT - 50, 40, 40);
ctx.fillStyle = COLORS.textSecondary;
ctx.font = '14px Mono';
ctx.fillText('SYS.VER.2026.01', 90, HEIGHT - 25);
}
// --- Image 1: Cover ---
function generateCover(title1, title2, slogan1, slogan2) {
const canvas = createCanvas(WIDTH, HEIGHT);
const ctx = canvas.getContext('2d');
ctx.fillStyle = COLORS.bg;
ctx.fillRect(0, 0, WIDTH, HEIGHT);
drawGrid(ctx, WIDTH, HEIGHT);
drawTechDecoration(ctx);
ctx.fillStyle = COLORS.accent1;
ctx.font = 'bold 24px Mono';
ctx.fillText('// WE ARE HIRING', PADDING, 300);
ctx.fillStyle = COLORS.textPrimary;
ctx.font = 'bold 100px Mono';
ctx.fillText(title1 || 'AGENT', PADDING, 420);
ctx.fillText(title2 || 'DESIGNER', PADDING, 520);
const cx = WIDTH / 2;
const cy = HEIGHT / 2 + 150;
ctx.strokeStyle = COLORS.accent2;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(cx, cy, 150, 0, Math.PI * 2);
ctx.stroke();
ctx.strokeStyle = COLORS.accent1;
ctx.setLineDash([10, 10]);
ctx.beginPath();
ctx.arc(cx, cy, 170, 0, Math.PI * 2);
ctx.stroke();
ctx.setLineDash([]);
ctx.strokeStyle = COLORS.textSecondary;
ctx.lineWidth = 1;
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(cx + Math.cos(angle) * 150, cy + Math.sin(angle) * 150);
ctx.lineTo(cx + Math.cos(angle) * 300, cy + Math.sin(angle) * 300);
ctx.stroke();
ctx.fillStyle = COLORS.surface;
ctx.fillRect(cx + Math.cos(angle) * 300 - 10, cy + Math.sin(angle) * 300 - 10, 20, 20);
ctx.strokeRect(cx + Math.cos(angle) * 300 - 10, cy + Math.sin(angle) * 300 - 10, 20, 20);
}
ctx.fillStyle = COLORS.textPrimary;
ctx.font = 'normal 48px Sans';
ctx.fillText(slogan1 || '寻找未来的定义者', PADDING, HEIGHT - 200);
ctx.fillStyle = COLORS.textSecondary;
ctx.font = 'normal 32px Sans';
ctx.fillText(slogan2 || 'Redefine the Human-AI Interaction', PADDING, HEIGHT - 150);
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync('cover.png', buffer);
console.log('Created cover.png');
}
// --- Image 2: JD ---
function generateJD(roleTitle, roleDesc, responsibilities, requirements, email) {
const canvas = createCanvas(WIDTH, HEIGHT);
const ctx = canvas.getContext('2d');
ctx.fillStyle = COLORS.bg;
ctx.fillRect(0, 0, WIDTH, HEIGHT);
ctx.globalAlpha = 0.3;
drawGrid(ctx, WIDTH, HEIGHT);
ctx.globalAlpha = 1.0;
ctx.fillStyle = COLORS.surface;
ctx.fillRect(0, 0, WIDTH, 200);
ctx.fillStyle = COLORS.accent1;
ctx.font = 'bold 20px Mono';
ctx.fillText('// OPEN POSITION', PADDING, 60);
ctx.fillStyle = COLORS.textPrimary;
ctx.font = 'bold 60px Mono';
ctx.fillText(roleTitle || 'AGENT DESIGNER', PADDING, 140);
let cursorY = 260;
const contentWidth = WIDTH - PADDING * 2;
// 1. Role Description
ctx.fillStyle = COLORS.accent2;
ctx.font = 'bold 32px Mono';
ctx.fillText('< ROLE >', PADDING, cursorY);
cursorY += 50;
ctx.fillStyle = COLORS.textPrimary;
ctx.font = 'normal 30px Sans';
cursorY = drawParagraph(ctx, roleDesc, PADDING, cursorY, contentWidth, 45);
cursorY += 40;
// 2. Responsibilities
ctx.fillStyle = COLORS.accent2;
ctx.font = 'bold 32px Mono';
ctx.fillText('< RESPONSIBILITIES >', PADDING, cursorY);
cursorY += 50;
ctx.font = 'normal 28px Sans';
(responsibilities || []).forEach((duty) => {
ctx.fillStyle = COLORS.textPrimary;
cursorY = drawParagraph(ctx, duty, PADDING, cursorY, contentWidth, 40);
cursorY += 10;
});
cursorY += 30;
// 3. Requirements
ctx.fillStyle = COLORS.accent2;
ctx.font = 'bold 32px Mono';
ctx.fillText('< REQUIREMENTS >', PADDING, cursorY);
cursorY += 50;
(requirements || []).forEach((req) => {
ctx.fillStyle = COLORS.textPrimary;
cursorY = drawParagraph(ctx, req, PADDING, cursorY, contentWidth, 40);
cursorY += 10;
});
cursorY += 30;
// 4. Contact Box
const boxY = HEIGHT - 250;
ctx.strokeStyle = COLORS.accent1;
ctx.lineWidth = 2;
ctx.setLineDash([10, 10]);
ctx.strokeRect(PADDING, boxY, contentWidth, 150);
ctx.setLineDash([]);
ctx.fillStyle = COLORS.surface;
ctx.fillRect(PADDING, boxY, contentWidth, 150);
ctx.fillStyle = COLORS.accent1;
ctx.font = 'bold 24px Mono';
ctx.fillText('APPLY NOW >>', PADDING + 30, boxY + 50);
ctx.fillStyle = COLORS.textPrimary;
ctx.font = 'bold 40px Sans';
ctx.fillText(email || 'contact@example.com', PADDING + 30, boxY + 110);
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync('jd_details.png', buffer);
console.log('Created jd_details.png');
}
// --- Main execution ---
// Defaults
const defaults = {};
// You could parse process.argv here to override defaults if needed
// For now, we'll use the hardcoded structure or let the user edit this file.
generateCover(defaults.title1, defaults.title2, defaults.slogan1, defaults.slogan2);
generateJD(defaults.roleTitle, defaults.roleDesc, defaults.responsibilities, defaults.requirements, defaults.email);