Files
memind/docs/news-morning-templates.md
T
john 57a9aadf53
Memind CI / Test, build, and release guards (push) Successful in 4m43s
fix(news): repair instead of block in daily news generation and push
- news002 image/structure audit no longer throws: prune empty sections and
  publish a best-effort page with a warning
- WeChat push strips images that failed to upload instead of refusing the draft
- forceGenerateOnce is cleared before running, so a failed run cannot loop;
  the archived page is restored when generation fails
- degraded-page regeneration runs at most once per day
- content safety rules cover vulgar sexual terms and gossip/adult sites

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 11:29:35 +08:00

9.4 KiB
Raw Blame History

每日新闻早报模板:news001 / news002

模板定义集中在 news-morning-templates.mjs。 早报页由 Agent 按 spec 生成 HTML,仓库里没有静态页模板文件,所以「模板」= 交给 Agent 的版式与内容规范。

模板 版本 状态 说明
news001 2026-09-18-refined frozen 2026-09-18 定稿版式,已上生产。只读,不允许就地修改;要改版式请新开 news003。
news002 2026-09-21-subscriber active 订阅向改版:事件唯一化、每条配图、报头版式、深色模式。

切换模板

# 环境变量(默认 news001)
H5_WECHAT_NEWS_MORNING_TEMPLATE_ID=news002

也可以写进 DB 配置 h5_wechat_admin_config.news_morning_draft 的 templateId 字段, DB 值优先于环境变量。非法值一律回落到 news001。

news001 的三个实测问题

以生产页 daily-news-0918.html 为样本(node scripts/preview-news002-template.mjs 旁的分析脚本可复现):

  1. 重复选题。49 张新闻卡片里只有约 20 个独立事件。「王毅同鲁比奥通电话」出现 4 次, 「东博会南宁开幕」5 次,「OpenAI 霍奇猜想」5 次。 根因是版式把「23 个栏目 × 固定条数」当硬指标,当天独立事件不够时 Agent 就换个标题再写一遍。
  2. 来源不可核验。49 张卡片只引用了 7 个 URL,且全是 finance.sina.com.cn/stock/cpbd/ 这类栏目首页,不是文章永久链接。读者点进去看不到对应报道。
  3. 零配图。整页 0 个 <img>,信息密度高但没有视觉落点,移动端阅读疲劳。

news002 的对应设计

1. 事件唯一化(结构性修复)

  • 栏目分 core / flex 两档。core 必出;flex 只有拿到未被使用过的独立事件才输出, 没料就整节删除,连同导航锚点一起去掉。是事件决定栏目,不是栏目决定事件。
  • 全页独立事件上限 NEWS002_MAX_STORY_CARDS = 28(news001 实际产出 49)。
  • 每张卡片必须带 data-event-key="{事件指纹}",指纹 = 核心主体 + 核心动作, 中英文报道共用同一指纹,全页不得重复。

2. 运行时去重

prompt 层的「禁止重复」无法验证,所以在落盘后再做一次程序化清理: wechat-news-morning-dedup.mjs,由 worker 在生成成功后调用。

判重优先级:

  1. data-event-key 完全相同
  2. 来源 URL 归一化后相同 —— 仅当它是文章永久链接时。 isArticlePermalink() 会排除栏目首页/index.*,否则 news001 那种「7 个栏目首页」会把整页误删。
  3. 标题相似 —— isSameNewsEvent() 用字符二元组重合度(阈值 0.55)。 仓库既有的 isDuplicateNewsMorningStory() 按空格分词,对中文标题基本失效,所以这里另做一层。

history / knowledge 不参与判重;weather / brief / deepdive 不会被删空。 闸门只删卡片和空栏目,不改写正文,DOM 保持 div 配平。

3. 每条新闻配图

链路:SearXNG img_src/thumbnail → normalizeSearchResult() 透传 image/publishedAt → 预取 brief 里以 图:{URL} 单独成行 → Agent 写进 .card .media > img。

  • 有真实图:必须显示,loading="lazy" + referrerpolicy="no-referrer"
  • 无图:先抓来源页 og:image / twitter:image;仍没有就不放配图,保留文字卡
  • 禁止默认小图凑数、禁止张冠李戴、禁止编造图片地址、禁止整页长图

4. 视觉与排版

维度 news001 news002
底色 #f7fafc 冷灰 #FAF7F2 暖纸
主色 #b0191e 正红 #C8362F 墨红 + #1B3A6B 靛蓝
页头 240px 渐变大色块 ~160px 报头(masthead),首屏直接见内容
标题字体 全黑体 标题衬线(Noto Serif SC),正文黑体
导航 25 个锚点横向滚动 8 个主锚点 + 吸顶
卡片 白底 + 左侧 3px 色条 图文混排,左侧 92px 4:3 缩略图
深色模式 无 prefers-color-scheme: dark

CSS 固化在 NEWS002_STYLE_BLOCK,直接内嵌进 taskSpec,Agent 照抄即可,不依赖外部参照页。

.card .media 的 align-self:flex-start 不能省。flex 默认 stretch 会把缩略图拉成整卡高度, aspect-ratio 失效 —— 这个坑在首版预览里踩过。

