跳到主要内容

Blog 自动化创建工作流

这篇文档记录的是“AI 接手创建一篇新 blog”的完整流程。

它和 生活类 Blog 写作工作流 的关系是:

  • life-blog-writing-workflow.md 继续保留,负责生活类 blog 的写作风格、图片排版、frontmatter、TravelGallery layout="article"、标签体系等基础规范。
  • 本文负责把一次完整的创建过程写成可执行流程,从用户给素材开始,到图片上传、中英文写作、审阅暂停、TTS、排障、构建和可选 git 提交结束。
  • 后续如果两篇文档有交叉,以旧文档负责“文章应该长什么样”,本文负责“AI 应该按什么顺序做事”。

一句话概括:

以后用户只说“我想创建一个新的 blog”,AI 就应该按本文进入标准流程,并提示用户准备素材。

触发方式

当用户说下面这些话时,默认进入本文流程:

  • 我想创建一个新的 blog
  • 我想写一篇生活 blog
  • 帮我把这次吃饭 / 出门 / 看展 / 体验记录到 blog
  • 我有一组照片,想发到博客里
  • 帮我写成中英文 blog,并生成语音

AI 不应该一上来就写文章,而是先确认素材是否足够。

如果用户只给一句话,先提示他准备下面这些东西。

用户需要准备什么

最少需要:

- 主题:
- 日期:
- 地点 / 店名 / 事件:
- 想记录的核心内容:
- 图片素材:
- 是否允许 AI 读取原片:
- 是否需要英文版:
- 是否需要 TTS 语音:

推荐给得更完整一点:

- 中文标题:
- 英文标题:
- 文件名 slug 倾向:
- 菜品 / 物品 / 场景清单:
- 照片顺序或照片缩略图:
- 每张照片大概对应什么:
- 想重点写的细节:
- 不想出现的表达:
- 语气偏好:
- 封面图候选:
- 是否需要上传图片到 OSS:
- 是否需要版本更新和 git 提交:

如果是餐厅、聚餐、看展、旅行边缘但不放进 docs/Travel 的生活类图文 blog,最推荐用户按这个模板给:

- 事件:去七一广场滨寿司吃回转寿司
- 日期:2026-05-02
- 地点:七一广场滨寿司
- 标题:滨寿司:在回转寿司大吃一顿哦~
- 英文标题:Hama Sushi: A Big Conveyor-Belt Sushi Meal
- 菜品:
- 火炙焦糖三文鱼
- 大切鹅肝
- 温泉蛋乌冬面
- 想强调:
- 点单平板没有购物车,点了就下单
- 去得晚,几乎没排队
- 最喜欢火炙焦糖三文鱼、大切鹅肝、温泉蛋乌冬面
- 图片限制:只允许看缩略图,不允许读取原片
- 需要英文版:需要
- TTS:正文审阅通过后再生成

素材接收规则

AI 接到素材后,先做判断,不急着写。

必须确认:

  • 事件日期,用来决定文件名日期,不用写作当天日期代替。
  • 标题,如果用户已经给了标题,以用户标题为准。
  • 英文标题,如果用户给了英文标题,不要另起一个。
  • 是否允许读取原片。
  • 图片是否已经有 OSS URL,还是需要上传。
  • 是否要把图片重命名。
  • 是否需要英文版。
  • 是否需要 TTS。
  • 是否需要 git 提交。

如果用户明确说“不允许读取原片”,这就是硬约束。

这种情况下 AI 只能使用:

  • 用户给的缩略图
  • 用户给的截图
  • 用户给的文字描述
  • 文件名
  • 已经生成的 URL 清单

不得再打开原图、读取 EXIF、重新预览原片,也不要用任何图像处理命令去读取原片内容。

图片处理与上传流程

如果用户需要处理本地图片,按下面顺序做。

1. 先建立图片命名表

命名格式默认:

YYYY-MM-DD-topic-image-name.jpg

单词使用英文小写,单词之间用 - 连接。

例如滨寿司这篇:

2026-05-02-hamasushi-aburi-caramel-salmon.jpg
2026-05-02-hamasushi-large-cut-foie-gras.jpg
2026-05-02-hamasushi-onsen-egg-udon.jpg
2026-05-02-hamasushi-tempura-platter-01.jpg

如果同一菜品有多张图,使用 -01-02

2. 再按文章结构分组

不要把所有图片塞进一个大数组。

生活类图文 blog 默认按场景分组:

  • storePhotos:店面、取号屏、点单屏、桌边调料
  • sushiPhotos:主角寿司
  • warmFoodPhotos:乌冬、拉面、天妇罗
  • lateOrderPhotos:后半段加点的菜
  • endingPhotos:桌面、小票、收尾图

