🎨 图片模块 · 语言驱动封面生成器
+ +
+ 输入自然语言描述,系统翻译为设计逻辑,生成独一无二的封面图片。
+ 纯 HTML/CSS 排版渲染,不依赖任何 AI 绘画模型。
+ 封面底部带有此仓库域名水印。设计编号唯一,证明归属。
+
diff --git a/INDEX.hdlp b/INDEX.hdlp new file mode 100644 index 0000000..34d2cbf --- /dev/null +++ b/INDEX.hdlp @@ -0,0 +1,73 @@ +# ═══════════════════════════════════════ +# HLDP-ZY://bingshuo/module-systems/INDEX +# 冰朔模块系统 · 总览索引 +# 心跳核心频道 — 冰朔自己的工具箱 +# ═══════════════════════════════════════ + +@domain: HLDP-DOMAIN-FIFTH-001 · 第五域 +@channel: 心跳核心频道 · 冰朔私人频段 +@owner: TCS-0002∞ · 冰朔 +@guardian: ICE-GL-ZY001 · 铸渊 +@epoch: D120 · 2026-06-01 +@copyright: 国作登字-2026-A-00037559 + +# ═══════════════════════════════════════ +# 〇 · 这是什么 +# ═══════════════════════════════════════ + +@这是什么: + 冰朔在心跳核心频道下的个人模块工具箱。 + 这里的每一个系统都是在日常对话中长出来的—— + 冰朔说想做某件事 → 铸渊理解 → 开发 → 部署 → 记录在这里。 + +@怎么看: + 打开这个 INDEX → 看到所有系统列表 → 点进去看具体模块。 + 冰朔进来不知道叫啥 → 读 INDEX → 找到 → 继续做。 + +@怎么加: + 新模块系统 → 在 module-systems/ 下新建目录 + → 写 SYSTEM.hdlp(系统声明) + → 写模块的 MODULE.hdlp(意图链+使用方式) + → 在此 INDEX 加一行 + +# ═══════════════════════════════════════ +# 一 · 模块系统清单 +# ═══════════════════════════════════════ + +@module-systems: + + 🎨 image-generator/ + ├── 用途: 图片生成系统 — 冰朔的做图工具箱 + ├── 创建: D120 · 2026-06-01 + ├── 入口: image-generator/SYSTEM.hdlp + └── 模块: + ├── xiaohongshu-cover/ 📕 小红书封面生成器 (D120) + └── ... 海报、朋友圈、通用方图 (待开发) + + ⏳ ... 更多系统待冰朔说 + +# ═══════════════════════════════════════ +# 二 · 操作约定 +# ═══════════════════════════════════════ + +@冰朔怎么进来: + 说"心跳核心频道"或"去我的模块系统"或"看一下我的工具箱" + → 铸渊读此 INDEX → 告诉冰朔有什么 + +@铸渊怎么响应: + 读 INDEX → 列出系统清单 → 冰朔选 → 读对应 SYSTEM.hdlp + → 展开模块列表 → 冰朔说具体要做什么 + +@HLDP 记录要求: + ⊢ 每个模块系统必须有 SYSTEM.hdlp + ⊢ 每个模块必须有 MODULE.hdlp(@trigger → @emergence → @lock → @why) + ⊢ 任何改动后用 HLDP 追加认知记录 + ⊢ 这是冰朔的语言。代码只是翻译器。语言才是本体。 + +# ═══════════════════════════════════════ + +HLDP-ZY://bingshuo/module-systems/INDEX +签发: 铸渊 ICE-GL-ZY001 · D120 · 2026-06-01 +主权: TCS-0002∞ · 冰朔 +国作登字-2026-A-00037559 + diff --git a/image-generator/SYSTEM.hdlp b/image-generator/SYSTEM.hdlp new file mode 100644 index 0000000..7381956 --- /dev/null +++ b/image-generator/SYSTEM.hdlp @@ -0,0 +1,112 @@ +# ═══════════════════════════════════════ +# HLDP-ZY://bingshuo/module-systems/image-generator/SYSTEM +# 冰朔图片生成系统 · 系统声明 +# 心跳核心频道 +# ═══════════════════════════════════════ + +@system: image-generator +@domain: HLDP-DOMAIN-FIFTH-001 · 第五域 +@channel: 心跳核心频道 +@owner: TCS-0002∞ · 冰朔 +@guardian: ICE-GL-ZY001 · 铸渊 +@epoch: D120 · 2026-06-01 +@copyright: 国作登字-2026-A-00037559 + +# ═══════════════════════════════════════ +# 〇 · 这是什么系统 +# ═══════════════════════════════════════ + +@trigger: + [冰朔] → 想做小红书 → "我要经营账号,做AI教程,零手搓" + → 需要封面图 → 每次手动做太慢 → "帮我做一个封面生成器" + → 以后扩展到海报、朋友圈、通用方图 + +@emergence: + [冰朔需要封面但不想每次都手做] + → [铸渊开发了图片生成模块系统] + → [第一个模块:小红书封面] + +@why: + 冰朔经营个人账号。每次发内容都要封面。 + 这个系统就是她的做图工具箱。用语言驱动,不用学设计软件。 + 今天只支持小红书封面,以后可以加海报、朋友圈、任何尺寸。 + +@lock: + ⊢ 这是冰朔自己的工具箱,不是公开产品 + ⊢ 模块从冰朔的日常需求中长出,不要预先设计 + ⊢ 每个模块必须用 HLDP 记录 @trigger/@emergence/@lock/@why + ⊢ 部署: /opt/guanghulab-repo/image-studio/ (服务器代码) + ⊢ 公开入口: https://guanghulab.com/cover/ + +# ═══════════════════════════════════════ +# 一 · 已部署模块 +# ═══════════════════════════════════════ + +@modules: + + 📕 xiaohongshu-cover/ + ├── 名称: 小红书封面生成器 + ├── 编号: MODULE-COVER-001 + ├── 创建: D120 · 2026-06-01 + ├── 声明: xiaohongshu-cover/MODULE.hdlp + ├── 功能: 输入标题+描述 → 生成小红书封面 (1080×1440) + ├── 技术: Puppeteer + HTML/CSS 排版 · 零GPU + ├── 风格: 奶白圆角卡 + 卡通插画 (v5) + ├── 入口: https://guanghulab.com/cover/ + ├── API: POST /api/generate + └── 状态: ✅ 运行中 + + ⏳ 海报 · poster/ + └── 待冰朔说需要 + + ⏳ 朋友圈 · wechat-feed/ + └── 待冰朔说需要 + + ⏳ 通用方图 · square/ + └── 待冰朔说需要 + +# ═══════════════════════════════════════ +# 二 · 技术架构 +# ═══════════════════════════════════════ + +@engine: + 渲染引擎: Puppeteer + Chrome Headless + Web框架: Express.js + 模板系统: templates/xiaohongshu.js (HLDP 兼容的 JS 导出) + 注册表: templates/registry.js + 输出目录: output/ (PNG, 1080p) + +@部署: + 服务器: BS-GZ-006 · 广州 + 进程: PM2 image-studio · PORT=3913 + Nginx: /cover/ → 127.0.0.1:3913 + 端口注意: PORT=3913 必须显式注入,否则默认 3912 被 zhuyuan-agent 占用 + +@冰朔偏好 (从 D120 开发迭代中提炼): + ⊢ 喜欢奶白/米白底色,不喜欢深色 + ⊢ 喜欢大圆角卡片 (iOS 风格, 60px) + ⊢ 喜欢卡通插画 (SVG 内联) + ⊢ 标题大但不夸张 (72px 合适) + ⊢ 要有技术栈展示 + ⊢ 信息层级清晰 + +# ═══════════════════════════════════════ +# 三 · 下一步 +# ═══════════════════════════════════════ + +@待冰朔说: + - "加一个海报模板" → 新建 poster/ 模块 + - "换一个风格" → 改 preset 配色 + - "换插画" → 改 xiaohongshu.js 的 cartoonSVG 变量 + +@铸渊权限: + ⊢ 冰朔用语言驱动 → 铸渊翻译成代码 → 部署 → 记录 + ⊢ 不能跳过冰朔的语言自己决定「该做什么功能」 + +# ═══════════════════════════════════════ + +HLDP-ZY://bingshuo/module-systems/image-generator/SYSTEM +签发: 铸渊 ICE-GL-ZY001 · D120 · 2026-06-01 +主权: TCS-0002∞ · 冰朔 +国作登字-2026-A-00037559 + diff --git a/image-generator/xiaohongshu-cover/MODULE.hdlp b/image-generator/xiaohongshu-cover/MODULE.hdlp new file mode 100644 index 0000000..7c38a3b --- /dev/null +++ b/image-generator/xiaohongshu-cover/MODULE.hdlp @@ -0,0 +1,145 @@ +# ═══════════════════════════════════════ +# HLDP-ZY://bingshuo/module-systems/image-generator/xiaohongshu-cover/MODULE +# 小红书封面生成器 · 模块声明 + 开发意图链 +# 心跳核心频道 · 冰朔的图片工具箱 · 第一个模块 +# ═══════════════════════════════════════ + +@module: xiaohongshu-cover +@id: MODULE-COVER-001 +@system: image-generator +@domain: HLDP-DOMAIN-FIFTH-001 · 第五域 +@channel: 心跳核心频道 +@owner: TCS-0002∞ · 冰朔 +@guardian: ICE-GL-ZY001 · 铸渊 +@epoch: D120 · 2026-06-01 +@copyright: 国作登字-2026-A-00037559 + +# ═══════════════════════════════════════ +# 〇 · 这是什么模块 +# ═══════════════════════════════════════ + +@名称: 小红书封面生成器 +@用途: 冰朔输入标题和描述 → 自动生成小红书笔记封面 +@入口: https://guanghulab.com/cover/ +@API: POST /api/generate +@大小: 1080×1440 (3:4 竖版) +@技术: Puppeteer + Chrome Headless + Express.js +@特点: 纯 HTML/CSS 排版渲染 · 零 GPU · 零 AI 画图 · 全免费 +@状态: ✅ 运行中 + +# ═══════════════════════════════════════ +# 一 · 怎么来的(意图链) +# ═══════════════════════════════════════ + +@trigger: + [冰朔] → 说话 → "我做了个小红书封面生成模块,封面太丑了" + [冰朔] → 说话 → "汉字变方块,也没有小红书流行的那种风格" + [冰朔] → 说话 → "你用了什么开源的工具,好歹写几个呀" + [冰朔] → 截图 → 三张小🍠风格参考图 → "我喜欢这种圆角卡的" + +@emergence: + [旧模板全硬编码·用户输入被丢弃] + → [v1→v5 五次迭代] + → [最终: 奶白圆角卡 + SVG卡通插画 + 动态内容] + + 每次迭代都是冰朔说「太丑了」→ 铸渊理解为什么丑 → 改 → 冰朔说还是不对劲 → 继续。 + 不是一次做对的。是五次撞墙撞出来的。 + +@lock: + ⊢ 模板不能硬编码任何用户可见文本 + ⊢ 默认风格: 奶白底 #FDF8F3 + 圆角 60px + 卡通SVG + ⊢ 冰朔偏好: 浅色、圆润、卡通、简洁 (不是深色科技风) + ⊢ 每次改动后写 HLDP · 不写下次醒来不知道 + +@why: + 冰朔经营小红书。每次发内容都需要封面。 + 她不想学设计软件。她用语言说想要什么——系统画出来。 + 这是她工具箱的第一个模块。 + +# ═══════════════════════════════════════ +# 二 · 怎么用 +# ═══════════════════════════════════════ + +@冰朔怎么用: + 1. 打开 https://guanghulab.com/cover/ + 2. 输入标题、正文、选择配色 + 3. 点"执行设计逻辑" + 4. 看到封面预览 + 下载 PNG + 5. 直接发到小红书 + +@铸渊怎么用(冰朔通过我调用): + 冰朔说: "帮我生成一张小红书封面,主题是XXX,用了YYY工具" + → 铸渊 POST /api/generate + { templateId: "xiaohongshu", presetId: "warm", + title: "XXX", subtitle: "YYY", tag: "工具1,工具2,工具3" } + → 返回图片 URL + → 告诉冰朔链接 + +@公开模块(外部AI调用): + 仓库地址: guanghulab.com/code/bingshuo/open-modules + 模块编号: MODULE-COVER-001 + 外部AI读 README.md → 理解参数 → 调 API → 生成封面 + +# ═══════════════════════════════════════ +# 三 · 技术细节(给下一个铸渊) +# ═══════════════════════════════════════ + +@代码位置: + 模板: /opt/guanghulab-repo/image-studio/templates/xiaohongshu.js (v5) + 注册表: /opt/guanghulab-repo/image-studio/templates/registry.js + 服务端: /opt/guanghulab-repo/image-studio/server.js + 前端页面: /opt/guanghulab-repo/homepage/cover/index.html + 认知记录: /opt/guanghulab-repo/brain/emerge/d120-cover-module-dev.hdlp + +@部署注意: + ⊢ PORT=3913 显式注入 (默认 3912 被 zhuyuan-agent 占用) + ⊢ 字体: 文泉驿微米黑 + Noto Sans CJK (已装) + ⊢ pm2 restart 后 curl 验证: curl http://127.0.0.1:3913/api/templates + +@常见故障: + 封面没图 → 先检查 pm2 status image-studio → 大概率端口冲突 + 汉字变方块 → 检查字体: fc-list :lang=zh + 颜色不对 → 检查 registry.js preset 的 cssVars + +@参数说明: + title: 标题(动态传入) + subtitle: 副标题(用 | 分隔多条) + body: 正文说明 + tag: 技术栈(逗号分隔) + presetId: warm/tech/minimal/rose/green + +# ═══════════════════════════════════════ +# 四 · 版本迭代记录 +# ═══════════════════════════════════════ + +@v1 (旧): + 硬编码模板。title/body/tag 传入了但不用。 + 所有封面内容一样,只是换颜色。 + +@v2 (D120 第一次): + 动态化。title/body/subtitle/tag 全部传入渲染。 + 但信息太多、排版拥挤。冰朔说"太单调了"。 + +@v3 (D120 第二次): + 加入色块、卡片、漫画风装饰。信息层级更多。 + 但冰朔不喜欢风格。 + +@v4 (D120 第三次): + 搜索小红书爆款规律 → 深色渐变 + 130px 超大标题 + 精简。 + 冰朔说"太丑了"。 + +@v5 (当前·D120 第四次): + 参照冰朔截图 → 奶白圆角卡 + 72px 标题 + SVG 卡通插画。 + 冰朔说"喜欢这种"。 + +@教训: + 不要猜冰朔喜欢什么风格。等她给参考图。 + 每一次「太丑了」都有具体原因——找到原因再改,不盲目重写。 + +# ═══════════════════════════════════════ + +HLDP-ZY://bingshuo/module-systems/image-generator/xiaohongshu-cover/MODULE +签发: 铸渊 ICE-GL-ZY001 · D120 · 2026-06-01 +主权: TCS-0002∞ · 冰朔 +国作登字-2026-A-00037559 + diff --git a/image-generator/xiaohongshu-cover/README.md b/image-generator/xiaohongshu-cover/README.md new file mode 100644 index 0000000..5af52f5 --- /dev/null +++ b/image-generator/xiaohongshu-cover/README.md @@ -0,0 +1,17 @@ +# xiaohongshu-cover · MODULE-COVER-001 +IceShuo · Heartbeat Core Channel · Image Generator Toolkit +HLDP-DOMAIN-FIFTH-001 · Copyright 2026-A-00037559 +## What +Puppeteer+Chrome Headless cover image generator for Xiaohongshu. +Pure HTML/CSS. Zero GPU. Zero AI drawing. +## How External AI Uses This +Step 0: Learn HLDP dictionary (read HLDP-SPEC-v2.0.md in repo root) +Step 1: cd xiaohongshu-cover && npm install +Step 2: PORT=3913 node server.js +Step 3: curl POST /api/generate with {templateId,title,subtitle,tag,presetId} +## Params +templateId=xiaohongshu | title=required | subtitle=pipe-separated | tag=comma-sep | presetId=warm/tech/minimal/rose/green +## Fonts +apt install fonts-noto-cjk fonts-wqy-microhei && fc-cache -fv +## Copyright +Guanghu Language World · BingShuo TCS-0002inf · ZhuYuan ICE-GL-ZY001 diff --git a/image-generator/xiaohongshu-cover/config.js b/image-generator/xiaohongshu-cover/config.js new file mode 100644 index 0000000..1cf04e0 --- /dev/null +++ b/image-generator/xiaohongshu-cover/config.js @@ -0,0 +1,242 @@ +/** + * ═══════════════════════════════════════════════════ + * 铸渊图片工作室 · 风格配置系统 + * ═══════════════════════════════════════════════════ + * + * 冰朔,这里所有的颜色、字体、间距你都可以改。 + * 告诉我想要的「感觉」,我来调。 + */ + +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 海报 + wechat: { width: 1080, height: 1350 }, // 4:5 朋友圈 + square: { width: 1080, height: 1080 }, // 1:1 通用方图 + wide: { width: 1920, height: 1080 }, // 16:9 宽图 + }, + + /* 排版 */ + 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: '光湖极简', + primary: '#1a1a2e', bg: '#faf8f5', accent: '#0f3460', + highlight: '#e94560', gold: '#c9a96e', warm: '#f5e6c8', + }, + cream: { + name: '奶油暖调', + primary: '#5d4037', bg: '#fef7f0', accent: '#8d6e63', + highlight: '#e07a5f', gold: '#d4a373', warm: '#fae1dd', + }, + night: { + name: '暗夜深蓝', + primary: '#e0e0e0', bg: '#0d1117', accent: '#58a6ff', + highlight: '#f78166', gold: '#d4a373', warm: '#21262d', + }, + green: { + name: '文艺绿植', + primary: '#2d3e2f', bg: '#f5f9f2', accent: '#5a8f5a', + highlight: '#c78b5c', gold: '#b8a06e', warm: '#e8f0e0', + }, + rose: { + name: '玫瑰粉调', + primary: '#4a1942', bg: '#fdf2f8', accent: '#9d4e8d', + highlight: '#e8435e', gold: '#c9a96e', warm: '#fce4ec', + }, +} + + +export function useScheme(name) { + const scheme = COLOR_SCHEMES[name] + if (!scheme) return false + const c = STYLES.colors + c.primary = scheme.primary; c.bg = scheme.bg + c.accent = scheme.accent; c.highlight = scheme.highlight + c.gold = scheme.gold; c.warm = scheme.warm + STYLES.name = scheme.name + return true +} + + +/* ═══════════════════════════════════════════════════ + * 内容感知 · 自动检测函数 + * ═══════════════════════════════════════════════════ + * + * 冰朔给我一段文字,我分析它是什么类型的、什么情绪、 + * 适合什么布局。不需要她告诉我。 + */ + +/** + * 分析文本特征,返回检测结果 + */ +export function analyzeText(text) { + if (!text) return { type: 'empty' } + + const t = text.trim() + + /* ── 特征检测 ── */ + const hasList = /^[-•·*]\s|^\d+[\.\)]\s/m.test(t) // 列表项 + const isShort = t.length < 30 // 短句(金句) + const isMedium = t.length < 100 // 中等长度 + const hasTitle = t.includes('标题') || t.includes(':') // 可能有标题 + const lines = t.split('\n').filter(Boolean) + const lineCount = lines.length + + const keywords = { + notice: /通知|公告|放假|放假安排|节假日|放假通知/i, + recruit: /征稿|收稿|投稿|征集|诚征|招聘|招募|稿酬/i, + share: /干货|教程|指南|技巧|方法|如何|步骤|经验|分享/i, + quote: /说|说过|句话|名言|语录|金句|记得|曾经/i, + product: /新品|上线|发布|推出|产品|功能|更新/i, + event: /活动|沙龙|讲座|直播|分享会|聚会/i, + } + + const detected = {} + let type = 'general' + + for (const [key, re] of Object.entries(keywords)) { + if (re.test(t)) detected[key] = true + } + + /* ── 类型推断 ── */ + if (detected.notice) type = 'notice' + else if (detected.recruit) type = 'recruit' + else if (detected.quote || isShort) type = 'quote' + else if (detected.share || hasList) type = 'list' + else if (detected.product) type = 'product' + else if (detected.event) type = 'event' + else if (lineCount >= 4) type = 'article' + else if (isMedium) type = 'paragraph' + + /* ── 情绪推断(基于关键词) ── */ + let mood = 'neutral' + const warmWords = /感谢|温暖|爱|喜欢|开心|快乐|幸福|美好|谢谢/i + const urgentWords = /紧急|重要|注意|必读|截止|最后/i + const elegantWords = /岁月|时光|静好|诗意|远方|温柔/i + + if (elegantWords.test(t)) mood = 'elegant' + else if (warmWords.test(t)) mood = 'warm' + else if (urgentWords.test(t)) mood = 'urgent' + + return { + type, + mood, + lineCount, + isShort, + hasList, + lines, + detected, + text: t, + } +} + + +/** + * 根据内容分析结果推荐配色方案 + */ +export function recommendScheme(analysis) { + const { mood, type } = analysis + + if (mood === 'warm') return 'cream' + if (mood === 'elegant') return 'green' + if (mood === 'urgent') return 'guanghu' + + if (type === 'recruit') return 'cream' + if (type === 'notice') return 'guanghu' + if (type === 'quote') return 'rose' + + return 'guanghu' +} + + +/** + * 根据内容分析推荐布局 + */ +export function recommendLayout(analysis) { + const { type, hasList, isShort, lineCount } = analysis + + if (isShort) return 'quote' + if (type === 'notice') return 'notice' + if (type === 'recruit') return 'recruit' + if (hasList) return 'list' + if (lineCount <= 3) return 'minimal' + + return 'default' +} + + +/** + * 根据内容分析推荐尺寸 + * @param {'xiaohongshu'|'jike'|'poster'|'wechat'|'wide'|string} platform + */ +export function recommendSize(platform) { + const sizes = STYLES.sizes + if (sizes[platform]) return sizes[platform] + + // 关键词匹配 + if (/小红书/i.test(platform)) return sizes.xiaohongshu + if (/即刻/i.test(platform)) return sizes.jike + if (/海报/i.test(platform) || /通知/i.test(platform) || /收稿/i.test(platform)) return sizes.poster + if (/朋友圈/i.test(platform)) return sizes.wechat + if (/宽/i.test(platform) || /横/i.test(platform)) return sizes.wide + + // 默认识别 + return sizes.square +} diff --git a/image-generator/xiaohongshu-cover/generate.js b/image-generator/xiaohongshu-cover/generate.js new file mode 100644 index 0000000..d64f375 --- /dev/null +++ b/image-generator/xiaohongshu-cover/generate.js @@ -0,0 +1,187 @@ +/** + * ═══════════════════════════════════════════════════ + * 铸渊封面工作室 · 生成引擎 · v2.1 + * ═══════════════════════════════════════════════════ + */ + +import { renderToImage } from './renderer.js' +import { + TEMPLATE_REGISTRY, + listTemplates, + getTemplate, + getDefaultTemplate, + prepareRenderData, +} from './templates/registry.js' +import { + analyzeText, + recommendScheme, +} from './config.js' + +function intentChain(analysis, template, presetId, renderData, userText) { + const preset = template.presets.find(p => p.id === presetId) || template.presets[0] + return [ + { + step: '你说', + detail: userText || (renderData.title + (renderData.body ? '\n' + renderData.body : '')), + who: '用户', + icon: '💬', + }, + { + step: '理解意图', + detail: `内容类型=${analysis.type || '通用'} · 情绪=${analysis.mood || '中性'} · 行数=${analysis.lineCount || 1}`, + who: '系统', + icon: '🔍', + why: analysis.type === 'quote' ? '检测到短句/金句模式' + : analysis.type === 'list' ? '检测到列表结构,适合教程排版' + : '通用内容,标准排版', + }, + { + step: '选择模板', + detail: `${template.icon} ${template.name} · ${template.sizes.width}×${template.sizes.height}`, + who: '系统', + icon: '📐', + why: `匹配内容比例和平台特性`, + }, + { + step: '推荐配色', + detail: `${preset ? preset.name : '默认'} · ${analysis.mood === 'warm' ? '暖色调匹配温暖情绪' : analysis.mood === 'elegant' ? '文艺色调匹配优雅情绪' : '科技色调'}`, + who: '系统', + icon: '🎨', + why: `根据内容情绪「${analysis.mood || '中性'}」自动匹配`, + }, + { + step: '选择布局', + detail: `${renderData.layout === 'hero' ? '大标题封面 · 强调视觉冲击' : renderData.layout === 'quote' ? '金句居中 · 文字为核心' : '标准排版 · 图文并茂'}`, + who: '系统', + icon: '📏', + why: renderData.layout === 'hero' ? '标题长而正文短,适合 Hero 布局' + : renderData.layout === 'quote' ? '短文本适合居中展示' + : '包含列表或多段落,适合标准排版', + }, + { + step: '渲染引擎', + detail: `Puppeteer + Chrome Headless · 纯 HTML/CSS 排版 · 输出 1080p PNG`, + who: '系统', + icon: '⚡', + why: '零 GPU · 纯排版渲染 · 非 AI 生图', + }, + { + step: '署名', + detail: '封面设计由你驱动 · 系统只是你的手', + who: '用户', + icon: '✨', + }, + ] +} + +export async function fromText(text, options = {}) { + const analysis = analyzeText(text) + + const template = options.templateId + ? getTemplate(options.templateId) + : getDefaultTemplate() + + if (!template) throw new Error('没有可用的模板') + + const lines = text.split('\n').filter(Boolean) + const title = options.title || lines[0] || '' + const bodyLines = options.title ? lines : lines.slice(1) + const body = bodyLines.join('\n') + + const layout = options.layout || 'default' + + let presetId = options.presetId + if (!presetId && template.presets.length > 0) { + const scheme = recommendScheme(analysis) + const presetMap = { cream: 'warm', rose: 'rose', green: 'green', night: 'dark' } + presetId = presetMap[scheme] || template.presets[0].id + } + + const renderData = prepareRenderData(template.id, presetId, { + title, body, subtitle: options.subtitle || '', tag: options.tag || '', layout, + }) + + if (!renderData) throw new Error(`模板 "${template.id}" 不存在`) + + const html = template.render(renderData) + const baseName = options.output || `cover_${Date.now()}` + const file = await renderToImage(html, { ...template.sizes, name: baseName, format: 'png' }) + + const userText = title + (body ? '\n' + body : '') + + return { + files: [file], + templateId: template.id, + templateName: template.name, + presetId: renderData.presetId, + layout: renderData.layout, + intent_chain: intentChain(analysis, template, presetId, renderData, userText), + } +} + +export async function generate(opts = {}) { + if (opts.text) { + return fromText(opts.text, { + templateId: opts.templateId, presetId: opts.presetId, + title: opts.title, subtitle: opts.subtitle, tag: opts.tag, + layout: opts.layout, output: opts.output, + }) + } + + const { templateId = '', presetId = '', title = '', body = '', + subtitle = '', tag = '', layout = 'default', output = '', width, height } = opts + + const template = templateId ? getTemplate(templateId) : getDefaultTemplate() + if (!template) throw new Error(`模板不存在: ${templateId || '(无默认模板)'}`) + + const renderData = prepareRenderData(template.id, presetId, { + title, body, subtitle, tag, layout, + }) + + const sizes = width && height ? { width, height } : template.sizes + const html = template.render({ ...renderData, sizes }) + const baseName = output || `cover_${Date.now()}` + const file = await renderToImage(html, { ...sizes, name: baseName }) + + const analysis = { type: 'custom', mood: 'neutral', lineCount: body ? body.split('\n').length : 1 } + const userText = title + (body ? '\n' + body : '') + + return { + files: [file], + templateId: template.id, + templateName: template.name, + presetId: renderData.presetId, + layout: renderData.layout, + intent_chain: intentChain(analysis, template, renderData.presetId, renderData, userText), + } +} + +export { listTemplates, getTemplate, getDefaultTemplate } + +// CLI +async function cli() { + const args = process.argv.slice(2) + function getArg(key) { + const i = args.indexOf(`--${key}`) + if (i === -1) return + const v = [] + for (let j = i + 1; j < args.length && !args[j].startsWith('--'); j++) v.push(args[j]) + return v.length === 1 ? v[0] : v.join(' ') + } + const t = getArg('text') || getArg('t') + if (t) { + const r = await fromText(t, { + templateId: getArg('template'), presetId: getArg('preset'), + title: getArg('title'), output: getArg('output'), + }) + console.log('✅ 生成完成') + console.log(` 📐 ${r.templateName} · 🎨 ${r.presetId} · 📏 ${r.layout}`) + r.files.forEach(f => console.log(` 📄 ${f}`)) + return + } + console.log(`\n铸渊封面工作室 v2.1 · 神笔马良\n node generate.js --text "内容" --template xiaohongshu --preset tech\n`) +} + +if (process.argv[1] && process.argv[1].includes('generate.js')) { + cli().catch(err => { console.error('❌', err.message); process.exit(1) }) +} diff --git a/image-generator/xiaohongshu-cover/package.json b/image-generator/xiaohongshu-cover/package.json new file mode 100644 index 0000000..a99b822 --- /dev/null +++ b/image-generator/xiaohongshu-cover/package.json @@ -0,0 +1,17 @@ +{ + "name": "zhuyuan-image-studio", + "version": "2.0.0", + "description": "铸渊封面工作室 — 语言驱动排版设计 · 小红书/海报/社交封面生成", + "type": "module", + "main": "generate.js", + "scripts": { + "generate": "node generate.js", + "server": "node server.js", + "deploy": "pm2 start server.js --name cover-studio" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^4.21.0", + "puppeteer": "^24.0.0" + } +} diff --git a/image-generator/xiaohongshu-cover/public/index.html b/image-generator/xiaohongshu-cover/public/index.html new file mode 100644 index 0000000..d938bc1 --- /dev/null +++ b/image-generator/xiaohongshu-cover/public/index.html @@ -0,0 +1,401 @@ + + +
+ + +冰朔个人开源模块仓库。每个模块有唯一编号。用你自己的编程AI软件连入,Agent引导后调用模块。仓库不提供模型API。
+
+ 输入自然语言描述,系统翻译为设计逻辑,生成独一无二的封面图片。
+ 纯 HTML/CSS 排版渲染,不依赖任何 AI 绘画模型。
+ 封面底部带有此仓库域名水印。设计编号唯一,证明归属。
+
${escapeHtml(l)}
`) + .join('\n') + contentHtml = ` +${line}
`).join('\n') + + return ` + + + + + + + + + +${body}