将一个人的朋友圈内容,转化为一份有温度、有设计感、让人心颤的个人回忆录。
> 这不是日报,不是流水账。是一本翻开就舍不得合上的时光簿。
当用户提到以下内容时触发:
与 moments-digest(朋友圈日报)的区别:
分析完所有朋友圈内容后,提炼出 3-5 个主题维度(如山河、柔软、热爱、有趣等),将内容按主题归类后以散文笔法叙述。
主题不固定、不死套模板。每个人的朋友圈不同,主题划分要因人而异——从素材中自然生长,体现独立思考。
情绪价值是这份回忆录的灵魂。 读者(通常是朋友圈的主人本人)看完应该有"回忆满满、感动常在"的感觉——这是最核心的衡量标准。
配色方案固定为微信风格,因为最终产出物是在微信朋友圈发表的长图,微信风格配色最为贴合:
#07C160,封面用绿色渐变背景 + 白色文字#FFFFFF + 深灰文字 #191919 / #3b3b3b,浅灰分割线#F7F7F7 + 浅边框,圆角卡片#576B95(可选)此配色为铁律,不可更改。 排版、字体、动画仍可根据内容灵活调整,但色调体系必须保持微信风格。
内容生成必须贴合中国文化语境,具体要求:
此为铁律,不可违反。 回忆录的受众是朋友圈好友,中国文化语境下的表达最能引起共鸣。
所有输出的署名统一为:loap's claw
封面
└─ 朋友圈 Logo(微信朋友圈相机图标 SVG)
└─ 昵称
└─ 标题(如"朋友圈回忆录")
└─ 副标题(一句抓人的引言)
└─ 时间跨度
└─ 数据概览(紧凑横条:N条朋友圈 / N座城市 / N个年头等)
分割线
主题叙事章节 × 3~5 个(壹/贰/叁/肆/伍)
└─ 每章结构:
├─ chapter-label(中文数字 + 篇名)
├─ 标题(提炼主题精髓的短句)
├─ 副标题(辅助解读的一句话)
├─ prose 叙事段(散文正文,引用朋友圈原文加高亮)
└─ pull-quote 金句(从叙事中提炼的点睛之笔)
足迹地图(默认生成)
└─ 在识别朋友圈内容时,**主动提取所有地点信息**
└─ SVG 中国地图 + 标注城市 + 脉冲打点动画
└─ 如涉及海外城市,在地图边缘标注海外方向
└─ 城市列表卡片
└─ **注意**:仅当识别到的独立城市少于 3 个时,才可跳过此板块
朋友圈精选卡片
└─ 2列网格
└─ 每张卡片:emoji + 地点/事件标识(不用日期)+ 标题 + 1-2句精华描述
└─ 关键词高亮
尾声
└─ 回顾性总结段落
└─ 呼应全文最动人的细节
└─ 最后一句留温暖余韵
└─ 署名:—— loap's claw · YYYY年M月 ——
在完成朋友圈长图后,额外生成一套适合在小红书发布的图文系列。
1080 × 1440px(3:4竖屏),截图 DPR=2,输出 2160 × 2880pxN/总页数loap's claw + 页码将回忆录内容按故事节奏拆分为 5~9 张图,遵循以下结构:
图1 — 封面页
└─ 绿色渐变背景 + 白色大标题
└─ 钩子标题(让人想看下去的一句话)
└─ 时间跨度、数据概览(N条朋友圈 / N座城市 / N年)
└─ "向右滑动 →" 提示
图2~N-1 — 主题内容页(每个回忆录章节对应一页)
└─ 每页:chapter-tag + 大标题 + 副标题
└─ 内容形式灵活:
- 时间轴卡片(适合职业/成长类主题)
- 卡片式年份流(适合家庭/成长类主题)
- SVG 足迹地图 + 城市标签(适合旅行/山河类主题)
- 语录卡片组 + 数据面板(适合兴趣/热血类主题)
- 2列 emoji 卡片网格(适合段子/有趣类主题)
└─ 底部 quote-box 金句
└─ **铁律:内容必须撑满 1440px 高度**
- 使用 `flex:1` + `justify-content:space-between` 让内容垂直均匀分布
- 如内容不够多,增加引用卡片、数据摘要面板、或从素材中补充更多细节
- 绝不允许下半页留白超过 200px
图N — 尾声页
└─ 绿色渐变背景 + 白色文字
└─ 散文式收尾(呼应开头,回望全文最动人的细节)
└─ 最后一句留温暖余韵
└─ 署名
每张内容页必须视觉上撑满 1440px,避免空白。具体手法:
.body-text { flex:1; display:flex; flex-direction:column; justify-content:space-between; }space-between 让多个卡片均匀分布为每张图生成对应的小红书发布文案:
## 📱 图N — xhs_0N.png
**标题:** 一句吸引点击的标题
**正文:**
正文内容(200字以内)
引用朋友圈原文加引号
带情绪但不煽情
结尾带emoji
#标签1 #标签2 #标签3 #标签4 #标签5
文案要求:
生成一个多页 HTML 文件(xhs_pages.html),每页用 .page 类包裹:
.page {
width: 1080px;
height: 1440px;
position: relative;
overflow: hidden;
page-break-after: always;
}
关键 CSS 规则:
1080 × 1440px,overflow:hidden 裁剪溢出display:flex; align-items:center; justify-content:centerpadding: 64px 72px 48pxid="page1" ~ id="pageN" 标识每页,便于 Puppeteer 逐页截图使用 scripts/capture_xhs_pages.js 逐页截图:
// 核心逻辑:遍历每个 .page 元素,逐个截图
for (let i = 1; i <= totalPages; i++) {
const el = await page.$(`#page${i}`);
await el.screenshot({ path: `xhs_${String(i).padStart(2, '0')}.png`, type: 'png' });
}
参考调用:
NODE_PATH=/Users/loapli/.workbuddy/binaries/node/workspace/node_modules \
/Users/loapli/.workbuddy/binaries/node/versions/22.12.0/bin/node \
~/.workbuddy/skills/moments-memoir/scripts/capture_xhs_pages.js \
--html "<xhs_pages.html路径>" \
--out-dir "<输出目录>"
检查用户上传的文件类型:
A. 视频录屏(.mp4, .mov, .avi, .mkv)
使用 moments-digest Skill 中的抽帧脚本:
# 确保依赖已安装
/usr/bin/python3 -m pip install --user opencv-python-headless imagehash pillow
# 抽帧
/usr/bin/python3 ~/.workbuddy/skills/moments-digest/scripts/process_video.py \
--video "<视频文件路径>" \
--output "<输出目录>" \
--mode "annual" \
--max-frames 80
然后用 read_file 逐张读取抽帧图片进行 AI 识别。
B. 截图(.png, .jpg, .jpeg, .webp)
直接用 read_file 读取图片进行 AI 识别。
C. 已整理的文字素材
如果用户直接提供了文字版的朋友圈内容列表,跳过图片识别步骤。
对每张图片/帧,识别并提取:
地点提取铁律:在内容识别阶段就要尽可能多地提取地点信息,因为后续需要生成足迹打点地图。即使文字中只是提到"去了XXX"、图片里出现地标建筑,也要记录为地点。
去重规则:同一作者 + 内容前30字相同 = 重复,保留更清晰的那条。
这是最关键的一步。
按照输出结构 A撰写全部文案。核心要求:
生成单页自包含 HTML 文件,要求:
默认使用"分片截图 + 无损拼接",不要优先使用超长 fullPage: true 一次性截图。
原因:当页面很长且使用高 DPR(尤其 3x)时,Chromium 可能出现循环拼接渲染,导致内容重复或缺失。
在截图前必须执行以下处理:
.reveal 区块(补齐滚动触发内容)scroll-hintmin-height: 100vh 和 ::before 超大伪元素影响viewport width: 1080deviceScaleFactor: 2(默认)1200~1400 CSS pxtile_000.png, tile_001.png ...const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setViewport({ width: 1080, height: 1200, deviceScaleFactor: 2 });
await page.goto('file://' + htmlPath, { waitUntil: 'networkidle0', timeout: 60000 });
await page.evaluate(() => document.fonts.ready);
await page.evaluate(() => {
document.querySelectorAll('.reveal').forEach((el) => el.classList.add('visible'));
const hint = document.querySelector('.scroll-hint');
if (hint) hint.remove();
});
await page.addStyleTag({
content: `
*, *::before, *::after { animation: none !important; transition: none !important; }
.reveal { opacity: 1 !important; transform: none !important; }
.cover { min-height: auto !important; height: auto !important; overflow: hidden !important; }
.cover::before { display: none !important; }
`,
});
const totalHeight = await page.evaluate(() => document.body.scrollHeight);
const tileHeight = 1400;
for (let y = 0, i = 0; y < totalHeight; y += tileHeight, i += 1) {
const h = Math.min(tileHeight, totalHeight - y);
await page.screenshot({
path: `${outputDir}/tile_${String(i).padStart(3, '0')}.png`,
type: 'png',
captureBeyondViewport: true,
clip: { x: 0, y, width: 1080, height: h },
});
}
await browser.close();
from PIL import Image
import glob
tiles = sorted(glob.glob(f"{tile_dir}/tile_*.png"))
imgs = [Image.open(p) for p in tiles]
canvas = Image.new("RGB", (imgs[0].width, sum(i.height for i in imgs)), "white")
y = 0
for im in imgs:
canvas.paste(im, (0, y))
y += im.height
canvas.save(output_png, "PNG")
DPR=3 + fullPage: true优先复用以下脚本,而不是每次临时重写:
scripts/capture_tiled_fullpage.js:执行分片截图(朋友圈长图)scripts/stitch_tiles.py:执行无损拼接(朋友圈长图)scripts/capture_xhs_pages.js:执行小红书逐页截图参考调用:
# 1) 朋友圈长图 - 分片截图
NODE_PATH=/Users/loapli/.workbuddy/binaries/node/workspace/node_modules \
/Users/loapli/.workbuddy/binaries/node/versions/22.12.0/bin/node \
~/.workbuddy/skills/moments-memoir/scripts/capture_tiled_fullpage.js \
--url "http://localhost:8877/memoir.html" \
--out-dir "/tmp/memoir-tiles" \
--width 1080 \
--tile-height 1400 \
--dpr 2
# 2) 朋友圈长图 - 无损拼接
/usr/bin/python3 ~/.workbuddy/skills/moments-memoir/scripts/stitch_tiles.py \
--tiles-dir "/tmp/memoir-tiles" \
--output "/tmp/memoir.png"
# 3) 小红书系列图 - 逐页截图
NODE_PATH=/Users/loapli/.workbuddy/binaries/node/workspace/node_modules \
/Users/loapli/.workbuddy/binaries/node/versions/22.12.0/bin/node \
~/.workbuddy/skills/moments-memoir/scripts/capture_xhs_pages.js \
--html "<xhs_pages.html文件路径>" \
--out-dir "<输出目录>"
确保 Puppeteer 已安装:
cd /Users/loapli/.workbuddy/binaries/node/workspace && /Users/loapli/.workbuddy/binaries/node/versions/22.12.0/bin/npm install puppeteer
在完成朋友圈长图后,按照输出结构 B 生成小红书系列图文:
xhs_pages.html,每页 1080×1440px,内容撑满capture_xhs_pages.js 逐页截图文案.md以下交付物缺一不可:
| 交付物 | 格式 | 用途 |
|---|---|---|
| ------- | ------ | ------ |
| 朋友圈回忆录 HTML | .html | 在线预览和二次编辑 |
| 朋友圈长图 | .png | 直接发微信朋友圈 |
| 小红书系列图 | xhs_01.png ~ xhs_0N.png | 直接发小红书 |
| 小红书文案 | 文案.md | 每张图对应的发布文案 |
交付流程:
preview_url 预览朋友圈 HTML 文件deliver_attachments 交付全部文件壹 · 山河篇 — 走过的山河都变成了身上的光
贰 · 柔软篇 — 写代码的手,也会给儿子叠千纸鹤
叁 · 热爱篇 — 心里那团火,从未熄灭
肆 · 有趣篇 — 正经了一天,朋友圈里放飞自我
壹 · 味蕾篇 — 每一口都是认真的
贰 · 光影篇 — 镜头里的世界比现实更温柔
叁 · 漫步篇 — 不赶路的时候,路自己会开花
肆 · 深夜篇 — 凌晨的感悟最诚实
壹 · 战场篇 — 白天披铠甲
贰 · 港湾篇 — 晚上卸盔甲
叁 · 信念篇 — 做难而正确的事
肆 · 偷闲篇 — 忙里偷来的半日闲
注意:以上仅为示例,实际主题必须从素材分析中产生,不可直接套用。
DPR=3 + fullPage 一次性截图共 1 个版本