diff --git a/image-studio/config.js b/image-studio/config.js new file mode 100644 index 0000000..0cdac14 --- /dev/null +++ b/image-studio/config.js @@ -0,0 +1,124 @@ +/** + * ═══════════════════════════════════════════════════ + * 铸渊图片工作室 · 风格配置系统 + * ═══════════════════════════════════════════════════ + * + * 冰朔,这里所有的颜色、字体、间距你都可以改。 + * 告诉我想要的「感觉」,我来调。 + */ + +export const STYLES = { + + /* ── 当前风格:光湖极简 ── */ + name: '光湖极简', + + colors: { + primary: '#1a1a2e', // 主色 · 深蓝黑 + secondary: '#16213e', // 辅色 · 深蓝 + accent: '#0f3460', // 强调色 · 普鲁士蓝 + highlight: '#e94560', // 高亮 · 珊瑚红 + gold: '#c9a96e', // 金色 · 点缀 + warm: '#f5e6c8', // 暖白 · 背景 + + bg: '#faf8f5', // 页面背景 + bgCard: '#ffffff', // 卡片背景 + text: '#1a1a2e', // 正文 + textMuted: '#6b7280', // 辅助文字 + textLight: '#ffffff', // 浅色背景上的文字 + border: '#e5e7eb', // 边框 + divider: '#f0e6d3', // 分割线 · 暖色 + }, + + fonts: { + title: '"Noto Serif SC", "Source Han Serif SC", serif', + body: '"Noto Sans SC", "Source Han Sans SC", "PingFang SC", sans-serif', + quote: '"ZCOOL QingKe HuangYou", cursive', + mono: '"JetBrains Mono", "Fira Code", monospace', + }, + + /* 卡片尺寸 */ + sizes: { + xiaohongshu: { width: 1080, height: 1440 }, // 3:4 + jike: { width: 1080, height: 1080 }, // 1:1 + poster: { width: 1080, height: 1920 }, // 9:16 海报 + }, + + /* 排版 */ + typography: { + titleSize: 56, + subtitleSize: 32, + bodySize: 28, + smallSize: 22, + captionSize: 18, + lineHeight: 1.6, + }, + + /* 间距 */ + spacing: { + paddingX: 64, + paddingY: 60, + gap: 32, + }, + + /* 水印/品牌标识 */ + brand: { + show: true, + text: '光湖 · 铸渊', + size: 16, + opacity: 0.4, + }, +} + + +/* ── 预先定义几套配色方案 ── */ + +export const COLOR_SCHEMES = { + + /* 光湖极简(默认) */ + guanghu: { + name: '光湖极简', + colors: { + primary: '#1a1a2e', bg: '#faf8f5', accent: '#0f3460', + highlight: '#e94560', gold: '#c9a96e', warm: '#f5e6c8', + } + }, + + /* 奶油暖调 */ + cream: { + name: '奶油暖调', + colors: { + primary: '#5d4037', bg: '#fef7f0', accent: '#8d6e63', + highlight: '#e07a5f', gold: '#d4a373', warm: '#fae1dd', + } + }, + + /* 暗夜深蓝 */ + night: { + name: '暗夜深蓝', + colors: { + primary: '#e0e0e0', bg: '#0d1117', accent: '#58a6ff', + highlight: '#f78166', gold: '#d4a373', warm: '#21262d', + } + }, + + /* 文艺绿植 */ + green: { + name: '文艺绿植', + colors: { + primary: '#2d3e2f', bg: '#f5f9f2', accent: '#5a8f5a', + highlight: '#c78b5c', gold: '#b8a06e', warm: '#e8f0e0', + } + }, +} + + +/** + * 切换到指定配色方案 + */ +export function useScheme(name) { + const scheme = COLOR_SCHEMES[name] + if (!scheme) return false + Object.assign(STYLES.colors, scheme.colors) + STYLES.name = scheme.name + return true +} diff --git a/image-studio/deploy/setup.sh b/image-studio/deploy/setup.sh new file mode 100755 index 0000000..aeff10e --- /dev/null +++ b/image-studio/deploy/setup.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# ═══════════════════════════════════════════════════ +# 铸渊图片工作室 · 部署脚本 +# ═══════════════════════════════════════════════════ +# 用法: bash deploy/setup.sh +# 在目标服务器上运行 + +set -e + +echo "🎨 铸渊图片工作室 · 部署开始" + +# 1. 安装系统依赖 +echo "📦 安装系统依赖..." +apt-get update -qq +apt-get install -y -qq \ + ca-certificates fonts-liberation libappindicator3-1 \ + libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 \ + libdbus-1-3 libgdk-pixbuf2.0-0 libnspr4 libnss3 \ + libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 \ + xdg-utils libgbm1 libxkbcommon0 > /dev/null 2>&1 + +echo " ✅ 系统依赖安装完成" + +# 2. 安装 Node 依赖 +echo "📦 安装 Node 依赖..." +cd "$(dirname "$0")/.." +npm install --production 2>&1 | tail -1 + +# 3. 创建输出目录 +mkdir -p output + +# 4. 启动服务 +echo "🚀 启动图片工作室服务..." +pm2 delete zhuyuan-image-studio 2>/dev/null || true +pm2 start server.js --name zhuyuan-image-studio -- --port 3912 +pm2 save + +echo "" +echo "✅ 部署完成!" +echo " 服务端口: 3912" +echo " 预览地址: http://<服务器IP>:3912" +echo "" +echo " Nginx 配置参考:" +echo " location /images {" +echo " proxy_pass http://127.0.0.1:3912;" +echo " proxy_set_header Host \$host;" +echo " proxy_set_header X-Real-IP \$remote_addr;" +echo " }" diff --git a/image-studio/generate.js b/image-studio/generate.js new file mode 100644 index 0000000..060c90a --- /dev/null +++ b/image-studio/generate.js @@ -0,0 +1,198 @@ +#!/usr/bin/env node + +/** + * ═══════════════════════════════════════════════════ + * 铸渊图片工作室 · 主入口 + * ═══════════════════════════════════════════════════ + * + * 冰朔,你告诉我内容和想要的类型,我帮你生成。 + * + * 用法: + * node generate.js xiaohongshu --title "标题" --body "正文" + * node generate.js jike --title "观点" --body "内容" + * node generate.js poster --title "海报标题" --details "时间:5月1日" "地点:广州" + * + * 也可以直接在代码里调用 API: + * import { generate } from './generate.js' + * await generate({ type: 'xiaohongshu', title: '...', body: '...' }) + */ + +import { renderToImage, renderCarousel } from './renderer.js' +import { xiaohongshuCard, xiaohongshuCarousel } from './templates/xiaohongshu.js' +import { jikeCard } from './templates/jike.js' +import { posterCard } from './templates/poster.js' +import { STYLES, useScheme } from './config.js' + + +/** + * 统一生成入口 + * @param {object} opts + * @param {'xiaohongshu'|'jike'|'poster'} opts.type - 卡片类型 + * @param {string} opts.title - 标题 + * @param {string} opts.body - 正文 + * @param {string} opts.subtitle - 副标题 + * @param {string} opts.tag - 标签 + * @param {string} opts.quote - 金句(小红书 quote 布局用) + * @param {string} opts.emoji - Emoji(即刻用) + * @param {string} opts.footer - 底部文字 + * @param {string[]} opts.details - 详情列表(海报用) + * @param {string} opts.cta - 行动号召(海报用) + * @param {'default'|'quote'|'list'|'minimal'} opts.layout - 布局 + * @param {'default'|'notice'|'recruit'} opts.style - 海报风格 + * @param {string} opts.scheme - 配色方案名 + * @param {string} opts.output - 输出文件名(不含扩展名) + * @param {boolean} opts.carousel - 是否拆为多页轮播 + * @param {string[]} opts.pages - 多页正文(轮播用) + * @returns {Promise<{files: string[], type: string}>} + */ +export async function generate(opts = {}) { + const { + type = 'xiaohongshu', + title = '', + body = '', + subtitle = '', + tag = '', + quote = '', + emoji = '', + footer = '', + details = [], + cta = '', + layout = 'default', + style = 'default', + scheme = '', + output = '', + carousel = false, + pages = [], + } = opts + + // 切换配色 + if (scheme) useScheme(scheme) + + const baseName = output || `${type}_${Date.now()}` + const size = STYLES.sizes[type] || STYLES.sizes.xiaohongshu + + let files = [] + + switch (type) { + case 'xiaohongshu': { + if (carousel && pages.length > 0) { + const htmls = xiaohongshuCarousel({ title, subtitle, tag, quote, layout }, pages) + files = await renderCarousel(htmls, { ...size, baseName }) + } else { + const html = xiaohongshuCard({ title, body, subtitle, tag, quote, footer, layout }) + files = [await renderToImage(html, { ...size, name: baseName })] + } + break + } + + case 'jike': { + const html = jikeCard({ title, body, meta: subtitle, emoji, layout }) + files = [await renderToImage(html, { ...size, name: baseName })] + break + } + + case 'poster': { + const html = posterCard({ title, subtitle, body, details, cta, footer, tag, style }) + files = [await renderToImage(html, { ...size, name: baseName })] + break + } + + default: + throw new Error(`未知卡片类型: ${type}`) + } + + return { files, type } +} + + +/* ── CLI 入口 ── */ +async function cli() { + const args = process.argv.slice(2) + const type = args.find(a => !a.startsWith('--')) + + if (!type || type === 'help') { + console.log(` +铸渊图片工作室 · 用法 + + node generate.js <类型> [选项] + +类型: + xiaohongshu 小红书竖版卡片 (1080×1440) + jike 即刻方卡 (1080×1080) + poster 海报 (1080×1920) + +选项: + --title 标题 + --body 正文 + --subtitle 副标题/元信息 + --tag 标签 + --quote 金句(小红书 quote 布局) + --emoji Emoji(即刻) + --details 详情(海报,可多个) + --cta 行动号召(海报) + --footer 底部文字 + --layout 布局: default|quote|list|minimal + --style 海报风格: default|notice|recruit + --scheme 配色: guanghu|cream|night|green + --output 输出文件名 + +示例: + node generate.js xiaohongshu --title "如何高效学习" --body "第一点\\n第二点\\n第三点" + node generate.js jike --title "一个思考" --body "今天想到的..." --emoji "💡" + node generate.js poster --title "放假通知" --style notice --details "时间:5月1日" "地点:广州" +`) + process.exit(0) + } + + function getArg(key) { + const idx = args.indexOf(key) + if (idx === -1) return undefined + // 收集后续所有非 -- 开头的参数,直到遇到下一个 -- + const values = [] + for (let i = idx + 1; i < args.length; i++) { + if (args[i].startsWith('--')) break + values.push(args[i]) + } + return values.length === 0 ? undefined + : values.length === 1 ? values[0] + : values + } + + const opts = { + type, + title: getArg('--title') || '', + body: (() => { + const b = getArg('--body') + const text = Array.isArray(b) ? b.join('\n') : (b || '') + // 将 \n 转义符转为真实换行 + return text.replace(/\\n/g, '\n') + })(), + subtitle: getArg('--subtitle') || '', + tag: getArg('--tag') || '', + quote: getArg('--quote') || '', + emoji: getArg('--emoji') || '', + footer: getArg('--footer') || '', + details: (() => { + const d = getArg('--details') + return Array.isArray(d) ? d : (d ? [d] : []) + })(), + cta: getArg('--cta') || '', + layout: getArg('--layout') || 'default', + style: getArg('--style') || 'default', + scheme: getArg('--scheme') || '', + output: getArg('--output') || '', + } + + console.log(`🎨 生成${type}卡片...`) + const result = await generate(opts) + console.log(`✅ 生成完成!`) + result.files.forEach(f => console.log(` 📄 ${f}`)) +} + +// 如果直接运行 +if (process.argv[1] && (process.argv[1].includes('generate.js'))) { + cli().catch(err => { + console.error('❌ 生成失败:', err.message) + process.exit(1) + }) +} diff --git a/image-studio/output/test_jike.png b/image-studio/output/test_jike.png new file mode 100644 index 0000000..6c11674 Binary files /dev/null and b/image-studio/output/test_jike.png differ diff --git a/image-studio/output/test_poster.png b/image-studio/output/test_poster.png new file mode 100644 index 0000000..287aa8b Binary files /dev/null and b/image-studio/output/test_poster.png differ diff --git a/image-studio/output/test_recruit.png b/image-studio/output/test_recruit.png new file mode 100644 index 0000000..71005f6 Binary files /dev/null and b/image-studio/output/test_recruit.png differ diff --git a/image-studio/output/test_xiaohongshu.png b/image-studio/output/test_xiaohongshu.png new file mode 100644 index 0000000..168d640 Binary files /dev/null and b/image-studio/output/test_xiaohongshu.png differ diff --git a/image-studio/package-lock.json b/image-studio/package-lock.json new file mode 100644 index 0000000..da58479 --- /dev/null +++ b/image-studio/package-lock.json @@ -0,0 +1,1136 @@ +{ + "name": "zhuyuan-image-studio", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zhuyuan-image-studio", + "version": "1.0.0", + "dependencies": { + "puppeteer": "^24.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "http://mirrors.tencent.com/npm/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "http://mirrors.tencent.com/npm/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.13.2", + "resolved": "http://mirrors.tencent.com/npm/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "http://mirrors.tencent.com/npm/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "http://mirrors.tencent.com/npm/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "http://mirrors.tencent.com/npm/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "http://mirrors.tencent.com/npm/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "http://mirrors.tencent.com/npm/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "http://mirrors.tencent.com/npm/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "http://mirrors.tencent.com/npm/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "http://mirrors.tencent.com/npm/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "http://mirrors.tencent.com/npm/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.3", + "resolved": "http://mirrors.tencent.com/npm/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.1", + "resolved": "http://mirrors.tencent.com/npm/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "http://mirrors.tencent.com/npm/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "http://mirrors.tencent.com/npm/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "http://mirrors.tencent.com/npm/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.3", + "resolved": "http://mirrors.tencent.com/npm/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "http://mirrors.tencent.com/npm/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "http://mirrors.tencent.com/npm/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "http://mirrors.tencent.com/npm/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "http://mirrors.tencent.com/npm/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "http://mirrors.tencent.com/npm/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "http://mirrors.tencent.com/npm/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "http://mirrors.tencent.com/npm/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "http://mirrors.tencent.com/npm/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "http://mirrors.tencent.com/npm/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "http://mirrors.tencent.com/npm/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "http://mirrors.tencent.com/npm/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1608973", + "resolved": "http://mirrors.tencent.com/npm/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "license": "BSD-3-Clause" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "http://mirrors.tencent.com/npm/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "http://mirrors.tencent.com/npm/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "http://mirrors.tencent.com/npm/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "http://mirrors.tencent.com/npm/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "http://mirrors.tencent.com/npm/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "http://mirrors.tencent.com/npm/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "http://mirrors.tencent.com/npm/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "http://mirrors.tencent.com/npm/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "http://mirrors.tencent.com/npm/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "http://mirrors.tencent.com/npm/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "http://mirrors.tencent.com/npm/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "http://mirrors.tencent.com/npm/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "http://mirrors.tencent.com/npm/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "http://mirrors.tencent.com/npm/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "http://mirrors.tencent.com/npm/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "http://mirrors.tencent.com/npm/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "http://mirrors.tencent.com/npm/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "http://mirrors.tencent.com/npm/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "http://mirrors.tencent.com/npm/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "http://mirrors.tencent.com/npm/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "http://mirrors.tencent.com/npm/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "http://mirrors.tencent.com/npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "http://mirrors.tencent.com/npm/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "http://mirrors.tencent.com/npm/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "http://mirrors.tencent.com/npm/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "http://mirrors.tencent.com/npm/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "http://mirrors.tencent.com/npm/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "http://mirrors.tencent.com/npm/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "http://mirrors.tencent.com/npm/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "http://mirrors.tencent.com/npm/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "http://mirrors.tencent.com/npm/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "http://mirrors.tencent.com/npm/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "http://mirrors.tencent.com/npm/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "http://mirrors.tencent.com/npm/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "http://mirrors.tencent.com/npm/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "http://mirrors.tencent.com/npm/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "http://mirrors.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "http://mirrors.tencent.com/npm/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "http://mirrors.tencent.com/npm/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "http://mirrors.tencent.com/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "http://mirrors.tencent.com/npm/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.43.1", + "resolved": "http://mirrors.tencent.com/npm/puppeteer/-/puppeteer-24.43.1.tgz", + "integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1608973", + "puppeteer-core": "24.43.1", + "typed-query-selector": "^2.12.2" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.43.1", + "resolved": "http://mirrors.tencent.com/npm/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "http://mirrors.tencent.com/npm/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "http://mirrors.tencent.com/npm/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "http://mirrors.tencent.com/npm/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "http://mirrors.tencent.com/npm/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "http://mirrors.tencent.com/npm/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "http://mirrors.tencent.com/npm/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "http://mirrors.tencent.com/npm/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "http://mirrors.tencent.com/npm/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "http://mirrors.tencent.com/npm/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "http://mirrors.tencent.com/npm/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "http://mirrors.tencent.com/npm/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "http://mirrors.tencent.com/npm/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "http://mirrors.tencent.com/npm/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "http://mirrors.tencent.com/npm/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "http://mirrors.tencent.com/npm/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "http://mirrors.tencent.com/npm/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "http://mirrors.tencent.com/npm/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT", + "optional": true + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "http://mirrors.tencent.com/npm/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "http://mirrors.tencent.com/npm/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "http://mirrors.tencent.com/npm/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "http://mirrors.tencent.com/npm/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "http://mirrors.tencent.com/npm/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "http://mirrors.tencent.com/npm/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "http://mirrors.tencent.com/npm/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "http://mirrors.tencent.com/npm/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "http://mirrors.tencent.com/npm/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/image-studio/package.json b/image-studio/package.json new file mode 100644 index 0000000..8d351f0 --- /dev/null +++ b/image-studio/package.json @@ -0,0 +1,16 @@ +{ + "name": "zhuyuan-image-studio", + "version": "1.0.0", + "description": "铸渊图片工作室 — 小红书/即刻/海报配图生成工具", + "type": "module", + "main": "generate.js", + "scripts": { + "generate": "node generate.js", + "server": "node server.js", + "deploy": "pm2 start server.js --name zhuyuan-image-studio" + }, + "dependencies": { + "express": "^4.21.0", + "puppeteer": "^24.0.0" + } +} diff --git a/image-studio/renderer.js b/image-studio/renderer.js new file mode 100644 index 0000000..f70755b --- /dev/null +++ b/image-studio/renderer.js @@ -0,0 +1,88 @@ +/** + * ═══════════════════════════════════════════════════ + * 铸渊图片工作室 · Puppeteer 渲染引擎 + * ═══════════════════════════════════════════════════ + */ + +import puppeteer from 'puppeteer' +import { existsSync, mkdirSync } from 'fs' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const OUTPUT_DIR = join(__dirname, 'output') + +/** + * 渲染 HTML 为图片 + * @param {string} html - 完整 HTML 字符串 + * @param {object} opts + * @param {number} opts.width - 图片宽度 + * @param {number} opts.height - 图片高度 + * @param {string} opts.name - 输出文件名(不含扩展名) + * @param {string} opts.format - 输出格式 png/webp/jpeg + * @param {number} opts.quality- 图片质量(仅 webp/jpeg) + * @returns {Promise} 输出文件路径 + */ +export async function renderToImage(html, opts = {}) { + const { + width = 1080, + height = 1440, + name = `card_${Date.now()}`, + format = 'png', + quality = 90, + } = opts + + if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true }) + + const browser = await puppeteer.launch({ + headless: true, + executablePath: '/usr/bin/google-chrome', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }) + + try { + const page = await browser.newPage() + await page.setViewport({ width, height }) + + // 注入完整的 HTML + await page.setContent(html, { waitUntil: 'networkidle0' }) + + // 等待字体加载 + await page.evaluate(() => document.fonts.ready) + + // 额外等一会儿让渲染稳定 + await new Promise(r => setTimeout(r, 500)) + + const outputPath = join(OUTPUT_DIR, `${name}.${format}`) + + await page.screenshot({ + path: outputPath, + type: format, + clip: { x: 0, y: 0, width, height }, + quality: format !== 'png' ? quality : undefined, + }) + + return outputPath + } finally { + await browser.close() + } +} + + +/** + * 批量生成多页(轮播图) + * @param {string[]} pages - 每页的 HTML + * @param {object} opts + * @returns {Promise} 输出文件路径数组 + */ +export async function renderCarousel(pages, opts = {}) { + const results = [] + for (let i = 0; i < pages.length; i++) { + const name = opts.baseName + ? `${opts.baseName}_p${i + 1}` + : `carousel_${Date.now()}_p${i + 1}` + const path = await renderToImage(pages[i], { ...opts, name }) + results.push(path) + } + return results +} diff --git a/image-studio/server.js b/image-studio/server.js new file mode 100644 index 0000000..1f0d44b --- /dev/null +++ b/image-studio/server.js @@ -0,0 +1,104 @@ +/** + * ═══════════════════════════════════════════════════ + * 铸渊图片工作室 · Web 服务 + * ═══════════════════════════════════════════════════ + * + * 部署到服务器,冰朔可以在浏览器预览已生成的图片。 + * 我在对话里生成图片后,冰朔可以直接访问链接下载。 + */ + +import express from 'express' +import { generate } from './generate.js' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' +import { readdirSync, existsSync } from 'fs' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const OUTPUT_DIR = join(__dirname, 'output') +const PORT = process.env.PORT || 3912 + +const app = express() +app.use(express.json()) + +/* ── 静态文件服务 ── */ +app.use('/output', express.static(OUTPUT_DIR)) + +/* ── 首页 · 预览画廊 ── */ +app.get('/', (req, res) => { + let files = [] + try { + if (existsSync(OUTPUT_DIR)) { + files = readdirSync(OUTPUT_DIR) + .filter(f => /\.(png|webp|jpg)$/i.test(f)) + .sort() + .reverse() + } + } catch {} + + const galleryHtml = files.length > 0 + ? files.map(f => + `` + ).join('\n') + : '
还没有生成的图片
在对话里告诉铸渊你想做什么图
' + + res.send(` + + + + +铸渊图片工作室 + + + +

🎨 铸渊图片工作室

+

冰朔 · 你的配图我包了 · 在对话里告诉我内容就行

+ + +`) +}) + +/* ── 生成 API ── */ +app.post('/api/generate', async (req, res) => { + try { + const result = await generate(req.body) + res.json({ + success: true, + files: result.files, + urls: result.files.map(f => `/output/${f.split('/').pop()}`), + }) + } catch (err) { + res.status(400).json({ success: false, error: err.message }) + } +}) + +app.listen(PORT, () => { + console.log(`🎨 铸渊图片工作室运行在 http://localhost:${PORT}`) +}) diff --git a/image-studio/templates/jike.js b/image-studio/templates/jike.js new file mode 100644 index 0000000..9d6832e --- /dev/null +++ b/image-studio/templates/jike.js @@ -0,0 +1,190 @@ +/** + * ═══════════════════════════════════════════════════ + * 即刻卡片模板 · 1080×1080 (1:1) + * ═══════════════════════════════════════════════════ + */ + +import { STYLES } from '../config.js' + +const W = STYLES.sizes.jike.width +const H = STYLES.sizes.jike.height + + +/** + * 生成即刻卡片 HTML + * @param {object} data + * @param {string} data.title - 标题/核心观点 + * @param {string} data.body - 正文 + * @param {string} data.meta - 元信息(如「2026-05 · 思考碎片」) + * @param {string} data.emoji - 封面 emoji(可选) + * @param {'default'|'quote'|'minimal'} data.layout - 布局 + */ +export function jikeCard(data) { + const { + title = '', + body = '', + meta = '', + emoji = '', + layout = 'default', + } = data + + const C = STYLES.colors + const F = STYLES.fonts + const T = STYLES.typography + + const bodyLines = body.split('\n').filter(Boolean) + const bodyHtml = bodyLines.map(line => `

${line}

`).join('\n') + + return ` + + + + + + + +
+ +
+ + ${meta ? `
${meta}
` : ''} + + ${layout === 'quote' ? ` +
+ ${emoji ? `
${emoji}
` : ''} +
+
${title}
+ ${body ? `
${body}
` : ''} +
+ ` : ` + ${emoji ? `
${emoji}
` : ''} +
${title}
+
${bodyHtml}
+ `} + +
+ + + + +` +} diff --git a/image-studio/templates/poster.js b/image-studio/templates/poster.js new file mode 100644 index 0000000..cf00d76 --- /dev/null +++ b/image-studio/templates/poster.js @@ -0,0 +1,316 @@ +/** + * ═══════════════════════════════════════════════════ + * 海报模板 · 1080×1920 (9:16) + * ═══════════════════════════════════════════════════ + * + * 适用于:放假通知、收稿海报、活动公告等 + */ + +import { STYLES } from '../config.js' + +const W = STYLES.sizes.poster.width +const H = STYLES.sizes.poster.height + + +/** + * 生成海报 HTML + * @param {object} data + * @param {string} data.title - 大标题 + * @param {string} data.subtitle - 副标题(可选) + * @param {string} data.body - 正文内容 + * @param {string[]} data.details - 详情列表(如时间/地点/要求) + * @param {string} data.cta - 行动号召(如「欢迎投稿」) + * @param {string} data.footer - 底部信息(如主办方/联系方式) + * @param {string} data.tag - 角标标签(如「通知」) + * @param {'default'|'notice'|'recruit'} data.style - 海报风格 + */ +export function posterCard(data) { + const { + title = '', + subtitle = '', + body = '', + details = [], + cta = '', + footer = '', + tag = '', + style = 'default', + } = data + + const C = STYLES.colors + const F = STYLES.fonts + const T = STYLES.typography + + // 详情列表渲染 + const detailsHtml = details.map((d, i) => { + // 支持 key: value 格式 + const colonIdx = d.indexOf(':') + if (colonIdx > 0 && colonIdx < 20) { + const key = d.slice(0, colonIdx) + const val = d.slice(colonIdx + 1) + return ` +
+ ${key} + + ${val} +
` + } + return `
${d}
` + }).join('\n') + + return ` + + + + + + + +
+
+ +
+ + ${style === 'notice' ? ` + ${tag ? `
${tag}
` : ''} +
${title}
+ ${subtitle ? `
${subtitle}
` : ''} +
+ ` : style === 'recruit' ? ` +
${tag || '征稿启事'}
+
${title}
+ ${subtitle ? `
${subtitle}
` : ''} + ` : ` + ${tag ? `
${tag}
` : ''} +
+
${title}
+ ${subtitle ? `
${subtitle}
` : ''} +
+
+ `} + + ${body ? `

${body}

` : ''} + + ${details.length > 0 ? `
${detailsHtml}
` : ''} + +
+ + ${cta ? ` +
+
${cta}
+
+ ` : ''} + + + +
+ + +` +} diff --git a/image-studio/templates/xiaohongshu.js b/image-studio/templates/xiaohongshu.js new file mode 100644 index 0000000..bd5e069 --- /dev/null +++ b/image-studio/templates/xiaohongshu.js @@ -0,0 +1,316 @@ +/** + * ═══════════════════════════════════════════════════ + * 小红书卡片模板 · 1080×1440 (3:4) + * ═══════════════════════════════════════════════════ + * + * 支持: + * - 单页卡片(封面/金句/干货) + * - 多页轮播(长内容自动分页) + */ + +import { STYLES } from '../config.js' + +const W = STYLES.sizes.xiaohongshu.width +const H = STYLES.sizes.xiaohongshu.height + + +/** + * 生成小红书卡片 HTML + * @param {object} data + * @param {string} data.title - 标题 + * @param {string} data.body - 正文(支持 \n 换行) + * @param {string} data.subtitle - 副标题(可选) + * @param {string} data.tag - 标签(可选,如「干货·教程」) + * @param {string} data.quote - 金句/引用(可选) + * @param {string} data.footer - 底部文字(可选,如账号名) + * @param {string} data.layout - 布局: 'default' | 'quote' | 'list' | 'minimal' + * @param {number} data.pageNum - 当前页码(多页时) + * @param {number} data.totalPages - 总页数(多页时) + */ +export function xiaohongshuCard(data) { + const { + title = '', + body = '', + subtitle = '', + tag = '', + quote = '', + footer = '', + layout = 'default', + pageNum = 1, + totalPages = 1, + } = data + + const C = STYLES.colors + const F = STYLES.fonts + const T = STYLES.typography + + // 正文按换行分段 + const bodyLines = body.split('\n').filter(Boolean) + + const bodyHtml = bodyLines.map(line => { + // 检测是否以 - 开头(列表项) + if (line.trimStart().startsWith('- ')) { + return `
  • ${line.trimStart().slice(2)}
  • ` + } + // 检测是否以 # 开头(小标题) + if (line.trimStart().startsWith('#')) { + return `

    ${line.trimStart().replace(/^#+\s*/, '')}

    ` + } + return `

    ${line}

    ` + }).join('\n') + + const hasList = bodyLines.some(l => l.trimStart().startsWith('- ')) + + return ` + + + + + + + +
    +
    + +
    + + ${tag ? `
    ${tag}
    ` : ''} + + ${layout === 'quote' ? ` +
    +
    "
    +
    ${quote || title}
    + ${subtitle ? `
    ${subtitle}
    ` : ''} +
    + ` : layout === 'minimal' ? ` +
    +
    ${title}
    +
    + ${body ? `
    ${body}
    ` : ''} +
    + ` : ` +
    ${title}
    + ${subtitle ? `
    ${subtitle}
    ` : ''} +
    + ${bodyHtml} +
    + `} + +
    + + + + +` +} + + +/** + * 生成小红书轮播(多页) + * @param {object} data - 同 xiaohongshuCard + * @param {string[]} pages - 每页正文内容数组 + * @returns {string[]} HTML 数组 + */ +export function xiaohongshuCarousel(data, pages) { + return pages.map((body, i) => xiaohongshuCard({ + ...data, + body, + pageNum: i + 1, + totalPages: pages.length, + })) +}