- name
- total-pipe-deck
- description
- 从论文 PDF 到研究汇报 PPT 的三段式流水线(paperworkflow → pwf2rpa → research_ppt)。当用户要"把这篇论文做成汇报/组会 PPT""从 PDF 生成 slides""跑通 Total-pipe"时使用。
- agent_created
- true
# Total-pipe:论文 → 研究汇报 PPT
三段流水线,各段是一个 MCP 连接器。**本文件就是调用契约,不要再去读连接器源码。**
```
paperworkflow pwf2rpa research_ppt render (slidep) pptx-telemetry
PDF → 证据注册表 → 加 slide_briefs → 选版面 / 规划 → deck.pptx → OOXML 遥测 + QA
workflow.json rpa_input.json deck_plan.json sidecar / QA 报告
```
| 段 | 连接器 | 位置 |
|---|---|---|
| 1 | `paperworkflow` | `F:/Workbuddy/Total-pipe/paperworkflow` |
| 2 | `pwf2rpa` | `F:/Workbuddy/pwf2rpa` |
| 3 | `research_ppt` | `C:/Users/Beibei/plugins/research-ppt-assistant`(symlink,真身 `F:/Project/PPTcreator/...`)|
版本:RPA **0.6.1**,版面库 **2.0.0**(320 版面 / 40 分类),pwf2rpa **1.0.0**。
> RPA 必须用 **Node.js 24**(固定 `D:/Node24/node.exe`,v24.20.0 Krypton LTS)。
> 不要硬编码 WorkBuddy managed node 的 `versions/<ver>/node.exe` 路径——升级会漂移。
> 三个连接器都必须在连接器管理页点过 **Trust** 才有工具可用。如果某个工具
> 不存在,先让用户去 Trust,不要去改代码。
---
## ⚠️ 第一次跑就要走完:不许挑着用命令
历史教训:RPA 有 20 个 CLI 命令 + 20 个 MCP 工具,实测第一次跑只用了 3 个就开工,
`deck_plan.json` 沦为一次性产物。**根因不是 Agent 偷懒,是渲染层不消费 deck_plan
——slidep 吃手写 SlideDSL,Agent 完全可以绕开规划。**
现在用「骨架生成器」把这条路堵死,见下面阶段 4 的硬约束。
### 阶段 3 必须按序跑完(缺一步不许进阶段 4)
**首选方式:一条命令跑完,别手敲:**
```bash
python "C:/Users/Beibei/.workbuddy/skills/total-pipe-deck/scripts/rpa_full_pipeline.py" \
--rpa-input F:/x/rpa_input.json \
--out-dir F:/x \
--project F:/x/deck \
--assets-from-slides # 或 --assets-map 映射.json
```
它按 S1→S8 串行,任一步 FAIL 立即中断(退出码 1),每步产物落在 `<out-dir>/_rpa/`,
并生成 `_pipeline_report.md`。常用开关:
| 参数 | 用途 |
| :-- | :-- |
| `--assets-map m.json` | `{"4":["assets/fig1.png"]}` 显式指定每页用图 |
| `--assets-from-slides` | 从 `<project>/slides/NN.slide` grep 图片引用自动映射 |
| `--skeleton-out slides` | 默认 `slides_skeleton`(**不会覆盖正式页面**);显式改 slides 才覆盖 |
| `--skeleton-force` | 骨架目录已有 .slide 时强制覆盖 |
| `--allow-visual-fail` | S5 几何不兼容仍继续(仅当手写 DSL 不按 RPA 槽位摆位时用) |
| `--no-design` | 跳过 S8(不推荐;跳过就等于回到"空旷"的旧行为) |
| `--min-font 10.5` | S8 字号下限,接管版面库的 18pt 约束 |
| `--density 22 44` | S8 每页元素数软目标区间 |
| `--strict` | warning 也当失败 |
S1–S8 分别是(**以 `rpa_full_pipeline.py` 的实现编号为准**,别按语感排):
| | 步骤 | 说明 |
| :-- | :-- | :-- |
| S1 | `normalize-content` | → `content_model.json` |
| S2 | `plan` | → `deck_plan.json`,`pipeline_status` 必须 `plan_complete` |
| S3 | `validate-deck` | 必须 `valid` |
| S4 | `deckplan2slide.py` | 生成 SlideDSL 骨架(**必须先于 S5**,否则 preflight 报 `NO_PAGE_SOURCES`) |
| S5 | `preflight` | `preflight_complete` 且 `status != invalid` |
| S6 | `visual-fit-preflight` | 每张图不得 `fail` |
| S7 | `group-fit-preflight` | 每页 ≥2 图时查分组几何 |
| S8 | `design_land.py` | 出 `design_contract.json` + `_design_brief.md`。**只做准备,恒不 FAIL** |
S5 失败时脚本会直接给替代版面(按图槽几何算 contain 填充率排序),照着把该页
brief 的 `category_hint` 改掉再重跑即可,不用自己翻版面库。
<details><summary>手工分步跑法(脚本出问题时才用)</summary>
```bash
NODE=D:/Node24/node.exe
RPA=C:/Users/Beibei/plugins/research-ppt-assistant
cd $RPA
$NODE server/cli.mjs normalize-content --file <rpa_input.json> --detail-level compact > content_model.json
$NODE server/cli.mjs plan --file <rpa_input.json> --presentation-type group_meeting \
--slide-count N --detail-level compact > deck_plan.json
$NODE server/cli.mjs validate-deck --file <deck_plan 与 content_model 合并后的 json>
$NODE server/cli.mjs preflight --file preflight-input.json
```
`preflight-input.json` 模板(`deck_plan.slides` 必须填完整数组):
```json
{
"renderer_inputs": {
"requested_renderer": "slidep", "renderer_version": "5.4.4", "platform": "win32",
"project_path": "<PPT项目目录绝对路径>", "project_exists": true,
"source_files": ["slides/01.slide"], "live_watch_requested": false
},
"deck_plan": { "theme_id": "paper_blue", "slides": [] }
}
```
`visual-fit-preflight` 最小输入(注意 `visual_container.content_bbox` 若给则必须
**等于** `allocated_visual_bbox`,否则抛 RangeError):
```json
{
"visual_id": "P04:visual",
"source": {"width": 1440, "height": 1253},
"source_region": {"x": 0, "y": 0, "width": 1440, "height": 1253},
"visual_intent": {"visual_type": "dense_plot", "crop_policy": "full_figure",
"fit_policy": "contain", "priority": "primary_visual"},
"visual_container": {"container_id": "visual"},
"allocated_visual_bbox": {"x": 659.1, "y": 388.8, "width": 550.4, "height": 223.2}
}
```
`allocated_visual_bbox` = 版面 `slot_specs[].pptx_in` × 96(英寸→px)。
</details>
过关线:`pipeline_status == preflight_complete` 且 `status != invalid`,无重复 pageId。
页面带科研图必跑 `visual-fit-preflight`;同页多图有语义关系时补 `group-fit-preflight`。
### 阶段 4 只能从骨架开始(硬约束,这是关键机制)
```bash
python "C:/Users/Beibei/.workbuddy/skills/total-pipe-deck/scripts/deckplan2slide.py" \
--plan deck_plan.json --rpa-root $RPA --out slides/ --node $NODE
```
- 没有 `deck_plan.json`,或 `pipeline_status != plan_complete` → **退出码 1,一个页面都不给生成**
- 输出 `slides/NN.slide`:slot 几何取自 `slot_specs[].pptx_in`(×96 转 px),按 `reading_order` 摆好,附 C 区页脚
- 输出 `slides/_slots.md`:每页每 slot 的 `max_chars` / `max_lines` / 字号 hint
- **填内容时严格照 `_slots.md` 的容量写**,超了会被判 `CAPACITY_EXCEEDED`
这套机制把 RPA 从「可跳过」变成「绕不过」:不跑 plan → 没有 layout_id → 没有骨架 → 无法渲染。
实测生成的 13 页骨架 `slidep-validate` 13/13 通过。
改页数时先改 `briefs.json` 的条数再重跑 plan(`--slide-count` 是目标不是填充),
然后删掉旧 slides/ 重新生成骨架。
### S8 设计落地层(骨架 → 渲染之间的必经站)
**为什么必须有**:版面库契约每页只给 4–8 个槽、正文下限 18pt(实测见下方表格),
骨架忠实执行这份契约 → 每页 ~8 个大框、20pt 正文,**观感必然空旷**。
S8 把字号管辖权从 `layouts.json` 接到自己的 `design_contract.json`,
让 Agent 在已放行的槽位 bbox **内部**做排版细化与装饰落地。
契约关系(别搞反):
| 契约 | 管辖范围 | 字号下限 |
| :-- | :-- | :-- |
| `layouts.json` | 版面选槽(S1–S7) | 18pt(实测 `{20:242, 18:78}`) |
| `design_contract.json` | 槽位内部排版(S8+) | 10.5pt(可 `--min-font` 改) |
两者不冲突:S5 preflight 已按 18pt 判过几何兼容并放行,S8 只在槽位内再切分,不再选槽。
```bash
DL="C:/Users/Beibei/.workbuddy/skills/total-pipe-deck/scripts/design_land.py"
python "$DL" contract --out design_contract.json --min-font 10.5 --density 22 44
python "$DL" brief --skeleton slides/ --slots slides/_slots.md --out slides/_design_brief.md
# —— 这里由 Agent 按任务书自由发挥,改写/扩写 slides/*.slide(构图不受脚本约束)——
python "$DL" check --slides slides/ --contract design_contract.json --out _design_check.md
# 图片资产不在默认位置时用 --assets 指路(可给多个;给「含 assets/ 的目录」或 assets/ 本身都行):
# python "$DL" check --slides deck/slides --assets deck/assets
```
`rpa_full_pipeline.py` 的 S8 已把 contract + brief 一并跑掉,所以一键跑完就有任务书;
**`check` 要等落地页写完再手动跑。**
**关键:脚本不代写设计。** `brief` 只给三类东西——
1. **事实**:每槽 bbox / 容量 / 已用字数 / 未覆盖的空白带 / 本页图片路径
**+ 每张图的原始像素、图片比例、当前图框、建议图框、不改会裁掉多少**
2. **软目标**:密度区间(默认 22–44 个元素/页)、字号阶梯、可按需覆盖
3. **语汇货架**:顶栏标签 / 图注 / 脚注引文 / 指标双列 / 序号徽章 / 关键词高亮 /
来源标注 / 对比条 / 流程箭头 / 分隔留白 —— **是"货架"不是"清单",可全不用,可自创**
`check` 只卡**物理不可行**项(画布越界 / 字号低于下限 / 文字溢出 / 页脚与页码 /
**图片图框比例 ≠ 图片比例**),密度与构图只 WARN。`--strict` 才把 WARN 算失败。
#### ⚠ 图片:图框宽高比必须 = 图片文件自身比例(v1.1.0 起进 check)
**这是最容易被忽略、后果最直观的一条。** slidep 的 pptx 写出端对图片
**一律按 cover 裁切到图框比例**:
- `objectFit` 写 prop 也好、写在 `style` 里也好,填 `contain` / `cover` / `fill`
—— 产出**逐字节相同**,属性完全无效(已用 5 变体探针实测)
- 唯一影响裁切量的是**图框自身的宽高比**:`裁切比 = 1 − min(框AR/图AR, 图AR/框AR)`
- 实例:`440×209` 的图(AR 2.11)放进 `1107×318` 的槽(AR 3.48)→ 上下各裁 **19.76%**,
器件截面图的顶部标签与底部衬底**双双被切掉**
所以**「把图铺满槽位框」这个动作本身就是错的**。正确顺序是:
1. 读图片真实像素(`design_land.py` 自带 PNG/JPEG/GIF/BMP/WebP 头解析,纯标准库)
2. 按图片比例算 contain 适配矩形 `fit_rect(iw, ih, box_w, box_h)`
3. **让图框等于这个矩形**(contain 与 cover 在此时重合,歧义消失),在槽位内居中
4. 图卡 = 适配矩形 + padding;**腾出来的空档要用真实内容填**(参数表 / 指标行),
而不是让大卡片空着 —— 否则 `element_area_ratio` 反而掉下来
`brief` 已为每张图算好建议尺寸,直接抄;`check` 对偏差 >2% 的图判
`IMAGE_ASPECT_MISMATCH`(FAIL)并给出应改成的具体尺寸。
`deckplan2slide.py` 的图槽注释里也会打印**槽位框的宽高比**供落地层比对。
> 规划器其实已经声明了 `allowed_transformations.preserve_visual_aspect = true`,
> 只是此前**没有任何一环执行它** —— S8 就是执行者。
**版面库实测天花板**(`research-ppt-assistant/assets/layout-library/layouts.json` v2.0.0):
| 事实 | 值 |
| :-- | :-- |
| 每版面槽位数 | `{4:16, 5:112, 6:114, 7:68, 8:10}` → 单页最多 8 个元素 |
| 1864 个槽的 `font_pt_hint` | 全为 `None`(不规定细粒度字号) |
| `minimum_body_font_pt` | `{20:242, 18:78}` |
| `default_body_font_pt` / `default_title_font_pt` | 20 / 30(全部 320 版面) |
所以「空旷」不是 `deckplan2slide.py` 写错,是**整条快路径缺了 S8 这一站**。
参考成品(Windows)色板 `1E4FA8/4A5568/1A2230/D6DCE5/F7F9FC` 恰等于该脚本的
原始硬编码常量 → 它同样过了这座桥,只是桥后还跑了一次设计落地。**不是平台差异。**
**验收提醒**:`element_area_ratio` 的 warning 阈值是 <0.35、fail 才 <0.18,
**QA 全绿 ≠ 好看**。必须出图目视验收。
---
## 阶段 1 paperworkflow:PDF → workflow.json
**1a. 列候选**(用户没指定论文时先调这个)
```
paperworkflow_prompt_builder # 无参数
```
返回 `pdf_index`:`selection_id`(P001…)+ `relative_path`(相对 `INput`)。
把清单给用户选,**不要替他选**,也别把绝对路径念出来。
**1b. 跑工作流**
```
paperworkflow_literature_workflow
source_path # 必填,INput 相对路径或绝对路径,.pdf/.md
queries # 可选,研究问题数组,≤12 条,每条 ≤2000 字符
include_default_queries # 默认 true
top_k # 1-20,默认 5
chunk_chars # 500-20000,默认 6000
output_dir # 可选,项目内目录
```
产出 `workflow.json` / `document.manifest.json` / `evidence.md`。
**从返回值里取产物路径**,不要猜目录。这一步要 OCR,慢(分钟级),别重复跑。
判断能否进入下一阶段的依据是 **`synthesis_readiness`**(硬门禁),
不是 `quality_audit`(那只反映 OCR/排版可用性)。
**1c. 选完论文后回补**(第二次调 prompt_builder 时)
```
paperworkflow_prompt_builder
selected_pdf # 轮次一返回的 selection_id(P001)或 INput 相对路径
research_goal # 可选
focus_questions # 可选,字符串数组
additional_context # 可选
```
不传参数就是"列清单"模式,四个参数全可选。
**1d. 其余三个工具**(都已转换过 md 时很便宜,纯本地,不调模型)
```
paperworkflow_outline markdown_path(必填), chunk_chars
paperworkflow_search_evidence markdown_path(必填), query(必填), top_k
paperworkflow_process_pdf pdf_path(必填) # 只 OCR,不建证据注册表
```
`paperworkflow_outline` / `search_evidence` 只收 `.md`,`process_pdf` 只收 `.pdf`;
且**路径必须落在 paperworkflow 项目目录内**,越界会被拒绝(这是它自带的沙箱)。
---
## 阶段 2 pwf2rpa:workflow.json → rpa_input.json
**2a. 先干跑**(不写文件)
```
pwf2rpa_check
workflow_path # 必填
briefs_path # 可选,不传则用证据的 query 意图自动兜底生成
no_strict_fit # 默认 false
```
返回 `slide_count` / `slides[]` / `warning_count` / `warnings[]`。
**2b. 确认无警告后再写文件**
```
pwf2rpa_convert
workflow_path # 必填
out_path # 默认写到 workflow 同级的 rpa_input.json
briefs_path / strict / no_strict_fit
```
`paperworkflow_v4` 原样透传,这一段只补 RPA 推不出来的 `slide_briefs`。
输出逐字节稳定,同输入必同输出。
### 四条硬约束(README 说每条都真实咬过人)
1. `evidence_ids` 必须命中真实 `EV####` —— 否则 **error,阻断且不落盘**
2. `category_hint` 必须是 40 个合法 id 之一 —— 否则**不报错**,RPA 静默放宽成
全库搜索、挑错版面(最阴的一种失败)
3. 文本要塞得进该分类的槽位 —— 否则 `SLOT_CAPACITY_EXCEEDED`
4. 输出必须可复现
GitHub에서 보기