这比一个 photos 数组更容易写出文章节奏。

3. 上传到合理 OSS 目录

默认路径格式:

blog/<topic>/<YYYY-MM-DD>/

例如:

blog/hamasushi/2026-05-02/

发布 URL 使用:

https://picture.nevergpdzy.cn/blog/hamasushi/2026-05-02/2026-05-02-hamasushi-aburi-caramel-salmon.jpg

上传后必须保存一份 URL 清单,方便后续写 MDX 和排错。

4. 选封面图

有图文章必须在 frontmatter 写 image

封面图优先选择整篇最有代表性的主图,不一定是店面图。滨寿司这篇最后使用的是:

image: https://picture.nevergpdzy.cn/blog/hamasushi/2026-05-02/2026-05-02-hamasushi-aburi-caramel-salmon.jpg

因为这张火炙焦糖三文鱼比店面图更能代表文章主题。

中文博客创建流程

中文是默认语言,文件放在:

blog/YYYY-MM-DD-slug.mdx

例如:

blog/2026-05-02-hamasushi-big-conveyor-belt-sushi-meal.mdx

生活类图文 blog 默认使用 .mdx,因为需要 TravelGallery

frontmatter 模板:

---
title: "滨寿司:在回转寿司大吃一顿哦~"
authors: DingZhiyu
tags: [life, food]
description: 记录一次在学校旁边七一广场滨寿司的大吃一顿,从火炙焦糖三文鱼开始,把最近很火的回转寿司吃成了一篇生活博客。
image: https://picture.nevergpdzy.cn/blog/hamasushi/2026-05-02/2026-05-02-hamasushi-aburi-caramel-salmon.jpg
---

正文必须包含:

  • import TravelGallery from "@site/src/components/TravelGallery";
  • 图片数组,包含 srcaltcaption
  • H1,和标题保持一致
  • 合理位置的 <!--truncate-->
  • 多个 <TravelGallery images={...} layout="article" />

写作时继续遵守 生活类 Blog 写作工作流

  • 不写成店铺评分或攻略。
  • 不写推荐指数、人均、几星,除非用户明确要求。
  • 不虚构对话、人物反应和未发生的情节。
  • 抓住真实细节,不要只罗列菜单。
  • 图片 caption 不要写成长段正文。

英文博客创建流程

英文镜像文件放在:

i18n/en/docusaurus-plugin-content-blog/YYYY-MM-DD-slug.mdx

例如:

i18n/en/docusaurus-plugin-content-blog/2026-05-02-hamasushi-big-conveyor-belt-sushi-meal.mdx

英文翻译必须完全跟随中文稿。

翻译范围:

  • 翻译 titledescription
  • 翻译正文
  • 翻译图片 altcaption
  • 翻译用户可见的自然语言

不翻译:

  • URL
  • import
  • export const
  • 变量名
  • tags
  • authors
  • slug
  • JSX 组件名和属性名

如果用户手动修改中文稿,必须重新检查英文稿。不能只局部猜测,应该以最终中文稿为准重新同步英文表达。

审阅暂停点

这是硬规则。

中英文博客写完后,必须先拿给用户审阅。

在用户确认之前,不得生成 TTS。

默认交付给用户审阅的内容包括:

  • 中文 MDX 路径
  • 英文 MDX 路径
  • 标题
  • 封面图
  • 文章正文是否已经按用户补充点写入
  • 是否还有明显待确认点

用户可能会手动改中文稿。出现这种情况时:

  1. 重新读取中文稿。
  2. 按中文稿重新同步英文稿。
  3. 再把中英文内容给用户审阅。
  4. 用户确认后再继续 TTS。

滨寿司这次就是这样处理的:中文稿多次被用户手动修改,英文稿随后按中文稿重写,最后才进入语音生成。

TTS 生成流程

用户明确确认中英文稿之后,才开始生成语音。

TTS 项目路径:

D:\Code\tts-blog-generator

1. 先 dry-run

中文:

cd D:\Code\tts-blog-generator
python generate.py --include hamasushi-big-conveyor-belt-sushi-meal --dry-run

英文:

python generate.py --lang en --blog-dir "../Dev-Knowledge-Base/i18n/en/docusaurus-plugin-content-blog" --include hamasushi-big-conveyor-belt-sushi-meal --dry-run

dry-run 要检查:

  • 是否只命中目标文章
  • slug 是否正确
  • 中文和英文 manifest key 是否正确
  • chunk 数量是否合理
  • 文件名是否符合预期

2. 再生成中文

普通文章:

