const fs = require("node:fs"); const path = require("node:path"); const blogRoot = "d:\\Code\\Obsidian\\博客"; const coverDir = path.join(blogRoot, "covers"); fs.mkdirSync(coverDir, { recursive: true }); const existingCoverMap = new Map([ [ path.join(blogRoot, "AI与大模型", "深入解析 Claude Code:Vibe Coding 时代的 AI 编程利器.md"), "claude-code-vibe-coding-cover.svg", ], [path.join(blogRoot, "编程与工具", "Docker部署完全指南.md"), "docker-deployment-guide-cover.svg"], [path.join(blogRoot, "机器学习", "视觉语言模型技术综述.md"), "vision-language-model-cover.svg"], ]); function escapeXml(text) { return String(text ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); } function walkMarkdownFiles(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (entry.name === "covers") continue; files.push(...walkMarkdownFiles(fullPath)); continue; } if (entry.isFile() && entry.name.endsWith(".md")) { files.push(fullPath); } } return files.sort((a, b) => a.localeCompare(b, "zh-CN")); } function getArticleTitle(filePath) { const raw = fs.readFileSync(filePath, "utf8"); const titleMatch = raw.match(/^title:\s*(.+)$/m); if (titleMatch) { return titleMatch[1].trim().replace(/^['"]|['"]$/g, ""); } const h1Match = raw.match(/^#\s+(.+)$/m); if (h1Match) { return h1Match[1].trim(); } return path.basename(filePath, ".md"); } function getCategory(filePath) { return path.basename(path.dirname(filePath)); } function getSafeCoverName(filePath) { if (existingCoverMap.has(filePath)) { return existingCoverMap.get(filePath); } const baseName = path.basename(filePath, ".md"); const safeName = baseName.replace(/[<>:"/\\|?*]/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); return `${safeName}-cover.svg`; } function splitTitle(title) { const clean = title.trim(); if (clean.length <= 14) return [clean]; if (clean.length <= 28) return [clean.slice(0, Math.ceil(clean.length / 2)), clean.slice(Math.ceil(clean.length / 2))]; const first = clean.slice(0, 12); const second = clean.slice(12, 24); const third = clean.slice(24); return [first, second, third].filter(Boolean); } function getTheme(category, title) { const theme = { key: "default", label: "博客封面", subtitle: "知识沉淀 / 主题文章", bg1: "#0B1020", bg2: "#172554", bg3: "#0F766E", accent1: "#67E8F9", accent2: "#A78BFA", accent3: "#F472B6", panel: "rgba(7,12,28,0.70)", tags: ["博客", "封面", "文章"], }; const categoryThemes = { "AI与大模型": { key: "ai", label: "AI 与大模型", subtitle: "模型能力 / Agent / 智能协作", bg1: "#0B1020", bg2: "#1E1B4B", bg3: "#0F766E", accent1: "#67E8F9", accent2: "#A855F7", accent3: "#22D3EE", tags: ["AI", "模型", "智能"], }, "机器学习": { key: "ml", label: "机器学习", subtitle: "算法原理 / 神经网络 / 模型理解", bg1: "#0B1020", bg2: "#172554", bg3: "#4C1D95", accent1: "#67E8F9", accent2: "#C084FC", accent3: "#F472B6", tags: ["学习", "网络", "模型"], }, "编程与工具": { key: "tools", label: "编程与工具", subtitle: "工程实践 / 工具链 / 效率提升", bg1: "#071A33", bg2: "#0F3D74", bg3: "#0B7285", accent1: "#7DD3FC", accent2: "#FDBA74", accent3: "#60A5FA", tags: ["工具", "工程", "实践"], }, "数据分析与报告": { key: "report", label: "数据分析与报告", subtitle: "指标洞察 / 数据审查 / 结果汇总", bg1: "#111827", bg2: "#1F2937", bg3: "#0F766E", accent1: "#34D399", accent2: "#2DD4BF", accent3: "#A7F3D0", tags: ["数据", "分析", "报告"], }, "学术与效率": { key: "academic", label: "学术与效率", subtitle: "论文写作 / 知识表达 / 工具方法", bg1: "#1F172A", bg2: "#312E81", bg3: "#0F766E", accent1: "#C4B5FD", accent2: "#93C5FD", accent3: "#A7F3D0", tags: ["学术", "写作", "效率"], }, 其他: { key: "outline", label: "内容策划", subtitle: "选题梳理 / 结构设计 / 创作准备", bg1: "#0F172A", bg2: "#1E293B", bg3: "#334155", accent1: "#93C5FD", accent2: "#C4B5FD", accent3: "#F9A8D4", tags: ["策划", "结构", "草稿"], }, }; Object.assign(theme, categoryThemes[category] || {}); const keywordRules = [ [/Claude|Vibe|CLI|Agent/i, { subtitle: "AI 编程 / Agentic CLI / 智能协作", tags: ["Agent", "CLI", "AI"] }], [/DeepSeek|大模型/, { subtitle: "模型解析 / 趋势洞察 / 架构思考", tags: ["模型", "解析", "趋势"] }], [/Docker|部署/, { subtitle: "容器部署 / 服务编排 / 工程实战", tags: ["部署", "容器", "实战"] }], [/Git/, { subtitle: "版本控制 / 团队协作 / 工作流", tags: ["Git", "协作", "工作流"] }], [/OpenClaw/, { subtitle: "AI 编程助手 / 安装配置 / 使用实践", tags: ["AI 工具", "安装", "指南"] }], [/\buv\b|uv工具/, { subtitle: "Python 工具链 / 依赖管理 / 开发效率", tags: ["Python", "uv", "效率"] }], [/SEO|爬取|摘要|标签|文章列表|技术栈/, { subtitle: "数据整理 / 指标审查 / 结果报告", tags: ["数据", "报告", "审查"] }], [/LaTeX|论文/, { subtitle: "论文写作 / 排版工具 / 学术效率", tags: ["论文", "LaTeX", "效率"] }], [/LinearRegression|线性回归/, { subtitle: "监督学习 / 回归模型 / 入门理解", tags: ["回归", "基础", "算法"] }], [/卷积|全连接层/, { subtitle: "神经网络 / 表征学习 / 结构演进", tags: ["卷积", "神经网络", "视觉"] }], [/深度学习/, { subtitle: "模型体系 / 核心概念 / 系统学习", tags: ["深度学习", "模型", "指南"] }], [/视觉语言模型/, { subtitle: "多模态 AI / 视觉理解 / 语言推理", tags: ["VLM", "多模态", "推理"] }], [/大纲/, { subtitle: "内容策划 / 结构拆解 / 写作准备", tags: ["大纲", "结构", "规划"] }], ]; for (const [regex, patch] of keywordRules) { if (regex.test(title)) { Object.assign(theme, patch); break; } } return theme; } function getGraphicMarkup(theme) { const { key, accent1, accent2, accent3 } = theme; if (key === "ai") { return ` `; } if (key === "ml") { return ` `; } if (key === "tools") { return ` `; } if (key === "report") { return ` `; } if (key === "academic") { return ` LaTeX Σ x² + y² `; } return ` `; } function buildTitleMarkup(title) { const lines = splitTitle(title); const fontSize = lines.length === 1 ? 48 : lines.length === 2 ? 44 : 36; const lineHeight = lines.length === 3 ? 48 : 56; const startY = lines.length === 1 ? 250 : lines.length === 2 ? 224 : 196; return lines .map((line, index) => { const y = startY + index * lineHeight; return ` ${escapeXml(line)}`; }) .join("\n"); } function buildTagMarkup(tags) { const fills = ["rgba(103,232,249,0.12)", "rgba(168,85,247,0.12)", "rgba(244,114,182,0.12)"]; const strokes = ["rgba(103,232,249,0.30)", "rgba(196,181,253,0.30)", "rgba(251,207,232,0.26)"]; const textColors = ["#BAE6FD", "#DDD6FE", "#FBCFE8"]; let x = 112; return tags.slice(0, 3).map((tag, index) => { const width = Math.max(118, 54 + tag.length * 22); const rect = ` `; const text = ` ${escapeXml(tag)}`; x += width + 18; return `${rect}\n${text}`; }).join("\n"); } function buildSvg(title, theme) { return ` ${escapeXml(theme.label)} ${buildTitleMarkup(title)} ${escapeXml(theme.subtitle)} ${buildTagMarkup(theme.tags)} ${getGraphicMarkup(theme)} `; } const articles = walkMarkdownFiles(blogRoot); const created = []; const skipped = []; for (const articlePath of articles) { if (existingCoverMap.has(articlePath)) { skipped.push(`${articlePath} => ${existingCoverMap.get(articlePath)}`); continue; } const title = getArticleTitle(articlePath); const category = getCategory(articlePath); const theme = getTheme(category, title); const coverName = getSafeCoverName(articlePath); const coverPath = path.join(coverDir, coverName); if (fs.existsSync(coverPath)) { skipped.push(`${articlePath} => ${coverName}`); continue; } fs.writeFileSync(coverPath, buildSvg(title, theme), "utf8"); created.push(coverPath); } console.log(`Created: ${created.length}`); for (const file of created) console.log(file); console.log(`Skipped: ${skipped.length}`); for (const file of skipped) console.log(file);