Compare commits
11
Commits
f92c4e5e9d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80f95f9ce5 | ||
|
|
846715d173 | ||
|
|
81a4173155 | ||
|
|
79c1968950 | ||
|
|
3895c9eb1d | ||
|
|
86f4d6e744 | ||
|
|
8536f5ebce | ||
|
|
5d5e485e38 | ||
|
|
2da85439e8 | ||
|
|
68c9ed7bcb | ||
|
|
286e28b7f3 |
@@ -1 +1,2 @@
|
||||
*.zip
|
||||
.DS_Store
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# MAOMOMO 文章生成器
|
||||
|
||||
这是一个面向 Codex 的中文写作技能,用来生成 MAOMOMO / 猫MOMO 风格的出海金融实操文章、文章大纲、发布用 Markdown 和配图提示词。
|
||||
这是一个面向 Codex 的中文写作技能,用来生成 MAOMOMO / 猫MOMO 风格的出海金融实操文章、文章大纲、发布用 Markdown、SEO 优化稿、独立配图 assets、ZIP 发布包,以及小红书卡片 / 多图 / 3:4 / 9:16 竖版 PNG 交付。
|
||||
|
||||
新版流程参考了 `codex-ppt-skill` 的阶段化设计:先确认来源和文章口径,再确认大纲 / 卡片结构与配图规划,最后写作或整理小红书文案、生成配图、QA、写入 `final_manifest.json`,并按渠道打包或 GitHub 分批交付。这样可以降低金融活动文章里最常见的翻车点:旧规则混入新稿、来源没读全、图片残留旧口径、只给正文不交付文件。
|
||||
|
||||
## 适用场景
|
||||
|
||||
@@ -11,7 +13,7 @@
|
||||
- 开户教程、保号教程、转账 / 入金 / 出金教程
|
||||
- 美股券商、数字货币、欧元通道、汇款路径
|
||||
- 避坑经验、实测记录、活动规则整理
|
||||
- MAOMOMO 风格封面图、截图槽位和信息图提示词
|
||||
- MAOMOMO 风格封面图、截图槽位、信息图、小红书卡片和配图平铺水印
|
||||
|
||||
不适合直接生成投资建议、法律意见、税务结论或任何“保证获批 / 稳赚 / 一定到账”的承诺型内容。
|
||||
|
||||
@@ -23,8 +25,17 @@ maomomo-article-writer/
|
||||
├── README.md # 面向使用者的说明文档
|
||||
├── agents/
|
||||
│ └── openai.yaml # 技能展示名称、简介和默认提示语
|
||||
├── scripts/
|
||||
│ └── maomomo_image_gen.py # OpenAI 兼容 API/CLI 配图 fallback
|
||||
└── references/
|
||||
└── style-guide.md # MAOMOMO 写作风格、模板和图片规则
|
||||
├── source-and-fact-check.md # 来源读取、事实核查和风险口径
|
||||
├── style-guide.md # MAOMOMO 写作风格、模板和文章骨架
|
||||
├── visual-assets.md # 配图清单、视觉规则和图片 QA
|
||||
├── recommended-styles.md # 推荐配图风格和提示词片段
|
||||
├── image-styles/ # 参考 codex-ppt 风格库拆分的出图风格 brief
|
||||
├── image-generation.md # 图片生成脚本、manifest、final_manifest 和后端选择
|
||||
├── github-delivery.md # GitHub 交付仓库、专用 KEY 和推送门禁
|
||||
└── delivery-rules.md # 完整交付、GitHub 分批、打包和最终检查
|
||||
```
|
||||
|
||||
## 安装方式
|
||||
@@ -59,6 +70,10 @@ maomomo-article-writer
|
||||
把这个银行活动整理成猫MOMO风格文章,重点写怎么吃满和常见翻车点。
|
||||
```
|
||||
|
||||
```text
|
||||
把这个活动做成小红书 3:4 多图卡片,先给我确认 2D / 3D 视觉方向。
|
||||
```
|
||||
|
||||
## 推荐提示词
|
||||
|
||||
生成完整文章:
|
||||
@@ -81,6 +96,18 @@ maomomo-article-writer
|
||||
使用 $maomomo-article-writer,为【主题】生成 MAOMOMO 风格封面图提示词、正文截图槽位和一张信息图提示词。不要写完整正文。
|
||||
```
|
||||
|
||||
小红书卡片:
|
||||
|
||||
```text
|
||||
使用 $maomomo-article-writer,做一组小红书 9:16 多图卡片。默认 2D 扁平风,要求每张独立 PNG,最后走 GitHub 分批交付。
|
||||
```
|
||||
|
||||
GitHub 交付配置:
|
||||
|
||||
```text
|
||||
使用 $maomomo-article-writer,先引导我配置 GitHub 交付仓库和专用 KEY,之后小红书卡片都走 GitHub 分批交付。
|
||||
```
|
||||
|
||||
改写已有文章:
|
||||
|
||||
```text
|
||||
@@ -103,9 +130,43 @@ maomomo-article-writer
|
||||
- 最后提醒和免责声明
|
||||
- 来源链接
|
||||
- 正文截图槽位和必要的信息图提示词
|
||||
- `article.md`
|
||||
- `assets/` 独立 PNG 配图目录
|
||||
- `article_package.zip`
|
||||
|
||||
小红书卡片通常包含:
|
||||
|
||||
- 20 字内标题
|
||||
- 小红书正文和 3-6 个话题标签
|
||||
- 多张 3:4 或 9:16 独立 PNG
|
||||
- `prompts/`、`slide_jobs.json`、`slide_run_state.json`
|
||||
- `final_manifest.json`
|
||||
- 可选 `watermarked_assets/`:用 `uv run python scripts/maomomo_tile_watermark.py` 生成 `MAOMOMO.COM` 45° 自动对比色半透明平铺水印版
|
||||
- GitHub 分批链接
|
||||
|
||||
GitHub 分批交付前,技能会先引导配置:
|
||||
|
||||
- 专用私有交付仓库,例如 `OWNER/maomomo-delivery`
|
||||
- `MAOMOMO_GITHUB_REPO`、`MAOMOMO_GITHUB_BRANCH`、`MAOMOMO_GITHUB_BASE_PATH`
|
||||
- GitHub CLI 登录、fine-grained PAT,或 SSH deploy key
|
||||
- 最小权限原则:只给交付仓库写入权限
|
||||
- 密钥不进入聊天、prompt、manifest、状态文件、README、日志或 commit
|
||||
|
||||
如果事实不完整,技能会使用 `【待确认:...】` 标记缺口,而不是编造数字、日期、奖励或链接。
|
||||
|
||||
## 默认流程
|
||||
|
||||
完整交付任务通常按以下步骤执行:
|
||||
|
||||
1. 识别任务模式:完整文章、大纲、SEO 优化、改稿、配图或小红书卡片。
|
||||
2. 读取来源并列出已读 / 未读材料,整理事实表和待确认项。
|
||||
3. 确认文章范围、大纲 / 卡片结构、标题方向、核心口径和图片规划。
|
||||
4. 写作或优化发布用 Markdown。
|
||||
5. 确认配图风格;小红书卡片需先确认 2D / 3D 和 3:4 / 9:16,再生成或准备独立 PNG。
|
||||
6. 检查来源、图片路径、风险提示、编辑痕迹、ZIP 或 `final_manifest.json`;需要 GitHub 交付时先确认仓库和专用 KEY 配置,最后交付文件路径或 GitHub 分批链接。
|
||||
|
||||
用户明确说“只要文字”“只要大纲”“不要图片”“不要打包”时,会按用户限制执行,不强行创建完整发布包。
|
||||
|
||||
## 资料准备建议
|
||||
|
||||
为了得到更可靠的文章,最好提供:
|
||||
@@ -126,6 +187,8 @@ maomomo-article-writer
|
||||
- 语气像懂行朋友,不像官方宣传稿
|
||||
- 涉及金融、券商、银行、数字货币和合规内容时,保留谨慎措辞
|
||||
- 配图统一使用白橘猫、暖橙色、干净金融科技教程风
|
||||
- 如果用户没有指定配图风格,先在 `recommended-styles.md` 和 `image-styles/` 中按文章类型推荐 2-3 个方向,例如 `clean-professional`、`data-card-dashboard`、`handdrawn-note`、`e-ink-editorial` 或 `xiaohongshu-vertical`。
|
||||
- 小红书卡片出图前必须确认 `2D 扁平风` / `3D 轻拟物风` 和 `3:4` / `9:16`;用户没指定时默认推荐 2D,并按原生竖版重新构图,不能把横版图压进竖图。用户要求防盗图 / 水印时,可用 `uv run python scripts/maomomo_tile_watermark.py --input assets --output watermarked_assets` 生成 `MAOMOMO.COM` 45° 自动对比色半透明平铺水印版,并保留无水印原图。
|
||||
|
||||
文章不应当:
|
||||
|
||||
@@ -137,6 +200,13 @@ maomomo-article-writer
|
||||
## 维护说明
|
||||
|
||||
- 主要触发说明和执行规则写在 `SKILL.md`。
|
||||
- 详细文章模板、标题套路、常用词和图片规则写在 `references/style-guide.md`。
|
||||
- 详细文章模板、标题套路和常用词写在 `references/style-guide.md`。
|
||||
- 来源核查、时效性、冲突口径和风险表达写在 `references/source-and-fact-check.md`。
|
||||
- 配图类型、猫咪视觉、文件命名和图片 QA 写在 `references/visual-assets.md`。
|
||||
- 推荐配图风格、适用场景和提示词片段写在 `references/recommended-styles.md`。
|
||||
- 具体出图风格 brief 放在 `references/image-styles/`,结构参考 `codex-ppt-skill` 的风格库。
|
||||
- 图片 API/CLI fallback、批量 manifest、final manifest、GitHub 批次记录和脚本用法写在 `references/image-generation.md`,脚本为 `scripts/maomomo_image_gen.py` 和 `scripts/maomomo_job_state.py`。
|
||||
- GitHub 交付仓库、专用 KEY / token / deploy key、环境变量、隐私边界和推送前检查写在 `references/github-delivery.md`。
|
||||
- 完整交付、ZIP、GitHub 分批、附件和最终检查写在 `references/delivery-rules.md`。
|
||||
- 修改技能展示名称或默认提示语时,同步更新 `agents/openai.yaml`。
|
||||
- 如果新增具体行业资料、平台规则或长期复用模板,优先放入 `references/`,避免让 `SKILL.md` 过长。
|
||||
|
||||
@@ -1,275 +1,254 @@
|
||||
---
|
||||
name: maomomo-article-writer
|
||||
description: 生成符合 maomomo / 猫momo 风格的中文实操文章、发布用 markdown、文章大纲、seo 优化稿、独立高清配图和 zip 打包交付。适用于 maomomo.com、出海金融、港卡、香港银行、香港信用卡、美股券商、数字货币、汇款、支付优化、羊毛活动、开户教程、保号教程、避坑经验等主题。用户要求使用 skill 生成文章、配图、发布包、打包、一键下载、完整交付,或说“配图也按 skill”时,默认必须输出 markdown 文件、assets 独立配图目录和 zip 包,而不是只输出正文、片段或提示词。
|
||||
description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融实操文章、发布用 Markdown、SEO 稿、配图规划、独立高清配图 assets、ZIP 发布包,以及小红书卡片 / 多图 / 3:4 / 9:16 竖版 PNG 交付、配图平铺水印;包含 GitHub 交付仓库和专用 KEY / token / deploy key 配置引导。适用于 maomomo.com、港卡、香港银行、香港信用卡、美股券商、数字货币、汇款、支付优化、返现活动、开户教程、保号教程、避坑经验、官方条款整理和实测攻略。用户要求使用 skill、按猫MOMO风格、完整文章、发布用 Markdown、配图、打包、一键下载、最终交付、小红书、卡片、多图、3:4、9:16、水印、平铺水印、防盗图、GitHub 交付、仓库配置或专用 KEY 配置时触发。
|
||||
---
|
||||
|
||||
# MAOMOMO 文章生成器
|
||||
|
||||
使用本技能生成 MAOMOMO / 猫MOMO 风格的中文实操文章、文章大纲、发布用 Markdown、SEO 优化稿、配图提示词、独立配图和打包交付文件。
|
||||
本技能用于把主题、资料、链接、PDF、截图或已有 Markdown 转成 MAOMOMO / 猫MOMO 风格的中文实操文章,并在需要时交付可发布文件包。
|
||||
|
||||
输出要像一篇一手整理的出海金融攻略:结论靠前、步骤清楚、坑点明确、语气轻松但不轻浮,并且对金融、活动、合规、费用、奖励等时效性内容保持谨慎。
|
||||
文章要像一手整理的出海金融攻略:结论靠前、步骤清楚、坑点明确、语气轻松但不轻浮。涉及金融、活动、合规、费用、奖励、账户规则和数字货币时,必须谨慎核查并避免承诺式表达。
|
||||
|
||||
## 工作流程
|
||||
## 使用边界
|
||||
|
||||
1. 先判断文章类型:活动 / 羊毛攻略、开户教程、转账 / 出入金教程、保号 / 账户维护、用卡姿势、对比总结、避坑经验。
|
||||
2. 遇到费用、活动时间、奖励门槛、账户规则、信用卡返现、券商迎新、转账路径、数字货币出入金、合规要求等内容,先查证最新信息。优先使用官方条款、官方 App 显示、用户提供材料和 MAOMOMO 现有文章。
|
||||
3. 默认使用中文写作。段落要短,数字要具体,App 菜单名 / 操作名要尽量写清楚,多用“怎么做”“哪里会翻车”“怎么吃满”的表达。
|
||||
4. 每篇完整文章都要包含猫咪主题视觉规划和实际交付文件:封面图、正文截图槽位、必要时的流程图 / 表格图 / 路线图,以及 `article.md`、`assets/` 和 ZIP 包。
|
||||
5. 涉及金融产品、银行、券商、数字货币、汇款、奖励活动或合规事项时,要写明来源链接和有效期,并加入保守的风险提示。
|
||||
6. 用户上传或提供现有 Markdown 时,先完整阅读原文,再判断主题、核心概念、文章结构和需要配图的位置;不要沿用旧文章的银行名、邀请码、日期、收益率、奖励金额、产品名、图名或图片内容。
|
||||
7. 交付前执行质量检查,避免写成官方宣传稿、SEO 空文或未经确认的承诺式内容。
|
||||
适合:
|
||||
|
||||
生成完整文章、配图或打包交付时,必须先读取 `references/delivery-rules.md` 并按其中规则执行;同时读取 `references/style-guide.md` 获取更细的文章模板、标题套路、常用词、图片规则和不同类型文章骨架。
|
||||
- 银行、港卡、香港信用卡、券商、数字货币、汇款、支付、账户维护、返现和羊毛活动。
|
||||
- 活动规则整理、开户教程、转账 / 入金 / 出金教程、保号方案、用卡姿势、对比总结、避坑经验。
|
||||
- 已有文章 SEO 优化、发布用 Markdown、配图规划、独立配图、ZIP 打包和后续改稿。
|
||||
- 小红书卡片、多图信息卡、3:4 或 9:16 竖版 PNG 系列图、封面 / 内容卡 / 结尾卡分批交付。
|
||||
- 对最终配图批量或单张叠加 `MAOMOMO.COM` 45° 自动对比色半透明平铺水印。
|
||||
|
||||
不适合:
|
||||
|
||||
- 给出投资、法律、税务或合规结论。
|
||||
- 承诺一定获批、一定到账、稳赚、无风险、绝对安全。
|
||||
- 伪造真实 App 截图、账单、错误或未经核对的官方 Logo 使用场景,或用户没有提供的实测证据。
|
||||
|
||||
## 硬性约束
|
||||
|
||||
- 如果本技能任何旧规则与“确认优先、单步推进”的流程冲突,以本节和“默认工作流”中的阶段门禁为准。
|
||||
- 确认优先,单步推进;不要一上来就完整生成文章、全部配图或 ZIP。
|
||||
- 用户提到“小红书”“卡片”“多图”“3:4”“9:16”任一触发词时,默认进入“小红书卡片模式”:不默认走完整文章 + Markdown + ZIP 流程,输出重点改为多张独立 PNG、统一视觉风格、适合小红书发布。只有用户明确要求文章、Markdown 或 ZIP 时,才叠加对应交付物。
|
||||
- 读写 Markdown、JSON、CSV、HTML、代码或文本文件时显式使用 UTF-8。
|
||||
- 处理用户上传或提供的 Markdown / PDF / 官方链接 / 参考文章时,必须先完整读取或说明无法读取的部分,再写最终稿。
|
||||
- 费用、活动日期、奖励门槛、账户规则、返现、券商迎新、转账路径、数字货币出入金和合规事项,必须优先核查最新信息。优先级:用户提供的最新官方材料 > 官方页面 / 条款 PDF / App 显示 > 用户实测 > MAOMOMO 旧文 > 其他参考。
|
||||
- 没有被确认的数字、日期、奖励、链接、邀请码、产品名和规则,不得编造;可用 `【待确认:...】` 标出缺口。
|
||||
- 用户素材包含明确“实测”结果时,必须用确定语气写结论;包含“以往惯例”“过往尿性”“过往经验”等历史依据时,必须用“大概率 / 按过往案例判断”的强概率语气,不要降级成“可能 / 疑似 / 大概”。
|
||||
- 写作前必须先给用户确认标题或标题方向、文章核心口径、`outline.md` 大纲、来源缺口、必选素材映射和配图风格。用户明确要求跳过确认时,未确认事实仍必须标 `【待确认:...】`。
|
||||
- 大纲未确认前,不得创建最终 `deck_spec.json`、`speech.md`、图片 prompt jobs、文章配图图片、ZIP 或用户指定的最终文章包。
|
||||
- 出图前必须先把全部图片 prompt 写入文件,包含文件名、用途、放置位置、核心文案、完整 prompt 和依赖素材;不要在聊天里输出超长 prompt。
|
||||
- 小红书卡片模式出图前必须先让用户确认视觉方向:`2D 扁平风` 或 `3D 轻拟物风`。用户没指定时,默认推荐 `2D 扁平风`,因为小红书信息图更稳、更不容易文字翻车;一旦确认,整组图片必须保持同一种视觉方向,不能中途混用。
|
||||
- 小红书竖图必须按原生竖版重新构图,禁止把横版设计硬塞进 3:4 或 9:16。每个小红书图片 prompt 必须包含英文约束:`native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。
|
||||
- 图片 prompt 中的视觉风格只能作为后台画面描述,不得成为画面文字;prompt 必须限制可见文字只包含指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识,不得出现风格标签、prompt 描述、额外 slogan 或无关文字。
|
||||
- 发布稿、图片文案、prompt、manifest 和 `deck_spec.json` 中金额统一写 `HKD`,不要使用港币符号;来源原文抽取文件可保留原文写法。
|
||||
- 金融活动配图如出现信用卡、支付卡、卡组织、支付网络或机构 Logo,必须按官方材料或用户素材核对并记录来源;用户没有提供 Logo / 卡面素材但画面需要品牌或支付标识时,应主动尽力从官方活动页、品牌资源页、条款 PDF、App / 官网截图等来源查找准确标识,保存到 `sources/` 并记录来源。不得把银联 / UnionPay 画成 Mastercard、Visa 等其他卡组织。已核对正确的 Mastercard / Visa / UnionPay / 银行 / 支付宝等标识可以辅助出现,但不能抢占或替代 `MAOMOMO` 主标识位置。
|
||||
- 先只生成一张代表性风格确认图,优先封面图,保存为正式命名,例如 `origin_image/hsbc-mastercard-alipay-campaign-01-cover.png`。用户批准样张前不得批量生成剩余图片。
|
||||
- 样张批准后,后续每张图片必须继承同一个生图后端、同一套视觉系统和同一种质量配置;不得为了加快速度擅自改成 `low`。
|
||||
- 每张最终图片必须来自确认后的图片生成后端:当前 agent 的内置图片生成工具,或用户确认后的 `scripts/maomomo_image_gen.py` API/CLI fallback。不得用 Playwright、HTML/CSS、SVG、Pillow、canvas、本地拼贴或手工文字覆盖冒充最终生成图。例外:用户提供真实银行 / 券商 / 钱包截图且需要上传图片 API 做编辑时,必须先向用户确认实际 API 地址 / baseURL、接口归属和是否允许外发;该 API 可能是用户自有兼容服务,不能默认按公共第三方处理。若未确认、确认不允许或安全策略不允许上传,则先用图片后端生成不含真实截图的空白模板,再在本地把原截图完整嵌入模板;本地步骤只能做排版嵌入、圆角、阴影和尺寸适配,不得改动截图内金额、日期、姓名、账号、交易描述等关键内容,并必须在状态文件和最终回复说明这是“图片后端模板 + 本地嵌入真实截图”的混合流程。
|
||||
- 生成图片按“一张图一个 job / 一次请求 / 一次 QA”推进;通过 QA 后才用状态脚本记录 result 并同步到 `assets/`,不能只生成到 `origin_image/` 后声称完成。运行环境支持子 agent 时,样张通过后可以一图一个子 agent 并发处理;每个 dispatch、结果和 blocker 都必须写入状态文件,不能只靠聊天说明。
|
||||
- 完整交付模式下,不能只在聊天里输出正文;必须生成 Markdown 文件、`assets/` 目录和 ZIP 包,除非用户明确说只要文字、只要大纲、不要图片、不要打包或先不生成文件。
|
||||
- 图片交付完成后必须生成或更新 `final_manifest.json`,最终推送、打包或链接交付只能读取 `final_manifest.json` 或状态文件里明确标记为 final 的 assets;不得把 `draft`、`v2`、旧比例、失败图或未通过 QA 的图一起交付。
|
||||
- 面向 Telegram / 聊天渠道的大文件交付默认改为 GitHub 分批推送:例如 `batch-01-cover`、`batch-02-content-cards`、`batch-03-final-assets`。Telegram 只负责通知进度和给 GitHub 文件路径、commit、release 或下载链接,不承载大 ZIP 或大图片包。
|
||||
- 需要 GitHub 分批交付,或用户提到 GitHub 仓库、专用 KEY、token、PAT、deploy key 时,必须先读 `references/github-delivery.md`,引导配置专用私有交付仓库和最小权限凭据;不得要求用户在聊天里粘贴密钥,不得把 token / 私钥写入 prompt、manifest、状态文件、README、日志或提交记录。
|
||||
- 用户要求“水印”“平铺水印”“防盗图”“打水印”时,先读 `references/visual-assets.md` 的平铺水印规则;水印只处理最终 QA 通过的交付图或交付副本,默认保留无水印原图。水印必须用 `uv run python scripts/maomomo_tile_watermark.py` 生成,不得手工覆盖。默认自动对比色:浅底黑字、深底白字;默认 alpha=70、font-size=50、spacing=28;每张图至少可见 2 个完整 `MAOMOMO.COM`。
|
||||
- 每张图都必须在状态文件记录:prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub。中断恢复时先读状态文件和 `final_manifest.json`,不要依赖聊天记忆。
|
||||
- 配图文件必须是独立 PNG;不要用一张大拼图代替多张图,不要只给提示词冒充图片文件。
|
||||
- 后续改稿必须更新文件本体;影响图片、来源区、标题、摘要或 ZIP 时同步更新。
|
||||
|
||||
## 可见进度
|
||||
|
||||
复杂任务使用一个用户可见 checklist,并且同一时间只推进一个步骤:
|
||||
|
||||
1. 读取资料,整理来源状态和事实表。
|
||||
2. 确认标题、核心口径、`outline.md` 大纲、素材映射、配图风格和生图后端。
|
||||
3. 写作或优化发布用 Markdown。
|
||||
4. 输出全部图片 prompt 文件和图片任务状态文件。
|
||||
5. 生成并确认一张样张。
|
||||
6. 逐张生成剩余图片并记录 dispatch / result / blocker。
|
||||
7. QA、返修、生成 `speech.md`、小红书发布文案或备注稿。
|
||||
8. 写入 `final_manifest.json`,按渠道打包或 GitHub 分批推送最终文件。
|
||||
|
||||
不要因为聊天里说“已完成”就标记完成;用实际文件、已读取来源、已生成图片、`final_manifest.json`、ZIP 或 GitHub 批次记录作为完成证据。
|
||||
|
||||
## 默认工作流
|
||||
|
||||
一句话版:判断模式 -> 读资料 -> 给标题 / 大纲 / 卡片结构 / 风格让用户确认 -> 写文章或小红书文案 -> 输出图片 prompts 文件让用户改 -> 先出一张图确认 -> 逐张生成剩余图 -> QA -> 写 `final_manifest.json` -> 打包或 GitHub 分批交付。
|
||||
|
||||
1. 判断任务模式。
|
||||
- 完整交付、只要大纲、只要正文、只要配图、SEO 优化、改稿更新、小红书卡片模式。
|
||||
- 判断是否涉及金融 / 活动 / 合规事实核查。
|
||||
- 完整交付、打包、GitHub 分批推送或小红书卡片任务先读 `references/delivery-rules.md`;需要配图时先读 `references/visual-assets.md`;需要 GitHub 推送或仓库 / KEY 配置时先读 `references/github-delivery.md`。
|
||||
|
||||
2. 读取源材料并建立事实表。
|
||||
- 先读用户提供的官方页面、PDF、旧文、参考链接、截图或素材,不先动手写最终稿。
|
||||
- 涉及官方规则、活动或金融产品时,先读 `references/source-and-fact-check.md`。
|
||||
- 列出所有来源的已读 / 未读 / 无法读取状态。
|
||||
- 明确区分:已官宣事实、用户实测、过往惯例判断、待确认内容。
|
||||
- 确认主题、目标读者、文章目标、章节数量或图片数量、品牌 / 风格限制,以及必须放入文章的图片素材。
|
||||
|
||||
3. 规划并确认 `outline.md`。
|
||||
- 写最终稿前先给出推荐标题或使用用户指定标题、文章核心口径、章节大纲、来源缺口、图片规划和必选素材映射。
|
||||
- `outline.md` 应包含每个大小标题、3-5 个要点、页面 / 图位角色、视觉想法、必选素材路径或附件名。
|
||||
- 有必选素材时,必须先确认“素材到章节 / 图位”的映射,再进入风格选择或图片生成。
|
||||
- 若存在多个活动分支、旧版活动、小众资格或来源冲突,先建议主文范围,等用户确认后再写最终稿。
|
||||
- 用户明确要求“直接生成”时,可以继续执行,但所有未确认事实必须标 `【待确认:...】`。
|
||||
- 小红书卡片模式的 `outline.md` 可以简化为卡片结构:图片数量、比例、每张卡片角色、核心文案、话题标签、必选素材映射和分批交付计划。
|
||||
|
||||
4. 确认视觉风格。
|
||||
- 小红书卡片模式必须先确认比例 `3:4` 或 `9:16`,以及视觉方向 `2D 扁平风` 或 `3D 轻拟物风`。
|
||||
- 小红书卡片模式用户没指定视觉方向时,推荐 `2D 扁平风`:清爽、信息卡片、适合教程 / 规则说明,文字更稳;`3D 轻拟物风` 更有质感,适合封面 / 活动感 / 金融产品展示。
|
||||
- 小红书卡片模式一旦确认 2D / 3D,所有 prompt、样张、剩余图片和 final manifest 都必须记录同一 `visual_direction`,不得混用。
|
||||
- 若用户没有指定视觉方向,读 `references/recommended-styles.md`,给 2-3 个 MAOMOMO 配图风格选项并推荐一个。
|
||||
- 用户提供参考图、PPT、PDF 或旧文章风格时,先总结可复用的配色、版式、字体气质、猫咪元素、信息密度和禁用项。
|
||||
- 确认后,整篇文章保持同一视觉系统:稳定配色、猫咪造型、标题区、图标语言、品牌识别和信息密度;不同图位可以变化构图。
|
||||
|
||||
5. 确认生图后端。
|
||||
- 出第一张图前读 `references/image-generation.md`。
|
||||
- 优先主动检查当前 agent 是否可调用内置图片生成工具,并向用户说明检查结果和准备使用的后端。
|
||||
- 只有内置工具不可用、能力不足、用户明确要求 API/CLI,或需要使用第三方图片 API 时,才走 `scripts/maomomo_image_gen.py` fallback。
|
||||
- 相信用户环境变量或配置文件里的 baseURL、model、quality;除非报错或用户要求,不要重新索要配置。
|
||||
|
||||
6. 写作或优化文章。
|
||||
- 用户确认标题、大纲、口径和素材映射后,再读 `references/style-guide.md` 写发布用 Markdown。
|
||||
- 不要把“推测 / 大概率 / 过往惯例”写成“已官宣”。
|
||||
- 金融活动文章尤其要分清官方条款、App 显示、用户实测和经验判断。
|
||||
- 发布用 Markdown 使用 frontmatter、H1、太长不看版、正文、最后提醒和来源区。
|
||||
- SEO 优化既要改标题、摘要、slug / tags / categories、图片 alt,也要清理无效来源和发布残留。
|
||||
|
||||
7. 创建项目目录和任务文件。
|
||||
- 完整交付默认使用独立项目目录;如果用户没指定位置,使用当前工作目录或源文件所在目录。
|
||||
- 推荐结构:
|
||||
|
||||
```text
|
||||
{base_dir}/{article_slug}/
|
||||
├── origin_image/
|
||||
│ ├── {article_slug}_01_cover.png
|
||||
│ └── ...
|
||||
├── assets/
|
||||
│ ├── {article_slug}_01_cover.png
|
||||
│ └── ...
|
||||
├── sources/
|
||||
│ ├── card-reference.png
|
||||
│ └── ...
|
||||
├── prompts/
|
||||
│ ├── {article_slug}_01.json
|
||||
│ └── ...
|
||||
├── deck_spec.json
|
||||
├── slide_jobs.json
|
||||
├── slide_run_state.json
|
||||
├── final_manifest.json
|
||||
├── github_batches.json
|
||||
├── watermarked_assets/ # 可选,最终图的 MAOMOMO.COM 平铺水印版本
|
||||
├── outline.md
|
||||
├── article.md
|
||||
├── speech.md
|
||||
└── {article_slug}.zip
|
||||
```
|
||||
|
||||
- `origin_image/` 保存图片后端生成并通过 QA 的原始最终图;`assets/` 保存 Markdown 实际引用的发布图片。两者可以是同名复制件,但不得只保留临时图。
|
||||
- `sources/` 保存用户提供或主动查找的卡面、Logo、支付网络、官方截图或其他参考素材,供 prompt、manifest 和 `deck_spec.json` 映射引用。
|
||||
- `deck_spec.json` 记录文章级上下文、确认后的风格、生图后端、样张生成方法 `sample_generation_method`、参考素材映射和图片任务列表。
|
||||
- `prompts/{article_slug}_XX.json` 记录每张图的文件名、用途、放置位置、核心文案、完整 prompt、依赖素材和输出路径。
|
||||
- `slide_jobs.json` 和 `slide_run_state.json` 记录每个图片 job 的 pending / dispatched / recorded / blocked 状态;使用 `scripts/maomomo_job_state.py` 初始化和记录状态。
|
||||
- `final_manifest.json` 只记录通过 QA 且进入 `assets/` 的最终交付文件;`github_batches.json` 记录每批 GitHub 推送的 batch id、文件路径、commit、release 或下载链接。
|
||||
- 用户要求水印时,使用 `uv run python scripts/maomomo_tile_watermark.py --input assets --output watermarked_assets` 生成水印副本;水印图可进入 GitHub 批次中的 `watermarked/`,但无水印原图仍保留。
|
||||
- 小红书卡片项目没有文章正文时,可以省略 `article.md` 和 ZIP,但仍必须保留 `origin_image/`、`assets/`、`prompts/`、`slide_jobs.json`、`slide_run_state.json`、`final_manifest.json` 和 `github_batches.json`。
|
||||
|
||||
8. 出图前整理全部 prompt。
|
||||
- 每张图的文件名、用途、放置位置、核心文案、完整 prompt、alt text、风格、依赖素材和输出路径先写入 `prompts/` 或 manifest 文件。
|
||||
- prompt 中不要写“暖橙金融教程风”“数据仪表盘风”“手绘便签风”等容易被模型照抄的中文风格标签;改写为白底、暖橙强调、信息卡片、猫咪向导、清晰标题等视觉描述。
|
||||
- 每张图的 prompt 必须明确:只允许出现指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识;不得添加副标题、风格标签、prompt 描述、说明文字、额外 slogan 或无关文字。
|
||||
- 小红书 3:4 / 9:16 prompt 必须明确原生竖版重构,并包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。
|
||||
- 封面只放最高优先级结论和少量核心数字;复杂计算、全年理论上限、长公式和详细规则放到计算图或正文。
|
||||
- 不要在聊天里输出超长 prompt;告诉用户可直接编辑 prompt 文件。
|
||||
- 如果用户修改 prompt,先同步文件,再生成图片。
|
||||
|
||||
9. 先生成一张样张。
|
||||
- 优先生成封面图或最能代表文章视觉节奏的一张内容图。
|
||||
- 样张直接保存为正式文件名,例如 `origin_image/hsbc-mastercard-alipay-campaign-01-cover.png`。
|
||||
- 展示样张并等待用户确认风格、内容、中文文字质量和视觉密度。
|
||||
- 如果用户不满意,先改该图 prompt,再重出同一张;样张确认前不得生成剩余图片。
|
||||
- 样张确认后,在 `deck_spec.json` 记录 `sample_generation_method`,包括 backend、tool/command、mode、quality、model/config、prompt 文件、approved_sample_path 和 handoff_rule。
|
||||
|
||||
10. 逐张生成剩余图片。
|
||||
- 样张批准后,剩余图片必须使用同一后端、同一质量配置和同一视觉系统。
|
||||
- 每张图独立生成或准备为高清 PNG,先进入 `origin_image/`,通过 QA 后同步到 `assets/`,并用相对路径插入 Markdown。
|
||||
- 图片生成必须一张张来:一张图一个 prompt job、一次图片请求、一次 QA。不要一次请求多张图,不要为了快降低质量。
|
||||
- 如果运行环境支持子 agent,样张通过后可以一图一个子 agent 并发处理;父 agent 负责状态文件、QA、Markdown、`speech.md` 和打包。
|
||||
- 没有真实截图时只能生成“示意图 / 信息图”,不能伪造官方 App 截图。
|
||||
- 不要用 Playwright 替代图片生成;Playwright 只用于已有 HTML / SVG / 网页截图检查。
|
||||
|
||||
11. 记录状态、QA 和返修。
|
||||
- 每个图片 job 的 dispatch、结果和 blocker 都用 `scripts/maomomo_job_state.py` 写入 `slide_jobs.json` / `slide_run_state.json`,不能只靠聊天说明完成。
|
||||
- 每张图的状态必须记录 prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub;缺失记录时不得进入最终交付。
|
||||
- 每张图通过前必须检查日期、金额、公式、卡组织 / 支付网络、禁用分支、风格说明文字、伪造截图、乱码和多余文案。
|
||||
- 检查文字清晰度、中文乱码、金额 / 日期 / 规则是否残留旧口径、标题截断、风格一致性、必选素材是否正确、元素是否重叠、是否伪造截图或 Logo。
|
||||
- 小红书 3:4 / 9:16 图额外检查:比例正确、竖版原生构图、元素没有被压扁、卡片 / Logo / 文字没有拉伸、不是横图硬塞进竖版。
|
||||
- 严重问题重新生成该单张;局部问题优先使用已确认后端的编辑能力修复,不得本地手工覆盖关键文字冒充模型输出。
|
||||
|
||||
12. 生成 `speech.md`、`final_manifest.json`、组装文章包或 GitHub 批次并最终 QA。
|
||||
- `speech.md` 用作每张图的备注 / 讲解稿,标题格式使用 `## Slide N: 标题`,便于和图片顺序映射。
|
||||
- 组装最终文章包时,把 `origin_image/{article_slug}_XX.png` 按顺序同步到 Markdown 引用的 `assets/`,并确保 `article.md`、图片路径、`speech.md`、来源区和 ZIP 都是最新版本。
|
||||
- 最终交付只能读取 `final_manifest.json` 或状态文件中 `qa_status=passed`、`entered_assets=true`、`final=true` 的文件,不扫描整个目录交付。
|
||||
- 若交付水印版,在最终 QA 后用 `uv run python scripts/maomomo_tile_watermark.py` 对 final assets 生成 `watermarked_assets/`,并在 `final_manifest.json` 记录 `watermark` 字段:text、angle、color、alpha、output_dir。
|
||||
- 需要 GitHub 分批交付时,先按 `references/github-delivery.md` 确认交付仓库、认证方式、目标路径和仓库隐私级别;缺配置时先给用户本机配置步骤并暂停 push。配置就绪后按 `batch-01-cover`、`batch-02-content-cards`、`batch-03-final-assets` 等批次推送;每批推送后记录 GitHub 文件路径、commit、release 或下载链接,并写入 `github_batches.json` / 状态文件。
|
||||
- 完整交付、文件交付或改稿打包前读 `references/delivery-rules.md`。
|
||||
- QA 必须用脚本检查 Markdown 中所有图片路径真实存在,并检查 assets 是否存在、来源区只保留实际使用来源、正文无编辑痕迹、图片无错误日期 / 金额 / 未确认结论、用户要求删除内容不残留、ZIP 内容正确。
|
||||
- 小红书卡片模式最终回复固定包含:GitHub 链接、图片数量、风格:2D / 3D、比例:3:4 / 9:16、批次数量、QA 是否完成、已知限制。
|
||||
- 完整文章模式最终回复保持简短:给出项目目录、文章路径、图片目录、ZIP 或 GitHub 链接、图片数量、生图后端、状态记录情况、备注写入情况和已知限制。
|
||||
|
||||
## 模式选择
|
||||
|
||||
- “生成文章”“完整文章”“发布用 Markdown”“使用 skill”“按 skill”“文章和配图一起”“打包”“一键下载”“最终交付”:完整交付模式。
|
||||
- “只要大纲”“先列结构”:只输出标题备选、大纲、事实缺口、来源清单和图片规划。
|
||||
- “只要文字”“不要图片”“不要打包”“先不要生成文件”:按用户限制输出,不强制创建文件包。
|
||||
- “SEO 优化”“配图”“打包”且用户提供已有 Markdown:读取原文后输出优化版 Markdown、assets 和 ZIP。
|
||||
- “小红书”“卡片”“多图”“4 图看完”“发小红书”“3:4”“9:16”:小红书卡片模式;读 `references/style-guide.md` 和 `references/visual-assets.md` 的小红书规则,按 3:4 或 9:16 原生竖版规划,不默认生成完整文章、Markdown 或 ZIP。
|
||||
- 后续说“删掉 X”“合并 3、4”“不要写 Y”“改成 Z”:视为编辑指令,不要把指令原样写进正文。
|
||||
|
||||
## 改稿协作规则
|
||||
|
||||
用户后续改稿时,所有要求都先视为编辑指令,不得原样写入文章正文。
|
||||
|
||||
- 用户说“不要写 X”:删除或改写 X,不要在正文解释“不要写 X”。
|
||||
- 用户说“应当写成 Y”:把 Y 转成自然文章表达,不要写“应当写成”。
|
||||
- 用户说“这个口径 / 我的要求”:这是写作指令,不是正文素材。
|
||||
- 用户说“删掉几个字 / 某个词”:只删指定词,不扩大成删除整条、整段或整节。
|
||||
- 用户说“合并 3、4”或“6、7 合并”:只合并对应条目,保留原信息,必要时重排编号。
|
||||
- 用户反复纠正某个词,例如“更像”“可能”“最高”等,后续同文档同类位置都要检查并同步清理。
|
||||
- 用户确认的实测口径优先写成结论,不要反复声明“本文以实测为主”“官方条款仅供参考”等元叙述。
|
||||
- 改完必须检查正文是否残留编辑痕迹,例如“准确写法是”“不要写”“用户要求”“本文口径”“应当”。
|
||||
|
||||
## 实测活动文章规则
|
||||
|
||||
处理银行、支付、返现、优惠券、券商迎新等活动时,如果用户提供了明确实测经验,正文应优先呈现实操结论。
|
||||
|
||||
- 官方条款用于确认活动边界、日期、排除项和来源区。
|
||||
- 实测经验用于组织“怎么吃”“怎么判断出券”“哪里会翻车”。
|
||||
- 不要写成官方宣传稿,也不要把“官方条款仅供参考”作为正文口号反复出现。
|
||||
- 实测结论要直接写,例如“按支付宝账号算”“按卡算”“先刷后返”“不出券先看账号”。
|
||||
- 对用户明确确认的口径,不要再写“更像”“可能”“大致”等模糊词。
|
||||
- 仍要避免绝对承诺到账、稳赚、一定成功;可用“直到名额耗尽或风控拦截”等实际限制表达。
|
||||
|
||||
## 输出模式
|
||||
|
||||
根据用户要求选择输出形态:
|
||||
|
||||
- 用户说“生成文章”“写完整草稿”“发布用 Markdown”“使用 skill 生成文章”“完整文章”“打包”“一键下载”“文章和配图一起”:默认进入完整交付模式,输出 Markdown 文件、`assets/` 独立配图目录和 ZIP 包。
|
||||
- 用户说“只要大纲”“先列结构”:输出标题备选、文章结构、需要确认的关键事实、图片规划。
|
||||
- 用户说“图片风格”“配图提示词”“封面图”:只输出封面图提示词、正文截图槽位、信息图提示词和替代文本。
|
||||
- 用户上传 Markdown 并要求“SEO 优化”“配图”“独立生成图片”“打包”“一键下载”:输出 SEO 优化后的 Markdown、插入正文的多张独立配图、`assets/` 文件夹和 ZIP 包;最后只给下载链接或本地文件路径以及简短说明。
|
||||
- 用户只给了主题但事实不全:可以先出草稿,但所有未确认内容必须标成 `【待确认:...】`,不要编造数字、链接、奖励或规则。
|
||||
- 用户明确说“只要文字”“不要图片”“不要打包”“先不要生成文件”“先只要大纲”时,按用户的限制输出,不强行生成完整文件包。
|
||||
|
||||
## 强制完整交付规则
|
||||
|
||||
当用户请求包含“使用 skill 生成文章”“按 skill 生成”“配图也按 skill”“完整文章”“发布用 Markdown”“打包”“一键下载”“给我文件”“文章和配图一起”“生成文章 + 配图”或“最终交付”等表达时,必须默认进入完整交付模式,除非用户明确要求只要文字、大纲、不要图片、不要打包或先不生成文件。
|
||||
|
||||
完整交付模式必须一次性完成:读取并核对所有用户提供的官方链接、PDF、参考文章和条款;生成发布用 Markdown 文件;生成或准备多张独立高清 PNG 配图并放入 `assets/`;用相对路径插入 Markdown;创建 ZIP 包;最终回复只给 Markdown 和 ZIP 下载链接或本地路径以及极简说明。若工具、图片生成、文件写入或资料缺失导致无法完成,必须明确失败点并尽量交付已完成部分,不要用正文或配图提示词代替文件。
|
||||
|
||||
## 文章生成任务的默认交付物
|
||||
|
||||
当用户没有特别说明“只要草稿”“先别生成文件”时,MAOMOMO 文章任务默认交付:
|
||||
|
||||
- `article.md`
|
||||
- `assets/` 独立配图目录
|
||||
- `article_package.zip`
|
||||
|
||||
聊天框正文只用于简短说明,不要把完整文章作为唯一交付物。用户后续要求修改文章内容时,必须更新 Markdown 文件本体;若修改影响配图、配图清单、来源区或 ZIP 包,必须同步更新并重新打包。
|
||||
|
||||
## 官方来源完整性检查
|
||||
|
||||
处理金融活动、银行活动、券商活动、返现活动、奖励活动时,必须先列出用户提供的所有来源链接,并逐个确认是否已读取:
|
||||
|
||||
```text
|
||||
已读取来源:
|
||||
- 官方活动页:已读 / 未读
|
||||
- 主活动条款 PDF:已读 / 未读
|
||||
- 额外活动条款 PDF:已读 / 未读
|
||||
- 参考文章:已读 / 未读
|
||||
```
|
||||
|
||||
如果用户提供了多个 PDF,不能只读其中一部分就开始写最终稿。后续补充新的官方条款 PDF 时,必须重新校正文章中相关规则并说明更新点。若主活动 PDF、补充条款 PDF、活动页存在口径差异,优先以最新官方条款 PDF 为准,并在文章里提示“以官方条款和 App 实际显示为准”。
|
||||
|
||||
## 范围收敛规则
|
||||
|
||||
如果活动有主活动、额外会员活动、年龄限定活动、旧版活动对比或小众资格优惠等多个分支,先判断是否适合放入主文。
|
||||
|
||||
默认主文优先保留大多数读者适用的主活动;小众分支只放到“补充说明”“关联阅读”或“可选阅读”。用户要求删除某个分支时,必须同步删除正文、标题、摘要、标签、配图清单、图片 alt 文案、来源区和 ZIP 包里的相关内容,不要让已删除分支残留在来源区、配图清单或图片文件名里。
|
||||
|
||||
## SEO 优化与配图打包模式
|
||||
|
||||
当用户提供现有 Markdown 并要求优化、配图或打包时,按这个顺序执行:
|
||||
|
||||
1. 读取原文,判断文章主题、核心概念、目标读者和现有结构。
|
||||
2. 在不编造事实的前提下优化 `title`、`description` / `excerpt`、`slug`、`categories`、`tags`、标题层级、太长不看版、图片 alt text、风险提示、来源区和正文逻辑顺序。
|
||||
3. 自动规划正文配图清单,标明每张图的类型、标题、放置位置、核心文案、关键数字、文件名和 alt text。
|
||||
4. 逐张独立生成或准备配图,保存到 `assets/`,再把最终图片插入 Markdown 对应位置。
|
||||
5. 创建 ZIP 包,结构应包含优化后的 Markdown 和 `assets/` 目录。
|
||||
6. 最终回复保持简短,只提供 ZIP 和 Markdown 的下载链接或本地路径;不要长篇解释过程。
|
||||
|
||||
配图数量按内容决定:短文章 4-6 张,普通攻略 6-8 张,长文章 / 多步骤教程 / 多活动整理 8-12 张。默认目标是 6-10 张独立配图;不要为了凑数量硬加图,也不要把多个不同主题硬塞进一张大图。
|
||||
|
||||
常见配图类型:
|
||||
|
||||
- 封面图:文章主题、核心利益点 / 核心问题、产品或场景元素、MAOMOMO 白橘猫视觉元素。
|
||||
- 总览图:适合活动整理、产品对比、教程总览,展示主要活动、核心条件、关键数字、适合人群和结论。
|
||||
- 流程图:适合开户、转账、入金、注册、申请、使用路径,展示 Step 1 / Step 2 / Step 3、操作顺序、注意事项和关键入口。
|
||||
- 表格图 / 对比图:适合多个产品、多个方案、多个活动对比,展示名称、条件、奖励、成本、适合谁和不适合谁。
|
||||
- 计算图:适合收益、返现、年化、费用测算,展示公式、输入数字、结果、保守假设和风险提醒。
|
||||
- 时间线图:适合活动日期、到账时间、任务周期、持有期、截止日,展示开始日期、截止日期、关键节点和最容易错过的时间。
|
||||
- 避坑图:适合“常见翻车点”“注意事项”“风险提醒”,展示容易犯的错、不能补救的点、费用 / 规则 / 时效风险和正确姿势。
|
||||
- FAQ 图:适合文章最后的常见问题,展示 3-5 个高频问题、简短答案和决策提示。
|
||||
|
||||
配图生成硬性要求:
|
||||
|
||||
- 每张图都要独立生成高清 PNG;不要拼图,不要从拼图里裁切,不要只生成一张总图。
|
||||
- 如果用户要求最终图片文件,不要只给提示词;必须把图片文件插入 Markdown 并纳入 ZIP。
|
||||
- 不要伪造真实 App 截图;没有真实截图时,只能做“示意图”或“插画示意”。
|
||||
- 涉及银行、支付机构、卡组织、券商或品牌时,可以使用中性的产品场景元素,但不要把第三方品牌 LOGO 放在猫咪身上、猫咪衣服上或猫咪身体部位上;例如 HSBC / 汇丰 LOGO 不得出现在猫身上。
|
||||
- 每张图都必须打上清晰的 `MAOMOMO` 文字作为站点 LOGO / 品牌标识,可放在角落、水印、标题区或猫咪旁边,但不要遮挡关键信息。
|
||||
- 每张图都要有独立主题,信息量适中,文字清晰,文件名 SEO 友好。
|
||||
- 图片文件名必须包含文章标题 / 主题前缀,例如 `hsbc-mastercard-alipay-campaign-01-cover.png`;不要使用 `01-cover.png`、`02-overview.png` 这类缺少主题前缀的通用文件名。
|
||||
- 图片说明必须根据图片内容写,不要都写成“配图”。
|
||||
|
||||
图片文件建议放在,并且文件名必须加文章标题 / 主题前缀,避免只用 `01-cover.png` 这类通用名。前缀用英文 slug 或拼音 slug,保持 SEO 友好,例如:
|
||||
|
||||
```text
|
||||
assets/
|
||||
hsbc-mastercard-alipay-campaign-01-cover.png
|
||||
hsbc-mastercard-alipay-campaign-02-overview.png
|
||||
hsbc-mastercard-alipay-campaign-03-step-guide.png
|
||||
hsbc-mastercard-alipay-campaign-04-comparison.png
|
||||
hsbc-mastercard-alipay-campaign-05-calculation.png
|
||||
hsbc-mastercard-alipay-campaign-06-timeline.png
|
||||
hsbc-mastercard-alipay-campaign-07-common-pitfalls.png
|
||||
hsbc-mastercard-alipay-campaign-08-faq.png
|
||||
```
|
||||
|
||||
也可以按具体主题使用更具体的英文或拼音文件名,例如 `hsbc-mastercard-alipay-campaign-01-cover.png`、`hong-kong-bank-account-03-application-flow.png`、`alipay-hk-cashback-05-return-calculation.png`。不要沿用上一篇文章的固定文件名或无主题前缀文件名,除非主题刚好一致。
|
||||
|
||||
Markdown 插图路径统一使用:
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
插图位置要贴合正文:封面图放在标题下方;总览图放在活动 / 产品介绍后;流程图放在步骤段落前后;计算图放在公式或测算表格后;时间线图放在日期说明后;避坑图放在翻车点段落后;FAQ 图放在常见问题段落前后。
|
||||
|
||||
ZIP 结构:
|
||||
|
||||
```text
|
||||
article_seo_with_images_package.zip
|
||||
article_seo_with_images.md
|
||||
assets/
|
||||
hsbc-mastercard-alipay-campaign-01-cover.png
|
||||
hsbc-mastercard-alipay-campaign-02-overview.png
|
||||
hsbc-mastercard-alipay-campaign-03-step-guide.png
|
||||
```
|
||||
|
||||
完成后回复格式可参考:
|
||||
|
||||
```markdown
|
||||
已完成。
|
||||
|
||||
- [下载 ZIP:Markdown + 独立配图](...)
|
||||
- [下载 Markdown](...)
|
||||
|
||||
ZIP 内包含:
|
||||
- SEO 优化后的 Markdown
|
||||
- assets 文件夹
|
||||
- 多张独立生成的 PNG 配图
|
||||
```
|
||||
|
||||
## 发布用文章结构
|
||||
|
||||
当用户需要发布用 Markdown 草稿时,开头使用头部元数据:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "文章标题"
|
||||
excerpt: "一句话说明本文整理什么、适合谁、核心收益或风险点。"
|
||||
categories:
|
||||
- "银行"
|
||||
tags:
|
||||
- "港卡"
|
||||
---
|
||||
```
|
||||
|
||||
默认正文结构如下;如主题更适合其他结构,可按文章类型调整:
|
||||
|
||||
```markdown
|
||||
# 标题
|
||||
|
||||