$env:MIMO_API_KEY='你的 key'
python generate.py --include hamasushi-big-conveyor-belt-sushi-meal --force --article-jobs 1

如果中文文章较长,或者已经出现过跳读、漏读、音频时长异常,就降低分块上限:

$env:MIMO_API_KEY='你的 key'
python generate.py --include hamasushi-big-conveyor-belt-sushi-meal --force --article-jobs 1 --chunk-char-limit 900

滨寿司中文音频一开始单段生成时漏读了开头中间一段。后来确认抽取文本完整,于是用 --chunk-char-limit 900 重生成成 3 段,问题才解决。

3. 生成英文

$env:MIMO_API_KEY='你的 key'
python generate.py --lang en --blog-dir "../Dev-Knowledge-Base/i18n/en/docusaurus-plugin-content-blog" --include hamasushi-big-conveyor-belt-sushi-meal --force --article-jobs 1

英文长文通常会自动分成多段。滨寿司英文音频生成了 4 段。

4. 复制博客专用 manifest

只能复制博客专用清单:

Copy-Item output\blog-manifest.json ..\Dev-Knowledge-Base\src\data\blogAudioManifest.json
Copy-Item output\blog-manifest.json ..\Dev-Knowledge-Base\static\audio\blog\manifest.json

不要把 output/manifest.json 复制进站点。output/manifest.json 是总清单,可能混有 docs 音频条目。

5. 检查 manifest 条目

中文 key:

"hamasushi-big-conveyor-belt-sushi-meal"

英文 key:

"en/hamasushi-big-conveyor-belt-sushi-meal"

中文如果是 3 段,应该是:

"urls": [
"https://picture.nevergpdzy.cn/Audio/blog/hamasushi-big-conveyor-belt-sushi-meal_001.mp3",
"https://picture.nevergpdzy.cn/Audio/blog/hamasushi-big-conveyor-belt-sushi-meal_002.mp3",
"https://picture.nevergpdzy.cn/Audio/blog/hamasushi-big-conveyor-belt-sushi-meal_003.mp3"
]

TTS 错误处理

1. 音频漏读或跳读

先判断是“文本抽取丢了”,还是“TTS 生成时跳读”。

用生成器导出抽取文本:

cd D:\Code\tts-blog-generator

@'
from pathlib import Path
import generate

slug = "hamasushi-big-conveyor-belt-sushi-meal"
path = Path("../Dev-Knowledge-Base/blog") / "2026-05-02-hamasushi-big-conveyor-belt-sushi-meal.mdx"
text = generate.extract_text(path)
chunks = generate.chunk_text(text, limit=900)

Path("output").mkdir(exist_ok=True)
Path("output/check_zh_hamasushi_text.txt").write_text(
"\n\n".join(
f"=== chunk {i:03d} / {len(chunks)} ({len(chunk)} chars) ===\n{chunk}"
for i, chunk in enumerate(chunks, 1)
),
encoding="utf-8",
)
print(len(text), [len(chunk) for chunk in chunks])
'@ | python -

如果检查文件里缺了正文,修 extract_text() 或 MDX 结构。

如果检查文件里正文完整,但音频漏读,就降低 --chunk-char-limit 后重生成。

2. 旧错误音频需要删除

如果旧音频已经被新 manifest 替代,还要清理旧对象。

检查 manifest 中是否还引用旧 URL:

rg -n "hamasushi-big-conveyor-belt-sushi-meal\.mp3" src\data\blogAudioManifest.json static\audio\blog\manifest.json

本地删除旧 MP3:

Remove-Item -LiteralPath "D:\Code\tts-blog-generator\output\hamasushi-big-conveyor-belt-sushi-meal.mp3" -Force

OSS 旧对象也要删除,例如:

Audio/blog/hamasushi-big-conveyor-belt-sushi-meal.mp3

删除后再确认 manifest 只引用新的 _001_002_003 等分段文件。

3. manifest 意外改动旧文章 URL

生成器或复制流程可能意外让旧英文音频路径变化,例如把旧的:

Audio/blog/en/en_article.mp3

改成:

Audio/blog/en_article.mp3

这种不属于当前文章修改范围,不能直接提交。

处理方式:

  1. 用 Git HEAD 里的旧 manifest 作为基底。
  2. 只合并当前新文章的中英文条目。
  3. 再 diff,确认旧文章 URL 没有变化。

常见错误与处理

1. Tags [food] are not defined in tags.yml

原因是文章 frontmatter 使用了新 tag,但 tag 文件没有定义。

处理:

  • 中文:更新 blog/tags.yml
  • 英文:更新 i18n/en/docusaurus-plugin-content-blog/tags.yml

例如:

