From fafcb14d8683e95a50afcade02d9cffa6245dd45 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 19 May 2026 10:19:09 +0800 Subject: [PATCH] =?UTF-8?q?skill(proposal):=20mermaid=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8D=20hash=E2=86=92caption=20+=20quality=5Fcheck=20?= =?UTF-8?q?=E5=8A=A0=E5=9B=BE=E7=9B=B8=E5=85=B3=204=20=E6=8B=A6=E6=88=AA?= =?UTF-8?q?=20+=20SKILL.md=20=E7=B2=BE=E7=AE=80;=20web=20cache=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户报"图没渲染到 docx",诊断后修三件事(同一根因链): - web/app.py /v1/files/download 加 Cache-Control: no-cache Starlette FileResponse 只发 ETag/Last-Modified, 浏览器走启发式缓存, workspace 文件改了 SPA 预览看不到新版 - quality_check 新 check_figures(): 4 条规则 1) figures/ 有 png 但 sections 0 个 ![]() 引用 2) fenced 代码块出现 box-drawing 字符 (┌─┐│└─┘ 等) 3) mermaid 块必须有首行 %% caption: <题> 4) 同 task 内 mermaid caption 不能撞名 - render_diagrams.py: hash → caption 命名 pass-1 验证 caption 完整 + 全 task 唯一, 缺/撞 退 2 pass-2 渲染落 fig_.png, 总是覆盖 - render_docx.py: mermaid 块按 caption 查 fig_.png 无 caption / 清洗空 / png 缺 → ASCII fallback - SKILL.md ~193 → ~160 行: 插图段 49→22 行(压 matplotlib 细节 + 删类型选择展开) 反模式合并 ASCII/占位/手写图编号/缺 caption/撞名 删"为什么两段式"长说理段 Co-Authored-By: Claude Opus 4.7 (1M context) --- PROGRESS.md | 1 + skills/proposal/SKILL.md | 80 +++++--------- skills/proposal/scripts/quality_check.py | 108 ++++++++++++++++++- skills/proposal/scripts/render_diagrams.py | 116 ++++++++++++++------- skills/proposal/scripts/render_docx.py | 34 +++--- web/app.py | 9 +- 6 files changed, 243 insertions(+), 105 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index 0ad565e..769b9ec 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -21,6 +21,7 @@ ## 已完成关键能力 +- **05-19 / dev SPA `/v1/files/download` 加 `Cache-Control: no-cache` + proposal skill mermaid 文件名 hash → caption + quality_check 加图相关 4 条拦截 + SKILL.md 精简 ~30%**:用户反馈"申报 skill 生成的图没有渲染到 docx 里"。诊断分两层:① 当下这次的真因不是 hash 也不是渲染管线 —— 是模型在 sections 里全写 ASCII 字符画(`┌─┐│`)+ 裸 ```...``` 围栏,从未用 mermaid + `![]()`,matplotlib 生成的 `figures/fig*.png` 静静躺着没人引用,render_docx 按规矩把 ASCII 当代码块原样画上,看起来"没图";② 接着用户反馈"实际文件已更新但浏览器还是旧版,新浏览器能看到新版"——SPA 预览端 fetch `/v1/files/download` 命中浏览器**启发式缓存**(Starlette FileResponse 只发 Last-Modified/ETag,无 Cache-Control,RFC 7234 默认按 mtime 启发式可缓数小时),旧浏览器没 conditional revalidation 就拿了缓存。**修法**:① `web/app.py::download_file` 加 `headers={"Cache-Control": "no-cache"}` —— 浏览器每次都重取(Starlette 不实现服务端 304,no-cache 在这里等价 no-store,workspace 文件小可接受;以后真要省流量再加 If-None-Match 处理);② `skills/proposal/scripts/quality_check.py::check_figures` 新加(共 4 条):**1) `figures/` 有 png 但 sections 0 个 `![](...)` 引用 → 图全没挂上**,2) 任何 fenced 代码块里出现 box-drawing 字符(`┌┐└┘├┤┬┴┼─│╔╗╚╝╠╣╦╩╬═║▲▼◀▶`)→ ASCII 字符画当图,3) mermaid 块必须有首行 `%% caption: <题>`,4) 同 task 内 mermaid caption 不能撞名;③ **hash → caption 命名重构**(讨论中用户先反对单字段 caption 想用 png 内容,后我提两字段 name+caption,用户最终拍板回归单字段 caption 简化):`render_diagrams.py` 删 `mermaid_hash()` + 改 caption 必填(缺 → 退 2)+ 全 task caption 唯一(撞名 → 退 2)+ 新 `caption_to_stem()` 清洗(保留 CJK/字母/数字,其它折 `_`,截 40 字)+ pass-1 验证 / pass-2 渲染两段式 + 总是覆盖渲染(去 cache 防 caption 不变源变了的孤儿);`render_docx.py` 删 `mermaid_hash()` + 改 caption 查表(同清洗规则),无 caption / 清洗空 / png 缺 → 走原 ASCII fallback;④ **SKILL.md 精简**(~193 行 → ~160 行):资源段更新 4 条脚本描述(render_diagrams 现在 caption 命名 / quality_check 现在 5 类拦截)+ 阶段三段不再吹 "render_diagrams 是可选前置"(改 caption 强制约定段)+ 插图段从 49 行压到 ~22 行(删类型选择细节展开 / 删 matplotlib 配色 dpi figsize 大段细节 → 一行;删 "为什么两段式"长说理段;反模式段合并 ASCII / 占位 / 手写图编号 / 缺 caption / 撞名为一条 "插图相关(`quality_check` 会拦)")。**为什么这一波改这么散**:四件事其实是一根线 —— 用户最初观察"图没出来"实际上是两个 bug 叠加(模型没用 mermaid + 浏览器缓存),修缓存是表层,加 quality_check 是防再犯,caption 命名是顺手把 hash 这层不可读性也清掉,SKILL.md 精简是承接两次改完后该删的冗余。**端到端 smoke**(`/tmp/zcbot_repro` 临时 task):mermaid 块 `%% caption: 总体架构` → `figures/fig_总体架构.png` 落盘 → docx `figures: 1` 报告对、`word/media/image1.png` 1278 bytes 嵌入;negative:缺 caption 退 2 / 撞名退 2(列出 md 位置 + 改名建议);quality_check 拦四条全打:`figures/ 有 N 张 png 0 个 ![]()` / `[md:L] ASCII 字符画 ┌─┐│└─┘` / `[md:L] mermaid 缺首行 %% caption` / `mermaid caption 撞名 X 出现在 md1:L1, md2:L2`。**没动**:`render_docx.py` 主体渲染逻辑(只换 mermaid 块查表那 ~5 行)/ matplotlib 章节生成的 png 命名习惯(`fig1_xxx.png` 风格留着,反正不冲突,`figures/` 同时存在 mermaid 的 `fig_.png` 与 matplotlib 的 `fig_.png` 两种风格)/ `templates/*.md` 里 mermaid 示例首行 `%% caption:` 本来就有(只是历史可选,现在强制约定到位)。**hash → caption 兼容性**:dev phase no compat,直接切;旧 task 里若有 hash 命名的 png 留着,render_docx 找不到对应 `fig_.png` 就走 ASCII fallback,用户重跑 render_diagrams 自动按新规则落 png 即可。**文档**:**只动 PROGRESS + skills/proposal/SKILL.md**(skill 内容/脚本接口变化按 CLAUDE.md 规则不动 DESIGN/RUN —— skill 不是 zcbot 对外 CLI/env/文件布局;但 `Cache-Control` 改动是 `/v1/files/download` 行为微调,客户端无感、文档化为后续 follow-up 可选)。 - **05-19 / dev SPA 文件预览弹框**:用户提:"web 右侧点击文件可以弹框加载预览,带下载按钮"。原行为是 click → 直接 `downloadFile`(走 `/v1/files/download`)落盘,不能在线看。**方案**:复用现有 `/v1/files/download`(blob URL 绕过 auth header 限制,不动后端),前端按扩展名分派渲染器。新加 `#file-preview-modal`(90vw × 90vh,max 1200px),头部 filename + 下载 + × 关,body 按 cat 切不同布局。**分派**:① image(jpg/png/gif/webp/bmp/svg/ico)→ `` blob URL;② pdf → `