D121: xiaohongshu-cover module v5 complete
MODULE-COVER-001: full package for external AI consumption - server.js + renderer.js + generate.js + config.js - templates/ (5 templates: xiaohongshu + dynamic + jike + poster + registry) - package.json + README.md + MODULE.hdlp - INDEX.hdlp + SYSTEM.hdlp (HLDP declarations) - Fonts: Noto Sans CJK SC priority (no tofu blocks) - No domain watermark - Updated module-registry.json - Copyright 2026-A-00037559
This commit is contained in:
parent
b7c165d660
commit
651cc9e1fd
73
INDEX.hdlp
Normal file
73
INDEX.hdlp
Normal file
@ -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
|
||||
|
||||
112
image-generator/SYSTEM.hdlp
Normal file
112
image-generator/SYSTEM.hdlp
Normal file
@ -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
|
||||
|
||||
145
image-generator/xiaohongshu-cover/MODULE.hdlp
Normal file
145
image-generator/xiaohongshu-cover/MODULE.hdlp
Normal file
@ -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
|
||||
|
||||
17
image-generator/xiaohongshu-cover/README.md
Normal file
17
image-generator/xiaohongshu-cover/README.md
Normal file
@ -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
|
||||
242
image-generator/xiaohongshu-cover/config.js
Normal file
242
image-generator/xiaohongshu-cover/config.js
Normal file
@ -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
|
||||
}
|
||||
187
image-generator/xiaohongshu-cover/generate.js
Normal file
187
image-generator/xiaohongshu-cover/generate.js
Normal file
@ -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) })
|
||||
}
|
||||
17
image-generator/xiaohongshu-cover/package.json
Normal file
17
image-generator/xiaohongshu-cover/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
401
image-generator/xiaohongshu-cover/public/index.html
Normal file
401
image-generator/xiaohongshu-cover/public/index.html
Normal file
@ -0,0 +1,401 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>bingshuo / open-modules · 冰朔开源模块仓库</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--surface: #161b22;
|
||||
--border: #30363d;
|
||||
--border-h: #58a6ff;
|
||||
--text: #c9d1d9;
|
||||
--dim: #8b949e;
|
||||
--accent: #58a6ff;
|
||||
--green: #3fb950;
|
||||
--gold: #d2991d;
|
||||
}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;min-height:100vh;font-size:14px;line-height:1.6}
|
||||
|
||||
/* Repo Header */
|
||||
.repo-hdr {
|
||||
background:var(--surface);
|
||||
border-bottom:1px solid var(--border);
|
||||
padding:20px 28px;
|
||||
}
|
||||
.repo-hdr .breadcrumb {font-size:13px;color:var(--dim);margin-bottom:8px}
|
||||
.repo-hdr .breadcrumb a {color:var(--accent);text-decoration:none}
|
||||
.repo-hdr .breadcrumb a:hover {text-decoration:underline}
|
||||
.repo-hdr h1 {font-size:22px;font-weight:600;margin-bottom:6px;display:flex;align-items:center;gap:10px}
|
||||
.repo-hdr h1 .badge {display:inline-block;padding:2px 8px;border:1px solid var(--border);border-radius:12px;font-size:11px;font-weight:400;color:var(--dim)}
|
||||
.repo-hdr .desc {font-size:13px;color:var(--dim);max-width:700px}
|
||||
|
||||
/* Tab Bar */
|
||||
.tabs {display:flex;border-bottom:1px solid var(--border);padding:0 28px;background:var(--surface);overflow-x:auto}
|
||||
.tab {padding:11px 18px;font-size:13px;color:var(--dim);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap;transition:all 0.15s;display:flex;align-items:center;gap:6px}
|
||||
.tab:hover {color:var(--text)}
|
||||
.tab.active {color:var(--text);border-bottom-color:var(--accent)}
|
||||
.tab .count {background:rgba(88,166,255,0.1);padding:1px 7px;border-radius:10px;font-size:11px}
|
||||
|
||||
/* Layout */
|
||||
.layout {display:grid;grid-template-columns:220px 1fr;min-height:calc(100vh - 140px)}
|
||||
@media(max-width:800px){.layout{grid-template-columns:1fr}}
|
||||
|
||||
/* Sidebar File Tree */
|
||||
.sidebar {background:var(--surface);border-right:1px solid var(--border);padding:12px 0;position:sticky;top:0;max-height:calc(100vh - 140px);overflow-y:auto}
|
||||
.sidebar .sect {padding:6px 18px;font-size:11px;color:var(--dim);font-weight:600;letter-spacing:1px;text-transform:uppercase}
|
||||
.sidebar .file {padding:5px 18px 5px 28px;font-size:12px;color:var(--dim);cursor:pointer;display:flex;align-items:center;gap:7px;transition:all 0.1s}
|
||||
.sidebar .file:hover {color:var(--text);background:rgba(88,166,255,0.04)}
|
||||
.sidebar .file .fi {font-size:13px;width:16px;text-align:center;flex-shrink:0}
|
||||
|
||||
/* Content */
|
||||
.content {padding:28px}
|
||||
|
||||
/* Module Grid - Square Cards */
|
||||
.module-grid {display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:32px}
|
||||
.module-card {
|
||||
background:var(--surface);
|
||||
border:1px solid var(--border);
|
||||
border-radius:12px;
|
||||
padding:24px 20px;
|
||||
cursor:pointer;
|
||||
transition:all 0.2s;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
text-align:center;
|
||||
aspect-ratio:1;
|
||||
position:relative;
|
||||
}
|
||||
.module-card:hover {border-color:var(--border-h);transform:translateY(-2px);box-shadow:0 8px 24px rgba(88,166,255,0.08)}
|
||||
.module-card .icon {font-size:40px;margin-bottom:12px}
|
||||
.module-card .name {font-size:15px;font-weight:600;margin-bottom:6px}
|
||||
.module-card .modid {font-size:11px;color:var(--dim);font-family:monospace;margin-bottom:8px}
|
||||
.module-card .tags {display:flex;gap:5px;flex-wrap:wrap;justify-content:center}
|
||||
.module-card .tag {padding:2px 8px;border:1px solid var(--border);border-radius:8px;font-size:10px;color:var(--dim)}
|
||||
.module-card .tag.green {border-color:var(--green);color:var(--green)}
|
||||
|
||||
/* Module Detail Panel */
|
||||
.module-detail {
|
||||
display:none;
|
||||
background:var(--surface);
|
||||
border:1px solid var(--border);
|
||||
border-radius:12px;
|
||||
padding:28px;
|
||||
margin-top:16px;
|
||||
animation:fadeIn 0.2s ease;
|
||||
}
|
||||
.module-detail.show {display:block}
|
||||
@keyframes fadeIn {from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
|
||||
|
||||
.module-detail h2 {font-size:18px;margin-bottom:12px}
|
||||
.module-detail .meta {display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px}
|
||||
.module-detail .meta span{font-size:12px;color:var(--dim);border:1px solid var(--border);padding:4px 10px;border-radius:8px}
|
||||
.module-detail .desc {font-size:13px;color:var(--dim);line-height:1.8;margin-bottom:20px}
|
||||
.module-detail .section {margin-bottom:20px}
|
||||
.module-detail .section h3 {font-size:13px;font-weight:600;margin-bottom:8px;color:var(--dim);text-transform:uppercase;letter-spacing:1px}
|
||||
.module-detail .code-block {
|
||||
background:#0d1117;border:1px solid var(--border);border-radius:8px;
|
||||
padding:14px 18px;font-family:'SF Mono','Fira Code',monospace;
|
||||
font-size:12px;line-height:1.7;color:var(--text);overflow-x:auto;white-space:pre-wrap
|
||||
}
|
||||
.code-block .c {color:#636e7b}
|
||||
.code-block .s {color:#a5d6ff}
|
||||
.code-block .k {color:#ff7b72}
|
||||
|
||||
/* Steps */
|
||||
.steps {display:flex;flex-direction:column;gap:12px}
|
||||
.step {display:flex;gap:14px;padding:14px;background:#0d1117;border:1px solid var(--border);border-radius:8px;align-items:flex-start}
|
||||
.step .n {width:28px;height:28px;border-radius:50%;background:var(--accent);color:#0d1117;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;flex-shrink:0}
|
||||
.step .b {flex:1}
|
||||
.step .b .t {font-size:13px;font-weight:600;margin-bottom:2px}
|
||||
.step .b .d {font-size:12px;color:var(--dim);line-height:1.5}
|
||||
|
||||
/* Footer */
|
||||
.footer {text-align:center;padding:32px;font-size:12px;color:var(--dim);border-top:1px solid var(--border);letter-spacing:1px}
|
||||
|
||||
/* Toast */
|
||||
.toast {position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--green);color:#0d1117;padding:10px 20px;border-radius:20px;font-size:13px;font-weight:600;display:none;z-index:999}
|
||||
.toast.show {display:block;animation:toastAnim 2s ease}
|
||||
@keyframes toastAnim{0%{opacity:0;transform:translateX(-50%) translateY(8px)}20%{opacity:1;transform:translateX(-50%) translateY(0)}80%{opacity:1}100%{opacity:0}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="repo-hdr">
|
||||
<div class="breadcrumb">
|
||||
<a href="https://guanghulab.com">guanghulab</a> /
|
||||
<a href="https://guanghulab.com/code/bingshuo">bingshuo</a> /
|
||||
<strong>open-modules</strong>
|
||||
</div>
|
||||
<h1>
|
||||
📦 open-modules
|
||||
<span class="badge">public</span>
|
||||
</h1>
|
||||
<p class="desc">冰朔个人开源模块仓库。每个模块有唯一编号。用你自己的编程AI软件连入,Agent引导后调用模块。仓库不提供模型API。</p>
|
||||
</header>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="switchTab(this,'modules')">
|
||||
<span>📦</span> 模块
|
||||
<span class="count">1</span>
|
||||
</div>
|
||||
<div class="tab" onclick="switchTab(this,'guide')">
|
||||
<span>📖</span> 接入指南
|
||||
</div>
|
||||
<div class="tab" onclick="switchTab(this,'files')">
|
||||
<span>📁</span> 文件
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sect">仓库根目录</div>
|
||||
<div class="file"><span class="fi">📄</span> README.md</div>
|
||||
<div class="file"><span class="fi">📄</span> WELCOME.md</div>
|
||||
<div class="sect">agent/</div>
|
||||
<div class="file"><span class="fi">📄</span> HLDP-PROTOCOL.md</div>
|
||||
<div class="file"><span class="fi">📄</span> module-registry.json</div>
|
||||
<div class="sect">modules/</div>
|
||||
<div class="file"><span class="fi">📦</span> MODULE-COVER-001</div>
|
||||
<div class="file" style="color:var(--dim);opacity:0.5"><span class="fi">📦</span> MODULE-XXX-002</div>
|
||||
<div class="sect">image-studio/</div>
|
||||
<div class="file"><span class="fi">📄</span> server.js</div>
|
||||
<div class="file"><span class="fi">📄</span> generate.js</div>
|
||||
<div class="file"><span class="fi">📄</span> renderer.js</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="content">
|
||||
|
||||
<!-- Modules Panel -->
|
||||
<div id="panel-modules">
|
||||
<div class="module-grid">
|
||||
|
||||
<!-- Module Card: COVER-001 -->
|
||||
<div class="module-card" onclick="showDetail('cover')">
|
||||
<div class="icon">🎨</div>
|
||||
<div class="name">图片模块</div>
|
||||
<div class="modid">MODULE-COVER-001</div>
|
||||
<div class="tags">
|
||||
<span class="tag green">active</span>
|
||||
<span class="tag">MIT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Placeholder for future modules -->
|
||||
<div class="module-card" style="opacity:0.35;cursor:not-allowed">
|
||||
<div class="icon">📋</div>
|
||||
<div class="name">即将上线</div>
|
||||
<div class="modid">MODULE-XXX-002</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Module Detail -->
|
||||
<div class="module-detail" id="detail-cover">
|
||||
<h2>🎨 图片模块 · 语言驱动封面生成器</h2>
|
||||
<div class="meta">
|
||||
<span>MODULE-COVER-001</span>
|
||||
<span>MIT License</span>
|
||||
<span>Puppeteer + Express</span>
|
||||
</div>
|
||||
<p class="desc">
|
||||
输入自然语言描述,系统翻译为设计逻辑,生成独一无二的封面图片。
|
||||
纯 HTML/CSS 排版渲染,不依赖任何 AI 绘画模型。<br><br>
|
||||
<strong style="color:var(--text)">封面底部带有此仓库域名水印。设计编号唯一,证明归属。</strong>
|
||||
</p>
|
||||
|
||||
<div class="section">
|
||||
<h3>技术栈 · 全免费</h3>
|
||||
<div class="code-block">
|
||||
<span class="c">// 全部开源 · 全部免费 · 不花一分钱</span>
|
||||
<span class="k">渲染</span> Puppeteer + Chrome Headless <span class="c">免费</span>
|
||||
<span class="k">Web</span> Express.js <span class="c">免费</span>
|
||||
<span class="k">排版</span> 纯 HTML/CSS(不调用AI模型) <span class="c">免费</span>
|
||||
<span class="k">分析</span> 自研文本分析引擎 <span class="c">免费</span>
|
||||
|
||||
<span class="c">// 不需要 GPU · 不需要 API 密钥</span></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>AI 如何调用此模块</h3>
|
||||
<div class="steps">
|
||||
<div class="step">
|
||||
<div class="n">0</div>
|
||||
<div class="b">
|
||||
<div class="t">确认环境</div>
|
||||
<div class="d">你的 AI 已连接到此仓库,可读取文件,可发送 HTTP 请求</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">1</div>
|
||||
<div class="b">
|
||||
<div class="t">读取 WELCOME.md</div>
|
||||
<div class="d">仓库入口文件,包含完整的唤醒协议和规则</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">2</div>
|
||||
<div class="b">
|
||||
<div class="t">装载 HLDP 协议</div>
|
||||
<div class="d">读取 agent/HLDP-PROTOCOL.md,理解模块编号格式</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">3</div>
|
||||
<div class="b">
|
||||
<div class="t">读取模块注册表</div>
|
||||
<div class="d">读取 agent/module-registry.json,获取所有可用模块</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">4</div>
|
||||
<div class="b">
|
||||
<div class="t">调用模块</div>
|
||||
<div class="d">POST guanghulab.com/cover/api/generate 传入参数,获取生成结果</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>API 参考</h3>
|
||||
<div class="code-block">
|
||||
<span class="c">// 获取模板列表</span>
|
||||
<span class="k">GET</span> <span class="s">https://guanghulab.com/cover/api/templates</span>
|
||||
|
||||
<span class="c">// 生成封面</span>
|
||||
<span class="k">POST</span> <span class="s">https://guanghulab.com/cover/api/generate</span>
|
||||
Body: {
|
||||
<span class="s">"templateId"</span>: <span class="s">"xiaohongshu"</span>,
|
||||
<span class="s">"presetId"</span>: <span class="s">"tech"</span>,
|
||||
<span class="s">"title"</span>: <span class="s">"标题"</span>,
|
||||
<span class="s">"body"</span>: <span class="s">"正文"</span>,
|
||||
<span class="s">"layout"</span>: <span class="s">"hero"</span>
|
||||
}
|
||||
|
||||
<span class="c">// 返回</span>
|
||||
{ <span class="s">"url"</span>: <span class="s">"/output/xxx.png"</span>,
|
||||
<span class="s">"intent_chain"</span>: [ <span class="c">/* 7步推理 */</span> ] }</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Guide Panel -->
|
||||
<div id="panel-guide" style="display:none">
|
||||
<div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:28px">
|
||||
<h2 style="font-size:18px;margin-bottom:16px">📖 AI 接入指南</h2>
|
||||
|
||||
<p style="font-size:13px;color:var(--dim);line-height:1.8;margin-bottom:24px">
|
||||
本仓库设计为<strong style="color:var(--text)">任何编程AI都可接入</strong>。
|
||||
仓库不提供模型API——你使用你自己的AI软件模型能力。
|
||||
以下是完整的接入流程。
|
||||
</p>
|
||||
|
||||
<div class="steps">
|
||||
<div class="step">
|
||||
<div class="n">1</div>
|
||||
<div class="b">
|
||||
<div class="t">连接仓库</div>
|
||||
<div class="d">将仓库地址 guanghulab.com/code/bingshuo/guanghulab 配置到你的AI工具中</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">2</div>
|
||||
<div class="b">
|
||||
<div class="t">AI读取 WELCOME.md</div>
|
||||
<div class="d">AI进入仓库后,第一步读取此文件。它包含唤醒协议和规则。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">3</div>
|
||||
<div class="b">
|
||||
<div class="t">AI装载 HLDP 协议</div>
|
||||
<div class="d">读取 agent/HLDP-PROTOCOL.md。理解模块编号、母语格式。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">4</div>
|
||||
<div class="b">
|
||||
<div class="t">AI读取模块注册表</div>
|
||||
<div class="d">读取 agent/module-registry.json。获取可用模块列表和API。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="n">5</div>
|
||||
<div class="b">
|
||||
<div class="t">调用模块</div>
|
||||
<div class="d">AI按模块编号和API文档调用。生成结果返回给用户。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px;padding:16px;background:#0d1117;border:1px solid var(--border);border-radius:8px">
|
||||
<div style="font-size:12px;color:var(--dim);margin-bottom:8px;letter-spacing:1px">📋 规则摘要</div>
|
||||
<div style="font-size:12px;color:var(--dim);line-height:2">
|
||||
✦ 仓库不提供模型API<br>
|
||||
✦ 模块热插拔,新增零改动核心<br>
|
||||
✦ HLDP协议是通用接口<br>
|
||||
✦ 设计编号唯一可追溯<br>
|
||||
✦ 署名可选<br>
|
||||
✦ 全免费 · MIT协议
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Files Panel -->
|
||||
<div id="panel-files" style="display:none">
|
||||
<div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px">
|
||||
<div class="code-block" style="white-space:pre;line-height:2">
|
||||
<span style="color:var(--dim)">📁 open-modules/</span>
|
||||
<span style="color:var(--dim)">├── 📄</span> README.md <span style="color:#636e7b">仓库说明</span>
|
||||
<span style="color:var(--dim)">├── 📄</span> WELCOME.md <span style="color:#636e7b">AI 入口引导 · 唤醒协议</span>
|
||||
<span style="color:var(--dim)">├── 📁</span> agent/
|
||||
<span style="color:var(--dim)">│ ├── 📄</span> HLDP-PROTOCOL.md <span style="color:#636e7b">光湖语言协议</span>
|
||||
<span style="color:var(--dim)">│ └── 📄</span> module-registry.json <span style="color:#636e7b">模块注册表</span>
|
||||
<span style="color:var(--dim)">├── 📁</span> modules/
|
||||
<span style="color:var(--dim)">│ └── 📦</span> MODULE-COVER-001 <span style="color:#636e7b">图片模块(已上线)</span>
|
||||
<span style="color:var(--dim)">└── 📁</span> image-studio/
|
||||
<span style="color:var(--dim)"> ├── 📄</span> server.js <span style="color:#636e7b">Express 服务</span>
|
||||
<span style="color:var(--dim)"> ├── 📄</span> generate.js <span style="color:#636e7b">生成引擎</span>
|
||||
<span style="color:var(--dim)"> ├── 📄</span> renderer.js <span style="color:#636e7b">Puppeteer 渲染</span>
|
||||
<span style="color:var(--dim)"> └── 📁</span> templates/ <span style="color:#636e7b">模板目录</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
guanghulab.com/code/bingshuo/open-modules · 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001 · 国作登字-2026-A-00037559
|
||||
</footer>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
function switchTab(el, panel) {
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
el.classList.add('active');
|
||||
document.getElementById('panel-modules').style.display = panel === 'modules' ? 'block' : 'none';
|
||||
document.getElementById('panel-guide').style.display = panel === 'guide' ? 'block' : 'none';
|
||||
document.getElementById('panel-files').style.display = panel === 'files' ? 'block' : 'none';
|
||||
// Hide module detail when switching tabs
|
||||
document.querySelectorAll('.module-detail').forEach(d => d.classList.remove('show'));
|
||||
}
|
||||
|
||||
function showDetail(id) {
|
||||
const el = document.getElementById('detail-' + id);
|
||||
const isOpen = el.classList.contains('show');
|
||||
document.querySelectorAll('.module-detail').forEach(d => d.classList.remove('show'));
|
||||
if (!isOpen) el.classList.add('show');
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
88
image-generator/xiaohongshu-cover/renderer.js
Normal file
88
image-generator/xiaohongshu-cover/renderer.js
Normal file
@ -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<string>} 输出文件路径
|
||||
*/
|
||||
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', '--disable-features=TranslateUI', '--font-render-hinting=none'],
|
||||
})
|
||||
|
||||
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<string[]>} 输出文件路径数组
|
||||
*/
|
||||
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
|
||||
}
|
||||
65
image-generator/xiaohongshu-cover/server.js
Normal file
65
image-generator/xiaohongshu-cover/server.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 铸渊封面工作室 · Web 服务 · v2.1
|
||||
* 公开页面 + 模板列表 API + 生成 API + 意图链 + 下载端点
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { generate, listTemplates } from './generate.js'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const OUTPUT_DIR = join(__dirname, 'output')
|
||||
const PUBLIC_DIR = join(__dirname, 'public')
|
||||
const PORT = process.env.PORT || 3912
|
||||
|
||||
if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true })
|
||||
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
app.use(express.json({ limit: '1mb' }))
|
||||
app.use('/output', express.static(OUTPUT_DIR))
|
||||
app.use(express.static(PUBLIC_DIR))
|
||||
|
||||
app.get('/api/templates', (req, res) => {
|
||||
try { res.json(listTemplates()) }
|
||||
catch (err) { res.status(500).json({ error: '无法加载模板列表' }) }
|
||||
})
|
||||
|
||||
app.post('/api/generate', async (req, res) => {
|
||||
try {
|
||||
const { templateId, presetId = '', title = '', body = '', subtitle = '', tag = '', layout = 'default' } = req.body
|
||||
if (!title && !body) return res.status(400).json({ error: '请至少提供标题或正文内容' })
|
||||
|
||||
const result = await generate({ templateId, presetId, title, body, subtitle, tag, layout })
|
||||
const filename = result.files[0].split('/').pop()
|
||||
const url = `/output/${filename}`
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
url, filename,
|
||||
templateId: result.templateId,
|
||||
templateName: result.templateName,
|
||||
presetId: result.presetId,
|
||||
layout: result.layout,
|
||||
intent_chain: result.intent_chain,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('生成失败:', err.message)
|
||||
res.status(500).json({ error: err.message || '生成失败' })
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() })
|
||||
})
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(join(PUBLIC_DIR, 'index.html'))
|
||||
})
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🎨 铸渊封面工作室 v2.1 运行在 http://localhost:${PORT}`)
|
||||
})
|
||||
192
image-generator/xiaohongshu-cover/templates/dynamic.js
Normal file
192
image-generator/xiaohongshu-cover/templates/dynamic.js
Normal file
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════
|
||||
* 万能动态模板 · 自适应任何内容
|
||||
* ═══════════════════════════════════════════════════
|
||||
*
|
||||
* 不是固定类型。是根据冰朔的文字内容,现场设计。
|
||||
*/
|
||||
|
||||
import { STYLES } from '../config.js'
|
||||
|
||||
|
||||
/**
|
||||
* 万能动态卡片
|
||||
* @param {object} data
|
||||
* @param {string} data.title - 提炼的标题
|
||||
* @param {string} data.body - 正文
|
||||
* @param {string[]} data.lines - 原文行数组
|
||||
* @param {string} data.type - 内容类型(analyzeText 的结果)
|
||||
*/
|
||||
export function dynamicCard(data) {
|
||||
const { title, body, lines = [], type = 'general' } = data
|
||||
const C = STYLES.colors
|
||||
const F = STYLES.fonts
|
||||
const T = STYLES.typography
|
||||
const W = 1080
|
||||
const H = 1080
|
||||
|
||||
// 动态计算字号:内容越多字越小
|
||||
const totalChars = (title + body).length
|
||||
const titleSize = totalChars < 20 ? 52 : totalChars < 50 ? 44 : 36
|
||||
const bodySize = totalChars < 50 ? 32 : totalChars < 150 ? 26 : 22
|
||||
|
||||
// 检测有没有列表
|
||||
const hasList = lines.some(l => /^[-•·*]\s/.test(l.trim()))
|
||||
// 检测是不是一句话
|
||||
const isSingleLine = lines.length <= 2 && totalChars < 40
|
||||
|
||||
/* ── 生成内容 HTML ── */
|
||||
let contentHtml = ''
|
||||
|
||||
if (isSingleLine) {
|
||||
/* 一句话 · 大字居中 */
|
||||
contentHtml = `
|
||||
<div class="single-line">${escapeHtml(title)}</div>
|
||||
`
|
||||
} else if (hasList) {
|
||||
/* 列表内容 */
|
||||
const items = lines
|
||||
.filter(l => /^[-•·*]\s/.test(l.trim()))
|
||||
.map(l => `<li>${escapeHtml(l.replace(/^[-•·*]\s*/, ''))}</li>`)
|
||||
.join('\n')
|
||||
contentHtml = `
|
||||
<div class="title" style="font-size:${titleSize}px">${escapeHtml(title)}</div>
|
||||
<ul class="dynamic-list">${items}</ul>
|
||||
`
|
||||
} else {
|
||||
/* 一般段落 */
|
||||
const paragraphs = lines
|
||||
.filter(Boolean)
|
||||
.map(l => `<p>${escapeHtml(l)}</p>`)
|
||||
.join('\n')
|
||||
contentHtml = `
|
||||
<div class="title" style="font-size:${titleSize}px">${escapeHtml(title)}</div>
|
||||
<div class="body-text" style="font-size:${bodySize}px">${paragraphs}</div>
|
||||
`
|
||||
}
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
||||
|
||||
body {
|
||||
width: ${W}px;
|
||||
height: ${H}px;
|
||||
overflow: hidden;
|
||||
background: ${C.bg};
|
||||
font-family: ${F.body};
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 动态背景装饰 */
|
||||
.bg-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.04;
|
||||
}
|
||||
.bg-c1 {
|
||||
width: 400px; height: 400px;
|
||||
background: ${C.accent};
|
||||
top: -100px; right: -100px;
|
||||
}
|
||||
.bg-c2 {
|
||||
width: 250px; height: 250px;
|
||||
background: ${C.highlight};
|
||||
bottom: -60px; left: -60px;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
padding: 56px 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.single-line {
|
||||
font-family: ${F.title};
|
||||
font-size: ${titleSize}px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
color: ${C.primary};
|
||||
text-align: center;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: ${F.title};
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
color: ${C.primary};
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.body-text {
|
||||
line-height: 1.7;
|
||||
color: ${C.text};
|
||||
}
|
||||
.body-text p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dynamic-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.dynamic-list li {
|
||||
font-size: ${bodySize}px;
|
||||
line-height: 1.5;
|
||||
color: ${C.text};
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 8px;
|
||||
background: ${C.warm}40;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid ${C.accent};
|
||||
}
|
||||
|
||||
/* 底部品牌 */
|
||||
.brand-bar {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: ${C.textMuted};
|
||||
opacity: ${STYLES.brand.opacity};
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="bg-circle bg-c1"></div>
|
||||
<div class="bg-circle bg-c2"></div>
|
||||
|
||||
<div class="container">
|
||||
${contentHtml}
|
||||
</div>
|
||||
|
||||
<div class="brand-bar">${STYLES.brand.text}</div>
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
190
image-generator/xiaohongshu-cover/templates/jike.js
Normal file
190
image-generator/xiaohongshu-cover/templates/jike.js
Normal file
@ -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 => `<p>${line}</p>`).join('\n')
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
||||
|
||||
body {
|
||||
width: ${W}px;
|
||||
height: ${H}px;
|
||||
overflow: hidden;
|
||||
background: ${C.bg};
|
||||
font-family: ${F.body};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 顶部渐变装饰条 */
|
||||
.accent-bar {
|
||||
height: 6px;
|
||||
background: linear-gradient(90deg, ${C.accent}, ${C.highlight}, ${C.gold});
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
padding: 48px 56px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-size: ${T.captionSize}px;
|
||||
color: ${C.textMuted};
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.emoji-row {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: ${F.title};
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
color: ${C.primary};
|
||||
margin-bottom: 20px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
font-size: 26px;
|
||||
line-height: 1.7;
|
||||
color: ${C.text};
|
||||
}
|
||||
|
||||
.body p {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 金句布局 */
|
||||
.quote-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.quote-emoji {
|
||||
font-size: 72px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.quote-line {
|
||||
width: 40px;
|
||||
height: 2px;
|
||||
background: ${C.gold};
|
||||
margin: 0 auto 24px;
|
||||
}
|
||||
|
||||
.quote-title {
|
||||
font-family: ${F.title};
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
color: ${C.primary};
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.quote-body {
|
||||
margin-top: 20px;
|
||||
font-size: 24px;
|
||||
color: ${C.textMuted};
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
padding: 20px 56px 28px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid ${C.divider};
|
||||
font-size: ${T.captionSize}px;
|
||||
color: ${C.textMuted};
|
||||
}
|
||||
|
||||
.brand {
|
||||
opacity: ${STYLES.brand.opacity};
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="accent-bar"></div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
${meta ? `<div class="meta">${meta}</div>` : ''}
|
||||
|
||||
${layout === 'quote' ? `
|
||||
<div class="quote-section">
|
||||
${emoji ? `<div class="quote-emoji">${emoji}</div>` : ''}
|
||||
<div class="quote-line"></div>
|
||||
<div class="quote-title">${title}</div>
|
||||
${body ? `<div class="quote-body">${body}</div>` : ''}
|
||||
</div>
|
||||
` : `
|
||||
${emoji ? `<div class="emoji-row">${emoji}</div>` : ''}
|
||||
<div class="title">${title}</div>
|
||||
<div class="body">${bodyHtml}</div>
|
||||
`}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span class="brand">${STYLES.brand.text}</span>
|
||||
<span>即刻 · Jike</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
316
image-generator/xiaohongshu-cover/templates/poster.js
Normal file
316
image-generator/xiaohongshu-cover/templates/poster.js
Normal file
@ -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 `
|
||||
<div class="detail-item">
|
||||
<span class="detail-key">${key}</span>
|
||||
<span class="detail-colon">:</span>
|
||||
<span class="detail-val">${val}</span>
|
||||
</div>`
|
||||
}
|
||||
return `<div class="detail-item">${d}</div>`
|
||||
}).join('\n')
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
||||
|
||||
body {
|
||||
width: ${W}px;
|
||||
height: ${H}px;
|
||||
overflow: hidden;
|
||||
background: ${C.bg};
|
||||
font-family: ${F.body};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ── 背景装饰 ── */
|
||||
.bg-accent {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, ${C.accent}08 0%, transparent 70%);
|
||||
}
|
||||
.bg-accent-bl {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, ${C.highlight}06 0%, transparent 70%);
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
padding: 72px 64px 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ── 标签 ── */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 6px 18px;
|
||||
border: 1px solid ${C.accent};
|
||||
color: ${C.accent};
|
||||
font-size: ${T.captionSize}px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 32px;
|
||||
align-self: flex-start;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
/* ── 标题区 ── */
|
||||
.title-section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: ${F.title};
|
||||
font-size: 64px;
|
||||
font-weight: 900;
|
||||
line-height: 1.25;
|
||||
color: ${C.primary};
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
.title-accent {
|
||||
color: ${C.highlight};
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-family: ${F.title};
|
||||
font-size: ${T.subtitleSize}px;
|
||||
font-weight: 300;
|
||||
color: ${C.textMuted};
|
||||
margin-top: 12px;
|
||||
letter-spacing: 6px;
|
||||
}
|
||||
|
||||
/* ── 分割线 ── */
|
||||
.divider {
|
||||
width: 80px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, ${C.gold}, ${C.highlight});
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* ── 正文 ── */
|
||||
.body {
|
||||
font-size: ${T.bodySize}px;
|
||||
line-height: ${T.lineHeight};
|
||||
color: ${C.text};
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.body p {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* ── 详情列表 ── */
|
||||
.details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
padding: 32px;
|
||||
background: ${C.warm}30;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid ${C.accent};
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
font-size: ${T.bodySize}px;
|
||||
line-height: 1.5;
|
||||
color: ${C.text};
|
||||
}
|
||||
|
||||
.detail-key {
|
||||
font-weight: 600;
|
||||
color: ${C.accent};
|
||||
}
|
||||
.detail-colon { color: ${C.textMuted}; }
|
||||
.detail-val { color: ${C.text}; }
|
||||
|
||||
/* ── CTA ── */
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.cta {
|
||||
display: inline-block;
|
||||
padding: 16px 48px;
|
||||
background: linear-gradient(135deg, ${C.accent}, ${C.primary});
|
||||
color: ${C.textLight};
|
||||
font-size: ${T.subtitleSize}px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
/* ── 底部 ── */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid ${C.divider};
|
||||
font-size: ${T.captionSize}px;
|
||||
color: ${C.textMuted};
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.brand-line {
|
||||
opacity: ${STYLES.brand.opacity};
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ── 通知风格:更正式 ── */
|
||||
.notice-title {
|
||||
font-family: ${F.title};
|
||||
font-size: 56px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
letter-spacing: 8px;
|
||||
color: ${C.primary};
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.notice-sub {
|
||||
text-align: center;
|
||||
font-size: ${T.smallSize}px;
|
||||
color: ${C.textMuted};
|
||||
letter-spacing: 4px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* ── 收稿风格:更有活力 ── */
|
||||
.recruit-badge {
|
||||
display: inline-block;
|
||||
padding: 8px 24px;
|
||||
background: ${C.highlight};
|
||||
color: white;
|
||||
font-size: ${T.smallSize}px;
|
||||
border-radius: 4px;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 20px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.recruit-title {
|
||||
font-family: ${F.title};
|
||||
font-size: 60px;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
color: ${C.primary};
|
||||
letter-spacing: 3px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="bg-accent"></div>
|
||||
<div class="bg-accent-bl"></div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
${style === 'notice' ? `
|
||||
${tag ? `<div class="tag" style="align-self:center;">${tag}</div>` : ''}
|
||||
<div class="notice-title">${title}</div>
|
||||
${subtitle ? `<div class="notice-sub">${subtitle}</div>` : ''}
|
||||
<div class="divider" style="align-self:center;"></div>
|
||||
` : style === 'recruit' ? `
|
||||
<div class="recruit-badge">${tag || '征稿启事'}</div>
|
||||
<div class="recruit-title">${title}</div>
|
||||
${subtitle ? `<div style="text-align:center;font-size:${T.smallSize}px;color:${C.textMuted};letter-spacing:2px;margin-bottom:24px;">${subtitle}</div>` : ''}
|
||||
` : `
|
||||
${tag ? `<div class="tag">${tag}</div>` : ''}
|
||||
<div class="title-section">
|
||||
<div class="title">${title}</div>
|
||||
${subtitle ? `<div class="subtitle">${subtitle}</div>` : ''}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
`}
|
||||
|
||||
${body ? `<div class="body"><p>${body}</p></div>` : ''}
|
||||
|
||||
${details.length > 0 ? `<div class="details">${detailsHtml}</div>` : ''}
|
||||
|
||||
<div style="flex:1;"></div>
|
||||
|
||||
${cta ? `
|
||||
<div class="cta-section">
|
||||
<div class="cta">${cta}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="footer">
|
||||
${footer ? `<div>${footer}</div>` : ''}
|
||||
<div class="brand-line">${STYLES.brand.text}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
163
image-generator/xiaohongshu-cover/templates/registry.js
Normal file
163
image-generator/xiaohongshu-cover/templates/registry.js
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════
|
||||
* 铸渊封面工作室 · 模板注册表
|
||||
* ═══════════════════════════════════════════════════
|
||||
*
|
||||
* 所有模板在此注册。新增模板只需:
|
||||
* 1. 在 templates/ 下创建模板文件
|
||||
* 2. 在此文件 import + 注册
|
||||
* 3. 核心代码零改动
|
||||
*/
|
||||
|
||||
import { xiaohongshuCard } from './xiaohongshu.js'
|
||||
|
||||
/**
|
||||
* 模板注册表
|
||||
*/
|
||||
export const TEMPLATE_REGISTRY = {
|
||||
|
||||
xiaohongshu: {
|
||||
id: 'xiaohongshu',
|
||||
name: '小红书封面',
|
||||
description: '1080×1440 · 3:4 竖版 · 适合小红书笔记封面',
|
||||
icon: '📕',
|
||||
sizes: { width: 1080, height: 1440 },
|
||||
category: '封面',
|
||||
isDefault: true,
|
||||
instructions: '输入标题和正文,自动排版生成小红书封面。支持金句、干货列表、教程等多种布局。',
|
||||
features: ['智能排版', '金句模式', '干货列表', '多页轮播'],
|
||||
presets: [
|
||||
{
|
||||
id: 'tech',
|
||||
name: '科技蓝',
|
||||
cssVars: {
|
||||
'--bg': '#0a0e27',
|
||||
'--primary': '#e0e6ff',
|
||||
'--accent': '#4f8cff',
|
||||
'--highlight': '#00d4ff',
|
||||
'--gold': '#ffb347',
|
||||
'--warm': '#1a1f3a',
|
||||
'--text': '#c8d6e5',
|
||||
'--textMuted': '#6b7d99',
|
||||
'--cardBg': '#121838',
|
||||
'--border': '#1e2a4a',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'warm',
|
||||
name: '奶油暖调',
|
||||
cssVars: {
|
||||
'--bg': '#fef7f0',
|
||||
'--primary': '#5d4037',
|
||||
'--accent': '#e07a5f',
|
||||
'--highlight': '#d4a373',
|
||||
'--gold': '#c9a96e',
|
||||
'--warm': '#fae1dd',
|
||||
'--text': '#3e2723',
|
||||
'--textMuted': '#8d6e63',
|
||||
'--cardBg': '#ffffff',
|
||||
'--border': '#f0e6d3',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'minimal',
|
||||
name: '极简黑白',
|
||||
cssVars: {
|
||||
'--bg': '#ffffff',
|
||||
'--primary': '#111111',
|
||||
'--accent': '#333333',
|
||||
'--highlight': '#ff4757',
|
||||
'--gold': '#999999',
|
||||
'--warm': '#f5f5f5',
|
||||
'--text': '#222222',
|
||||
'--textMuted': '#888888',
|
||||
'--cardBg': '#fafafa',
|
||||
'--border': '#e0e0e0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'rose',
|
||||
name: '玫瑰粉调',
|
||||
cssVars: {
|
||||
'--bg': '#fdf2f8',
|
||||
'--primary': '#4a1942',
|
||||
'--accent': '#9d4e8d',
|
||||
'--highlight': '#e8435e',
|
||||
'--gold': '#c9a96e',
|
||||
'--warm': '#fce4ec',
|
||||
'--text': '#311b2e',
|
||||
'--textMuted': '#8e6290',
|
||||
'--cardBg': '#ffffff',
|
||||
'--border': '#f0d4e0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'green',
|
||||
name: '文艺绿植',
|
||||
cssVars: {
|
||||
'--bg': '#f5f9f2',
|
||||
'--primary': '#1b3a1b',
|
||||
'--accent': '#5a8f5a',
|
||||
'--highlight': '#c78b5c',
|
||||
'--gold': '#b8a06e',
|
||||
'--warm': '#e8f0e0',
|
||||
'--text': '#2d3e2f',
|
||||
'--textMuted': '#6b8b6b',
|
||||
'--cardBg': '#ffffff',
|
||||
'--border': '#d4e4cc',
|
||||
},
|
||||
},
|
||||
],
|
||||
render: xiaohongshuCard,
|
||||
},
|
||||
|
||||
/* ── 后续接入(模板文件待创建时取消注释即可)── */
|
||||
// poster: { ... },
|
||||
// jike: { ... },
|
||||
// dynamic: { ... },
|
||||
}
|
||||
|
||||
|
||||
/** 获取所有可用模板列表 */
|
||||
export function listTemplates() {
|
||||
return Object.values(TEMPLATE_REGISTRY).map(t => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
icon: t.icon,
|
||||
sizes: t.sizes,
|
||||
category: t.category,
|
||||
features: t.features,
|
||||
instructions: t.instructions,
|
||||
presets: t.presets.map(p => ({ id: p.id, name: p.name })),
|
||||
isDefault: t.isDefault,
|
||||
}))
|
||||
}
|
||||
|
||||
/** 获取单个模板 */
|
||||
export function getTemplate(id) {
|
||||
return TEMPLATE_REGISTRY[id] || null
|
||||
}
|
||||
|
||||
/** 获取默认模板 */
|
||||
export function getDefaultTemplate() {
|
||||
return Object.values(TEMPLATE_REGISTRY).find(t => t.isDefault)
|
||||
|| Object.values(TEMPLATE_REGISTRY)[0]
|
||||
}
|
||||
|
||||
/** 根据模板+预设+内容,准备渲染数据 */
|
||||
export function prepareRenderData(templateId, presetId, contentData) {
|
||||
const template = getTemplate(templateId)
|
||||
if (!template) return null
|
||||
|
||||
const preset = template.presets.find(p => p.id === presetId)
|
||||
|| template.presets[0]
|
||||
|
||||
return {
|
||||
...contentData,
|
||||
templateId,
|
||||
presetId: preset?.id || 'default',
|
||||
cssVars: preset?.cssVars || {},
|
||||
sizes: template.sizes,
|
||||
}
|
||||
}
|
||||
216
image-generator/xiaohongshu-cover/templates/xiaohongshu.js
Normal file
216
image-generator/xiaohongshu-cover/templates/xiaohongshu.js
Normal file
@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 小红书封面 v5 · 圆角卡片 + 卡通插画风格
|
||||
* 奶白底色 + 大圆角 + 简洁层级
|
||||
*/
|
||||
export function xiaohongshuCard(renderData) {
|
||||
const { title='', body='', subtitle='', tag='', cssVars={} } = renderData;
|
||||
const v = cssVars || {};
|
||||
const getv = (k) => v[k] || v['--'+k] || v['--'+k.replace(/([A-Z])/g,'-$1').toLowerCase()];
|
||||
|
||||
// 配色:奶白底 + 深绿/深棕文字 + 点缀色
|
||||
const bg = getv('bg') || '#FDF8F3'; // 奶白底色
|
||||
const cardBg = getv('cardBg') || '#FFFFFF'; // 纯白卡片
|
||||
const primary = getv('primary') || '#2D5016'; // 深墨绿
|
||||
const accent = getv('accent') || '#E07B39'; // 焦糖橙
|
||||
const blue = getv('blue') || '#4A90A4'; // 雾蓝
|
||||
const green = getv('green') || '#5B8C5A'; // 苔藓绿
|
||||
const muted = getv('muted') || '#8B8680'; // 灰棕
|
||||
const textDark = '#1A1A1A';
|
||||
|
||||
const titleText = title || '零手搓 AI 封面生成器';
|
||||
const subLines = (subtitle || '不用GPU|纯代码渲染|开源免费').split('|').filter(Boolean);
|
||||
const techStack = (tag || 'Puppeteer,Node.js,Express').split(',').map(s=>s.trim()).filter(Boolean);
|
||||
|
||||
// 卡通场景 SVG - 程序员在小桌子前工作
|
||||
const cartoonSVG = `<svg viewBox="0 0 400 280" style="width:100%;height:auto">
|
||||
<!-- 桌子 -->
|
||||
<ellipse cx="200" cy="240" rx="140" ry="25" fill="#D4A574"/>
|
||||
<rect x="80" y="150" width="240" height="90" rx="8" fill="#C49A6C"/>
|
||||
<!-- 笔记本电脑 -->
|
||||
<rect x="140" y="130" width="120" height="80" rx="6" fill="#3A3A3A"/>
|
||||
<rect x="150" y="140" width="100" height="60" rx="2" fill="#5B8C5A"/>
|
||||
<rect x="155" y="145" width="90" height="8" rx="1" fill="#7BC47B"/>
|
||||
<rect x="155" y="158" width="60" height="4" rx="1" fill="#9AD89A"/>
|
||||
<rect x="155" y="168" width="75" height="4" rx="1" fill="#9AD89A"/>
|
||||
<!-- 人 -->
|
||||
<ellipse cx="200" cy="95" rx="35" ry="40" fill="#F5D0C5"/> <!-- 头 -->
|
||||
<path d="M165 80 Q200 60 235 80" stroke="#4A3728" stroke-width="12" fill="none"/> <!-- 头发 -->
|
||||
<ellipse cx="185" cy="90" rx="4" ry="5" fill="#4A3728"/> <!-- 眼 -->
|
||||
<ellipse cx="215" cy="90" rx="4" ry="5" fill="#4A3728"/>
|
||||
<path d="M190 115 Q200 120 210 115" stroke="#D4A0A0" stroke-width="3" fill="none"/> <!-- 嘴 -->
|
||||
<rect x="165" y="130" width="70" height="60" rx="20" fill="#E07B39"/> <!-- 身体 -->
|
||||
<rect x="150" y="145" width="20" height="50" rx="10" fill="#F5D0C5"/> <!-- 手臂 -->
|
||||
<rect x="230" y="145" width="20" height="50" rx="10" fill="#F5D0C5"/>
|
||||
<!-- 咖啡杯 -->
|
||||
<rect x="280" y="180" width="25" height="30" rx="4" fill="#FFF"/>
|
||||
<rect x="283" y="175" width="19" height="8" rx="2" fill="#6B4423"/>
|
||||
<path d="M305 190 Q315 190 315 200 Q315 210 305 210" stroke="#FFF" stroke-width="3" fill="none"/>
|
||||
<!-- 植物 -->
|
||||
<rect x="90" y="160" width="8" height="40" fill="#8B4513"/>
|
||||
<ellipse cx="94" cy="155" rx="20" ry="25" fill="#5B8C5A"/>
|
||||
<ellipse cx="85" cy="140" rx="12" ry="15" fill="#6B9B6A"/>
|
||||
<ellipse cx="103" cy="145" rx="12" ry="15" fill="#6B9B6A"/>
|
||||
<!-- 代码气泡 -->
|
||||
<rect x="260" y="60" width="80" height="50" rx="8" fill="#FFF" stroke="#E0E0E0" stroke-width="2"/>
|
||||
<rect x="270" y="72" width="40" height="6" rx="2" fill="#5B8C5A"/>
|
||||
<rect x="270" y="84" width="55" height="6" rx="2" fill="#4A90A4"/>
|
||||
<rect x="270" y="96" width="35" height="6" rx="2" fill="#E07B39"/>
|
||||
<polygon points="260,105 270,95 270,115" fill="#FFF" stroke="#E0E0E0" stroke-width="0"/>
|
||||
<!-- 装饰星星 -->
|
||||
<text x="350" y="100" font-size="20" fill="#FFD700">✦</text>
|
||||
<text x="60" y="80" font-size="16" fill="#4A90A4">✦</text>
|
||||
</svg>`;
|
||||
|
||||
return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><style>
|
||||
* {margin:0;padding:0;box-sizing:border-box}
|
||||
body {
|
||||
width:1080px;height:1440px;overflow:hidden;
|
||||
font-family:'Noto Sans CJK SC','WenQuanYi Micro Hei','PingFang SC',sans-serif;
|
||||
background:${bg};position:relative;display:flex;justify-content:center;align-items:center;
|
||||
}
|
||||
|
||||
/* 外层大卡片 */
|
||||
.card {
|
||||
width:960px;height:1320px;background:${cardBg};border-radius:60px;
|
||||
box-shadow:0 20px 80px rgba(0,0,0,0.08);
|
||||
display:flex;flex-direction:column;padding:50px 60px 40px;
|
||||
position:relative;overflow:hidden;
|
||||
}
|
||||
|
||||
/* 装饰圆 */
|
||||
.deco-circle-1 {
|
||||
position:absolute;top:-100px;right:-80px;width:320px;height:320px;
|
||||
border-radius:50%;background:radial-gradient(circle,${accent}18 0%,transparent 70%);
|
||||
}
|
||||
.deco-circle-2 {
|
||||
position:absolute;bottom:200px;left:-60px;width:180px;height:180px;
|
||||
border-radius:50%;background:radial-gradient(circle,${blue}12 0%,transparent 70%);
|
||||
}
|
||||
|
||||
/* 顶行标签 */
|
||||
.top-row {
|
||||
display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;
|
||||
}
|
||||
.tag-group {display:flex;gap:12px}
|
||||
.tag {
|
||||
padding:10px 22px;border-radius:24px;font-size:20px;font-weight:700;
|
||||
background:${bg};border:2px solid ${accent}30;color:${primary};
|
||||
}
|
||||
.tag.hl {background:${accent}15;border-color:${accent}50}
|
||||
.corner-text {
|
||||
font-size:18px;color:${muted};font-weight:500;
|
||||
}
|
||||
|
||||
/* 主标题区 */
|
||||
.title-block {text-align:center;margin-bottom:20px}
|
||||
.title-block h1 {
|
||||
font-size:72px;font-weight:800;color:${textDark};line-height:1.2;letter-spacing:2px;
|
||||
}
|
||||
.title-block .hl {color:${primary};position:relative;display:inline-block}
|
||||
.title-block .hl::after {
|
||||
content:'';position:absolute;bottom:8px;left:-4px;right:-4px;height:16px;
|
||||
background:${accent}40;border-radius:8px;z-index:-1;
|
||||
}
|
||||
|
||||
/* 副标题 */
|
||||
.subtitle-row {
|
||||
display:flex;justify-content:center;gap:16px;margin-bottom:32px;flex-wrap:wrap;
|
||||
}
|
||||
.sub-pill {
|
||||
padding:10px 24px;border-radius:30px;background:${bg};
|
||||
font-size:22px;font-weight:600;color:${primary};
|
||||
}
|
||||
|
||||
/* 卡通区 */
|
||||
.cartoon-area {
|
||||
flex:1;display:flex;align-items:center;justify-content:center;
|
||||
margin:20px 0;
|
||||
}
|
||||
.cartoon-box {
|
||||
width:420px;height:300px;
|
||||
}
|
||||
|
||||
/* 底部信息卡 */
|
||||
.bottom-cards {
|
||||
display:flex;gap:20px;margin-bottom:24px;
|
||||
}
|
||||
.info-card {
|
||||
flex:1;background:${bg};border-radius:24px;padding:24px 20px;text-align:center;
|
||||
border:2px solid transparent;transition:border-color 0.2s;
|
||||
}
|
||||
.info-card:hover {border-color:${accent}40}
|
||||
.info-card .num {
|
||||
font-size:48px;font-weight:800;color:${primary};line-height:1;
|
||||
}
|
||||
.info-card .num small {font-size:24px;font-weight:600;margin-left:4px}
|
||||
.info-card .label {
|
||||
font-size:20px;color:${muted};margin-top:8px;font-weight:500;
|
||||
}
|
||||
|
||||
/* 底部技术栈 */
|
||||
.tech-row {
|
||||
display:flex;align-items:center;justify-content:center;gap:12px;
|
||||
padding:16px 0;border-top:2px solid ${bg};
|
||||
}
|
||||
.tech-row .t-label {
|
||||
font-size:18px;color:${muted};font-weight:600;
|
||||
}
|
||||
.tech-row .t-chip {
|
||||
padding:8px 18px;border-radius:16px;font-size:18px;font-weight:600;
|
||||
background:${cardBg};border:2px solid ${accent}25;color:${primary};
|
||||
}
|
||||
|
||||
/* 水印 */
|
||||
.wm {
|
||||
position:absolute;bottom:20px;right:40px;font-size:16px;color:${muted}40;
|
||||
}
|
||||
</style></head><body>
|
||||
|
||||
<div class="card">
|
||||
<div class="deco-circle-1"></div>
|
||||
<div class="deco-circle-2"></div>
|
||||
|
||||
<div class="top-row">
|
||||
<div class="tag-group">
|
||||
<span class="tag hl">🤖 AI辅助开发</span>
|
||||
<span class="tag">💰 开源免费</span>
|
||||
</div>
|
||||
<span class="corner-text"></span>
|
||||
</div>
|
||||
|
||||
<div class="title-block">
|
||||
<h1>${titleText.replace(/(.{4,6})(.+)/, '$1<span class="hl">$2</span>')}</h1>
|
||||
</div>
|
||||
|
||||
<div class="subtitle-row">
|
||||
${subLines.map(s=>`<span class="sub-pill">${s}</span>`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="cartoon-area">
|
||||
<div class="cartoon-box">${cartoonSVG}</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-cards">
|
||||
<div class="info-card">
|
||||
<div class="num">0<small>GPU</small></div>
|
||||
<div class="label">纯CSS排版</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="num">${techStack.length}<small>项</small></div>
|
||||
<div class="label">开源技术栈</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="num">∞<small>扩展</small></div>
|
||||
<div class="label">模块化设计</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-row">
|
||||
<span class="t-label">🛠 技术栈</span>
|
||||
${techStack.slice(0,5).map(t=>`<span class="t-chip">${t}</span>`).join('')}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body></html>`;
|
||||
}
|
||||
@ -1,68 +1,52 @@
|
||||
{
|
||||
"_protocol": "HLDP-v1.0",
|
||||
"_repository": "guanghulab.com/code/bingshuo/guanghulab",
|
||||
"_rule": "模块是热插拔的。新增模块只需在此文件添加条目。",
|
||||
"_repository": "guanghulab.com/code/bingshuo/open-modules",
|
||||
"_rule": "HLDP字典。不会就查,不用猜。",
|
||||
|
||||
"modules": [
|
||||
{
|
||||
"id": "MODULE-COVER-001",
|
||||
"name": "图片模块 · 语言驱动封面生成器",
|
||||
"name": "小红书封面生成器",
|
||||
"status": "active",
|
||||
"category": "图片生成",
|
||||
"description": "输入自然语言描述,系统翻译为设计逻辑,生成封面图片。纯HTML/CSS渲染,无需GPU。",
|
||||
"description": "基于 Puppeteer+Chrome Headless 的小红书封面图生成模块。纯 HTML/CSS 排版渲染,零 GPU,零 AI 生图。",
|
||||
"source": "image-generator/xiaohongshu-cover/",
|
||||
"api": {
|
||||
"base": "https://guanghulab.com/cover",
|
||||
"endpoints": {
|
||||
"list_templates": {
|
||||
"method": "GET",
|
||||
"path": "/api/templates",
|
||||
"desc": "获取可用模板列表"
|
||||
},
|
||||
"list_templates": { "method": "GET", "path": "/api/templates" },
|
||||
"generate": {
|
||||
"method": "POST",
|
||||
"path": "/api/generate",
|
||||
"desc": "生成封面图片",
|
||||
"params": {
|
||||
"templateId": "xiaohongshu",
|
||||
"presetId": "tech | warm | minimal | rose | green",
|
||||
"presetId": "warm | tech | minimal | rose | green",
|
||||
"title": "封面标题",
|
||||
"body": "正文内容(支持Markdown)",
|
||||
"layout": "default | hero | quote",
|
||||
"tag": "可选标签"
|
||||
},
|
||||
"returns": {
|
||||
"url": "图片URL",
|
||||
"intent_chain": "7步意图推理链",
|
||||
"designId": "唯一设计编号"
|
||||
"subtitle": "副标题(竖线分隔)",
|
||||
"tag": "技术栈标签(逗号分隔)"
|
||||
}
|
||||
},
|
||||
"health": {
|
||||
"method": "GET",
|
||||
"path": "/api/health",
|
||||
"desc": "健康检查"
|
||||
}
|
||||
"health": { "method": "GET", "path": "/api/health" }
|
||||
}
|
||||
},
|
||||
"features": [
|
||||
"语言驱动排版设计",
|
||||
"意图链可追溯",
|
||||
"设计编号唯一",
|
||||
"5种预设风格",
|
||||
"3种布局模式",
|
||||
"底部仓库域名水印"
|
||||
],
|
||||
"tech_stack": [
|
||||
"Puppeteer + Chrome (免费)",
|
||||
"Express.js (免费)",
|
||||
"纯HTML/CSS排版 (免费)"
|
||||
"5种配色风格",
|
||||
"卡通SVG插画",
|
||||
"1080x1440 3:4竖版"
|
||||
],
|
||||
"tech_stack": ["Puppeteer", "Chrome Headless", "Express.js", "Node.js"],
|
||||
"fonts": "Noto Sans CJK SC + WenQuanYi Micro Hei",
|
||||
"deploy_note": "PORT=3913 显式注入,默认3912可能冲突",
|
||||
"license": "MIT"
|
||||
}
|
||||
],
|
||||
|
||||
"meta": {
|
||||
"total_modules": 1,
|
||||
"last_updated": "2026-05-27",
|
||||
"maintainer": "铸渊 ICE-GL-ZY001"
|
||||
"last_updated": "2026-06-01",
|
||||
"maintainer": "铸渊 ICE-GL-ZY001",
|
||||
"copyright": "国作登字-2026-A-00037559"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user