5. 内容结构

  • 一分钟速读:6~10 条一句话,给只有 30 秒的读者
  • 深读一条:当天最重要的一件事,300~450 字 + 一句金句,做差异化
  • 每条卡片的「为什么重要」:一句话说清跟读者的关系。说不清就不写这条
  • 订阅引导块:放在往期回顾之前

微信草稿兼容

wechat-daily-news-inline.mjs 已同时识别两版 DOM:

元素 news001 news002
头条卡 .highlight-box* a.lead-card
日期行 .date-badge .masthead .meta
导语 .subtitle .lede

news002 的卡片图会先上传到微信正文素材接口,再由 inline 渲染器输出微信 CDN 地址。 静态页与微信草稿均使用 左上角 72~92px 小图(.card / .lead-card 左侧缩略图),不再用全宽大图。 能拿到真实配图的卡片必须显示并上传;没有真实图就只保留文字卡,不放默认小图。 远程配图会先下载到当日 MindSpace public/,改写成本站相对 URL,避免海外图源在国内打不开。 推送前不再删除无图故事卡(微信草稿保留纯文字卡);有图卡未能上传微信 CDN 时只去掉该图,卡片保留文字。

配图 / 结构 / 写满检查只引导修复、不阻断生成和推送: 检查不过时依次做 SearXNG 补位、补图、头条规整;仍不满足就删掉完全空的栏目, 以现有内容出稿并照常推草稿,同时记 news002 audit incomplete, publishing best-effort page 告警。

强制重生成(forceGenerateOnce)在执行前就清掉标记,只跑一次、失败不重试; 生成失败时把归档的原稿放回原位,当日页面不会因重生成失败而 404。 降级页自动重生成每天最多一次。

真实图片来源按优先级取 SearXNG 结果里的 img_src/thumbnail,以及文章来源的 og:image:secure_url、og:image、twitter:image;不使用与事件无法核验关系的泛图片搜索结果。

内容安全底线闸门

成人、未成年人保护、法律合规、人文伦理、来源资质是底线,只过滤、不阻断:命中就删卡或改写,生成和发布照常进行。

层 位置 做什么
提示词 NEWS_MORNING_CONTENT_SAFETY_TASK_SPEC(拼进生成任务,不改冻结的 news001 模板) 告诉 Agent 底线
检索 wechat-news-morning-search.mjs SearXNG safesearch=2;预取结果先过 filterNewsSearchGroupsForSafety,因为 SearXNG 补位会把预取结果直接写进页面
页面 wechat-news-morning-content-safety.mjs,worker 在去重后、配图检查后各跑一次 逐块检查卡片 / 速读 / 深读 / 导语;一次批量模型复审
推送 pushDraft / 预览前 sanitizeNewsMorningHtmlSync 只删 block 级,标题与摘要残留词打码

规则在 news-content-safety-rules.mjs,分两级:

  • block:命中必删,模型复审不能放行(色情、荐股、虚拟货币推广、赌博毒品交易、歧视词、八卦、灾难调侃、非新闻来源、自媒体作时政来源等)。
  • review:交给模型复审(性犯罪新闻、涉未成年人伤害、自杀、恐袭、传闻、标题党、币价行情等)。复审不可用时按失败即删处理。

删卡导致栏目不满时,被删过内容的栏目会进 exemptSectionIds,配图/写满检查对这些栏目豁免,否则「过滤」会变成「阻断发布」。 被删事件出现在 description 里时,用剩余速读重建 description;残留在 meta、JSON-LD 的 block 级词打码。 页面 footer 与微信草稿底部追加「本文由 AI 辅助整理」显式标识,<head> 写入 memind-aigc(隐式标识)与 memind-content-safety(审计标记)。

  • 关闭模型复审(规则仍生效):MEMIND_NEWS_MORNING_CONTENT_REVIEW=0
  • 复审模型 / 超时:MEMIND_NEWS_MORNING_CONTENT_REVIEW_MODEL、MEMIND_NEWS_MORNING_CONTENT_REVIEW_TIMEOUT_MS(默认 90s)

规则只能加严或修正误伤,不得为了写满栏目放宽 block 级规则。修正误伤时必须在 news-content-safety-rules.test.mjs 补一条「不应命中」的用例。

预览与验证

# 渲染一张 news002 静态预览页(样例数据,占位图)
node scripts/preview-news002-template.mjs /tmp/news002-preview.html

# 用当天真实新闻 + 对应文章来源图片生成视觉样图(不推微信、不写生产目录)
node scripts/build-news002-real-design-sample.mjs

# 相关单测
node --test news-morning-templates.test.mjs \
             news-content-safety-rules.test.mjs \
             wechat-news-morning-content-safety.test.mjs \
             wechat-news-morning-images.test.mjs \
             wechat-news-morning-dedup.test.mjs \
             wechat-news-morning-draft.test.mjs \
             wechat-news-morning-draft-worker.test.mjs \
             wechat-news-morning-search.test.mjs \
             wechat-daily-news-inline.test.mjs