food:
label: Food
permalink: food

2. build 失败

先跑:

npm run build

如果失败,先判断失败是否来自当前博客:

  • 当前博客 MDX 语法错误
  • 图片数组 JSX 写错
  • frontmatter YAML 写错
  • tag 未定义
  • 英文镜像文件路径或扩展名不一致

如果失败来自无关旧页面,要在最终说明里说清楚,不要顺手修改无关文件。

3. 工作区出现无关删除

提交前必须检查:

git status --short

如果看到大量无关文件被删除,必须暂停。

不要把这些删除纳入提交。

处理顺序:

  1. 确认是不是误删、目录异常或生成脚本副作用。
  2. 对无关 tracked 文件用 git restore -- <path> 恢复。
  3. 只暂存本次相关文件。

滨寿司流程后续提交时就出现过 blog/ 旧文章显示删除的异常,必须先恢复,不能提交。

4. 用户给了密钥

TTS key 只能作为当前进程环境变量使用。

不要写入仓库文件。

不要在最终回复里复述完整 key。

如果 key 已经暴露在聊天里,建议用户后续轮换。

发布验证

写完中英文文章、音频和 manifest 后,至少检查:

  • 中文 MDX 路径正确。
  • 英文镜像 MDX 路径正确。
  • 文件名日期是事件日期。
  • titleauthorstagsdescriptionimage 正确。
  • 新 tag 已同步中英文 tags.yml
  • TravelGallery 都使用 layout="article"
  • 图片 URL 使用 https://picture.nevergpdzy.cn/...
  • 封面图可以代表文章。
  • 英文稿和中文稿内容一致。
  • 用户确认后才生成 TTS。
  • manifest 中有中文和英文音频条目。
  • 旧错误音频不再被 manifest 引用。
  • npm run build 通过。

版本更新和 git 提交

如果用户要求提交,必须先读:

VERSIONING.md

常用判断:

  • 修错字、修文档流程、修 manifest:PATCH
  • 新增一篇公开 blog 或新增非破坏性可见内容:MINOR
  • 破坏路由、重构站点结构:MAJOR

运行对应命令:

npm run version:patch
npm run version:minor
npm run version:major

确认:

  • package.json 更新
  • package-lock.json 更新
  • npm run build 通过
  • git status --short 没有无关文件

提交信息必须是真正多行文本。

推荐格式:

feat(blog): add Hama Sushi dining post and audio/新增滨寿司用餐博客与语音

Add a new bilingual blog post about ...

Register the new food blog tag ...

新增一篇关于 ...

在中英文博客标签元数据中 ...

不要使用 \n 转义字符伪造多行提交信息。

滨寿司案例复盘

这次完整流程可以作为以后参考。

关键信息:

项目内容
中文标题滨寿司:在回转寿司大吃一顿哦~
英文标题Hama Sushi: A Big Conveyor-Belt Sushi Meal
日期2026-05-02
slughamasushi-big-conveyor-belt-sushi-meal
中文文件blog/2026-05-02-hamasushi-big-conveyor-belt-sushi-meal.mdx
英文文件i18n/en/docusaurus-plugin-content-blog/2026-05-02-hamasushi-big-conveyor-belt-sushi-meal.mdx
图片目录blog/hamasushi/2026-05-02/
封面图2026-05-02-hamasushi-aburi-caramel-salmon.jpg
标签[life, food]
中文音频3 段,--chunk-char-limit 900
英文音频4 段

这次最值得记住的坑:

  • 用户明确禁止读取原片时,必须遵守,只能用缩略图和描述。
  • 写完中英文稿后必须停下来让用户审阅。
  • 用户改中文后,英文要重新按中文稿同步。
  • 中文 TTS 单段可能跳读,不能只看生成成功,要听用户反馈并检查抽取文本。
  • 重生成音频后,旧错误音频要从本地和 OSS 删除。
  • TTS manifest 可能意外改旧 URL,提交前必须看 diff。
  • 新标签 food 要同步中英文 tag 文件。
  • 提交前必须检查有没有无关删除。

以后默认执行结论

以后用户说“我想创建一个新的 blog”,默认按下面流程走:

  1. 提示用户准备素材。
  2. 确认图片读取权限和日期。
  3. 如需上传图片,先重命名并上传 OSS。
  4. 创建中文 MDX。
  5. 创建英文镜像 MDX。
  6. 把中英文稿给用户审阅。
  7. 用户确认后生成中英文 TTS。
  8. 复制博客音频 manifest。
  9. npm run build
  10. 如果用户要求,再按 VERSIONING.md 更新版本并提交。

最重要的是第 6 步。

没有用户确认,不生成语音。