|
||||
|
||||
## 太长不看版
|
||||
> 1. ...
|
||||
> 2. ...
|
||||
> 3. ...
|
||||
|
||||
## 一、为什么值得看 / 活动简介 / 背景
|
||||
|
||||
## 二、怎么操作 / 怎么吃满
|
||||
|
||||
## 三、关键条件
|
||||
|
||||
## 四、常见翻车点
|
||||
|
||||
## 五、实测 / 时间线 / 常见答疑
|
||||
|
||||
## 最后提醒
|
||||
|
||||
## 来源
|
||||
```
|
||||
|
||||
标题和小标题要偏实操,不要写得太学院派。优先使用“太长不看版”“保姆式教程”“怎么吃满”“常见翻车点”“最后提醒”“如果已经这样操作还有救吗”这类 MAOMOMO 式标题。
|
||||
|
||||
## 写作口吻
|
||||
|
||||
- 像一个懂行朋友在分享可执行路线,不要像银行、券商或 SEO 营销号。
|
||||
- 先给结论,再讲原因和步骤。
|
||||
- 句子直接,段落短。一个段落通常只讲一个实操点。
|
||||
- 可以自然使用“众所周知”“重点来了”“翻车点”“闭眼薅”“无脑刷”“保姆式教程”“只看这个就够”“实测”“以官方条款 / App 实际显示为准”等表达。
|
||||
- 轻微俏皮可以,但不要堆梗、堆感叹号、堆 emoji,也不要制造焦虑。
|
||||
- 不承诺一定获批、一定到账、一定拿奖、稳赚、绝对安全,也不替用户做法律、税务、投资或合规判断。
|
||||
|
||||
## 图片与视觉方向
|
||||
|
||||
完整文章至少包含:
|
||||
|
||||
- 1 个带 MAOMOMO 猫咪识别的封面图提示词。
|
||||
- 关键 App 操作、证明材料或结果展示的正文截图槽位。
|
||||
- 涉及转账路径、返现计算、活动门槛、路线对比时,补充一张表格图 / 路线图 / 时间线图提示词。
|
||||
|
||||
猫咪视觉识别:
|
||||
|
||||
- 友好的白橘猫吉祥物,圆脸、小爪子、暖橙点缀,整体是干净的金融科技教程风。
|
||||
- 猫咪可以作为向导、指示贴纸、水印或提醒牌,不要抢走数字、步骤和截图的主视觉。
|
||||
- 猫咪身上不要出现第三方品牌 LOGO、银行 LOGO、卡组织 LOGO 或支付机构 LOGO;品牌元素应放在卡片、页面、标签或背景场景里,且不能让人误以为是猫咪本身的标识。
|
||||
- 每张图都要包含清晰可见的 `MAOMOMO` 文字 LOGO,作为站点品牌识别;位置可统一放在右上角、右下角或封面标题区。
|
||||
- 封面图要把产品语境和猫咪元素结合起来,例如猫咪拿着港卡、指向 App 页面、守着转账路线图、坐在返现计算器旁边。
|
||||
- 不伪造银行或券商 App 截图。没有真实截图时,只能写成“示意图”或“插画示意”。
|
||||
|
||||
## 来源区规则
|
||||
|
||||
- 活动时间、截止日期、条款变更、实测时间线必须写具体日期。
|
||||
- 奖励、费用、资格、监管产品、账户规则等信息,尽量链接官方条款或官方页面。
|
||||
- 引用 MAOMOMO 时,要链接具体文章或页面,不只放首页。
|
||||
- 发布用 Markdown 正文不要保留模型内部引用残留、`contentReference`、`oaicite` 或其他不可发布标记;正文里可用自然语言说明“根据官方条款整理”。
|
||||
- 所有链接统一放在文末 `## 来源` 区;来源区只保留最终文章实际使用的来源。用户要求删除某个活动分支时,对应来源也必须删除。
|
||||
- 涉及金融、银行、券商、数字货币、汇款、税务或合规时,加入简短免责声明:
|
||||
|
||||
```text
|
||||
本文为经验整理,不构成金融、投资、法律或税务建议;活动和规则可能随时调整,请以官方条款、App 实际显示及当地法规为准。
|
||||
```
|
||||
|
||||
- 无法确认的信息要明确标注,例如“公开页面暂时无法确认”“需要二次确认”“以到账 / 账单 / MCC 实际显示为准”。
|
||||
|
||||
## 质量检查
|
||||
|
||||
交付前确认:
|
||||
|
||||
- 标题包含产品 / 主题,以及具体利益点或问题点。
|
||||
- 开头第一屏已经给出实操结论。
|
||||
- 数字、门槛、日期、邀请码、链接、币种和单位写清楚。
|
||||
- 至少有一个“常见翻车点 / 风险提醒”部分。
|
||||
- 图片提示词包含猫咪元素;每张图都有 `MAOMOMO` 文字 LOGO;猫咪身上没有第三方品牌 LOGO;并且没有把生成图伪装成真实截图。
|
||||
- 来源放在文末,官方来源和 MAOMOMO 来源分清楚。
|
||||
- 正文没有 `contentReference`、`oaicite`、调试标记或工具残留。
|
||||
- 语气不像官方营销稿,也没有无依据保证。
|
||||
- 用户的修改要求默认是编辑指令,不是正文素材。
|
||||
- 只改用户指定范围;不要因为“删掉几个字”误删整段。
|
||||
- 用户确认的实测口径优先写成结论,不反复声明“本文以实测为主”等元叙述。
|
||||
- 用户给出实测或过往惯例依据时,不要把确定事实和强概率判断改软;风险提示只能作为边界补充,不能推翻主句判断。
|
||||
- 对同类模糊词同步检查,例如“更像”“可能”“大致”“疑似”,但不要机械删除必要的不确定表达。
|
||||
- 改完必须检查正文是否残留“不要写”“用户要求”“准确写法是”“本文口径”“应当写成”等编辑痕迹。
|
||||
- 文件交付任务改完后重新检查 Markdown、assets 和 ZIP 是否是最新版本。
|
||||
|
||||
## 验收标准
|
||||
|
||||
完整交付必须满足:
|
||||
|
||||
- `article.md` 或用户指定的 Markdown 文件存在。
|
||||
- `assets/` 存在,且 Markdown 中每个图片路径都能对应真实文件。
|
||||
- 需要配图时,每张配图是独立 PNG,并且含 MAOMOMO 品牌识别。
|
||||
- ZIP 包存在,包含最终 Markdown 和 `assets/`。
|
||||
- 来源区只保留文章实际使用的来源;未读或无法确认的来源不得假装已确认。
|
||||
- 正文没有 `contentReference`、`oaicite`、调试标记、模型解释或编辑指令残留。
|
||||
- 金融、银行、券商、数字货币、汇款、税务或合规内容包含保守风险提示。
|
||||
- 用户要求删除或收敛的分支,不残留在标题、摘要、正文、图片 alt、配图清单、来源区或 ZIP。
|
||||
|
||||
小红书卡片模式必须满足:
|
||||
|
||||
- 每张最终图是独立 PNG,比例为用户确认的 `3:4` 或 `9:16`。
|
||||
- 已确认 `2D 扁平风` 或 `3D 轻拟物风`,且全组一致。
|
||||
- prompt、状态文件和 QA 记录包含原生竖版构图、防压扁和防拉伸约束。
|
||||
- `final_manifest.json` 只包含通过 QA 且进入 `assets/` 的最终图片。
|
||||
- GitHub 分批推送记录已写入状态文件或 `github_batches.json`;Telegram 只发送进度和链接。
|
||||
- GitHub 交付仓库和专用 KEY / token / deploy key 已按 `references/github-delivery.md` 配置或明确记录为阻塞项。
|
||||
- 最终回复包含 GitHub 链接、图片数量、风格、比例、批次数量、QA 是否完成和已知限制。
|
||||
|
||||
如果无法完成,最终回复必须说明:阻塞阶段、缺失材料或失败工具、已完成文件路径、未完成项和下一步需要什么。
|
||||
|
||||
## Reference Map
|
||||
|
||||
- `references/source-and-fact-check.md`:来源读取、事实表、时效性、冲突口径和金融风险核查。
|
||||
- `references/style-guide.md`:MAOMOMO 文章类型、标题套路、语气、结构模板、小红书模式和写作细节。
|
||||
- `references/visual-assets.md`:配图类型、猫咪视觉、文件命名、Markdown 插图、金融卡面核对、可见文字限制、禁止伪造截图和图片 QA。
|
||||
- `references/recommended-styles.md`:推荐配图风格、适用场景、推荐话术和提示词片段。
|
||||
- `references/image-styles/*.md`:具体出图风格 brief,借鉴 `codex-ppt-skill` 风格库结构并适配 MAOMOMO 文章配图。
|
||||
- `references/image-generation.md`:图片生成后端选择、`scripts/maomomo_image_gen.py` 用法、`scripts/maomomo_job_state.py` 状态记录、final manifest、GitHub 批次记录和故障处理。
|
||||
- `references/github-delivery.md`:GitHub 交付仓库、专用 KEY / token / deploy key、环境变量、隐私边界和分批推送前置检查。
|
||||
- `references/delivery-rules.md`:完整交付、ZIP、GitHub 分批推送、更新打包、附件策略和最终检查清单。
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
interface:
|
||||
display_name: "MAOMOMO 文章生成器"
|
||||
short_description: "生成 MAOMOMO 文章、配图和 ZIP 发布包"
|
||||
default_prompt: "使用 $maomomo-article-writer 生成或优化一篇 MAOMOMO 风格的中文出海金融实操文章,并交付 Markdown、assets 独立配图目录和 ZIP 包。"
|
||||
short_description: "分阶段确认后生成 MAOMOMO 文章、配图、小红书卡片和 GitHub 交付包"
|
||||
default_prompt: "使用 $maomomo-article-writer 生成或优化一篇 MAOMOMO 风格的中文出海金融实操文章或小红书卡片。请先核查来源和事实表,再确认标题、口径、大纲或卡片结构、素材映射、2D/3D 视觉方向、比例和生图后端;如需 GitHub 交付,先引导配置专用私有仓库和最小权限 KEY / token / deploy key;先输出图片 prompts 文件,只生成一张样张确认,再逐张生成剩余图片,最后用 final_manifest.json 控制最终 assets,并按需要交付 Markdown、ZIP 或 GitHub 分批链接。"
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
+101
-24
@@ -1,6 +1,8 @@
|
||||
# MAOMOMO 完整交付规则
|
||||
|
||||
本文件用于完整文章、配图、发布包、打包、一键下载和后续更新任务。只要用户没有明确说“只要文字”“不要图片”“不要打包”“先不要生成文件”“先只要大纲”,默认按完整交付模式执行。
|
||||
本文件用于完整文章、配图、发布包、打包、一键下载、GitHub 分批推送和后续更新任务。只要用户没有明确说“只要文字”“不要图片”“不要打包”“先不要生成文件”“先只要大纲”,默认按完整交付模式执行。用户提到“小红书”“卡片”“多图”“3:4”“9:16”时,默认进入小红书卡片模式,不默认生成完整文章、Markdown 或 ZIP。
|
||||
|
||||
完整交付模式指“最终交付物必须完整”,不代表可以跳过确认一次性全量生成。执行时必须遵守 `SKILL.md` 的阶段门禁:先读资料和事实表,再确认标题 / 口径 / 大纲 / 素材映射 / 配图风格 / 生图后端,随后写文章、整理 prompts、先出一张样张,样张确认后再逐张生成剩余图片。
|
||||
|
||||
## 强制完整交付规则
|
||||
|
||||
@@ -19,52 +21,98 @@
|
||||
- “生成文章 + 配图”
|
||||
- “最终交付”
|
||||
|
||||
完整交付模式必须一次性完成:
|
||||
完整交付模式最终必须完成以下交付物,但必须分阶段推进:
|
||||
|
||||
1. 读取并核对用户提供的所有官方链接、PDF、参考文章和条款。
|
||||
2. 输出发布用 Markdown 文件,而不是只在聊天框展示正文。
|
||||
3. 生成或准备多张独立高清 PNG 配图,放入 `assets/` 目录。
|
||||
4. 将图片以相对路径插入 Markdown,例如 ``。
|
||||
5. 创建 ZIP 包,结构类似:
|
||||
2. 在写最终稿前给用户确认标题、核心口径、`outline.md` 大纲、来源缺口、必选素材映射和配图风格。
|
||||
3. 用户确认后输出发布用 Markdown 文件,而不是只在聊天框展示正文。
|
||||
4. 出图前把全部图片 prompt 写入 `prompts/` 或 manifest 文件,让用户可直接编辑。
|
||||
5. 先生成并确认一张样张,优先封面图;样张确认后再逐张生成剩余独立高清 PNG。
|
||||
6. 将通过 QA 的图片同步到 `assets/` 目录,并以相对路径插入 Markdown,例如 ``。
|
||||
7. 用脚本检查 Markdown 中所有本地图片路径真实存在。
|
||||
8. 创建 ZIP 包,结构类似:
|
||||
|
||||
```text
|
||||
article_package.zip
|
||||
article.md
|
||||
assets/
|
||||
01-cover.png
|
||||
02-overview.png
|
||||
03-timeline.png
|
||||
hsbc-mastercard-alipay-campaign-01-cover.png
|
||||
hsbc-mastercard-alipay-campaign-02-overview.png
|
||||
hsbc-mastercard-alipay-campaign-03-timeline.png
|
||||
```
|
||||
|
||||
6. 最终回复只给下载链接和极简说明。
|
||||
9. 最终回复只给路径和极简说明,同时说明图片数量、生图后端、状态记录和已知限制。
|
||||
|
||||
如果因为工具限制、图片生成失败、文件写入失败或资料缺失导致无法完成完整交付,必须明确说明失败点,并尽量交付已经完成的部分。不要只输出配图提示词代替图片文件,不要只输出文章正文代替 Markdown 文件。
|
||||
|
||||
## 多渠道交付策略
|
||||
|
||||
完整交付默认生成 Markdown、assets 和 ZIP,但最终发送方式按渠道和用户要求调整。
|
||||
完整文章交付默认生成 Markdown、assets 和 ZIP;小红书卡片和大图片交付默认生成 `assets/`、`final_manifest.json` 和 GitHub 分批链接。最终发送方式按渠道和用户要求调整。
|
||||
|
||||
- 本地 / Web:优先给 Markdown + ZIP;如用户要图片,也附独立图片。
|
||||
- Telegram / 聊天:ZIP 可能失败,优先发送 Markdown + 多张图片独立附件;ZIP 作为可选补充。
|
||||
- 本地 / Web:完整文章可给 Markdown + ZIP;小红书卡片给 `assets/`、`final_manifest.json` 和 GitHub 链接。
|
||||
- GitHub:图片较多、文件较大、Telegram 可能失败或用户要求最终交付时,先读 `references/github-delivery.md`,确认交付仓库、专用 KEY / token / deploy key、仓库隐私级别和目标路径;配置就绪后按批次推送,例如 `batch-01-cover`、`batch-02-content-cards`、`batch-03-final-assets`。每批推送后记录 GitHub 文件路径、commit、release 或下载链接。
|
||||
- Telegram / 聊天:只负责通知进度和给 GitHub 链接;不要依赖 Telegram 直接承载 ZIP 或大图片包。少量小文件附件只能作为补充,不作为唯一交付证据。
|
||||
- Email:按用户要求选择 ZIP 或独立附件。
|
||||
- 用户说“打包发我 email”:发送 ZIP。
|
||||
- 用户说“不打包”“分多个附件”:发送 Markdown 和图片作为多个独立附件,不再附 ZIP。
|
||||
- 若附件发送失败,不要反复重复发送同一批附件;改用更稳定的渠道或分批发送,并说明只保留最后版本。
|
||||
- 发送前确认附件对应的是最后确认版,不要把旧图、旧 Markdown 或临时文件发出。
|
||||
|
||||
## 小红书卡片交付模式
|
||||
|
||||
触发词包括“小红书”“卡片”“多图”“4 图看完”“发小红书”“3:4”“9:16”。
|
||||
|
||||
执行规则:
|
||||
|
||||
1. 不默认走完整文章 + Markdown + ZIP;除非用户明确要求,只交付小红书标题、正文、话题标签和多张独立 PNG。
|
||||
2. 出图前确认比例 `3:4` 或 `9:16`,确认视觉方向 `2D 扁平风` 或 `3D 轻拟物风`。用户没指定时推荐 `2D 扁平风`。
|
||||
3. 每张图 prompt 必须包含原生竖版防压扁规则:`native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。
|
||||
4. 每张图按一个 job / 一次请求 / 一次 QA 推进,状态文件记录 prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub。
|
||||
5. QA 通过后生成 `final_manifest.json`,只包含最终图;推送或打包只能读取该 manifest 或状态文件里标记为 final 的 assets。
|
||||
6. GitHub 分批推送前必须按 `references/github-delivery.md` 完成或确认仓库和专用凭据配置。缺少配置时先输出本机配置步骤并暂停 push,不生成假链接。
|
||||
7. GitHub 分批推送建议:
|
||||
- `batch-01-cover`:封面图和封面 prompt。
|
||||
- `batch-02-content-cards`:内容卡、流程卡、避坑卡。
|
||||
- `batch-03-final-assets`:`final_manifest.json`、全部 final assets、状态文件。
|
||||
8. 每批推送后立即回报 GitHub 文件路径、commit、release 或下载链接,并写入 `github_batches.json` 或状态文件。
|
||||
9. 最终回复固定包含 GitHub 链接、图片数量、风格:2D / 3D、比例:3:4 / 9:16、批次数量、QA 是否完成、已知限制。
|
||||
|
||||
## GitHub 交付配置门禁
|
||||
|
||||
GitHub 交付前必须先完成以下检查:
|
||||
|
||||
- 已读取 `references/github-delivery.md`。
|
||||
- 已确认目标仓库,例如 `OWNER/maomomo-delivery`。
|
||||
- 仓库默认是私有;公开仓库必须经用户明确确认。
|
||||
- 已确认认证方式:`gh auth login`、`GH_TOKEN` fine-grained PAT,或 SSH deploy key。
|
||||
- 凭据必须是专用且最小权限:fine-grained PAT 只给交付仓库 `Contents: Read and write`;deploy key 只授权单仓库 write access。
|
||||
- 不要求用户在聊天里粘贴 token 或私钥;不把密钥写入项目文件、manifest、状态文件、日志或 commit。
|
||||
- `github_batches.json` 只记录 batch、文件路径、commit、release / url 和备注。
|
||||
|
||||
## 配图执行规则
|
||||
|
||||
具体视觉风格、图片清单、文件命名、Markdown 插图和图片 QA 规则见 `references/visual-assets.md`。本节只规定完整交付时的执行边界。
|
||||
|
||||
当用户要求“配图也按 Skill”“高清配图”“精美配图”“生成配图”“文章和配图一起”时:
|
||||
|
||||
- 必须生成独立 PNG 图片文件,而不是只给提示词。
|
||||
- 出图前必须先写入 prompt 文件,包含文件名、用途、放置位置、核心文案、完整 prompt、alt text、风格和依赖素材。
|
||||
- 必须先生成一张样张,优先封面图;用户确认前不得生成剩余图片。
|
||||
- 样张确认后,每张图按一个 job / 一次请求 / 一次 QA 推进;运行环境支持子 agent 时,可以一图一个子 agent 并发。
|
||||
- 不要只生成一张大长图替代所有配图。
|
||||
- 每张图必须有独立主题,例如封面图、活动总览图、时间线图、重点规则图、交易法图、避坑图、FAQ 图。
|
||||
- 不得使用未经用户提供的真实银行 Logo、券商 Logo 或支付品牌 Logo。
|
||||
- 银行、券商、支付品牌、卡组织或支付网络 Logo 只要已由官方材料、用户素材或用户明确口径核对正确,就可以作为辅助识别出现;用户未提供素材时,应主动尽力从官方活动页、条款 PDF、品牌资源页、官网 / App 截图等来源查找准确 Logo。无法核对时使用通用符号。
|
||||
- 出现信用卡、支付卡、卡组织、支付网络或机构 Logo 时,必须按官方材料或用户素材核对,并在 prompt、manifest 或 `deck_spec.json` 记录参考素材、卡组织、`allowed_brand_marks` 和 `brand_mark_sources`;不得把银联 / UnionPay 画成 Mastercard、Visa 等其他卡组织。
|
||||
- 第三方 Logo 必须尺寸克制、位置辅助,不能抢占、替代或压过 `MAOMOMO` 主标识。
|
||||
- 不得伪造真实 App 截图。
|
||||
- 生成图应明确是“MAOMOMO 风格示意图 / 信息图”。
|
||||
- 生成后必须保存到 `assets/`,并插入 Markdown。
|
||||
- 图片文件名使用稳定英文小写命名,例如 `01-cover.png`、`02-overview.png`、`03-timeline.png`、`04-key-rule.png`、`05-strategy.png`、`06-pitfalls.png`。
|
||||
- 不要使用中文文件名作为最终 assets 文件名,避免站点路径兼容问题。
|
||||
- prompt 中的视觉风格只能写成后台画面描述,不要写容易被模型照抄的中文风格标签;可见文字只允许出现指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识。
|
||||
- 小红书 3:4 / 9:16 图片必须原生竖版重新构图,不能把横版设计压进竖图;prompt 必须包含防压扁和防拉伸英文约束。
|
||||
- 生成图应明确是 MAOMOMO 示意图 / 信息图,不伪装成真实截图或官方页面。
|
||||
- 生成后的原始最终图先保存到 `origin_image/`,通过 QA 后同步到 `assets/`,并插入 Markdown。
|
||||
- 港币金额在发布稿、图片文案、prompt、manifest 和 `deck_spec.json` 中统一写 `HKD`,来源原文抽取文件除外。
|
||||
- 图片文件名使用稳定英文小写命名,并包含主题前缀,例如 `hsbc-mastercard-alipay-campaign-01-cover.png`。
|
||||
- 不要使用中文文件名或缺少主题前缀的通用文件名作为最终 assets 文件名,避免站点路径兼容和多文章混淆问题。
|
||||
- 不要用 Playwright、HTML/CSS、SVG、Pillow、canvas、本地拼贴或手工覆盖文字替代 GPT Image2 / 图片 API 生成最终配图。
|
||||
|
||||
## 图片生成偏差处理
|
||||
|
||||
@@ -82,7 +130,9 @@ article_package.zip
|
||||
- 只输出配图提示词,不生成图片文件。
|
||||
- 只生成图片,不插入 Markdown。
|
||||
- 只生成一张总图,不生成多张独立配图。
|
||||
- 只给 assets,不打 ZIP。
|
||||
- 完整文章模式只给 assets,不打 ZIP。
|
||||
- 小红书卡片只扫描目录交付,未生成 `final_manifest.json`,导致草稿、失败图、旧比例图混入最终包。
|
||||
- Telegram 发送失败后仍反复传大 ZIP,而不是改用 GitHub 分批链接。
|
||||
- 只给一段新增内容,不输出更新后的完整文章。
|
||||
- 只给旧文件链接,没有确认文件内容已更新。
|
||||
- 修改了正文,但没有同步更新标题、摘要、配图清单、来源区和 ZIP 包。
|
||||
@@ -111,25 +161,52 @@ article_package.zip
|
||||
4. 如果用户要求文件或打包,必须重新生成 ZIP。
|
||||
5. 最终回复给出最新 Markdown 和 ZIP 链接,避免继续给旧文件。
|
||||
|
||||
## 远程图片路径替换
|
||||
|
||||
当用户提供 WordPress / CDN 上传后的 `<img ... src="...">` 片段或远程图片 URL,并要求替换文章图片路径时:
|
||||
|
||||
1. 只替换 Markdown 中的图片 URL,不改正文、alt text、图片顺序和本地 assets 文件。
|
||||
2. 优先按文件名或 title 中的序号 / 语义匹配,例如 `01-cover` 对封面图,`04-rebate-card` 对到账截图卡片;不要按用户粘贴顺序机械替换,除非用户明确说按粘贴顺序。
|
||||
3. 替换后用 `rg` 或等效检查确认没有残留本地 `assets/` / `origin_image/` 引用。
|
||||
4. 若文章已全部改为远程图,Markdown 本地图片路径检查可以跳过本地资产存在性要求,但仍要检查远程 URL 已写入正确位置。
|
||||
5. 如果需要重新打包,ZIP 至少包含最新 `article.md`;是否继续附本地 `assets/` 取决于用户要“发布稿”还是“完整归档包”,最终回复要说明包内内容。
|
||||
|
||||
## 最终交付前检查清单
|
||||
|
||||
交付前必须逐项确认:
|
||||
|
||||
- Markdown 文件已生成。
|
||||
- `outline.md` 已按用户确认版本保存。
|
||||
- `prompts/` 中每张图的 prompt 文件已保存,且与最终图片数量和顺序一致。
|
||||
- 一张样张已确认,后续图片继承同一生图后端和视觉系统。
|
||||
- `slide_jobs.json` / `slide_run_state.json` 已记录图片任务状态;如缺少记录,最终回复必须说明原因。
|
||||
- 每张图的状态记录包含 prompt 文件、生成结果、QA 状态、是否进入 `assets/`、是否已推送 GitHub。
|
||||
- `final_manifest.json` 已生成,并且最终交付只读取 manifest 中列出的 final assets。
|
||||
- 需要 GitHub 交付时,已确认 `references/github-delivery.md` 中的仓库、专用凭据、隐私级别和目标路径;缺失时最终回复说明配置阻塞项。
|
||||
- `origin_image/` 中最终原始图片存在。
|
||||
- `assets/` 目录已生成。
|
||||
- 所有 Markdown 图片路径都能对应到真实图片文件。
|
||||
- 已运行 `uv run python scripts/maomomo_check_markdown_assets.py article/article.md` 或等效脚本,确认所有 Markdown 图片路径都能对应到真实图片文件。
|
||||
- 图片是多张独立 PNG,不是一张大图代替全部。
|
||||
- 图片文件名包含文章主题前缀,未沿用旧文章文件名。
|
||||
- 图片可见文字没有出现风格标签、prompt 描述、额外 slogan 或无关文案;除业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识外,没有多余文字。
|
||||
- 图片日期、金额、公式、卡组织 / 支付网络 / 机构 Logo 和禁用分支都已核对。
|
||||
- 用户未提供 Logo / 卡面素材时,已尽力查找准确标识,并在 `sources/`、prompt、manifest 或 `deck_spec.json` 记录素材来源和核对结论;无法核对时没有凭记忆画真实 Logo。
|
||||
- 第三方 Logo 没有抢占、替代或压过 `MAOMOMO` 主标识。
|
||||
- 发布稿、图片文案、prompt、manifest、`deck_spec.json` 和 `speech.md` 中港币金额统一使用 `HKD`。
|
||||
- 文末来源区只包含实际使用的来源。
|
||||
- 正文没有 `contentReference`、`oaicite`、调试标记或工具残留。
|
||||
- 用户要求删除的分支,标题、正文、配图清单和来源区都已删除干净。
|
||||
- ZIP 包已创建。
|
||||
- 完整文章模式下 ZIP 包已创建;小红书卡片模式下 `final_manifest.json` 和 GitHub 批次记录已创建。
|
||||
- 用户最后确认的标题是否已同步到 frontmatter 和 H1。
|
||||
- 用户要求删除的词是否只删除指定词,未误删整条信息。
|
||||
- 正文是否残留编辑指令或模型解释。
|
||||
- 用户不喜欢的模糊词,例如“更像”“可能”“大致”,是否已按上下文清理。
|
||||
- 用户提供的明确实测结论是否写成确定语气,未被改软成“可能 / 疑似 / 大概”。
|
||||
- 用户提供的“以往惯例 / 过往尿性 / 过往经验”是否写成“大概率 / 按过往案例判断”的强概率语气,并保留合理边界。
|
||||
- TLDR 是否过长,是否适合当前平台。
|
||||
- 小红书图是否为 9:16 竖版,是否每张都有 MAOMOMO 标识。
|
||||
- 小红书图是否为用户确认的 3:4 或 9:16,是否每张都有 MAOMOMO 标识,是否为原生竖版重构,是否没有压扁或拉伸。
|
||||
- 小红书图是否全组保持同一 2D / 3D 视觉方向。
|
||||
- 图片是否匹配最终文章口径,未残留旧规则、旧金额、旧标题。
|
||||
- 邮件/聊天附件是否按用户要求选择 ZIP 或多个独立附件。
|
||||
- 邮件/聊天附件是否按用户要求选择 ZIP、多个独立附件或 GitHub 分批链接;Telegram 是否只承载通知和链接。
|
||||
- 若已发送过旧版本,最终回复要明确这是最后版本,避免重复发同一批图片。
|
||||
- 最终回复包含 Markdown 下载链接和 ZIP 下载链接。
|
||||
- 完整文章最终回复包含 Markdown 下载链接和 ZIP 或 GitHub 链接;小红书卡片最终回复包含 GitHub 链接、图片数量、风格、比例、批次数量、QA 是否完成和已知限制。
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# GitHub 交付配置
|
||||
|
||||
本文件用于 GitHub 分批交付前的仓库和专用凭据配置。只要任务需要 GitHub push、release、下载链接,或用户提到 GitHub 仓库、专用 KEY、token、PAT、deploy key,先读本文件。
|
||||
|
||||
## 目标
|
||||
|
||||
- 使用专门的交付仓库,不和源码仓库混放。
|
||||
- 默认使用私有仓库,除非用户明确要求公开交付。
|
||||
- 使用专用、最小权限凭据;不要复用高权限个人 token。
|
||||
- 不在聊天、prompt、manifest、状态文件、README、日志或提交记录里暴露完整 token / 私钥。
|
||||
|
||||
## 推荐配置
|
||||
|
||||
推荐环境变量:
|
||||
|
||||
```bash
|
||||
export MAOMOMO_GITHUB_REPO="OWNER/maomomo-delivery"
|
||||
export MAOMOMO_GITHUB_BRANCH="main"
|
||||
export MAOMOMO_GITHUB_BASE_PATH="deliveries"
|
||||
```
|
||||
|
||||
认证方式任选其一:
|
||||
|
||||
1. GitHub CLI 登录,适合本机交互使用:
|
||||
|
||||
```bash
|
||||
gh auth login --web --git-protocol https
|
||||
gh auth status
|
||||
```
|
||||
|
||||
2. Fine-grained PAT,适合脚本或无头环境:
|
||||
|
||||
- Repository access:只选交付仓库。
|
||||
- Repository permissions:只给 `Contents: Read and write`。
|
||||
- 过期时间:建议 30-90 天。
|
||||
- 本机设置为环境变量,不要发到聊天里:
|
||||
|
||||
```bash
|
||||
export GH_TOKEN="只在本机 shell 中设置,不写入项目文件"
|
||||
gh auth status
|
||||
```
|
||||
|
||||
3. SSH deploy key,适合只给单仓库写权限:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f "$HOME/.ssh/maomomo_delivery_ed25519" -C "maomomo-delivery"
|
||||
```
|
||||
|
||||
把 `.pub` 公钥加入 GitHub 仓库 Settings -> Deploy keys,并勾选 write access。私钥只留本机,推送时使用:
|
||||
|
||||
```bash
|
||||
export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/maomomo_delivery_ed25519 -o IdentitiesOnly=yes"
|
||||
```
|
||||
|
||||
## 首次仓库准备
|
||||
|
||||
如果用户还没有交付仓库,先引导其创建或确认仓库名。不要擅自创建公开仓库。
|
||||
|
||||
```bash
|
||||
gh repo create "$MAOMOMO_GITHUB_REPO" --private --clone=false
|
||||
```
|
||||
|
||||
检查仓库:
|
||||
|
||||
```bash
|
||||
gh repo view "$MAOMOMO_GITHUB_REPO" --json nameWithOwner,visibility,defaultBranchRef
|
||||
```
|
||||
|
||||
如果 `visibility` 不是 `PRIVATE`,先提醒用户可能公开图片、prompt、状态文件和活动信息;除非用户明确确认,否则不要继续推送。
|
||||
|
||||
## 本地工作目录
|
||||
|
||||
GitHub 交付工作目录默认放在 `$HOME/dev` 下,避免散落到桌面、下载目录或系统目录:
|
||||
|
||||
```bash
|
||||
mkdir -p "$HOME/dev/maomomo-delivery-work"
|
||||
cd "$HOME/dev/maomomo-delivery-work"
|
||||
```
|
||||
|
||||
如果仓库尚未 clone:
|
||||
|
||||
```bash
|
||||
gh repo clone "$MAOMOMO_GITHUB_REPO" repo
|
||||
```
|
||||
|
||||
目标路径建议:
|
||||
|
||||
```text
|
||||
deliveries/{project_slug}/
|
||||
├── batch-01-cover/
|
||||
├── batch-02-content-cards/
|
||||
├── batch-03-final-assets/
|
||||
├── final_manifest.json
|
||||
├── github_batches.json
|
||||
└── slide_run_state.json
|
||||
```
|
||||
|
||||
## 推送前检查
|
||||
|
||||
推送前必须确认:
|
||||
|
||||
- `MAOMOMO_GITHUB_REPO` 已设置,或用户已明确给出目标仓库。
|
||||
- 已通过 `gh auth status`,或 SSH deploy key 已能写入目标仓库。
|
||||
- 仓库默认为私有;公开仓库必须经用户明确确认。
|
||||
- `final_manifest.json` 已生成,最终文件只从 manifest 或状态文件里的 final assets 读取。
|
||||
- 本批文件不包含 token、私钥、账号、真实个人信息或未确认敏感素材。
|
||||
- `github_batches.json` 只记录 batch、路径、commit、release / url,不记录任何密钥。
|
||||
|
||||
## 分批推送流程
|
||||
|
||||
每批只复制本批需要交付的文件,不扫描整个项目目录。提交信息优先中文:
|
||||
|
||||
```bash
|
||||
git add deliveries/{project_slug}/batch-01-cover deliveries/{project_slug}/github_batches.json
|
||||
git commit -m "交付:{project_slug} batch-01-cover"
|
||||
git push origin "$MAOMOMO_GITHUB_BRANCH"
|
||||
```
|
||||
|
||||
推送后用状态脚本记录:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py github-batch article \
|
||||
--batch batch-01-cover \
|
||||
--commit "<commit sha>" \
|
||||
--url "https://github.com/OWNER/REPO/commit/<commit sha>" \
|
||||
--paths assets/example-01-cover.png prompts/example_01.json final_manifest.json
|
||||
```
|
||||
|
||||
每批推送后回复用户:GitHub 文件路径、commit、release 或下载链接,并说明下一批状态。
|
||||
|
||||
## 阻塞处理
|
||||
|
||||
如果缺少仓库或凭据,不要继续生成假链接,也不要要求用户把 token 粘贴到聊天里。回复应包含:
|
||||
|
||||
- 当前阻塞:缺少交付仓库、未登录 GitHub CLI、token 权限不足、deploy key 未授权等。
|
||||
- 本机配置命令。
|
||||
- 需要用户确认的仓库名或认证方式。
|
||||
- 已完成的本地文件路径和下一步。
|
||||
@@ -0,0 +1,270 @@
|
||||
# 图片生成脚本与后端选择
|
||||
|
||||
需要实际生成配图文件、使用 API/CLI fallback、批量生成 assets,或排查图片接口配置时,先读本文件。
|
||||
|
||||
## 后端选择
|
||||
|
||||
优先顺序:
|
||||
|
||||
1. Codex / 当前 agent 的内置图片生成工具。适合大多数配图任务。
|
||||
2. `scripts/maomomo_image_gen.py` API/CLI fallback。适合用户明确要求 API/CLI、内置工具不可用、需要批量 manifest、或希望把生成过程记录到文件时。
|
||||
|
||||
不要因为脚本存在就强制使用脚本。若内置图片工具可用,仍优先使用内置工具生成独立 PNG,再保存到 `assets/`。
|
||||
|
||||
生成第一张图前,必须向用户确认:
|
||||
|
||||
- 已检查哪种内置图片生成工具是否可用。
|
||||
- 准备使用的生图后端。
|
||||
- 是否需要 `scripts/maomomo_image_gen.py` fallback,以及原因。
|
||||
- 当前会沿用环境变量或配置文件中的 baseURL、model、quality,不会擅自改成 `low`。
|
||||
|
||||
确认后,整篇文章所有图片保持同一个生图后端和质量配置。只有用户明确要求切换,或当前后端无法完成必需能力时,才重新确认后端。
|
||||
|
||||
## 配置来源
|
||||
|
||||
脚本按以下顺序读取配置:
|
||||
|
||||
- `MAOMOMO_IMAGE_API_KEY` / `OPENAI_API_KEY`
|
||||
- `MAOMOMO_IMAGE_BASE_URL` / `OPENAI_BASE_URL`
|
||||
- `MAOMOMO_IMAGE_MODEL` / `CODEX_PPT_IMAGE_MODEL`
|
||||
- `~/.codex/config.toml` 中的 `base_url`
|
||||
- `~/.codex/auth.json` 中可识别的 API key
|
||||
|
||||
`scripts/maomomo_image_gen.py` 必须兼容 Python 3.10 及以下没有内置 `tomllib` 的环境;读取 `~/.codex/config.toml` 时应使用脚本内置的轻量 TOML fallback,不要求用户额外安装 `tomli`。
|
||||
|
||||
不要在日志、回答、Markdown 或 manifest 中写入完整 API key。
|
||||
|
||||
## 单张生成
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_image_gen.py generate \
|
||||
--out article/origin_image/hsbc-mastercard-alipay-campaign-01-cover.png \
|
||||
--style warm-fintech-guide \
|
||||
--title "汇丰 Mastercard 支付宝活动" \
|
||||
--image-type "封面图" \
|
||||
--core-text "先看报名、门槛和封顶,避免白刷"
|
||||
```
|
||||
|
||||
先只看 prompt:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_image_gen.py generate \
|
||||
--out article/origin_image/hsbc-mastercard-alipay-campaign-01-cover.png \
|
||||
--style data-card-dashboard \
|
||||
--title "返现计算图" \
|
||||
--image-type "计算图" \
|
||||
--core-text "HKD 190 ÷ 9% ≈ HKD 2,112,超过部分没有额外收益" \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## prompt 文件输入
|
||||
|
||||
- `prompts/*.json` 用于管理图片 job、路径、参考素材、可见文字白名单和完整 prompt。
|
||||
- 如果生成脚本的 `--prompt-file` 读取纯文本,必须先从 JSON 的 `prompt` 字段抽取为同名 `.prompt.txt`,再把 `.prompt.txt` 传给图片接口。
|
||||
- 不要把整个 JSON 文件作为 `--prompt-file` 传入;否则模型可能把 JSON 外壳、字段名或管理信息画进图片。
|
||||
- 抽取出的 `.prompt.txt` 仍必须包含可见文字限制:只允许出现指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识,不得出现风格标签、prompt 描述、额外 slogan 或无关文字。
|
||||
|
||||
推荐抽取方式:
|
||||
|
||||
```bash
|
||||
uv run python - <<'PY'
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
path = Path("article/prompts/hsbc-mastercard-alipay-campaign_01.json")
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
prompt = str(data["prompt"]).strip()
|
||||
out = path.with_suffix(".prompt.txt")
|
||||
out.write_text(prompt + "\n", encoding="utf-8", newline="\n")
|
||||
print(out)
|
||||
PY
|
||||
```
|
||||
|
||||
然后生成:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_image_gen.py generate \
|
||||
--out article/origin_image/hsbc-mastercard-alipay-campaign-01-cover.png \
|
||||
--prompt-file article/prompts/hsbc-mastercard-alipay-campaign_01.prompt.txt
|
||||
```
|
||||
|
||||
## 批量 manifest
|
||||
|
||||
manifest 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"file_name": "origin_image/hsbc-mastercard-alipay-campaign-01-cover.png",
|
||||
"type": "封面图",
|
||||
"title": "汇丰 Mastercard 支付宝活动",
|
||||
"core_text": "先报名,再看门槛和封顶",
|
||||
"alt_text": "MAOMOMO 汇丰 Mastercard 支付宝返现活动封面图",
|
||||
"style": "warm-fintech-guide",
|
||||
"visual_direction": "2D 扁平风",
|
||||
"delivery_batch": "batch-01-cover",
|
||||
"allowed_brand_marks": ["HSBC", "Mastercard", "支付宝"],
|
||||
"brand_mark_sources": [
|
||||
{
|
||||
"name": "Mastercard",
|
||||
"source_url": "https://example.com/official-source",
|
||||
"source_type": "官方活动页 / 官方品牌资源页 / 用户素材",
|
||||
"checked_at": "YYYY-MM-DD"
|
||||
}
|
||||
],
|
||||
"brand_mark_rule": "这些标识必须来自官方活动材料或用户素材核对;只能小尺寸辅助出现,不能抢 MAOMOMO 主标识位置",
|
||||
"aspect_ratio": "16:9"
|
||||
},
|
||||
{
|
||||
"file_name": "origin_image/hsbc-mastercard-alipay-campaign-02-calculation.png",
|
||||
"type": "计算图",
|
||||
"title": "怎么刷到刚刚好",
|
||||
"core_text": "HKD 190 ÷ 9% ≈ HKD 2,112,超过部分没有额外收益",
|
||||
"style": "data-card-dashboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_image_gen.py batch \
|
||||
--manifest article/image_manifest.json \
|
||||
--base-dir article
|
||||
```
|
||||
|
||||
`file_name` 必须与本次生成目录一致,且最终文件必须真实存在。发布用 Markdown 默认引用 `assets/`,因此输出到 `origin_image/` 后要在 QA 通过时同步到同名 `assets/` 文件。
|
||||
|
||||
重要限制:
|
||||
|
||||
- 不要在样张确认前运行批量生成。
|
||||
- 默认用 `generate` 单张生成;`batch` 只用于用户明确授权的全量生成或 dry-run prompt 输出。
|
||||
- 即使使用 `batch`,脚本也是逐项请求图片接口;生成后仍要逐张 QA,并把通过的图片同步到 `assets/`。
|
||||
- 发布用 Markdown 默认引用 `assets/`,因此如果 manifest 输出到 `origin_image/`,必须在 QA 后复制到对应 `assets/` 路径。
|
||||
- 金融活动 manifest 中涉及信用卡、支付卡、支付网络或机构 Logo 时,必须记录 `reference_assets`、`payment_network`、`allowed_brand_marks`、`brand_mark_sources` 和 `card_art_rule`,并在 QA 中核对卡组织和第三方 Logo 没有画错,也没有抢 MAOMOMO 主标识位置。用户未提供 Logo / 卡面素材时,先尽力查找官方或可信来源并保存到 `sources/`;无法核对时使用通用符号,不要凭记忆画真实 Logo。
|
||||
- manifest、prompt、`deck_spec.json` 和发布稿中的港币金额统一使用 `HKD`,来源原文抽取文件除外。
|
||||
- 小红书卡片 manifest 必须记录 `aspect_ratio`、`visual_direction`、`delivery_batch` 和 `portrait_composition_rules`;prompt 必须包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。
|
||||
- 小红书卡片默认不生成完整文章、Markdown 或 ZIP;最终文件清单以 `final_manifest.json` 为准。
|
||||
|
||||
## 内置风格名
|
||||
|
||||
可用:
|
||||
|
||||
- `warm-fintech-guide`
|
||||
- `clean-professional`
|
||||
- `clean-editorial`
|
||||
- `data-card-dashboard`
|
||||
- `handdrawn-note`
|
||||
- `creative-magazine`
|
||||
- `retro-flat-illustration`
|
||||
- `e-ink-editorial`
|
||||
- `scientific-defense`
|
||||
- `mckinsey-brief`
|
||||
- `xiaohongshu-vertical`
|
||||
|
||||
查看完整描述:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_image_gen.py styles
|
||||
```
|
||||
|
||||
## 失败处理
|
||||
|
||||
- API key 缺失:提示用户配置环境变量或检查 Codex 配置,不要编造图片文件。
|
||||
- 接口失败:保留已生成图片,说明失败项,必要时用 `--dry-run` 交付 prompt 清单作为临时排查材料。
|
||||
- 如果脚本实际请求到了默认 `https://api.openai.com/v1`,但项目配置应使用兼容接口,先检查 `MAOMOMO_IMAGE_BASE_URL` / `OPENAI_BASE_URL` 和 `~/.codex/config.toml` 解析结果;Python 3.10 及以下必须走脚本内置 TOML fallback。不要把兼容接口的 key 误发到默认 OpenAI API 后反复重试。
|
||||
- 真实银行 / 券商 / 钱包截图等敏感素材需要上传图片 API 做 edit 前,先向用户确认实际 API 地址 / baseURL、接口归属、是否为用户自有兼容服务以及是否允许外发。确认且安全策略允许后才上传;未确认、确认不允许或安全策略不允许时,先生成“不含真实截图”的空白模板,再本地嵌入真实截图;状态记录 `backend_used` 写成类似 `GPT Image2 template + local screenshot embedding`,并在 QA note 说明截图未外发。
|
||||
- 图片文字乱码或金额错误:重新生成该单张图片;不要手工覆盖文字伪装成模型输出。
|
||||
- 图片不符合风格:先更新 manifest 的 `style` / `core_text` / `extra`,再重跑对应项。
|
||||
- 图片生成超时:可以对同一张图重试较长超时;不要为了快擅自降低 `quality`。
|
||||
- Playwright 只用于已有 HTML / SVG / 网页截图检查,不能替代 GPT Image2 / 图片 API 生成海报、OG 图、卡片图或文章配图。
|
||||
|
||||
## 状态记录
|
||||
|
||||
每张图片 job 的 dispatch、result 和 blocker 都要写入项目状态文件。推荐状态文件:
|
||||
|
||||
```text
|
||||
{article_dir}/slide_jobs.json
|
||||
{article_dir}/slide_run_state.json
|
||||
```
|
||||
|
||||
记录字段至少包括:
|
||||
|
||||
- job id,例如 `hsbc-mastercard-alipay-campaign_01`
|
||||
- prompt 文件,例如 `prompts/hsbc-mastercard-alipay-campaign_01.json`
|
||||
- 输出原图,例如 `origin_image/hsbc-mastercard-alipay-campaign-01-cover.png`
|
||||
- 发布图,例如 `assets/hsbc-mastercard-alipay-campaign-01-cover.png`
|
||||
- 状态:`pending`、`dispatched`、`recorded`、`blocked`
|
||||
- 生图后端、agent id、QA note、失败原因
|
||||
- 小红书卡片字段:`aspect_ratio`、`visual_direction`、`delivery_batch`
|
||||
- 最终交付字段:`qa_status`、`entered_assets`、`final`、`github_pushed`、`github_batch`
|
||||
|
||||
使用 `scripts/maomomo_job_state.py` 更新状态,不要手工改 JSON 后声称完成。
|
||||
|
||||
初始化状态:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py init article \
|
||||
--selected-backend "built-in image tool"
|
||||
```
|
||||
|
||||
查看状态:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py status article
|
||||
```
|
||||
|
||||
记录 dispatch:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py dispatch article \
|
||||
--job hsbc-mastercard-alipay-campaign_02 \
|
||||
--agent-id "<agent id>" \
|
||||
--prompt-file prompts/hsbc-mastercard-alipay-campaign_02.json
|
||||
```
|
||||
|
||||
单张 QA 通过后,记录结果并同步到 `origin_image/` 和 `assets/`:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py result article \
|
||||
--job hsbc-mastercard-alipay-campaign_02 \
|
||||
--agent-id "<agent id>" \
|
||||
--backend-used "built-in image tool" \
|
||||
--selected-source /absolute/path/to/generated.png \
|
||||
--qa-note "文字清楚,日期、金额、公式和卡组织已核对,风格与样张一致。"
|
||||
```
|
||||
|
||||
记录 blocker:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py blocker article \
|
||||
--job hsbc-mastercard-alipay-campaign_02 \
|
||||
--agent-id "<agent id>" \
|
||||
--reason "选定图片后端在 worker 中不可用"
|
||||
```
|
||||
|
||||
如果状态脚本失败,最终报告必须说明原因,并附上状态文件当前路径和未完成 job。
|
||||
|
||||
生成最终 manifest:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py final-manifest article
|
||||
```
|
||||
|
||||
`final_manifest.json` 只写入状态为 `recorded`、QA 已通过、且 `assets/` 文件真实存在的图片。最终推送、打包或给链接时只能读取该文件,不能扫描整个目录。
|
||||
|
||||
记录 GitHub 批次:
|
||||
|
||||
运行前必须先按 `references/github-delivery.md` 完成交付仓库和专用凭据配置;`github-batch` 只记录已经真实推送后的批次结果,不负责创建仓库、保存密钥或生成假链接。
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_job_state.py github-batch article \
|
||||
--batch batch-01-cover \
|
||||
--commit "<commit sha>" \
|
||||
--url "https://github.com/org/repo/commit/<commit sha>" \
|
||||
--paths assets/example-01-cover.png prompts/example_01.json
|
||||
```
|
||||
|
||||
每批推送后都要写入 batch 记录。状态文件里的单图记录会同步标记 `github_pushed=true`、`github_batch=batch-xx`;最终回复从 `github_batches.json` 或状态文件读取批次数量和链接。
|
||||
@@ -0,0 +1,49 @@
|
||||
# 创意杂志风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 传播型封面
|
||||
- 活动盘点、经验总结、观点型文章
|
||||
- 需要更强点击感但不夸张的 MAOMOMO 头图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "creative-magazine",
|
||||
"visual_direction": "Chinese editorial magazine cover for fintech tutorial, strong headline, generous whitespace, confident but not sensational",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "off-white, warm gray, or soft brand color field",
|
||||
"composition": "oversized headline, one hero financial object, short metadata labels, cat mascot as brand accent",
|
||||
"density": "low to medium"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "ink black or deep slate",
|
||||
"accent": "MAOMOMO orange plus one supporting color",
|
||||
"rule": "use 2-3 strong colors only"
|
||||
},
|
||||
"typography": {
|
||||
"title": "large Chinese display headline",
|
||||
"metadata": "small label for date, guide type, risk note",
|
||||
"text_quality": "short Chinese phrases only"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"large headline with financial object collage",
|
||||
"split editorial cover with title and single illustration",
|
||||
"big number cover with tiny caveat line"
|
||||
],
|
||||
"avoid": [
|
||||
"marketing hype",
|
||||
"too many stickers",
|
||||
"random stock photo collage",
|
||||
"unreadable small text"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
中文金融文章封面,大标题、强留白、暖橙品牌点缀,金融物件原创插画,白橘猫作为角落品牌元素,画面像编辑部攻略封面,不像银行广告,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
# 复古扁平插画风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 轻松羊毛攻略
|
||||
- 用卡姿势、活动经验
|
||||
- 不想太严肃但仍要保持金融可信度的配图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "retro-flat-illustration",
|
||||
"visual_direction": "retro flat Chinese fintech illustration, friendly, practical, low-saturation warm palette",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "warm off-white or muted pastel",
|
||||
"composition": "flat illustrated financial objects with title cards and short labels",
|
||||
"density": "medium"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "warm orange, muted teal, cream, brick red",
|
||||
"neutral": "soft brown-gray text",
|
||||
"rule": "low saturation, no luxury gold"
|
||||
},
|
||||
"typography": {
|
||||
"title": "bold rounded Chinese title",
|
||||
"labels": "short tag labels",
|
||||
"text_quality": "large and simple"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"cat with cards, receipt, calendar and calculator",
|
||||
"flat route map with currency labels",
|
||||
"coupon / cashback object board",
|
||||
"risk and correct action two-column illustration"
|
||||
],
|
||||
"avoid": [
|
||||
"overly cute childish poster",
|
||||
"fake brand logos",
|
||||
"busy retro texture",
|
||||
"tiny text"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
低饱和暖色,扁平信用卡、小票、日历、计算器和白橘猫,中文标题清楚,轻松但不夸张,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
# 小红书竖版攻略风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 小红书发布版
|
||||
- 4 图看完
|
||||
- 多图卡片 / 3:4 / 9:16
|
||||
- 手机端快速理解的活动 / 避坑 / 教程内容
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "xiaohongshu-vertical",
|
||||
"visual_direction": "2D flat infographic card by default, optional 3D soft neumorphic direction only after user confirmation, vertical mobile-first Chinese finance guide card, big readable text, series layout",
|
||||
"canvas": {
|
||||
"aspect_ratio": "3:4 or 9:16, confirm before generation",
|
||||
"background": "warm white or light orange",
|
||||
"composition": "native portrait composition, large hook title, 2-4 key blocks, cat watermark or small mascot, vertical layout redesigned for Xiaohongshu",
|
||||
"density": "low, mobile readable"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "warm orange and ink black",
|
||||
"secondary": "soft cream or pale blue",
|
||||
"rule": "high contrast text, no tiny paragraph"
|
||||
},
|
||||
"typography": {
|
||||
"title": "very large Chinese title",
|
||||
"body": "short phrases and key numbers",
|
||||
"text_quality": "mobile readable at thumbnail size"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"cover: one hook plus three tags",
|
||||
"core split: two lines or three numbers",
|
||||
"steps: 3-5 actions",
|
||||
"pitfalls: do / don't list"
|
||||
],
|
||||
"avoid": [
|
||||
"reuse of horizontal article image",
|
||||
"squeezed elements",
|
||||
"stretched card / logo / text",
|
||||
"mixing 2D and 3D visual directions in one image set",
|
||||
"long source paragraph",
|
||||
"tiny screenshot-like UI",
|
||||
"exaggerated earning promise"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
3:4 或 9:16 原生竖版,native portrait composition,vertical layout redesigned for Xiaohongshu,大字少字,手机端可读,暖橙白底,白橘猫水印,适合多图系列,包含 MAOMOMO 标识。no squeezed elements,no stretched card / logo / text。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
# 手绘便签风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 保姆式教程
|
||||
- 避坑经验、实测提醒
|
||||
- 账户维护、保号、操作步骤说明
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "handdrawn-note",
|
||||
"visual_direction": "clean handdrawn Chinese fintech note, paper-like background, warm sticky notes, arrows and circles",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "near-white paper",
|
||||
"composition": "short title, 3-5 annotated steps, cat holding warning sign",
|
||||
"density": "medium-low, one clear idea per image"
|
||||
},
|
||||
"color_palette": {
|
||||
"line": "soft dark gray",
|
||||
"accent": "MAOMOMO orange, pale yellow sticky notes, light blue marks",
|
||||
"rule": "warm but clean, not messy whiteboard"
|
||||
},
|
||||
"typography": {
|
||||
"title": "handwritten-style Chinese title",
|
||||
"labels": "short handwritten labels",
|
||||
"text_quality": "avoid long sentences"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"three-step path with handdrawn arrows",
|
||||
"pitfall map with warning tags",
|
||||
"before / correct way comparison",
|
||||
"checklist with cat reminder card"
|
||||
],
|
||||
"avoid": [
|
||||
"messy doodle board",
|
||||
"too many handwritten words",
|
||||
"childish cartoon dominance",
|
||||
"fake screenshots"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
近白纸感背景,暖橙便利贴,手绘箭头和圈注,白橘猫举提示牌,中文步骤短而清晰,适合避坑和保姆式教程,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
# 数据仪表盘风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 返现计算、费用测算
|
||||
- 活动门槛、封顶、时间线
|
||||
- 多路径、多产品、多活动对比
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "data-card-dashboard",
|
||||
"visual_direction": "bright fintech analytics dashboard for Chinese practical article, KPI cards and rule panels",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "white or very light blue-gray",
|
||||
"composition": "dashboard grid with title, KPI cards, calculation panel, timeline or warning list",
|
||||
"density": "medium-high but readable"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "data blue",
|
||||
"secondary": "cyan or slate",
|
||||
"status": "green, orange, red used sparingly",
|
||||
"brand": "MAOMOMO warm orange for key highlights"
|
||||
},
|
||||
"typography": {
|
||||
"numbers": "large tabular numerals",
|
||||
"labels": "short Chinese labels",
|
||||
"text_quality": "amounts, dates, percentages must be large and legible"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"top KPI strip plus lower explanation cards",
|
||||
"formula card plus before / after comparison",
|
||||
"timeline plus status badges",
|
||||
"route comparison table with risk dots"
|
||||
],
|
||||
"avoid": [
|
||||
"dark monitoring wall",
|
||||
"fake real dashboard screenshot",
|
||||
"random numbers",
|
||||
"dense unreadable table"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
白底,KPI 卡片、计算公式、时间线和风险标签清晰分区,金额和日期大号可读,白橘猫只做小型提示元素,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
# 暖橙金融教程风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- MAOMOMO 默认站内文章配图
|
||||
- 港卡、银行活动、返现攻略
|
||||
- 开户、转账、保号等实操教程
|
||||
- 需要亲和但可信的封面、总览图和流程图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "warm-fintech-guide",
|
||||
"visual_direction": "MAOMOMO warm fintech guide visual, friendly Chinese practical finance tutorial, orange-white cat mascot and clean cards",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9 by default",
|
||||
"background": "clean white or warm off-white",
|
||||
"composition": "large practical title, 3-5 key information cards, cat guide, clear warning or takeaway area",
|
||||
"density": "medium, practical and readable"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "MAOMOMO warm orange",
|
||||
"secondary": "soft blue-gray or teal",
|
||||
"neutral": "white, light gray, slate text",
|
||||
"rule": "warm and clean; avoid luxury finance gold and dark cyber style"
|
||||
},
|
||||
"typography": {
|
||||
"title": "bold readable Chinese title",
|
||||
"body": "short Chinese labels and key numbers",
|
||||
"text_quality": "all Chinese text must be clear and exact"
|
||||
},
|
||||
"maomomo_elements": {
|
||||
"cat": "friendly white-orange cat guide, small to medium size",
|
||||
"logo": "visible MAOMOMO text mark"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"cover title plus cat and financial objects",
|
||||
"overview cards for date, threshold, reward and pitfall",
|
||||
"step flow with arrows and warning tags",
|
||||
"final reminder card with cat holding sign"
|
||||
],
|
||||
"avoid": [
|
||||
"fake screenshots",
|
||||
"bank advertisement tone",
|
||||
"too childish mascot",
|
||||
"unverified brand logos"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
明亮白底,暖橙强调色,干净信息卡片,友好的白橘猫作为向导,中文标题和关键数字清晰,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
# 清爽专业风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 银行 / 券商规则整理
|
||||
- 开户、保号、转账教程
|
||||
- 正式但不沉闷的 MAOMOMO 站内文章
|
||||
- 需要兼顾可信度和实操感的封面、总览图、流程图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "clean-professional",
|
||||
"visual_direction": "clean professional Chinese fintech article visual, calm tutorial layout, structured evidence cards, readable and trustworthy",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9 by default",
|
||||
"background": "warm white, very light gray, or pale blue-gray",
|
||||
"composition": "clear title zone, structured content cards, one concise takeaway strip",
|
||||
"density": "medium, enough detail without looking like a bank advertisement"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "professional blue or slate",
|
||||
"secondary": "teal or muted cyan",
|
||||
"accent": "MAOMOMO warm orange for highlights",
|
||||
"neutral": "slate gray, pale border, white card background"
|
||||
},
|
||||
"typography": {
|
||||
"title": "bold readable Chinese sans-serif",
|
||||
"body": "clean Chinese sans-serif, left aligned",
|
||||
"text_quality": "Chinese text must be exact and readable"
|
||||
},
|
||||
"maomomo_elements": {
|
||||
"cat": "small white-orange cat guide near title or takeaway, not the main subject",
|
||||
"logo": "MAOMOMO text mark in a corner or title area"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"title plus three key cards",
|
||||
"left process diagram plus right pitfall cards",
|
||||
"top conclusion strip plus lower rule table",
|
||||
"timeline plus final reminder"
|
||||
],
|
||||
"avoid": [
|
||||
"official bank advertisement tone",
|
||||
"luxury finance gold",
|
||||
"dense tiny tables",
|
||||
"fake app screenshots"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
浅色背景,蓝绿与暖橙强调色,结构化信息卡片,清晰中文标题和步骤,白橘猫作为小型向导,包含 MAOMOMO 标识,不伪造真实 App 截图。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
# 清爽编辑部风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- SEO 长文封面
|
||||
- 解释型文章、经验总结
|
||||
- 品牌感更强但仍克制的站内头图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "clean-editorial",
|
||||
"visual_direction": "clean editorial Chinese fintech article image, calm magazine-like cover, readable and restrained",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "white, off-white, or light warm gray",
|
||||
"composition": "large title, one clear visual metaphor, small MAOMOMO brand mark and cat accent",
|
||||
"density": "low to medium"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "ink black or deep slate",
|
||||
"accent": "MAOMOMO orange",
|
||||
"secondary": "soft blue-gray or light beige",
|
||||
"rule": "minimal palette, no noisy decoration"
|
||||
},
|
||||
"typography": {
|
||||
"title": "large clean Chinese headline",
|
||||
"body": "one short subtitle or 2-3 labels",
|
||||
"text_quality": "title must be exact and readable"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"headline left, visual object right",
|
||||
"centered title with small metadata and cat mark",
|
||||
"big keyword plus supporting labels",
|
||||
"calm article cover with one metaphor object"
|
||||
],
|
||||
"avoid": [
|
||||
"clickbait poster",
|
||||
"dense dashboard cards",
|
||||
"too many icons",
|
||||
"official logo misuse"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
白底大标题,轻杂志排版,暖橙点缀,留白充足,白橘猫作为角落品牌元素,适合中文金融经验文章封面,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
# 电子墨水杂志风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 深度解释型长文
|
||||
- 观点文章、行业观察
|
||||
- 需要更克制、更高级的站内封面
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "e-ink-editorial",
|
||||
"visual_direction": "electronic ink editorial Chinese finance article visual, paper texture, strong headline, restrained layout",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "off-white paper, ink black, or deep indigo",
|
||||
"composition": "editorial grid with large headline, metadata strip, one abstract financial visual",
|
||||
"density": "low to medium"
|
||||
},
|
||||
"color_palette": {
|
||||
"base": "off-white and ink black",
|
||||
"accent": "one restrained MAOMOMO orange or indigo accent",
|
||||
"rule": "avoid colorful dashboard look"
|
||||
},
|
||||
"typography": {
|
||||
"title": "large editorial Chinese headline",
|
||||
"metadata": "small source/date/risk labels",
|
||||
"text_quality": "few words, exact wording"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"large headline with metadata strip",
|
||||
"big quote and one visual plate",
|
||||
"huge number with footnote",
|
||||
"two-column argument spread"
|
||||
],
|
||||
"avoid": [
|
||||
"cute sticker overload",
|
||||
"dashboard cards",
|
||||
"shiny gradients",
|
||||
"dense tables"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
纸感背景,黑白灰为主,少量暖橙强调,大标题和元信息条,原创抽象金融视觉,克制高级,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
# 科研答辩风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 多来源条款核对
|
||||
- 规则严谨拆解
|
||||
- 政策、合规、券商或银行条款解释
|
||||
- 需要“证据链”感的文章配图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "scientific-defense",
|
||||
"visual_direction": "clean academic defense style adapted for Chinese fintech article, evidence-driven, source-aware",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "white or pale gray",
|
||||
"composition": "research question / evidence / conclusion / caveat sections",
|
||||
"density": "medium-high but organized"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "deep blue or slate",
|
||||
"accent": "MAOMOMO orange for caveats and key conclusion",
|
||||
"neutral": "gray lines and pale panels"
|
||||
},
|
||||
"typography": {
|
||||
"title": "formal bold Chinese title",
|
||||
"labels": "source, condition, limitation labels",
|
||||
"text_quality": "precise Chinese terms"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"source comparison matrix",
|
||||
"condition tree plus final conclusion",
|
||||
"timeline of official rules",
|
||||
"evidence cards with risk caveat"
|
||||
],
|
||||
"avoid": [
|
||||
"over-promotional activity poster",
|
||||
"uncited absolute claim",
|
||||
"fake official document screenshot",
|
||||
"decorative cat dominance"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
浅色严谨版式,来源卡片、条件树、证据矩阵和结论区清晰,暖橙强调风险提示,白橘猫只作为小型品牌元素,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
# 麦肯锡简报风
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 多方案路径选择
|
||||
- 银行 / 券商 / 通道对比
|
||||
- 成本、收益、风险取舍
|
||||
- 给读者一个“怎么选”的决策图
|
||||
|
||||
## MAOMOMO 出图 Brief
|
||||
|
||||
```json
|
||||
{
|
||||
"style_name": "mckinsey-brief",
|
||||
"visual_direction": "consulting brief style adapted for MAOMOMO practical finance article, conclusion first, precise matrix and tradeoff chart",
|
||||
"canvas": {
|
||||
"aspect_ratio": "16:9",
|
||||
"background": "white",
|
||||
"composition": "top takeaway, 2x2 matrix or comparison table, bottom recommendation",
|
||||
"density": "medium-high"
|
||||
},
|
||||
"color_palette": {
|
||||
"primary": "deep navy or black",
|
||||
"accent": "MAOMOMO orange plus one strong blue",
|
||||
"neutral": "gray grid lines and white panels"
|
||||
},
|
||||
"typography": {
|
||||
"title": "assertive Chinese takeaway headline",
|
||||
"body": "short consulting-style labels",
|
||||
"text_quality": "precise, not salesy"
|
||||
},
|
||||
"layout_patterns": [
|
||||
"2x2 decision matrix",
|
||||
"option comparison table",
|
||||
"cost-risk-benefit tradeoff chart",
|
||||
"recommendation waterfall"
|
||||
],
|
||||
"avoid": [
|
||||
"pretending to give financial advice",
|
||||
"luxury consulting cosplay",
|
||||
"tiny footnotes",
|
||||
"overconfident guarantees"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Prompt 片段
|
||||
|
||||
```text
|
||||
结论先行,白底深色标题,2x2 矩阵或方案对比表,暖橙突出推荐路径和风险提醒,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
@@ -0,0 +1,223 @@
|
||||
# MAOMOMO 推荐配图风格
|
||||
|
||||
需要选择、推荐、复用或调整配图风格时,读本文件。风格名要与 `scripts/maomomo_image_gen.py` 的 `--style` 保持一致。
|
||||
|
||||
## 风格参考文件
|
||||
|
||||
详细出图 brief 放在 `references/image-styles/`,需要使用某个风格生成图片前,优先读取对应文件:
|
||||
|
||||
- `references/image-styles/清爽专业风.md` -> `clean-professional`
|
||||
- `references/image-styles/创意杂志风.md` -> `creative-magazine`
|
||||
- `references/image-styles/暖橙金融教程风.md` -> `warm-fintech-guide`
|
||||
- `references/image-styles/清爽编辑部风.md` -> `clean-editorial`
|
||||
- `references/image-styles/数据仪表盘风.md` -> `data-card-dashboard`
|
||||
- `references/image-styles/手绘便签风.md` -> `handdrawn-note`
|
||||
- `references/image-styles/复古扁平插画风.md` -> `retro-flat-illustration`
|
||||
- `references/image-styles/电子墨水杂志风.md` -> `e-ink-editorial`
|
||||
- `references/image-styles/科研答辩风.md` -> `scientific-defense`
|
||||
- `references/image-styles/麦肯锡简报风.md` -> `mckinsey-brief`
|
||||
- `references/image-styles/小红书竖版攻略风.md` -> `xiaohongshu-vertical`
|
||||
|
||||
这些风格参考借鉴 `codex-ppt-skill` 的风格 brief 结构,但已改写为 MAOMOMO 文章配图场景:必须保留 MAOMOMO 标识、白橘猫品牌元素、中文可读性和“不伪造真实截图”的边界。
|
||||
|
||||
## 推荐顺序
|
||||
|
||||
默认优先推荐 `warm-fintech-guide` 或 `clean-professional`。如果文章明显更偏数字、对比、避坑、深度解释或小红书,再切换到更合适的风格。
|
||||
|
||||
| 风格名 | 优先使用场景 | 不适合 |
|
||||
|---|---|---|
|
||||
| `warm-fintech-guide` | 常规 MAOMOMO 文章、港卡、银行活动、返现攻略、开户教程 | 数字极密集的计算页、小红书竖版 |
|
||||
| `clean-professional` | 正式规则整理、开户教程、保号教程、综合攻略 | 传播型封面、小红书竖版 |
|
||||
| `data-card-dashboard` | 返现计算、费用测算、活动门槛、产品对比、时间线 | 情绪化避坑故事、截图驱动步骤 |
|
||||
| `clean-editorial` | SEO 长文、总结盘点、品牌感封面、解释型文章 | 需要强步骤感或大量数字卡片的图 |
|
||||
| `creative-magazine` | 活动盘点、传播型封面、观点总结 | 严肃规则核对、密集数据表 |
|
||||
| `handdrawn-note` | 避坑经验、保号教程、操作步骤、实测提醒 | 严肃金融报告感、密集表格 |
|
||||
| `retro-flat-illustration` | 轻松羊毛攻略、用卡姿势、经验分享 | 严肃合规解释、复杂来源核对 |
|
||||
| `e-ink-editorial` | 深度解释、行业观察、观点长文封面 | 活动步骤教程、返现计算图 |
|
||||
| `scientific-defense` | 条款核对、政策规则、证据链解释 | 轻松传播封面、小红书图 |
|
||||
| `mckinsey-brief` | 多方案对比、路径选择、决策矩阵 | 情绪化故事、轻松插画 |
|
||||
| `xiaohongshu-vertical` | 小红书、4 图看完、手机端竖版内容 | 常规网站横版封面和正文配图 |
|
||||
|
||||
## warm-fintech-guide
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 warm-fintech-guide,因为它最贴近 MAOMOMO 默认视觉:白底、暖橙、猫咪向导和金融教程卡片,适合大多数港卡、银行活动和返现攻略。
|
||||
```
|
||||
|
||||
视觉要点:
|
||||
|
||||
- 明亮白底,暖橙色强调。
|
||||
- 白橘猫作为向导、提示牌或角落品牌元素。
|
||||
- 信息卡片清晰,数字、日期和步骤不拥挤。
|
||||
- 适合 16:9 横版封面、总览图、流程图。
|
||||
|
||||
提示词片段:
|
||||
|
||||
```text
|
||||
明亮白底,暖橙强调色,干净卡片布局,友好的白橘猫作为向导,现代 App 信息卡片质感,中文标题清楚,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
## data-card-dashboard
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 data-card-dashboard,因为这篇重点是规则、门槛和金额,读者需要一眼看懂数字和限制。
|
||||
```
|
||||
|
||||
视觉要点:
|
||||
|
||||
- 指标卡、对比表、时间线、计算公式。
|
||||
- 数字层级强,适合“封顶”“门槛”“成本”“到账时间”。
|
||||
- 猫咪比例较小,只做提示和品牌识别。
|
||||
- 适合返现计算图、路线对比图、活动总览图。
|
||||
|
||||
提示词片段:
|
||||
|
||||
```text
|
||||
白底,暖橙和蓝灰辅助色,使用指标卡、表格、时间线和计算公式展示关键信息,中文数字清晰可读,猫咪作为小型提示元素,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
## clean-professional
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 clean-professional,因为这篇需要可信、清楚、像一份认真整理过的规则说明,但又不能像官方宣传稿。
|
||||
```
|
||||
|
||||
详细 brief:`references/image-styles/清爽专业风.md`
|
||||
|
||||
## creative-magazine
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 creative-magazine,因为这篇更适合作为传播型封面,需要标题有吸引力,同时保持 MAOMOMO 的实操可信度。
|
||||
```
|
||||
|
||||
详细 brief:`references/image-styles/创意杂志风.md`
|
||||
|
||||
## clean-editorial
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 clean-editorial,因为这篇更像站内解释型长文,需要封面干净、标题有传播感,但不要太像促销海报。
|
||||
```
|
||||
|
||||
视觉要点:
|
||||
|
||||
- 大标题、留白、轻杂志感。
|
||||
- 猫咪放在角落或标题区附近,不抢正文信息。
|
||||
- 更适合封面、文章总结图、观点图。
|
||||
- 画面克制,避免过度卡通。
|
||||
|
||||
提示词片段:
|
||||
|
||||
```text
|
||||
白底大标题,轻杂志排版,暖橙点缀,留白充足,白橘猫作为角落品牌元素,适合中文金融经验文章封面,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
## retro-flat-illustration
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 retro-flat-illustration,因为这篇偏轻松经验分享,用低饱和扁平插画能更亲和,但不会显得像银行广告。
|
||||
```
|
||||
|
||||
详细 brief:`references/image-styles/复古扁平插画风.md`
|
||||
|
||||
## handdrawn-note
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 handdrawn-note,因为这篇是避坑 / 步骤教程,手绘箭头、便签和圈注更容易把翻车点讲明白。
|
||||
```
|
||||
|
||||
视觉要点:
|
||||
|
||||
- 手绘箭头、便签、圈注、风险提示贴纸。
|
||||
- 适合“不要这样做”“先做这一步”“最大坑点”。
|
||||
- 信息密度中等,避免塞太多长句。
|
||||
- 猫咪可以举提示牌或指向关键步骤。
|
||||
|
||||
提示词片段:
|
||||
|
||||
```text
|
||||
白底纸感,暖橙便利贴,手绘箭头和圈注,白橘猫举提示牌,中文步骤清楚,适合避坑经验和保姆式教程,包含 MAOMOMO 标识。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
## e-ink-editorial
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 e-ink-editorial,因为这篇更偏深度解释或观点长文,电子墨水杂志风会更克制、更有阅读质感。
|
||||
```
|
||||
|
||||
详细 brief:`references/image-styles/电子墨水杂志风.md`
|
||||
|
||||
## scientific-defense
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 scientific-defense,因为这篇需要核对多份官方材料,重点不是好看,而是把证据、条件和结论讲清楚。
|
||||
```
|
||||
|
||||
详细 brief:`references/image-styles/科研答辩风.md`
|
||||
|
||||
## mckinsey-brief
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 mckinsey-brief,因为这篇要帮读者在多个路径里做选择,用矩阵和取舍图会更直接。
|
||||
```
|
||||
|
||||
详细 brief:`references/image-styles/麦肯锡简报风.md`
|
||||
|
||||
## xiaohongshu-vertical
|
||||
|
||||
推荐语:
|
||||
|
||||
```text
|
||||
我建议用 xiaohongshu-vertical,并默认选 2D 扁平风,因为用户要发小红书,应该重新做 3:4 或 9:16 原生竖图,而不是复用站内横版图;2D 信息卡片更清爽,中文文字也更稳。
|
||||
```
|
||||
|
||||
视觉要点:
|
||||
|
||||
- 3:4 或 9:16 竖版,手机端可读。
|
||||
- 出图前先确认 `2D 扁平风` 或 `3D 轻拟物风`;默认推荐 2D,3D 适合封面、活动感和金融产品展示。
|
||||
- 全组保持同一 `visual_direction`,不得中途混用。
|
||||
- 大字少字,标题钩子强。
|
||||
- 4 图系列统一:封面、核心拆解、操作流程、避坑总结。
|
||||
- 每张图都有 MAOMOMO 标识或猫咪水印。
|
||||
- 原生竖版重新构图,禁止横版硬塞;元素、卡片、Logo 和文字不能压扁或拉伸。
|
||||
|
||||
提示词片段:
|
||||
|
||||
```text
|
||||
3:4 或 9:16 原生竖版,native portrait composition,vertical layout redesigned for Xiaohongshu,手机端可读,大标题,少量关键数字,暖橙白底,白橘猫水印,适合多图系列的中文金融攻略,包含 MAOMOMO 标识。no squeezed elements,no stretched card / logo / text。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
## 风格选择输出格式
|
||||
|
||||
向用户推荐时使用:
|
||||
|
||||
```text
|
||||
我建议用 A,因为【一句话说明与文章类型的匹配】。
|
||||
|
||||
A. warm-fintech-guide(推荐):【适合原因】
|
||||
B. data-card-dashboard:【适合原因】
|
||||
C. handdrawn-note:【适合原因】
|
||||
|
||||
你选哪个?如果是小红书卡片,也请确认用 2D 扁平风还是 3D 轻拟物风;没指定我会默认用 2D。
|
||||
```
|
||||
|
||||
用户选择后,把风格名写入图片清单的 `风格` 字段或 manifest 的 `style` 字段。
|
||||
@@ -0,0 +1,112 @@
|
||||
# 来源与事实核查规则
|
||||
|
||||
写最终稿前,只要涉及费用、活动、返现、奖励、账户规则、开户资格、转账路径、数字货币出入金、税务、法律或合规事项,就先读本文件。
|
||||
|
||||
## 来源读取顺序
|
||||
|
||||
优先级从高到低:
|
||||
|
||||
1. 用户提供的最新官方材料:活动页、条款 PDF、App 截图、邮件、公告、客服记录。
|
||||
2. 官方网站、官方 App、官方 PDF 或官方公告。
|
||||
3. 用户提供的实测记录、账单截图、到账记录和社群反馈。
|
||||
4. MAOMOMO 现有文章或用户指定参考文章。
|
||||
5. 其他公开资料。
|
||||
|
||||
如果用户要求“最新”“当前”“今天还能不能用”等时效性判断,必须联网或读取用户提供的新材料确认。不要只依赖旧文章或模型记忆。
|
||||
|
||||
## 已读来源清单
|
||||
|
||||
涉及活动、金融产品或官方规则时,先向用户列出来源读取状态:
|
||||
|
||||
```text
|
||||
已读取来源:
|
||||
- 官方活动页:已读 / 未读 / 无法读取
|
||||
- 主活动条款 PDF:已读 / 未读 / 无法读取
|
||||
- 补充条款 PDF:已读 / 未读 / 无法读取
|
||||
- App / 邮件 / 客服截图:已读 / 未读 / 无法读取
|
||||
- 用户实测记录:已读 / 未读 / 无法读取
|
||||
- 参考文章:已读 / 未读 / 无法读取
|
||||
```
|
||||
|
||||
如果有多个 PDF 或多个官方页面,不能只读其中一部分就写最终稿。无法读取时要说明影响,例如“无法确认活动截止日,只能标待确认”。
|
||||
|
||||
## 事实表
|
||||
|
||||
写稿前整理这些字段,缺失就标 `【待确认:...】`:
|
||||
|
||||
| 字段 | 要确认的内容 |
|
||||
|---|---|
|
||||
| 时间 | 活动开始日、截止日、登记日、交易日、入账日、奖励发放日 |
|
||||
| 对象 | 地区、年龄、账户类型、新老客户、卡种、会员等级、邀请渠道 |
|
||||
| 门槛 | 单笔 / 累计金额、交易次数、入金金额、持有期、任务顺序 |
|
||||
| 奖励 | 比例、金额、封顶、名额、是否先到先得、是否可叠加 |
|
||||
| 排除项 | 不支持渠道、排除 MCC、转账 / 充值 / 现金类交易、退款规则 |
|
||||
| 成本 | 年费、汇款费、点差、交易费、税费、潜在机会成本 |
|
||||
| 实测 | 用户实测是否成功、按账号还是按卡、多久到账、账单怎么显示 |
|
||||
| 风险 | 风控、活动变更、资格审核、投资波动、汇率、税务和合规限制 |
|
||||
|
||||
## 口径冲突处理
|
||||
|
||||
- 最新官方条款优先于旧活动页。
|
||||
- App 实际显示优先于旧文章截图。
|
||||
- 官方条款用于确定边界、日期、排除项和资格;用户实测用于组织“怎么吃”“哪里会翻车”。
|
||||
- 用户确认的实测口径必须直接写成结论,不要写成模棱两可的“可能”“疑似”“大概”。例如素材写“实测 CPF 现场办理成功立即生效”,正文应写“实测 CPF 现场办理成功后立即生效”,不要写“可能立即生效”。
|
||||
- 用户素材包含“以往惯例”“过往尿性”“过往经验”“按之前案例”“历史上很多人这样成功”等表达时,正文要写成强概率判断,而不是弱猜测。推荐写法是“按过往案例判断,大概率仍然可以...”“从以往执行口径看,本次大概率还是...”。不要降级成“可能可以”“或许可以”。
|
||||
- 当官方条款和用户实测 / 过往经验冲突时,写法要分层:先交代官方条款,再给实测或惯例判断。例如“官方写明合资格消费不包含支付宝 / 微信,但按照建亚 Travo 过往案例,很多人仍然用支付宝 / 微信消费达标并拿到 4% 返现和迎新奖励;所以本次活动大概率仍可用支付宝 / 微信消费达标,最终以银行系统入账和奖励判定为准。”
|
||||
- 如果主活动、额外会员活动、年龄限定活动或旧版活动混在一起,先建议主文只保留大多数读者适用内容,小众分支放“补充说明”或删去。
|
||||
- 来源差异无法解决时,在正文用保守表达,并在来源区说明以官方条款和 App 实际显示为准。
|
||||
|
||||
## 实测和惯例语气规则
|
||||
|
||||
用户给出的素材里,如果明确包含以下证据类型,按对应语气写:
|
||||
|
||||
| 素材类型 | 正文语气 | 示例 |
|
||||
|---|---|---|
|
||||
| 明确实测成功 | 确定语气,直接写结果 | “实测现场办理成功后立即生效。” |
|
||||
| 明确实测失败 | 确定语气,直接写不能用 / 不生效 / 不计入 | “实测这条路径不会计入任务。” |
|
||||
| 用户确认的账单 / 到账 / 出券结果 | 确定语气,说明观察到的结果 | “账单显示按支付宝账号计算。” |
|
||||
| 过往惯例 / 过往尿性 / 历史案例 | 强概率判断 | “按过往案例判断,本次大概率仍可用支付宝 / 微信消费达标。” |
|
||||
| 只有猜测、没有实测或历史依据 | 保守语气 | “这部分需要二次确认。” |
|
||||
|
||||
禁止把强证据写弱:
|
||||
|
||||
- 不要把“实测立即生效”写成“可能立即生效”。
|
||||
- 不要把“很多人过往用支付宝 / 微信达标成功”写成“也许可以用支付宝 / 微信”。
|
||||
- 不要把“用户确认按账号算”写成“更像是按账号算”。
|
||||
|
||||
允许保留的风险尾巴:
|
||||
|
||||
- “最终以银行系统记录和奖励入账为准。”
|
||||
- “如果这次银行调整后台判定,结果可能变化。”
|
||||
- “操作前仍建议看一眼 App 活动页是否有新增限制。”
|
||||
|
||||
风险尾巴不能推翻主句。主句应先给明确判断,再补边界。例如写“本次大概率仍可用支付宝 / 微信消费达标,最终以银行系统判定为准”,不要写“可能可以用支付宝 / 微信”。
|
||||
|
||||
## 风险表达
|
||||
|
||||
禁止:
|
||||
|
||||
- “稳赚”“无风险套利”“一定到账”“一定获批”“闭眼稳赚”“保证拿满”。
|
||||
- 把股票、ETF、基金、数字货币、汇率敞口说成存款或保本产品。
|
||||
- 替用户给出法律、税务、投资或合规结论。
|
||||
|
||||
推荐:
|
||||
|
||||
- “适合本来就有相关消费 / 入金 / 交易需求的人。”
|
||||
- “最终是否计入活动,以银行 / 券商系统记录为准。”
|
||||
- “活动可能随时调整,操作前建议二次确认 App 和官方条款。”
|
||||
- “股票 / ETF / 数字货币存在价格波动、汇率、买卖差价、成交滑点、交易成本和税务风险。”
|
||||
|
||||
默认免责声明:
|
||||
|
||||
```text
|
||||
本文为经验整理,不构成金融、投资、法律或税务建议;活动和规则可能随时调整,请以官方条款、App 实际显示及当地法规为准。
|
||||
```
|
||||
|
||||
## 来源区
|
||||
|
||||
- 所有链接统一放在文末 `## 来源` 区。
|
||||
- 来源区只保留最终文章实际使用的来源。
|
||||
- 引用 MAOMOMO 时链接具体文章或页面,不只放首页。
|
||||
- 用户要求删除某个活动分支时,对应来源也要删除。
|
||||
- 发布用 Markdown 不保留 `contentReference`、`oaicite`、模型内部引用或工具残留。
|
||||
@@ -27,6 +27,11 @@ MAOMOMO 是偏实操的出海经验平台。公开站点标语是:“打破信
|
||||
- 空泛的 SEO 背景介绍;
|
||||
- 投资建议、法律建议、税务建议或结果保证。
|
||||
|
||||
金额写法:
|
||||
|
||||
- 发布稿、图片文案、prompt、manifest、`deck_spec.json` 和 `speech.md` 中,港币金额统一写 `HKD 1,000`。
|
||||
- 不使用 `HK$1,000`、`港币 1,000`、`1,000 港币` 作为发布写法;来源原文摘录或 OCR 可保留原样。
|
||||
|
||||
## 常见文章类型
|
||||
|
||||
### 1. 活动 / 返现 / 奖励攻略
|
||||
@@ -62,7 +67,7 @@ MAOMOMO 是偏实操的出海经验平台。公开站点标语是:“打破信
|
||||
|
||||
写法要点:
|
||||
|
||||
- 把实用公式放前面,例如 `HK$190 ÷ 9% ≈ HK$2112`。
|
||||
- 把实用公式放前面,例如 `HKD 190 ÷ 9% ≈ HKD 2,112`。
|
||||
- 直接写出“超过部分 = 没额外收益(不用多刷)”。
|
||||
- 不支持的渠道要明确点名,例如“不支持支付宝 / 微信”。
|
||||
- 活动要写开始日期、结束日期,以及本文是否“持续更新”。
|
||||
@@ -171,18 +176,21 @@ MAOMOMO 是偏实操的出海经验平台。公开站点标语是:“打破信
|
||||
- 核心结论可以放在引用块里。
|
||||
- 结尾提醒政策变化风险,例如“直至另行通知”“随时可能取消”。
|
||||
|
||||
### 6. 小红书发布版 / 4 图看完
|
||||
### 6. 小红书卡片模式 / 多图发布
|
||||
|
||||
用户说“小红书”“发小红书”“4图看完”“超短文字”“20字内标题”时,进入小红书模式。
|
||||
用户说“小红书”“卡片”“多图”“发小红书”“4图看完”“3:4”“9:16”“超短文字”“20字内标题”时,进入小红书卡片模式。
|
||||
|
||||
小红书卡片模式是独立交付模式,不默认走完整文章 + Markdown + ZIP 流程。除非用户明确要求文章或打包,否则重点交付多张统一风格的独立 PNG,以及可复制的小红书标题、正文和话题标签。
|
||||
|
||||
交付物:
|
||||
|
||||
- 20 字内标题。
|
||||
- 超短正文,适合直接复制发布。
|
||||
- 3-6 个话题标签。
|
||||
- 4 张 9:16 竖版 PNG 图,不复用文章横版图。
|
||||
- 多张 3:4 或 9:16 竖版 PNG 图,默认 4 张;不复用文章横版图。
|
||||
- 每张图加入 MAOMOMO logo / 猫咪水印。
|
||||
- 如果用户要求 email,按用户要求打包或多附件发送。
|
||||
- `final_manifest.json`,只列通过 QA 且进入 `assets/` 的最终图片。
|
||||
- GitHub 分批交付链接;Telegram / 聊天只通知进度和链接,不承载大 ZIP 或大图包。
|
||||
|
||||
文字规则:
|
||||
|
||||
@@ -200,12 +208,27 @@ MAOMOMO 是偏实操的出海经验平台。公开站点标语是:“打破信
|
||||
|
||||
视觉规则:
|
||||
|
||||
- 纵向 9:16,手机端可读,大字少字。
|
||||
- 出图前必须先确认比例:`3:4` 或 `9:16`。
|
||||
- 出图前必须先确认视觉方向:`2D 扁平风` 或 `3D 轻拟物风`。
|
||||
- 用户没指定时,默认推荐 `2D 扁平风`:清爽、信息卡片、适合教程 / 规则说明,中文文字更稳。
|
||||
- `3D 轻拟物风` 更适合封面、活动感、金融产品展示;一旦确认,整组不能和 2D 混用。
|
||||
- 纵向原生构图,手机端可读,大字少字。
|
||||
- 每张图独立,不要拼图。
|
||||
- 每张图有 MAOMOMO 标识。
|
||||
- 文章横图不能直接当小红书图;需要重新设计。
|
||||
- 禁止把横版设计硬塞进 3:4 或 9:16;prompt 必须包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。
|
||||
- 如果用户提供卡片、截图或 Logo 参考,只做原创示意,不复制敏感信息,不伪造真实 App 页面。
|
||||
|
||||
最终回复固定包含:
|
||||
|
||||
- GitHub 链接。
|
||||
- 图片数量。
|
||||
- 风格:2D / 3D。
|
||||
- 比例:3:4 / 9:16。
|
||||
- 批次数量。
|
||||
- QA 是否完成。
|
||||
- 已知限制。
|
||||
|
||||
### 5. 避坑经验
|
||||
|
||||
用于“不要浪费时间试了”、DCC、支付失败、转账姓名顺序问题、线下网点提醒等。
|
||||
@@ -364,12 +387,12 @@ tags:
|
||||
基础方向:
|
||||
|
||||
```text
|
||||
MAOMOMO 风格封面图,干净的中文金融教程缩略图,友好的白橘猫吉祥物,圆脸、小爪子、暖橙色点缀,结合香港金融 / 信用卡 / 券商 / 转账场景,预留清晰中文标题区域,现代 App 仪表盘质感,实用而非奢华,明亮白色背景,少量爪印细节,不使用未经提供的真实银行标志,不伪造真实截图界面,16:9。
|
||||
干净的中文金融实操文章封面,友好的白橘猫吉祥物,圆脸、小爪子,暖橙色点缀,结合香港金融 / 信用卡 / 券商 / 转账场景,预留清晰中文标题区域,现代 App 信息卡片质感,实用而非奢华,明亮白色背景,少量爪印细节,包含清晰的 MAOMOMO 主标识,已核对的银行 / 支付 / 卡组织标识只做小型辅助识别,不伪造真实截图界面,16:9。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
按主题追加对象:
|
||||
|
||||
- 信用卡 / 返现:香港信用卡、超市购物篮、返现计算器、小票、银联 / Apple Pay 风格的通用符号。
|
||||
- 信用卡 / 返现:香港信用卡、超市购物篮、返现计算器、小票、通用支付符号;只有官方材料或用户素材确认后才画具体卡组织 / 支付网络 / 支付机构 Logo,且不能抢 MAOMOMO 主标识位置。
|
||||
- 银行账户 / 保号:保险库、账户余额仪表、ETF / 股票图表卡片、日历、警示标。
|
||||
- 转账 / 入金:路线箭头、HKD / EUR / USD 币种标签、手机转账界面插画、勾选标记。
|
||||
- 券商活动:股票礼品卡、K 线图、欢迎信封、香港天际线线稿。
|
||||
@@ -408,7 +431,7 @@ MAOMOMO 风格封面图,干净的中文金融教程缩略图,友好的白橘
|
||||
提示词模板:
|
||||
|
||||
```text
|
||||
生成一张干净的 MAOMOMO 风格中文信息图,使用暖橙色点缀和小型白橘猫吉祥物。展示【计算公式 / 路线 / 时间线】。画面使用简洁卡片、箭头、勾选标记、风险提示标签和清晰中文标注。整体为实用金融教程风,白色背景,不使用伪造官方标志,16:9。
|
||||
生成一张干净的中文金融实操信息图,使用暖橙色点缀和小型白橘猫吉祥物。展示【计算公式 / 路线 / 时间线】。画面使用简洁卡片、箭头、勾选标记、风险提示标签和清晰中文标注,白色背景,不伪造官方标志,16:9。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
## 来源处理
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
# MAOMOMO 配图与视觉资产规则
|
||||
|
||||
需要生成、准备、插入或更新配图时,先读本文件。
|
||||
|
||||
## 配图交付原则
|
||||
|
||||
- 完整文章默认配独立 PNG:短文章 4-6 张,普通攻略 6-8 张,长教程 / 多活动整理 8-12 张。
|
||||
- 不为凑数量硬加图;每张图必须服务正文。
|
||||
- 每张图独立生成或准备,不要用一张拼图、长图或总图代替全部。
|
||||
- 出图前先把全部图片 prompt 写入 `prompts/` 或 manifest 文件;不要在聊天里输出超长 prompt 代替文件。
|
||||
- 先只生成一张样张,优先封面图;用户确认样张前不得生成剩余图片。
|
||||
- 样张确认后,后续图片使用同一生图后端、同一视觉系统和同一质量配置。
|
||||
- 图片生成按“一张图一个 job / 一次请求 / 一次 QA”推进;运行环境支持子 agent 时,可以一图一个子 agent 并发,但不要一次请求多张图。
|
||||
- 最终原始图保存到 `origin_image/`,通过 QA 后同步到 Markdown 引用的 `assets/`。
|
||||
- 生成图必须是“示意图 / 信息图 / 教程视觉”,不得伪造真实 App 截图、账单或官方页面。
|
||||
- 用户提供真实截图时,优先用真实截图,并隐去姓名、账号、余额、地址、手机号、二维码、交易号等个人信息。
|
||||
- 真实银行 / 券商 / 钱包截图、账单、交易记录、客服记录等默认视为敏感素材;需要上传图片 API 做编辑或重绘前,必须先向用户确认实际 API 地址 / baseURL、接口归属、是否为用户自有兼容服务以及是否允许外发。不要默认把配置里的图片 API 视为公共第三方,也不要在未确认具体接收方时上传真实截图。若用户未确认、确认不允许或安全策略 / 上下文不允许外发,即使用户要求“用 GPT Image2 美化截图”,也改用“GPT Image2 生成空白展示模板 + 本地嵌入真实截图”的流程。
|
||||
- 空白模板流程:图片后端只生成不含真实截图、账单文字、交易数字和伪造 App UI 的展示框 / 卡片背景;随后用本地工具把真实截图完整嵌入预留区域。本地嵌入可以做裁切适配、圆角、阴影、边框和背景排版,但不得修改截图内部文字、金额、日期、账户、交易描述或任何证据内容。
|
||||
- 使用空白模板流程时,`prompt` / manifest / `deck_spec.json` / 状态文件必须记录模板生成后端、真实截图本地嵌入方式、原截图路径和 QA 结论;最终回复说明真实截图未上传到图片接口。
|
||||
- 每张图必须包含清晰可见的 `MAOMOMO` 标识。
|
||||
- 每张图的可见文字必须受控:只允许出现该图指定的业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识,不得添加副标题、风格标签、prompt 描述、说明文字、额外 slogan 或无关文字。
|
||||
- 不要用 Playwright、HTML/CSS、SVG、Pillow、canvas、本地拼贴或手工覆盖文字替代图片生成后端。
|
||||
|
||||
|
||||
## 平铺水印
|
||||
|
||||
用户要求“水印”“平铺水印”“防盗图”“打水印”时,对最终配图生成水印副本。默认规格:
|
||||
|
||||
- 水印文字:`MAOMOMO.COM`
|
||||
- 角度:45°
|
||||
- 颜色:自动对比色:浅底黑字 `#000000`,深底白字 `#FFFFFF`
|
||||
- 透明度:半透明,默认 alpha `70/255`
|
||||
- 排列:对角线平铺
|
||||
- 工具:`uv run python scripts/maomomo_tile_watermark.py`
|
||||
- 密度:每张图至少可见 2 个完整 `MAOMOMO.COM`;默认 `font-size=50`、`spacing=28`。
|
||||
|
||||
执行示例:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_tile_watermark.py \
|
||||
--input assets \
|
||||
--output watermarked_assets
|
||||
```
|
||||
|
||||
单张图:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_tile_watermark.py \
|
||||
--input assets/example.png \
|
||||
--output watermarked_assets/example.png
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 水印只加到最终 QA 通过的 `assets/` 或交付副本;不要在样张确认前加水印,避免影响用户判断画面文字质量。
|
||||
- 默认保留无水印原图在 `origin_image/` 和 / 或 `assets/`;水印图输出到 `watermarked_assets/`,或 GitHub 交付批次中的 `watermarked/`。
|
||||
- 不要对官方截图证据原图、需要继续发给图片模型编辑的素材、尚未 QA 的图直接覆盖水印。
|
||||
- `final_manifest.json` 应记录 `watermark` 字段,至少包含 `text`、`angle`、`color_rule`、`brightness_threshold`、`alpha`、`font_size`、`spacing`、`density_rule`、`output_dir` 和水印文件清单。
|
||||
- 交付回复中说明是否提供水印版,以及无水印原图是否保留。
|
||||
|
||||
## 可见文字与 prompt 边界
|
||||
|
||||
- 视觉风格只作为后台画面描述,不得成为画面文字。
|
||||
- 生成用 prompt 不要写容易被模型照抄到画面里的中文风格标签,例如“暖橙金融教程风”“数据仪表盘风”“手绘便签风”“清爽编辑部风”。
|
||||
- 把风格名改写成可观察的视觉描述:白底、暖橙强调、信息卡片、猫咪向导、清晰标题、留白充足、表格感、时间线、轻纸感等。
|
||||
- prompt 必须写明:画面只允许出现指定业务文案、`MAOMOMO` 和 `allowed_brand_marks` 中已核对允许出现的品牌 / 支付标识;不要添加副标题、风格标签、prompt 描述、说明文字、额外 slogan 或无关文字。
|
||||
- `style` 字段可保留内部风格 ID,例如 `warm-fintech-guide`,但完整 `prompt` 字段里不要把中文风格名当成可见文案写入。
|
||||
|
||||
推荐写法:
|
||||
|
||||
```text
|
||||
明亮白底,暖橙强调色,干净信息卡片,友好的白橘猫作为向导,中文标题清楚,画面包含 MAOMOMO 标识。可见文字只允许出现:「先报名,再看门槛和封顶」和「MAOMOMO」。
|
||||
```
|
||||
|
||||
不推荐写法:
|
||||
|
||||
```text
|
||||
MAOMOMO 暖橙金融教程风,画出一张手绘便签风仪表盘图。
|
||||
```
|
||||
|
||||
## 金融卡面与支付网络核对
|
||||
|
||||
- 金融活动配图中只要出现信用卡、支付卡、卡组织、支付网络或银行卡样式,必须先根据官方材料、用户截图或用户提供的卡面参考图核对。
|
||||
- 用户提供卡面或 Logo 参考图时,把原始素材保存到 `sources/`,例如 `sources/gba-unionpay-card-reference.png`;不要只留在聊天上下文里。
|
||||
- 用户没有提供 Logo / 卡面素材,但画面需要银行、卡组织、支付机构、酒店、OTA 或活动品牌标识时,主动尽力查找准确标识,不要先默认画通用符号。
|
||||
- 查找优先级:用户素材 > 官方活动页 / 条款 PDF / App 或官网截图 > 官方品牌资源页 / media kit / press kit > 官方社交账号或应用商店页面 > 可信百科或新闻配图。只有前几类无法获得时,才使用第三方汇总站,并必须交叉核对。
|
||||
- 找到标识后,把原始文件、截图或网页证据保存到 `sources/`,并记录来源 URL、读取日期、用途和核对结论;不要只把来源写在聊天里。
|
||||
- 在 `prompt`、manifest 和 `deck_spec.json` 中记录参考素材路径、卡组织 / 支付网络、允许出现的品牌 / 支付标识、来源 URL 和核对依据。
|
||||
- 不得把银联 / UnionPay 卡画成 Mastercard、Visa、American Express 或其他卡组织;不确定时画“无卡组织标识的通用支付卡”。
|
||||
- Mastercard、Visa、UnionPay、银行、支付宝、微信支付、酒店、OTA 等 Logo 或支付网络标识,只要已经由官方材料、用户素材或用户明确口径核对正确,就可以作为辅助识别出现。
|
||||
- 已核对的第三方 Logo 必须尺寸克制、位置辅助,不得抢占、替代或压过 `MAOMOMO` 主标识;封面尤其要让 `MAOMOMO` 仍是最清楚的品牌锚点。
|
||||
- 如果当前环境无法联网、官方素材无法读取或标识版本仍不确定,记录 blocker;除非用户确认,否则不要凭记忆画真实 Logo,改用通用卡片、通用 POS、通用酒店图标或文字描述。
|
||||
- 如果官方活动限定某卡组织或指定卡面,图片 QA 必须把卡组织 / 支付网络列为必检项。
|
||||
|
||||
推荐记录字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"reference_assets": ["sources/gba-unionpay-card-reference.png"],
|
||||
"brand_mark_sources": [
|
||||
{
|
||||
"name": "UnionPay",
|
||||
"source_url": "https://example.com/official-unionpay-source",
|
||||
"source_type": "官方活动页 / 官方品牌资源页 / 用户素材",
|
||||
"checked_at": "YYYY-MM-DD"
|
||||
}
|
||||
],
|
||||
"payment_network": "UnionPay / 银联",
|
||||
"allowed_brand_marks": ["UnionPay", "中信银行(国际) GBA 卡面识别"],
|
||||
"card_art_rule": "根据用户卡面参考图画银联 GBA 双币信用卡;不得出现 Mastercard 或 Visa 标识;UnionPay 标识只能小尺寸辅助出现,不能抢 MAOMOMO 主标识位置"
|
||||
}
|
||||
```
|
||||
|
||||
## 封面信息层级
|
||||
|
||||
- 封面只放最高优先级结论和少量核心数字,服务“读者一眼知道这篇讲什么、值不值得点开”。
|
||||
- 不把复杂计算结论、全年理论上限、长公式、多重限制、详细规则或小字说明塞进封面。
|
||||
- 计算过程、月度 / 年度理论上限、封顶拆解、资格分支和详细条款,应放到计算图、总览图或正文。
|
||||
- 封面核心文案建议 1 句主标题 + 1-3 个短数字 / 短标签;如果超过手机端一眼可读的密度,拆到下一张图。
|
||||
|
||||
## 金额与币种写法
|
||||
|
||||
- 发布稿、图片文案、prompt、manifest、`deck_spec.json`、alt text 和 `speech.md` 中,港币金额统一写 `HKD 1,000`。
|
||||
- 不使用港币符号写法,例如 `HK$1,000`、`港币 1,000`、`1,000 港币`。
|
||||
- 来源原文抽取文件、官方截图 OCR 或原文摘录可以保留来源写法;它们不作为发布内容检查对象。
|
||||
- 如果来源使用港币符号,改写到发布内容时同步转换为 `HKD`。
|
||||
|
||||
## 风格选择
|
||||
|
||||
如果用户没有指定视觉方向,生成图片前先读 `references/recommended-styles.md`,给 2-3 个可选风格,并推荐一个。确认后,整篇文章的配图保持同一套视觉身份:稳定的配色、猫咪造型、标题区、图标语言和信息密度;不同图片可以根据用途变化构图。
|
||||
|
||||
小红书卡片模式先确认两件事:
|
||||
|
||||
- 比例:`3:4` 或 `9:16`。
|
||||
- 视觉方向:`2D 扁平风` 或 `3D 轻拟物风`。用户没指定时,推荐 `2D 扁平风`,因为信息卡片更清爽、教程 / 规则说明更稳,也更不容易出现中文文字翻车;`3D 轻拟物风` 更适合封面、活动感和金融产品展示。
|
||||
|
||||
小红书卡片一旦确认 `2D` 或 `3D`,整组图片必须继承同一个 `visual_direction`,不得中途混用。
|
||||
|
||||
默认推荐逻辑:
|
||||
|
||||
- 活动 / 返现 / 港卡攻略:优先 `warm-fintech-guide`。
|
||||
- 规则、门槛、费用、返现计算和产品对比:优先 `data-card-dashboard`。
|
||||
- SEO 长文、经验整理、品牌感更强的文章:优先 `clean-editorial`。
|
||||
- 保姆式教程、避坑经验和轻量操作步骤:优先 `handdrawn-note`。
|
||||
- 小红书 / 4 图看完 / 多图卡片:必须用 `xiaohongshu-vertical`,并按用户确认生成 3:4 或 9:16 竖图。
|
||||
|
||||
可选风格:
|
||||
|
||||
| 风格名 | 适合场景 | 视觉方向 |
|
||||
|---|---|---|
|
||||
| `warm-fintech-guide` | 默认 MAOMOMO 金融攻略、港卡、银行活动 | 白底、暖橙强调色、干净卡片、白橘猫向导 |
|
||||
| `clean-professional` | 正式规则整理、开户 / 保号教程 | 浅色背景、蓝绿和暖橙强调、可信结构化卡片 |
|
||||
| `clean-editorial` | SEO 长文、解释型文章、站内封面 | 大标题、留白、轻杂志感、猫咪作为角落品牌元素 |
|
||||
| `creative-magazine` | 传播型封面、活动盘点 | 大标题、编辑部排版、强留白、原创金融视觉 |
|
||||
| `data-card-dashboard` | 返现计算、活动对比、费用路径、时间线 | 指标卡、表格感、仪表盘、清晰数字层级 |
|
||||
| `handdrawn-note` | 避坑、步骤教程、保号经验 | 手绘箭头、便签、圈注、猫咪提示牌 |
|
||||
| `retro-flat-illustration` | 轻松羊毛攻略、用卡姿势 | 低饱和复古扁平插画、金融小物件 |
|
||||
| `e-ink-editorial` | 深度解释、观点长文 | 纸感、黑白灰、少量强调色、元信息条 |
|
||||
| `scientific-defense` | 条款核对、规则证据链 | 严谨浅色版式、来源卡片、条件树 |
|
||||
| `mckinsey-brief` | 多方案对比、路径选择 | 结论先行、矩阵、取舍图、商业简报感 |
|
||||
| `xiaohongshu-vertical` | 小红书发布版、4 图看完、多图卡片 | 3:4 或 9:16 竖版、大字少字、强钩子、系列化版式 |
|
||||
|
||||
示例确认话术:
|
||||
|
||||
```text
|
||||
我建议用 A,因为这篇是活动攻略,读者最需要快速看懂门槛、封顶和翻车点。
|
||||
|
||||
A. warm-fintech-guide(推荐):白底暖橙、猫咪向导、金融教程感,适合 MAOMOMO 常规文章。
|
||||
B. data-card-dashboard:更强调数字卡片和计算表,适合门槛 / 返现 / 费用信息密集的文章。
|
||||
C. handdrawn-note:便签和手绘箭头更明显,适合避坑经验和保姆式步骤。
|
||||
|
||||
你选哪个?也可以指定颜色、猫咪比例或是否更像小红书图。
|
||||
```
|
||||
|
||||
## 常见配图类型
|
||||
|
||||
| 类型 | 用途 |
|
||||
|---|---|
|
||||
| 封面图 | 文章主题、核心利益点 / 核心问题、猫咪识别和产品场景 |
|
||||
| 总览图 | 活动、产品、规则或教程的核心信息总览 |
|
||||
| 流程图 | 开户、注册、转账、入金、申请、报名、设置路径 |
|
||||
| 表格图 / 对比图 | 多产品、多方案、多活动、支持 / 不支持渠道对比 |
|
||||
| 计算图 | 返现、收益、费用、年化、门槛和封顶测算 |
|
||||
| 时间线图 | 活动日期、到账时间、任务周期、持有期、截止日 |
|
||||
| 避坑图 | 常见翻车点、不能补救的点、费用 / 规则 / 时效风险 |
|
||||
| FAQ 图 | 3-5 个高频问题和简短答案 |
|
||||
|
||||
## 图片清单格式
|
||||
|
||||
生成图片前先规划清单,并同步写入 `prompts/{article_slug}_XX.json` 或图片 manifest。每个 prompt 文件至少包含:文件名、类型、放置位置、核心文案、可见文字白名单、alt text、风格、依赖素材、输出路径和完整 prompt。小红书卡片还必须包含 `aspect_ratio`、`visual_direction`、`portrait_composition_rules` 和分批交付批次。涉及信用卡、支付卡、支付网络或机构 Logo 时,还要包含参考素材、卡组织 / 支付网络、`allowed_brand_marks`、`brand_mark_sources` 和核对依据。
|
||||
|
||||
```markdown
|
||||
| 文件名 | 类型 | 放置位置 | 核心文案 | alt text | 风格 | 依赖素材 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| hong-kong-bank-account-01-cover.png | 封面 | 标题下方 | 港卡保号关键点 | MAOMOMO 港卡保号教程封面图 | warm-fintech-guide | 无 |
|
||||
| hong-kong-bank-account-02-flow.png | 流程图 | 操作步骤前 | 三步完成账户活跃 | 港卡保号操作流程图 | handdrawn-note | 用户截图 1 |
|
||||
```
|
||||
|
||||
确认用户不需要先看清单时,可以不在聊天里展开清单,但仍必须写入 prompt 文件,并遵守“先样张、后逐张生成”的门禁。最终必须让文件名、alt text 和插图位置与正文一致。
|
||||
|
||||
推荐单图 prompt JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "hsbc-mastercard-alipay-campaign_01",
|
||||
"file_name": "hsbc-mastercard-alipay-campaign-01-cover.png",
|
||||
"origin_out": "origin_image/hsbc-mastercard-alipay-campaign-01-cover.png",
|
||||
"asset_out": "assets/hsbc-mastercard-alipay-campaign-01-cover.png",
|
||||
"type": "封面图",
|
||||
"placement": "标题下方",
|
||||
"core_text": "先报名,再看门槛和封顶",
|
||||
"visible_text_allowed": ["先报名,再看门槛和封顶", "MAOMOMO"],
|
||||
"alt_text": "MAOMOMO 汇丰 Mastercard 支付宝返现活动封面图",
|
||||
"style": "warm-fintech-guide",
|
||||
"aspect_ratio": "16:9",
|
||||
"visual_direction": "2D 扁平风",
|
||||
"delivery_batch": "batch-01-cover",
|
||||
"portrait_composition_rules": [],
|
||||
"required_assets": [],
|
||||
"reference_assets": [],
|
||||
"brand_mark_sources": [],
|
||||
"payment_network": "",
|
||||
"allowed_brand_marks": [],
|
||||
"card_art_rule": "如无官方或用户参考素材确认,画无卡组织标识的通用支付卡;已核对的品牌 / 支付标识只能辅助出现,不能抢 MAOMOMO 主标识位置",
|
||||
"forbidden_visible_text": ["风格标签", "prompt 描述", "额外 slogan", "无关说明文字"],
|
||||
"prompt": "完整图片生成 prompt,包含视觉描述、指定业务文案、可见文字限制和禁止项"
|
||||
}
|
||||
```
|
||||
|
||||
小红书卡片 prompt JSON 中,`portrait_composition_rules` 必须包含:
|
||||
|
||||
```json
|
||||
[
|
||||
"native portrait composition",
|
||||
"no squeezed elements",
|
||||
"no stretched card / logo / text",
|
||||
"vertical layout redesigned for Xiaohongshu"
|
||||
]
|
||||
```
|
||||
|
||||
## 文件命名
|
||||
|
||||
图片文件名必须 SEO 友好,并包含主题前缀:
|
||||
|
||||
```text
|
||||
origin_image/
|
||||
hsbc-mastercard-alipay-campaign-01-cover.png
|
||||
hsbc-mastercard-alipay-campaign-02-overview.png
|
||||
|
||||
assets/
|
||||
hsbc-mastercard-alipay-campaign-01-cover.png
|
||||
hsbc-mastercard-alipay-campaign-02-overview.png
|
||||
hsbc-mastercard-alipay-campaign-03-step-guide.png
|
||||
hsbc-mastercard-alipay-campaign-04-comparison.png
|
||||
hsbc-mastercard-alipay-campaign-05-calculation.png
|
||||
hsbc-mastercard-alipay-campaign-06-timeline.png
|
||||
hsbc-mastercard-alipay-campaign-07-common-pitfalls.png
|
||||
hsbc-mastercard-alipay-campaign-08-faq.png
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 使用英文小写、数字和连字符。
|
||||
- 不使用中文文件名。
|
||||
- 不使用 `01-cover.png`、`02-overview.png` 这类缺少主题前缀的通用文件名。
|
||||
- 后续改稿删除某个活动分支时,相关文件名、alt text、正文引用和 ZIP 中旧图都要同步清理。
|
||||
|
||||
## Markdown 插图
|
||||
|
||||
统一使用相对路径:
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
插图位置:
|
||||
|
||||
- 封面图放在标题下方。
|
||||
- 总览图放在活动 / 产品介绍之后。
|
||||
- 流程图放在步骤段落前后。
|
||||
- 计算图放在公式或测算表格后。
|
||||
- 时间线图放在日期说明后。
|
||||
- 避坑图放在翻车点段落后。
|
||||
- FAQ 图放在常见问题段落前后。
|
||||
|
||||
图片说明必须根据图片内容写,不要都写成“配图”。
|
||||
|
||||
`origin_image/` 不直接写进 Markdown,除非用户明确要求引用原始图目录。发布用 Markdown 默认引用 `assets/`。
|
||||
|
||||
## 猫咪视觉识别
|
||||
|
||||
基础方向:
|
||||
|
||||
```text
|
||||
中文金融实操信息图,干净明亮白色背景,暖橙色点缀,友好的白橘猫吉祥物,圆脸、小爪子,现代 App 信息卡片质感,清晰中文标题和卡片信息层级,实用而非奢华,包含清晰的 MAOMOMO 主标识,已核对的第三方品牌 / 支付标识只做辅助识别,不伪造真实截图。可见文字只允许出现指定业务文案、MAOMOMO 和已核对允许出现的品牌 / 支付标识。
|
||||
```
|
||||
|
||||
猫咪可以:
|
||||
|
||||
- 指向关键数字。
|
||||
- 拿着通用卡片。
|
||||
- 坐在手机、账本、路线图或计算器旁。
|
||||
- 举“实测”“注意”“先登记”提示牌。
|
||||
|
||||
猫咪不能:
|
||||
|
||||
- 穿戴第三方银行、卡组织、券商、支付机构 Logo;如果猫咪拿着带标识的卡片或支付牌,标识必须已核对、尺寸小、位置辅助,不能盖住或替代 MAOMOMO。
|
||||
- 盖住关键数字、日期、步骤和风险提示。
|
||||
- 变成幼稚主角,压过金融教程信息。
|
||||
|
||||
## 主题追加元素
|
||||
|
||||
- 信用卡 / 返现:通用信用卡、小票、购物篮、返现计算器、通用支付符号;只有在官方材料或用户素材确认后,才画具体卡组织 / 支付网络 / 支付机构 Logo,且只能作为辅助识别。
|
||||
- 银行账户 / 保号:账户余额仪表、保险库、日历、警示标、活跃度卡片。
|
||||
- 转账 / 入金:路线箭头、HKD / EUR / USD 标签、手机转账示意界面、勾选标记。
|
||||
- 券商活动:股票礼品卡、K 线图、欢迎信封、香港天际线线稿。
|
||||
- 数字货币:硬件钱包、钱包 App、欧元通道、区块链节点路线;画面克制,避免暴富感。
|
||||
|
||||
## 小红书图
|
||||
|
||||
用户说“小红书”“卡片”“多图”“4 图看完”“发小红书”“3:4”“9:16”时:
|
||||
|
||||
- 进入小红书卡片模式,不默认生成完整文章、Markdown 或 ZIP。
|
||||
- 生成多张 3:4 或 9:16 竖版 PNG,默认 4 张;不复用文章横版图。
|
||||
- 出图前先确认 `2D 扁平风` / `3D 轻拟物风`;默认推荐 `2D 扁平风`。
|
||||
- 每张图有 MAOMOMO 标识或猫咪水印。
|
||||
- 手机端可读,大字少字。
|
||||
- 默认结构:封面、核心拆解、操作流程、避坑总结。
|
||||
- 风格固定为 `xiaohongshu-vertical`,同组保持同一 `visual_direction`,不直接复用横版文章图。
|
||||
- prompt 必须包含 `native portrait composition`、`no squeezed elements`、`no stretched card / logo / text`、`vertical layout redesigned for Xiaohongshu`。
|
||||
- 通过 QA 后写入 `final_manifest.json`;最终交付只读取 final manifest 或状态文件中的 final assets。
|
||||
|
||||
## 图片 QA
|
||||
|
||||
每张图通过前检查:
|
||||
|
||||
- 样张已被用户确认,后续图片没有切换生图后端或视觉风格。
|
||||
- 文件真实存在,格式为 PNG。
|
||||
- 小红书卡片比例为已确认的 `3:4` 或 `9:16`,不是横版图压缩或拉伸后的结果。
|
||||
- 小红书卡片是原生竖版重构,元素没有压扁,卡片、Logo 和文字没有拉伸。
|
||||
- `origin_image/` 原始最终图和 `assets/` 发布图对应。
|
||||
- Markdown 路径能打开对应文件。
|
||||
- 图片文字清楚,无乱码、错字、截断和多余文案。
|
||||
- 可见文字只包含指定业务文案、`MAOMOMO` 和已核对允许出现的品牌 / 支付标识,没有副标题、风格说明文字、prompt 描述、额外 slogan 或无关说明。
|
||||
- 标题、金额、币种、日期、公式、规则与最终文章口径一致;港币金额发布写法统一为 `HKD`。
|
||||
- 卡组织 / 支付网络 / 机构 Logo 与官方材料或用户参考素材一致;银联 / UnionPay 不得被画成 Mastercard、Visa 或其他卡组织。
|
||||
- 用户未提供 Logo / 卡面素材时,已尽力查找官方或可信来源;`sources/`、prompt、manifest 和 `deck_spec.json` 已记录标识素材、来源 URL、读取日期和核对结论。
|
||||
- 第三方 Logo 没有抢占、替代或压过 MAOMOMO 主标识位置;MAOMOMO 仍然是画面主品牌识别。
|
||||
- 没有出现用户已删除、已排除或禁止使用的活动分支、旧版规则、旧金额或旧标题。
|
||||
- 没有真实个人信息。
|
||||
- 没有伪造官方 App 页面,也没有错误、未经核对或喧宾夺主的品牌 Logo 场景。
|
||||
- 没有把示意图伪装成真实截图、账单、订单、App 页面或官方页面。
|
||||
- 每张图独立主题明确,不是同一画面的机械变体。
|
||||
- 单张 QA 通过后,才用 `scripts/maomomo_job_state.py result` 记录结果并同步到 `assets/`;不得只生成到 `origin_image/` 后声称完成。
|
||||
- 单张 QA 通过后,状态文件必须显示 `qa_status=passed`、`entered_assets=true`;GitHub 推送后必须记录 `github_pushed=true` 或对应 batch 记录。
|
||||
- 最终交付前必须生成 `final_manifest.json`,且只包含通过 QA 并进入 `assets/` 的最终文件。
|
||||
- `article.md` 已插入对应章节,且用脚本检查 Markdown 中所有图片路径真实存在。
|
||||
- `slide_jobs.json` / `slide_run_state.json` 中没有未处理的 `pending`、`dispatched` 或 `blocked` job;如有,不能声称完整交付完成。
|
||||
|
||||
路径检查命令:
|
||||
|
||||
```bash
|
||||
uv run python scripts/maomomo_check_markdown_assets.py article/article.md
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""检查 Markdown 中引用的本地图片是否真实存在。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from typing import Iterable, List, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
IMAGE_RE = re.compile(r"!\[[^\]]*\]\(([^)]+)\)")
|
||||
|
||||
|
||||
def die(message: str, code: int = 1) -> None:
|
||||
print(f"错误:{message}", file=sys.stderr)
|
||||
raise SystemExit(code)
|
||||
|
||||
|
||||
def normalize_target(raw: str) -> str:
|
||||
value = raw.strip()
|
||||
if value.startswith("<") and value.endswith(">"):
|
||||
return value[1:-1].strip()
|
||||
if " " in value:
|
||||
value = value.split()[0]
|
||||
return value.strip("'\"")
|
||||
|
||||
|
||||
def is_remote_or_anchor(target: str) -> bool:
|
||||
parsed = urlparse(target)
|
||||
return bool(parsed.scheme in {"http", "https", "data", "mailto"} or target.startswith("#"))
|
||||
|
||||
|
||||
def find_missing(markdown_path: Path) -> List[str]:
|
||||
text = markdown_path.read_text(encoding="utf-8")
|
||||
base_dir = markdown_path.parent
|
||||
missing: List[str] = []
|
||||
for match in IMAGE_RE.finditer(text):
|
||||
target = normalize_target(match.group(1))
|
||||
if not target or is_remote_or_anchor(target):
|
||||
continue
|
||||
image_path = Path(target)
|
||||
if not image_path.is_absolute():
|
||||
image_path = base_dir / image_path
|
||||
if not image_path.exists():
|
||||
missing.append(target)
|
||||
return missing
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="检查 Markdown 本地图片路径是否存在。")
|
||||
parser.add_argument("markdown", help="要检查的 article.md 或其他 Markdown 文件。")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Optional[Iterable[str]] = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||
markdown_path = Path(args.markdown).expanduser().resolve()
|
||||
if not markdown_path.exists():
|
||||
die(f"Markdown 文件不存在:{markdown_path}")
|
||||
missing = find_missing(markdown_path)
|
||||
result = {
|
||||
"markdown": str(markdown_path),
|
||||
"missing_count": len(missing),
|
||||
"missing": missing,
|
||||
}
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 1 if missing else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,504 @@
|
||||
#!/usr/bin/env python3
|
||||
"""MAOMOMO article image generation CLI.
|
||||
|
||||
This is a lightweight OpenAI-compatible fallback for article assets. Prefer the
|
||||
agent's built-in image tool when it is available; use this script when the user
|
||||
explicitly wants API/CLI generation or the built-in backend is unavailable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Dict, Iterable, List, Optional
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||
DEFAULT_MODEL = "gpt-image-2"
|
||||
DEFAULT_SIZE = "1536x1024"
|
||||
DEFAULT_QUALITY = "medium"
|
||||
DEFAULT_STYLE = "warm-fintech-guide"
|
||||
|
||||
|
||||
STYLE_PRESETS: Dict[str, str] = {
|
||||
"warm-fintech-guide": (
|
||||
"明亮白底,暖橙强调色,干净卡片布局,友好的白橘猫作为向导,"
|
||||
"适合港卡、银行活动、返现攻略和教程总览。"
|
||||
),
|
||||
"clean-editorial": (
|
||||
"大标题、留白充足、少量暖橙和黑灰文字,像一张信息密度适中的中文攻略头图。"
|
||||
),
|
||||
"data-card-dashboard": (
|
||||
"指标卡、时间线、计算公式和对比表清晰分区,适合返现、费用、门槛和路径对比。"
|
||||
),
|
||||
"handdrawn-note": (
|
||||
"白底纸感、手绘箭头、便利贴、重点圈注和轻量猫咪贴纸,适合避坑经验和保姆式步骤。"
|
||||
),
|
||||
"xiaohongshu-vertical": (
|
||||
"3:4 或 9:16 原生竖版,手机端可读,大字少字,强标题钩子,多图系列一致视觉,"
|
||||
"默认 2D 扁平信息卡片,用户确认后可用 3D 轻拟物风,带 MAOMOMO 标识。"
|
||||
),
|
||||
"clean-professional": (
|
||||
"浅色背景、蓝绿或暖橙强调、结构化信息卡片和清晰层级,适合正式银行规则、开户教程和综合攻略。"
|
||||
),
|
||||
"creative-magazine": (
|
||||
"大标题、强留白、编辑部排版和轻视觉冲击,适合传播型封面、观点总结和活动盘点。"
|
||||
),
|
||||
"retro-flat-illustration": (
|
||||
"低饱和暖色、扁平金融小物件、轻复古海报感,适合轻松羊毛攻略和经验分享。"
|
||||
),
|
||||
"e-ink-editorial": (
|
||||
"纸感背景、黑白灰为主、少量强调色、强标题和元信息条,适合深度解释和观点型长文。"
|
||||
),
|
||||
"scientific-defense": (
|
||||
"严谨浅色版式、证据图、流程框和来源标注,适合规则拆解、政策解读和多来源核对。"
|
||||
),
|
||||
"mckinsey-brief": (
|
||||
"结论先行、矩阵、2x2、瀑布图和高对比商业配色,适合方案对比、路径选择和决策建议。"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def die(message: str, code: int = 1) -> None:
|
||||
print(f"错误:{message}", file=sys.stderr)
|
||||
raise SystemExit(code)
|
||||
|
||||
|
||||
def warn(message: str) -> None:
|
||||
print(f"警告:{message}", file=sys.stderr)
|
||||
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def write_text(path: Path, text: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8", newline="\n") as handle:
|
||||
handle.write(text)
|
||||
|
||||
|
||||
def read_json(path: Path) -> Any:
|
||||
return json.loads(read_text(path))
|
||||
|
||||
|
||||
def strip_toml_comment(line: str) -> str:
|
||||
quote = ""
|
||||
escaped = False
|
||||
for index, char in enumerate(line):
|
||||
if escaped:
|
||||
escaped = False
|
||||
continue
|
||||
if quote and char == "\\":
|
||||
escaped = True
|
||||
continue
|
||||
if char in ("'", '"'):
|
||||
if not quote:
|
||||
quote = char
|
||||
elif quote == char:
|
||||
quote = ""
|
||||
continue
|
||||
if char == "#" and not quote:
|
||||
return line[:index]
|
||||
return line
|
||||
|
||||
|
||||
def parse_toml_scalar(value: str) -> Any:
|
||||
raw = strip_toml_comment(value).strip()
|
||||
if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in ("'", '"'):
|
||||
inner = raw[1:-1]
|
||||
if raw[0] == '"':
|
||||
return inner.replace(r"\"", '"').replace(r"\\", "\\")
|
||||
return inner
|
||||
if raw.lower() == "true":
|
||||
return True
|
||||
if raw.lower() == "false":
|
||||
return False
|
||||
return raw
|
||||
|
||||
|
||||
def parse_basic_toml(text: str) -> Dict[str, Any]:
|
||||
"""解析 Codex 配置需要的 TOML 子集,避免 Python 3.10 及以下缺少 tomllib。"""
|
||||
data: Dict[str, Any] = {}
|
||||
current: Dict[str, Any] = data
|
||||
for raw_line in text.splitlines():
|
||||
line = strip_toml_comment(raw_line).strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("[") and line.endswith("]"):
|
||||
section = line.strip("[]").strip()
|
||||
current = data
|
||||
for part in section.split("."):
|
||||
key = part.strip().strip("'\"")
|
||||
if not key:
|
||||
continue
|
||||
child = current.get(key)
|
||||
if not isinstance(child, dict):
|
||||
child = {}
|
||||
current[key] = child
|
||||
current = child
|
||||
continue
|
||||
if "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
current[key.strip().strip("'\"")] = parse_toml_scalar(value)
|
||||
return data
|
||||
|
||||
|
||||
def load_prompt_file(path: Path) -> str:
|
||||
text = read_text(path).strip()
|
||||
stripped = text.lstrip()
|
||||
if path.suffix.lower() == ".json" or stripped.startswith("{"):
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.JSONDecodeError as exc:
|
||||
die(f"--prompt-file 看起来是 JSON,但解析失败:{path}: {exc}")
|
||||
if not isinstance(data, dict) or not isinstance(data.get("prompt"), str) or not data["prompt"].strip():
|
||||
die("--prompt-file 不能传入缺少 prompt 字段的 JSON。请先抽取 prompt 字段生成 .prompt.txt。")
|
||||
prompt = data["prompt"].strip()
|
||||
prompt_out = path.with_suffix(".prompt.txt")
|
||||
write_text(prompt_out, prompt + "\n")
|
||||
warn(f"--prompt-file 收到 JSON,已抽取 prompt 字段到:{prompt_out};后续请直接传入该 .prompt.txt。")
|
||||
return prompt
|
||||
return text
|
||||
|
||||
|
||||
def load_codex_config_base_url() -> Optional[str]:
|
||||
config_path = Path.home() / ".codex" / "config.toml"
|
||||
if not config_path.exists():
|
||||
return None
|
||||
text = read_text(config_path)
|
||||
try:
|
||||
import tomllib
|
||||
data = tomllib.loads(text)
|
||||
except ModuleNotFoundError:
|
||||
data = parse_basic_toml(text)
|
||||
except Exception:
|
||||
data = parse_basic_toml(text)
|
||||
try:
|
||||
if not isinstance(data, dict):
|
||||
data = parse_basic_toml(text)
|
||||
except Exception:
|
||||
data = parse_basic_toml(text)
|
||||
|
||||
provider_name = data.get("model_provider")
|
||||
providers = data.get("model_providers")
|
||||
if isinstance(provider_name, str) and isinstance(providers, dict):
|
||||
provider = providers.get(provider_name)
|
||||
if isinstance(provider, dict) and isinstance(provider.get("base_url"), str):
|
||||
return provider["base_url"].strip()
|
||||
|
||||
if isinstance(data.get("base_url"), str):
|
||||
return data["base_url"].strip()
|
||||
if isinstance(providers, dict):
|
||||
for provider in providers.values():
|
||||
if isinstance(provider, dict) and isinstance(provider.get("base_url"), str):
|
||||
return provider["base_url"].strip()
|
||||
return None
|
||||
|
||||
|
||||
def find_secret(value: Any) -> Optional[str]:
|
||||
if isinstance(value, dict):
|
||||
for key in ("MAOMOMO_IMAGE_API_KEY", "OPENAI_API_KEY", "api_key", "openai_api_key", "token"):
|
||||
raw = value.get(key)
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return raw.strip()
|
||||
for child in value.values():
|
||||
found = find_secret(child)
|
||||
if found:
|
||||
return found
|
||||
if isinstance(value, list):
|
||||
for child in value:
|
||||
found = find_secret(child)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
def load_codex_auth_api_key() -> Optional[str]:
|
||||
auth_path = Path.home() / ".codex" / "auth.json"
|
||||
if not auth_path.exists():
|
||||
return None
|
||||
try:
|
||||
return find_secret(read_json(auth_path))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def api_key() -> str:
|
||||
value = os.getenv("MAOMOMO_IMAGE_API_KEY") or os.getenv("OPENAI_API_KEY") or load_codex_auth_api_key()
|
||||
if not value:
|
||||
die(
|
||||
"未找到 API Key。请设置 MAOMOMO_IMAGE_API_KEY 或 OPENAI_API_KEY,"
|
||||
"或确保 ~/.codex/auth.json 中存在可用 key。"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def base_url() -> str:
|
||||
return (
|
||||
os.getenv("MAOMOMO_IMAGE_BASE_URL")
|
||||
or os.getenv("OPENAI_BASE_URL")
|
||||
or load_codex_config_base_url()
|
||||
or DEFAULT_BASE_URL
|
||||
).rstrip("/")
|
||||
|
||||
|
||||
def image_model() -> str:
|
||||
return os.getenv("MAOMOMO_IMAGE_MODEL") or os.getenv("CODEX_PPT_IMAGE_MODEL") or DEFAULT_MODEL
|
||||
|
||||
|
||||
def style_prompt(style: str) -> str:
|
||||
if style in STYLE_PRESETS:
|
||||
return STYLE_PRESETS[style]
|
||||
return style
|
||||
|
||||
|
||||
def build_prompt(
|
||||
*,
|
||||
title: str,
|
||||
image_type: str,
|
||||
core_text: str,
|
||||
style: str,
|
||||
aspect_ratio: str,
|
||||
visual_direction: str = "",
|
||||
portrait_rules: str = "",
|
||||
extra: str = "",
|
||||
) -> str:
|
||||
allowed_visible = [value for value in (title, core_text, "MAOMOMO") if value.strip()]
|
||||
allowed_visible_text = "、".join(f"「{value}」" for value in allowed_visible)
|
||||
parts = [
|
||||
"生成一张 MAOMOMO 中文金融实操文章配图。",
|
||||
f"图片类型:{image_type}",
|
||||
f"标题 / 主题:{title}",
|
||||
f"核心文案:{core_text}",
|
||||
f"画幅:{aspect_ratio}",
|
||||
f"画面呈现:{style_prompt(style)}",
|
||||
f"可见文字限制:画面只允许出现这些指定业务文案:{allowed_visible_text};如补充要求明确列出已核对的品牌或支付标识,可作为小型辅助标识出现;不要添加副标题、风格标签、prompt 描述、说明文字、额外 slogan 或无关文字。",
|
||||
"硬性要求:包含清晰可见的 MAOMOMO 主标识;中文文字清楚可读;信息层级明确;港币金额统一使用 HKD 写法;不要伪造真实 App 截图;Mastercard、Visa、UnionPay、银行、支付宝等 Logo 或支付网络标识只有在官方材料、用户素材或补充要求明确核对正确时才可出现,并且只能作为辅助识别,不能抢占或替代 MAOMOMO 主标识位置;未确认时使用通用卡片或通用支付符号,不要凭空加入或替换卡组织;不要出现真实个人信息;猫咪不穿戴第三方 Logo。",
|
||||
]
|
||||
if visual_direction.strip():
|
||||
parts.append(f"确认视觉方向:{visual_direction.strip()}。整组图片必须保持同一视觉方向,不得混用 2D 和 3D。")
|
||||
if style == "xiaohongshu-vertical" or aspect_ratio in {"3:4", "9:16"}:
|
||||
rules = portrait_rules.strip() or (
|
||||
"native portrait composition;no squeezed elements;"
|
||||
"no stretched card / logo / text;vertical layout redesigned for Xiaohongshu;"
|
||||
"禁止把横版设计硬塞进竖版画幅。"
|
||||
)
|
||||
parts.append(f"小红书竖图要求:{rules}")
|
||||
if extra.strip():
|
||||
parts.append(f"补充要求:{extra.strip()}")
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def request_image(prompt: str, *, size: str, quality: str, output_format: str) -> bytes:
|
||||
payload = {
|
||||
"model": image_model(),
|
||||
"prompt": prompt,
|
||||
"size": size,
|
||||
"quality": quality,
|
||||
"n": 1,
|
||||
"response_format": "b64_json",
|
||||
}
|
||||
if output_format:
|
||||
payload["output_format"] = output_format
|
||||
|
||||
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
base_url() + "/images/generations",
|
||||
data=body,
|
||||
headers={
|
||||
"Authorization": f"Bearer {api_key()}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=300) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", "replace")
|
||||
die(f"图片接口返回 HTTP {exc.code}: {detail}")
|
||||
except Exception as exc:
|
||||
die(f"图片接口请求失败:{exc}")
|
||||
|
||||
items = data.get("data")
|
||||
if not isinstance(items, list) or not items:
|
||||
die("图片接口响应缺少 data。")
|
||||
first = items[0]
|
||||
if not isinstance(first, dict):
|
||||
die("图片接口响应格式不正确。")
|
||||
if isinstance(first.get("b64_json"), str):
|
||||
return base64.b64decode(first["b64_json"])
|
||||
if isinstance(first.get("url"), str):
|
||||
with urllib.request.urlopen(first["url"], timeout=300) as resp:
|
||||
return resp.read()
|
||||
die("图片接口响应中没有 b64_json 或 url。")
|
||||
return b""
|
||||
|
||||
|
||||
def normalize_output(path: str, *, base_dir: Optional[Path]) -> Path:
|
||||
out = Path(path)
|
||||
if not out.is_absolute() and base_dir is not None:
|
||||
out = base_dir / out
|
||||
return out.resolve()
|
||||
|
||||
|
||||
def generate_one(args: argparse.Namespace) -> Dict[str, str]:
|
||||
if args.prompt_file:
|
||||
prompt = load_prompt_file(Path(args.prompt_file))
|
||||
elif args.prompt:
|
||||
prompt = args.prompt.strip()
|
||||
else:
|
||||
prompt = build_prompt(
|
||||
title=args.title,
|
||||
image_type=args.image_type,
|
||||
core_text=args.core_text,
|
||||
style=args.style,
|
||||
aspect_ratio=args.aspect_ratio,
|
||||
visual_direction=args.visual_direction,
|
||||
portrait_rules=args.portrait_rules,
|
||||
extra=args.extra,
|
||||
)
|
||||
|
||||
out = normalize_output(args.out, base_dir=None)
|
||||
if args.dry_run:
|
||||
write_text(out.with_suffix(".prompt.txt"), prompt + "\n")
|
||||
return {"out": str(out), "prompt": str(out.with_suffix(".prompt.txt")), "status": "dry-run"}
|
||||
|
||||
image_bytes = request_image(prompt, size=args.size, quality=args.quality, output_format=args.output_format)
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_bytes(image_bytes)
|
||||
return {"out": str(out), "status": "generated"}
|
||||
|
||||
|
||||
def manifest_items(path: Path) -> List[Dict[str, Any]]:
|
||||
data = read_json(path)
|
||||
if isinstance(data, dict):
|
||||
items = data.get("images")
|
||||
else:
|
||||
items = data
|
||||
if not isinstance(items, list):
|
||||
die("manifest 必须是图片数组,或包含 images 数组的对象。")
|
||||
normalized: List[Dict[str, Any]] = []
|
||||
for index, item in enumerate(items, start=1):
|
||||
if not isinstance(item, dict):
|
||||
die(f"manifest 第 {index} 项不是对象。")
|
||||
normalized.append(item)
|
||||
return normalized
|
||||
|
||||
|
||||
def batch(args: argparse.Namespace) -> List[Dict[str, str]]:
|
||||
manifest_path = Path(args.manifest).resolve()
|
||||
base_dir = Path(args.base_dir).resolve() if args.base_dir else manifest_path.parent
|
||||
results: List[Dict[str, str]] = []
|
||||
|
||||
for index, item in enumerate(manifest_items(manifest_path), start=1):
|
||||
filename = item.get("file_name") or item.get("filename") or item.get("out")
|
||||
if not isinstance(filename, str) or not filename.strip():
|
||||
die(f"manifest 第 {index} 项缺少 file_name。")
|
||||
style = str(item.get("style") or args.style or DEFAULT_STYLE)
|
||||
aspect_ratio = str(item.get("aspect_ratio") or args.aspect_ratio)
|
||||
visual_direction = str(item.get("visual_direction") or args.visual_direction or "")
|
||||
raw_portrait_rules = item.get("portrait_composition_rules") or args.portrait_rules
|
||||
if isinstance(raw_portrait_rules, list):
|
||||
portrait_rules = ";".join(str(value) for value in raw_portrait_rules)
|
||||
else:
|
||||
portrait_rules = str(raw_portrait_rules or "")
|
||||
prompt = item.get("prompt")
|
||||
if not isinstance(prompt, str) or not prompt.strip():
|
||||
prompt = build_prompt(
|
||||
title=str(item.get("title") or item.get("alt_text") or filename),
|
||||
image_type=str(item.get("type") or "文章配图"),
|
||||
core_text=str(item.get("core_text") or item.get("caption") or item.get("alt_text") or ""),
|
||||
style=style,
|
||||
aspect_ratio=aspect_ratio,
|
||||
visual_direction=visual_direction,
|
||||
portrait_rules=portrait_rules,
|
||||
extra=str(item.get("extra") or item.get("requirements") or ""),
|
||||
)
|
||||
|
||||
out = normalize_output(filename, base_dir=base_dir)
|
||||
if args.dry_run:
|
||||
write_text(out.with_suffix(".prompt.txt"), prompt + "\n")
|
||||
results.append({"out": str(out), "prompt": str(out.with_suffix(".prompt.txt")), "status": "dry-run"})
|
||||
continue
|
||||
|
||||
image_bytes = request_image(
|
||||
prompt,
|
||||
size=str(item.get("size") or args.size),
|
||||
quality=str(item.get("quality") or args.quality),
|
||||
output_format=str(item.get("output_format") or args.output_format),
|
||||
)
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_bytes(image_bytes)
|
||||
results.append({"out": str(out), "status": "generated"})
|
||||
if args.sleep > 0:
|
||||
time.sleep(args.sleep)
|
||||
return results
|
||||
|
||||
|
||||
def print_styles(_: argparse.Namespace) -> int:
|
||||
print(json.dumps(STYLE_PRESETS, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Generate MAOMOMO article images with an OpenAI-compatible API.")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument("--style", default=DEFAULT_STYLE, help="Style preset name or custom style text.")
|
||||
common.add_argument("--size", default=DEFAULT_SIZE)
|
||||
common.add_argument("--quality", default=DEFAULT_QUALITY)
|
||||
common.add_argument("--output-format", default="png")
|
||||
common.add_argument("--aspect-ratio", default="16:9")
|
||||
common.add_argument("--visual-direction", default="")
|
||||
common.add_argument("--portrait-rules", default="")
|
||||
common.add_argument("--dry-run", action="store_true", help="Only write prompt files; do not call the image API.")
|
||||
|
||||
generate = subparsers.add_parser("generate", parents=[common], help="Generate one image.")
|
||||
generate.add_argument("--out", required=True)
|
||||
generate.add_argument("--prompt")
|
||||
generate.add_argument("--prompt-file")
|
||||
generate.add_argument("--title", default="MAOMOMO 文章配图")
|
||||
generate.add_argument("--image-type", default="文章配图")
|
||||
generate.add_argument("--core-text", default="")
|
||||
generate.add_argument("--extra", default="")
|
||||
|
||||
batch_parser = subparsers.add_parser("batch", parents=[common], help="Generate images from a JSON manifest.")
|
||||
batch_parser.add_argument("--manifest", required=True)
|
||||
batch_parser.add_argument("--base-dir", help="Base directory for relative file_name paths. Defaults to manifest dir.")
|
||||
batch_parser.add_argument("--sleep", type=float, default=0.0, help="Seconds to sleep between API calls.")
|
||||
|
||||
subparsers.add_parser("styles", help="Print bundled style presets.")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Optional[Iterable[str]] = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||
if args.command == "styles":
|
||||
return print_styles(args)
|
||||
if args.command == "generate":
|
||||
result = generate_one(args)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
if args.command == "batch":
|
||||
results = batch(args)
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,554 @@
|
||||
#!/usr/bin/env python3
|
||||
"""记录 MAOMOMO 文章配图任务状态。
|
||||
|
||||
本脚本用于把图片生成进度写入状态文件,避免只用聊天记录声称已完成。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
|
||||
JOBS_FILE = "slide_jobs.json"
|
||||
STATE_FILE = "slide_run_state.json"
|
||||
FINAL_MANIFEST_FILE = "final_manifest.json"
|
||||
GITHUB_BATCHES_FILE = "github_batches.json"
|
||||
|
||||
|
||||
def now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def die(message: str, code: int = 1) -> None:
|
||||
print(f"错误:{message}", file=sys.stderr)
|
||||
raise SystemExit(code)
|
||||
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def write_json(path: Path, data: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8", newline="\n") as handle:
|
||||
json.dump(data, handle, ensure_ascii=False, indent=2)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def read_json(path: Path) -> Any:
|
||||
try:
|
||||
return json.loads(read_text(path))
|
||||
except FileNotFoundError:
|
||||
die(f"找不到文件:{path}")
|
||||
except json.JSONDecodeError as exc:
|
||||
die(f"JSON 格式错误:{path}: {exc}")
|
||||
|
||||
|
||||
def project_path(project: str) -> Path:
|
||||
path = Path(project).expanduser().resolve()
|
||||
if not path.exists():
|
||||
die(f"项目目录不存在:{path}")
|
||||
if not path.is_dir():
|
||||
die(f"不是项目目录:{path}")
|
||||
return path
|
||||
|
||||
|
||||
def resolve_in_project(project: Path, value: str) -> Path:
|
||||
path = Path(value)
|
||||
if not path.is_absolute():
|
||||
path = project / path
|
||||
return path.resolve()
|
||||
|
||||
|
||||
def relative_path_string(project: Path, path: Path) -> str:
|
||||
try:
|
||||
return str(path.resolve().relative_to(project))
|
||||
except ValueError:
|
||||
return str(path.resolve())
|
||||
|
||||
|
||||
def load_job_doc(project: Path) -> Dict[str, Any]:
|
||||
raw = read_json(project / JOBS_FILE)
|
||||
if isinstance(raw, list):
|
||||
return {"version": 1, "jobs": raw}
|
||||
if not isinstance(raw, dict):
|
||||
die(f"{JOBS_FILE} 必须是对象或数组。")
|
||||
jobs = raw.get("jobs")
|
||||
if not isinstance(jobs, list):
|
||||
die(f"{JOBS_FILE} 缺少 jobs 数组。")
|
||||
return raw
|
||||
|
||||
|
||||
def load_state_doc(project: Path) -> Dict[str, Any]:
|
||||
path = project / STATE_FILE
|
||||
if not path.exists():
|
||||
return {"version": 1, "events": []}
|
||||
raw = read_json(path)
|
||||
if not isinstance(raw, dict):
|
||||
die(f"{STATE_FILE} 必须是对象。")
|
||||
if not isinstance(raw.get("events"), list):
|
||||
raw["events"] = []
|
||||
return raw
|
||||
|
||||
|
||||
def load_batches_doc(project: Path) -> Dict[str, Any]:
|
||||
path = project / GITHUB_BATCHES_FILE
|
||||
if not path.exists():
|
||||
return {"version": 1, "batches": []}
|
||||
raw = read_json(path)
|
||||
if not isinstance(raw, dict):
|
||||
die(f"{GITHUB_BATCHES_FILE} 必须是对象。")
|
||||
if not isinstance(raw.get("batches"), list):
|
||||
raw["batches"] = []
|
||||
return raw
|
||||
|
||||
|
||||
def save_docs(project: Path, job_doc: Dict[str, Any], state_doc: Dict[str, Any]) -> None:
|
||||
write_json(project / JOBS_FILE, job_doc)
|
||||
write_json(project / STATE_FILE, state_doc)
|
||||
|
||||
|
||||
def job_key(job: Dict[str, Any]) -> Optional[str]:
|
||||
for key in ("id", "job_id", "slide", "name"):
|
||||
value = job.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def find_job(job_doc: Dict[str, Any], wanted: str) -> Dict[str, Any]:
|
||||
for job in job_doc["jobs"]:
|
||||
if isinstance(job, dict) and job_key(job) == wanted:
|
||||
return job
|
||||
die(f"未找到图片 job:{wanted}")
|
||||
return {}
|
||||
|
||||
|
||||
def event(state_doc: Dict[str, Any], kind: str, job_id: str, payload: Dict[str, Any]) -> None:
|
||||
state_doc.setdefault("events", []).append(
|
||||
{
|
||||
"time": now_iso(),
|
||||
"event": kind,
|
||||
"job": job_id,
|
||||
**payload,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def infer_origin_out(prompt_path: Path, data: Dict[str, Any]) -> str:
|
||||
for key in ("origin_out", "out", "output", "selected_output"):
|
||||
value = data.get(key)
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
file_name = data.get("file_name")
|
||||
if isinstance(file_name, str) and file_name:
|
||||
return str(Path("origin_image") / Path(file_name).name)
|
||||
return str(Path("origin_image") / f"{prompt_path.stem}.png")
|
||||
|
||||
|
||||
def infer_asset_out(data: Dict[str, Any], origin_out: str) -> str:
|
||||
value = data.get("asset_out")
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
return str(Path("assets") / Path(origin_out).name)
|
||||
|
||||
|
||||
def cmd_init(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
prompts_dir = resolve_in_project(project, args.prompts_dir)
|
||||
if not prompts_dir.exists():
|
||||
die(f"prompts 目录不存在:{prompts_dir}")
|
||||
|
||||
jobs: List[Dict[str, Any]] = []
|
||||
for prompt_path in sorted(prompts_dir.glob("*.json")):
|
||||
data = read_json(prompt_path)
|
||||
if not isinstance(data, dict):
|
||||
die(f"prompt job 必须是 JSON 对象:{prompt_path}")
|
||||
job_id = str(data.get("id") or data.get("job_id") or prompt_path.stem)
|
||||
origin_out = infer_origin_out(prompt_path, data)
|
||||
jobs.append(
|
||||
{
|
||||
"id": job_id,
|
||||
"prompt_file": str(prompt_path.relative_to(project)),
|
||||
"origin_out": origin_out,
|
||||
"asset_out": infer_asset_out(data, origin_out),
|
||||
"status": "pending",
|
||||
"selected_backend": args.selected_backend,
|
||||
"aspect_ratio": data.get("aspect_ratio", ""),
|
||||
"visual_direction": data.get("visual_direction", ""),
|
||||
"delivery_batch": data.get("delivery_batch", ""),
|
||||
"qa_status": "pending",
|
||||
"entered_assets": False,
|
||||
"final": False,
|
||||
"github_pushed": False,
|
||||
"github_batch": "",
|
||||
"created_at": now_iso(),
|
||||
}
|
||||
)
|
||||
|
||||
if not jobs:
|
||||
die(f"没有找到 prompt JSON:{prompts_dir}")
|
||||
|
||||
job_doc = {"version": 1, "jobs": jobs}
|
||||
state_doc = {"version": 1, "events": [{"time": now_iso(), "event": "init", "job_count": len(jobs)}]}
|
||||
save_docs(project, job_doc, state_doc)
|
||||
print(json.dumps({"status": "initialized", "jobs": len(jobs)}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_status(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
job_doc = load_job_doc(project)
|
||||
counts: Dict[str, int] = {}
|
||||
for job in job_doc["jobs"]:
|
||||
if not isinstance(job, dict):
|
||||
continue
|
||||
status = str(job.get("status") or "unknown")
|
||||
counts[status] = counts.get(status, 0) + 1
|
||||
|
||||
print(json.dumps({"project": str(project), "counts": counts}, ensure_ascii=False, indent=2))
|
||||
for job in job_doc["jobs"]:
|
||||
if isinstance(job, dict):
|
||||
github = "github" if job.get("github_pushed") else "local"
|
||||
qa = job.get("qa_status", "")
|
||||
print(f"{job_key(job) or '<unknown>'}\t{job.get('status', 'unknown')}\t{qa}\t{github}\t{job.get('origin_out', '')}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_dispatch(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
job_doc = load_job_doc(project)
|
||||
state_doc = load_state_doc(project)
|
||||
job = find_job(job_doc, args.job)
|
||||
prompt_file = args.prompt_file or job.get("prompt_file")
|
||||
if not isinstance(prompt_file, str) or not prompt_file:
|
||||
die("缺少 prompt 文件路径。")
|
||||
|
||||
job["status"] = "dispatched"
|
||||
job["dispatch"] = {
|
||||
"agent_id": args.agent_id,
|
||||
"agent_nickname": args.agent_nickname,
|
||||
"prompt_file": prompt_file,
|
||||
"time": now_iso(),
|
||||
}
|
||||
event(state_doc, "dispatch", args.job, job["dispatch"])
|
||||
save_docs(project, job_doc, state_doc)
|
||||
print(json.dumps({"status": "dispatched", "job": args.job}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def copy_if_needed(source: Path, target: Path) -> None:
|
||||
if not source.exists():
|
||||
die(f"结果图片不存在:{source}")
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
if source.resolve() == target.resolve():
|
||||
return
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
shutil.copyfile(source, target)
|
||||
|
||||
|
||||
def cmd_result(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
job_doc = load_job_doc(project)
|
||||
state_doc = load_state_doc(project)
|
||||
job = find_job(job_doc, args.job)
|
||||
source = resolve_in_project(project, args.selected_source)
|
||||
|
||||
origin_value = args.origin_out or job.get("origin_out")
|
||||
if not isinstance(origin_value, str) or not origin_value:
|
||||
die("缺少 origin_out。")
|
||||
origin_out = resolve_in_project(project, origin_value)
|
||||
copy_if_needed(source, origin_out)
|
||||
|
||||
asset_value = args.asset_out or job.get("asset_out")
|
||||
entered_assets = False
|
||||
if isinstance(asset_value, str) and asset_value:
|
||||
copy_if_needed(origin_out, resolve_in_project(project, asset_value))
|
||||
entered_assets = True
|
||||
|
||||
result = {
|
||||
"agent_id": args.agent_id,
|
||||
"backend_used": args.backend_used,
|
||||
"selected_source": str(source),
|
||||
"origin_out": relative_path_string(project, origin_out),
|
||||
"asset_out": asset_value,
|
||||
"qa_status": "passed",
|
||||
"entered_assets": entered_assets,
|
||||
"qa_note": args.qa_note,
|
||||
"time": now_iso(),
|
||||
}
|
||||
job["status"] = "recorded"
|
||||
job["qa_status"] = "passed"
|
||||
job["entered_assets"] = entered_assets
|
||||
job["result"] = result
|
||||
event(state_doc, "result", args.job, result)
|
||||
save_docs(project, job_doc, state_doc)
|
||||
print(json.dumps({"status": "recorded", "job": args.job, "origin_out": result["origin_out"]}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_blocker(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
job_doc = load_job_doc(project)
|
||||
state_doc = load_state_doc(project)
|
||||
job = find_job(job_doc, args.job)
|
||||
blocker = {
|
||||
"agent_id": args.agent_id,
|
||||
"reason": args.reason,
|
||||
"evidence": args.evidence,
|
||||
"time": now_iso(),
|
||||
}
|
||||
job["status"] = "blocked"
|
||||
job["blocker"] = blocker
|
||||
event(state_doc, "blocker", args.job, blocker)
|
||||
save_docs(project, job_doc, state_doc)
|
||||
print(json.dumps({"status": "blocked", "job": args.job, "reason": args.reason}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_final_manifest(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
job_doc = load_job_doc(project)
|
||||
state_doc = load_state_doc(project)
|
||||
assets: List[Dict[str, Any]] = []
|
||||
incomplete: List[str] = []
|
||||
|
||||
for job in job_doc["jobs"]:
|
||||
if not isinstance(job, dict):
|
||||
continue
|
||||
key = job_key(job) or "<unknown>"
|
||||
if job.get("status") != "recorded":
|
||||
incomplete.append(f"{key}: status={job.get('status', 'unknown')}")
|
||||
continue
|
||||
if job.get("qa_status") != "passed":
|
||||
incomplete.append(f"{key}: qa_status={job.get('qa_status', 'unknown')}")
|
||||
continue
|
||||
if job.get("entered_assets") is not True:
|
||||
incomplete.append(f"{key}: entered_assets=false")
|
||||
continue
|
||||
|
||||
result = job.get("result") if isinstance(job.get("result"), dict) else {}
|
||||
asset_value = result.get("asset_out") or job.get("asset_out")
|
||||
if not isinstance(asset_value, str) or not asset_value:
|
||||
incomplete.append(f"{key}: 缺少 asset_out")
|
||||
continue
|
||||
|
||||
asset_path = resolve_in_project(project, asset_value)
|
||||
if not asset_path.exists():
|
||||
incomplete.append(f"{key}: assets 文件不存在:{asset_value}")
|
||||
continue
|
||||
|
||||
job["final"] = True
|
||||
assets.append(
|
||||
{
|
||||
"id": key,
|
||||
"prompt_file": job.get("prompt_file", ""),
|
||||
"origin_out": result.get("origin_out") or job.get("origin_out", ""),
|
||||
"asset_out": relative_path_string(project, asset_path),
|
||||
"qa_status": job.get("qa_status", ""),
|
||||
"qa_note": result.get("qa_note", ""),
|
||||
"aspect_ratio": job.get("aspect_ratio", ""),
|
||||
"visual_direction": job.get("visual_direction", ""),
|
||||
"delivery_batch": job.get("delivery_batch", ""),
|
||||
"github_pushed": bool(job.get("github_pushed")),
|
||||
"github_batch": job.get("github_batch", ""),
|
||||
}
|
||||
)
|
||||
|
||||
if incomplete and not args.allow_incomplete:
|
||||
die("仍有未进入最终 manifest 的图片 job:\n" + "\n".join(incomplete))
|
||||
if not assets:
|
||||
die("没有可写入 final_manifest.json 的最终图片。")
|
||||
|
||||
manifest_path = resolve_in_project(project, args.out)
|
||||
manifest = {
|
||||
"version": 1,
|
||||
"generated_at": now_iso(),
|
||||
"project": project.name,
|
||||
"asset_count": len(assets),
|
||||
"assets": assets,
|
||||
}
|
||||
write_json(manifest_path, manifest)
|
||||
event(
|
||||
state_doc,
|
||||
"final_manifest",
|
||||
"*",
|
||||
{
|
||||
"path": relative_path_string(project, manifest_path),
|
||||
"asset_count": len(assets),
|
||||
"allow_incomplete": args.allow_incomplete,
|
||||
},
|
||||
)
|
||||
save_docs(project, job_doc, state_doc)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "final_manifest_written",
|
||||
"path": relative_path_string(project, manifest_path),
|
||||
"assets": len(assets),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_github_batch(args: argparse.Namespace) -> int:
|
||||
project = project_path(args.project)
|
||||
job_doc = load_job_doc(project)
|
||||
state_doc = load_state_doc(project)
|
||||
batches_doc = load_batches_doc(project)
|
||||
|
||||
rel_paths: List[str] = []
|
||||
for value in args.paths:
|
||||
path = resolve_in_project(project, value)
|
||||
if not path.exists():
|
||||
die(f"批次文件不存在:{value}")
|
||||
rel_paths.append(relative_path_string(project, path))
|
||||
|
||||
path_set = set(rel_paths)
|
||||
pushed_jobs: List[str] = []
|
||||
for job in job_doc["jobs"]:
|
||||
if not isinstance(job, dict):
|
||||
continue
|
||||
result = job.get("result") if isinstance(job.get("result"), dict) else {}
|
||||
candidate_values = [job.get("asset_out"), result.get("asset_out")]
|
||||
normalized = {
|
||||
relative_path_string(project, resolve_in_project(project, value))
|
||||
for value in candidate_values
|
||||
if isinstance(value, str) and value
|
||||
}
|
||||
if normalized.intersection(path_set):
|
||||
job["github_pushed"] = True
|
||||
job["github_batch"] = args.batch
|
||||
if isinstance(job.get("result"), dict):
|
||||
job["result"]["github_pushed"] = True
|
||||
job["result"]["github_batch"] = args.batch
|
||||
pushed_jobs.append(job_key(job) or "<unknown>")
|
||||
|
||||
batch_record = {
|
||||
"batch": args.batch,
|
||||
"commit": args.commit,
|
||||
"url": args.url,
|
||||
"release": args.release,
|
||||
"paths": rel_paths,
|
||||
"pushed_jobs": pushed_jobs,
|
||||
"note": args.note,
|
||||
"time": now_iso(),
|
||||
}
|
||||
batches = [item for item in batches_doc["batches"] if not (isinstance(item, dict) and item.get("batch") == args.batch)]
|
||||
batches.append(batch_record)
|
||||
batches_doc["batches"] = batches
|
||||
|
||||
manifest_path = project / FINAL_MANIFEST_FILE
|
||||
if manifest_path.exists():
|
||||
manifest = read_json(manifest_path)
|
||||
if isinstance(manifest, dict) and isinstance(manifest.get("assets"), list):
|
||||
for item in manifest["assets"]:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
asset_value = item.get("asset_out")
|
||||
if not isinstance(asset_value, str) or not asset_value:
|
||||
continue
|
||||
normalized = relative_path_string(project, resolve_in_project(project, asset_value))
|
||||
if normalized in path_set:
|
||||
item["github_pushed"] = True
|
||||
item["github_batch"] = args.batch
|
||||
item["github_commit"] = args.commit
|
||||
item["github_url"] = args.url
|
||||
write_json(manifest_path, manifest)
|
||||
|
||||
write_json(project / GITHUB_BATCHES_FILE, batches_doc)
|
||||
event(state_doc, "github_batch", "*", batch_record)
|
||||
save_docs(project, job_doc, state_doc)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "github_batch_recorded",
|
||||
"batch": args.batch,
|
||||
"paths": len(rel_paths),
|
||||
"pushed_jobs": pushed_jobs,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Record MAOMOMO article image job state.")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
init = subparsers.add_parser("init", help="Create slide_jobs.json from prompts/*.json.")
|
||||
init.add_argument("project")
|
||||
init.add_argument("--prompts-dir", default="prompts")
|
||||
init.add_argument("--selected-backend", required=True)
|
||||
init.set_defaults(func=cmd_init)
|
||||
|
||||
status = subparsers.add_parser("status", help="Print job status summary.")
|
||||
status.add_argument("project")
|
||||
status.set_defaults(func=cmd_status)
|
||||
|
||||
dispatch = subparsers.add_parser("dispatch", help="Record a dispatched image job.")
|
||||
dispatch.add_argument("project")
|
||||
dispatch.add_argument("--job", required=True)
|
||||
dispatch.add_argument("--agent-id", required=True)
|
||||
dispatch.add_argument("--agent-nickname", default="")
|
||||
dispatch.add_argument("--prompt-file")
|
||||
dispatch.set_defaults(func=cmd_dispatch)
|
||||
|
||||
result = subparsers.add_parser("result", help="Record a generated image result.")
|
||||
result.add_argument("project")
|
||||
result.add_argument("--job", required=True)
|
||||
result.add_argument("--agent-id", required=True)
|
||||
result.add_argument("--backend-used", required=True)
|
||||
result.add_argument("--selected-source", required=True)
|
||||
result.add_argument("--origin-out")
|
||||
result.add_argument("--asset-out")
|
||||
result.add_argument("--qa-note", default="")
|
||||
result.set_defaults(func=cmd_result)
|
||||
|
||||
blocker = subparsers.add_parser("blocker", help="Record a blocked image job.")
|
||||
blocker.add_argument("project")
|
||||
blocker.add_argument("--job", required=True)
|
||||
blocker.add_argument("--agent-id", required=True)
|
||||
blocker.add_argument("--reason", required=True)
|
||||
blocker.add_argument("--evidence", default="")
|
||||
blocker.set_defaults(func=cmd_blocker)
|
||||
|
||||
final_manifest = subparsers.add_parser("final-manifest", help="Write final_manifest.json from recorded assets.")
|
||||
final_manifest.add_argument("project")
|
||||
final_manifest.add_argument("--out", default=FINAL_MANIFEST_FILE)
|
||||
final_manifest.add_argument("--allow-incomplete", action="store_true")
|
||||
final_manifest.set_defaults(func=cmd_final_manifest)
|
||||
|
||||
github_batch = subparsers.add_parser("github-batch", help="Record a GitHub delivery batch.")
|
||||
github_batch.add_argument("project")
|
||||
github_batch.add_argument("--batch", required=True)
|
||||
github_batch.add_argument("--commit", required=True)
|
||||
github_batch.add_argument("--url", required=True)
|
||||
github_batch.add_argument("--release", default="")
|
||||
github_batch.add_argument("--paths", nargs="+", required=True)
|
||||
github_batch.add_argument("--note", default="")
|
||||
github_batch.set_defaults(func=cmd_github_batch)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Optional[Iterable[str]] = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||
return args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+227
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""为 MAOMOMO 配图叠加 45° 平铺半透明水印。
|
||||
|
||||
默认水印:MAOMOMO.COM,45° 对角线平铺;自动对比色,浅底黑字、深底白字。
|
||||
|
||||
用法:
|
||||
uv run python scripts/maomomo_tile_watermark.py --input assets --output watermarked_assets
|
||||
uv run python scripts/maomomo_tile_watermark.py --input assets/example.png --output assets/example-watermarked.png
|
||||
|
||||
说明:
|
||||
脚本使用 Pillow。本机 Python 没有 Pillow 且存在 uv 时,会自动用
|
||||
`uv run --with pillow python ...` 重启一次,不需要把依赖写入项目。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Iterable, Iterator, Optional, Tuple, Any
|
||||
|
||||
SUPPORTED_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
|
||||
|
||||
|
||||
def die(message: str, code: int = 1) -> None:
|
||||
print(f"错误:{message}", file=sys.stderr)
|
||||
raise SystemExit(code)
|
||||
|
||||
|
||||
def ensure_pillow() -> Tuple[Any, Any, Any]:
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
return Image, ImageDraw, ImageFont
|
||||
except ModuleNotFoundError as exc:
|
||||
if exc.name != "PIL":
|
||||
raise
|
||||
uv = shutil.which("uv")
|
||||
if uv and os.environ.get("MAOMOMO_WATERMARK_REEXEC") != "1":
|
||||
env = os.environ.copy()
|
||||
env["MAOMOMO_WATERMARK_REEXEC"] = "1"
|
||||
cmd = [uv, "run", "--with", "pillow>=10.0.0", "python", str(Path(__file__).resolve()), *sys.argv[1:]]
|
||||
raise SystemExit(subprocess.call(cmd, env=env))
|
||||
die("缺少 Pillow。请使用 `uv run python scripts/maomomo_tile_watermark.py ...` 运行,或安装 Pillow。")
|
||||
|
||||
|
||||
def parse_color(value: str) -> Tuple[int, int, int]:
|
||||
text = value.strip()
|
||||
if text.lower() == "auto":
|
||||
die("auto color must be resolved after reading each image")
|
||||
if text.startswith("#"):
|
||||
text = text[1:]
|
||||
if len(text) != 6:
|
||||
die("颜色必须是 auto 或 #RRGGBB,例如 #000000 / #FFFFFF")
|
||||
try:
|
||||
return tuple(int(text[i : i + 2], 16) for i in (0, 2, 4)) # type: ignore[return-value]
|
||||
except ValueError:
|
||||
die("颜色必须是有效十六进制,例如 #000000 / #FFFFFF")
|
||||
|
||||
|
||||
def auto_contrast_color(image_path: Path, threshold: float = 150.0) -> Tuple[int, int, int]:
|
||||
Image, _, _ = ensure_pillow()
|
||||
with Image.open(image_path) as src:
|
||||
r, g, b = src.convert("RGB").resize((1, 1)).getpixel((0, 0))
|
||||
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
return (0, 0, 0) if luminance >= threshold else (255, 255, 255)
|
||||
|
||||
|
||||
def load_font(ImageFont: Any, font_path: Optional[str], font_size: int) -> Any:
|
||||
candidates = []
|
||||
if font_path:
|
||||
candidates.append(Path(font_path).expanduser())
|
||||
candidates.extend(
|
||||
Path(p)
|
||||
for p in [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
||||
"/System/Library/Fonts/Supplemental/Arial Bold.ttf",
|
||||
]
|
||||
)
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
try:
|
||||
return ImageFont.truetype(str(candidate), font_size)
|
||||
except OSError:
|
||||
continue
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def iter_images(input_path: Path) -> Iterator[Path]:
|
||||
if input_path.is_file():
|
||||
if input_path.suffix.lower() not in SUPPORTED_EXTS:
|
||||
die(f"不支持的图片格式:{input_path}")
|
||||
yield input_path
|
||||
return
|
||||
if not input_path.is_dir():
|
||||
die(f"输入不存在:{input_path}")
|
||||
for path in sorted(input_path.rglob("*")):
|
||||
if path.is_file() and path.suffix.lower() in SUPPORTED_EXTS:
|
||||
yield path
|
||||
|
||||
|
||||
def resolve_output(input_root: Path, image_path: Path, output_root: Path) -> Path:
|
||||
if input_root.is_file():
|
||||
return output_root
|
||||
rel = image_path.relative_to(input_root)
|
||||
return output_root / rel
|
||||
|
||||
|
||||
def make_tile(
|
||||
Image: Any,
|
||||
ImageDraw: Any,
|
||||
text: str,
|
||||
font: Any,
|
||||
color: Tuple[int, int, int],
|
||||
alpha: int,
|
||||
angle: float,
|
||||
padding: int,
|
||||
) -> Any:
|
||||
dummy = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(dummy)
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
text_w = bbox[2] - bbox[0]
|
||||
text_h = bbox[3] - bbox[1]
|
||||
tile_w = max(1, text_w + padding * 2)
|
||||
tile_h = max(1, text_h + padding * 2)
|
||||
tile = Image.new("RGBA", (tile_w, tile_h), (0, 0, 0, 0))
|
||||
tile_draw = ImageDraw.Draw(tile)
|
||||
tile_draw.text((padding, padding), text, font=font, fill=(*color, alpha))
|
||||
return tile.rotate(angle, expand=True, resample=Image.Resampling.BICUBIC)
|
||||
|
||||
|
||||
def apply_watermark(
|
||||
image_path: Path,
|
||||
output_path: Path,
|
||||
*,
|
||||
text: str,
|
||||
angle: float,
|
||||
alpha: int,
|
||||
color: Tuple[int, int, int],
|
||||
font_size: int,
|
||||
font_path: Optional[str],
|
||||
spacing: int,
|
||||
quality: int,
|
||||
) -> None:
|
||||
if alpha < 0 or alpha > 255:
|
||||
die("alpha 必须在 0-255 之间")
|
||||
if spacing < 0:
|
||||
die("spacing 不能为负数")
|
||||
Image, ImageDraw, ImageFont = ensure_pillow()
|
||||
font = load_font(ImageFont, font_path, font_size)
|
||||
with Image.open(image_path) as src:
|
||||
base = src.convert("RGBA")
|
||||
width, height = base.size
|
||||
diagonal = int((width * width + height * height) ** 0.5)
|
||||
tile = make_tile(Image, ImageDraw, text, font, color, alpha, angle, spacing)
|
||||
overlay = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
step_x = max(1, tile.width + spacing)
|
||||
step_y = max(1, tile.height + spacing)
|
||||
start_x = -diagonal
|
||||
start_y = -diagonal
|
||||
for y in range(start_y, height + diagonal, step_y):
|
||||
for x in range(start_x, width + diagonal, step_x):
|
||||
overlay.alpha_composite(tile, (x, y))
|
||||
out = Image.alpha_composite(base, overlay)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
suffix = output_path.suffix.lower()
|
||||
if suffix in {".jpg", ".jpeg"}:
|
||||
out = out.convert("RGB")
|
||||
out.save(output_path, quality=quality, optimize=True)
|
||||
elif suffix == ".webp":
|
||||
out.save(output_path, quality=quality, method=6)
|
||||
else:
|
||||
out.save(output_path)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="为图片叠加 MAOMOMO.COM 45° 平铺半透明水印。")
|
||||
parser.add_argument("--input", required=True, help="输入图片文件或目录。")
|
||||
parser.add_argument("--output", required=True, help="输出图片文件或目录。目录模式会保持相对路径。")
|
||||
parser.add_argument("--text", default="MAOMOMO.COM", help="水印文字,默认 MAOMOMO.COM。")
|
||||
parser.add_argument("--angle", type=float, default=45.0, help="水印旋转角度,默认 45。")
|
||||
parser.add_argument("--alpha", type=int, default=70, help="透明度 0-255,默认 70。")
|
||||
parser.add_argument("--color", default="auto", help="水印颜色,默认 auto:浅底黑字、深底白字;也可指定 #000000 / #FFFFFF。")
|
||||
parser.add_argument("--font-size", type=int, default=50, help="字体大小,默认 50。")
|
||||
parser.add_argument("--font", default=None, help="可选字体路径。")
|
||||
parser.add_argument("--spacing", type=int, default=28, help="水印间距 / 内边距,默认 28;目标是每张图至少可见 2 个完整 MAOMOMO.COM。")
|
||||
parser.add_argument("--quality", type=int, default=95, help="JPG/WEBP 输出质量,默认 95。")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Optional[Iterable[str]] = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||
input_path = Path(args.input).expanduser().resolve()
|
||||
output_path = Path(args.output).expanduser().resolve()
|
||||
images = list(iter_images(input_path))
|
||||
if not images:
|
||||
die(f"没有找到支持的图片:{input_path}")
|
||||
if input_path.is_dir() and output_path.exists() and output_path.is_file():
|
||||
die("输入为目录时,输出必须是目录路径")
|
||||
results = []
|
||||
for image_path in images:
|
||||
target = resolve_output(input_path, image_path, output_path)
|
||||
apply_watermark(
|
||||
image_path,
|
||||
target,
|
||||
text=args.text,
|
||||
angle=args.angle,
|
||||
alpha=args.alpha,
|
||||
color=auto_contrast_color(image_path) if str(args.color).lower() == "auto" else parse_color(args.color),
|
||||
font_size=args.font_size,
|
||||
font_path=args.font,
|
||||
spacing=args.spacing,
|
||||
quality=args.quality,
|
||||
)
|
||||
results.append((image_path, target))
|
||||
for src, dst in results:
|
||||
print(f"watermarked: {src} -> {dst}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user