# AI PPT Maker 业务流程图（生成规划 / 出 PPT）

> 服务：`https://xybcloud.online/aippt/`
> 后端：`ai-ppt-maker-V5.1`，蓝图 `ppt_system/web/`，核心 orchestrator：`ppt_system/web/services/job_pipeline_runner.py:run_job_pipeline`
> 状态：源代码阅读整理，便于后续调试 / 二次开发定位

---

## 1. 端到端流程图

```mermaid
flowchart TD
    %% ====== 入口 ======
    UI[Web UI<br/>React/Vite] -->|POST /api/jobs| JobsAPI[blueprints/jobs_api.py<br/>api_create_job<br/>jobs_api_service.py:49]
    JobsAPI -->|resolve_generation_options| Opts[generation_options<br/>language / page_count / page_richness / ...]
    JobsAPI -->|build_job_state| State[(state.json)]
    JobsAPI -->|create_job_record| JobsDB[(jobs.sqlite3)]
    JobsAPI -->|JOB_EXECUTOR.submit| Exec[ThreadPoolExecutor]

    %% ====== 主调度 ======
    Exec --> Pipeline[run_job_pipeline<br/>job_pipeline_runner.py:171]

    Pipeline -->|reconcile_resume_state| State
    Pipeline -->|get_active_model_config| Models
    Models[对话模型 / 生图模型]:::modelConfig
    Pipeline --> Chat[chat_provider<br/>OpenAIChatProvider]
    Pipeline --> Img[image_provider<br/>OpenAIImageProvider]

    %% ====== Stage 1: planning ======
    Pipeline -->|planning running :230| P1[build_content_plan<br/>content_agent.py:54]
    Chat --> P1
    P1 -->|LLM 返回 JSON| Plan[plan: pages]
    Plan --> Eval[_attach_page_evaluations<br/>+ retry]
    Eval -->|score &lt; 0.7| P1
    Eval -->|OK| SavePlan[(state.plan)]
    SavePlan -->|planning completed :347| Done1[/已完成 N 页规划/]

    %% ====== Stage 2: reference_generation ======
    SavePlan -->|reference_generation running :503| R1[逐页调 image provider<br/>prompt = build_compact_reference_prompt]
    Img --> R1
    R1 --> RefPNG[01_reference_pages/page_NN_reference.png]
    R1 --> RefTxt[01_reference_pages/page_NN_reference_prompt.txt]
    RefPNG -->|reference_generation completed :775| Done2[/已完成 N 张带文字原稿图/]

    %% ====== (Optional) HD + narration ======
    Done2 -->|enable_narrations| Narration[generate_narrations_batch<br/>word_doc / hd_images_zip / hd_narration_zip]
    Narration --> ExportRef[/export/hd_narration.zip/]

    %% ====== Stage 3: elements_generation ======
    Done2 -->|elements_generation running :526| E1[去文字背景<br/>03_textless_background_pages/]
    E1 -->|image 调用 1| TBG[textless_background.jpg]
    TBG --> E2[抠元素图<br/>02_elements_pages/]
    E2 -->|image 调用 2<br/>build_elements_prompt| ElemPNG[02_elements_pages/page_NN_elements.png]
    TBG --> MB[04_master_background/master.*]
    ElemPNG -->|elements_generation completed :950| Done3[/已完成 N 张去文字元素 + M 张去文字背景/]

    %% ====== Stage 4: editable generation ======
    Done3 -->|editable generation running :960| Ed[03_ppt_build/page_NN/<br/>assets/, editable_delivery.bundle.json]
    Ed --> TextSc[generated_text_layout.py<br/>文字脚本生成]
    TextSc --> Done4[/已完成可编辑元素生成/]

    %% ====== Stage 5: PPTX export ======
    Done4 --> Export[export_pipeline.export_web_job_to_pptx<br/>export_pipeline.py / editable delivery]
    Export --> RfOnly[result.reference_only.pptx]
    Export --> Overlay[result.editable.overlay.pptx]
    Export --> SepSlides[result.editable.separate_slides.pptx]
    Overlay --> Frontend((完成))

    %% ====== 反馈通路 ======
    State -->|GET /runs/&lt;job_id&gt;/...| UI
    Frontend --> UI

    classDef modelConfig fill:#fdf6b2,stroke:#a07;
```

### 1.1 简版（同图精简）

```
POST /api/jobs
   │
   ├─ build_job_state  ──(state.json / jobs.sqlite3)
   │
   └─ JOB_EXECUTOR.submit(run_job_pipeline)
           │
           ├─ planning
           │    └─ chat_provider → build_content_plan
           │         ↘ evaluate / retry(<=page_evaluation_retry_count)
           │
           ├─ reference_generation   (并发 stage1_concurrency)
           │    └─ image_provider → build_compact_reference_prompt
           │         ↘ 01_reference_pages/*.png + *.prompt.txt
           │
           ├─ (可选) 高清图 + 讲解词 zip
           │
           ├─ elements_generation    (并发 stage2_concurrency)
           │    └─ 03_textless_background_pages/ + 02_elements_pages/
           │
           ├─ editable generation
           │    └─ 03_ppt_build/page_NN/{assets, text_layout}
           │
           └─ export
                ↘ result.reference_only.pptx
                  result.editable.overlay.pptx
                  result.editable.separate_slides.pptx
```

---

## 2. 阶段详情

### Stage 1 — Planning（对话模型拆页）

| 项 | 值 |
|---|---|
| 触发 | `run_job_pipeline:230 update_stage("planning", running)` |
| 核心函数 | `ppt_system/generation/content_agent.py:54 build_content_plan(chat_provider, content, page_count, image_size, style_notes, ...)` |
| 消费 | `content`（用户 markdown）+ `generation_options`（含 `language` / `page_richness_map` / `reference_style_adherence` ...）+ `style_reference_paths[]`（refs_dir 中的图） |
| 产出 | `plan = {pages:[…], style_type, audience, narrative, style_guide, evaluation}`；每页有 `title / summary / bullets / layout_intent / layout_family / image_prompt` |
| Prompt | `system prompt` 按 `language=="en"` 切两版（`content_agent.py:82-100`），但 prompt 模板字段在子函数仍硬编码中文 |
| 评估-重试 | `_attach_page_evaluations` 把分数写回 `page.evaluation`，总分 `>=0.7` 才进入下一阶段，否则循环 `page_evaluation_retry_count` 次重跑 `build_content_plan`（`job_pipeline_runner.py:282-330`） |
| 落盘 | `state["plan"]` + `state.stages.planning` + `state.current_stage = "planning"`，由 `update_stage` 统一托管 |
| 关键路径常量 | 函数最末：`page["image_prompt"] = build_reference_prompt_by_mode(...)`（`content_agent.py:653`） |

**planning 在做什么**：把用户长文 `content` 切分成 `page_count` 个语义单元，每页抽 `title / summary / bullets`，决定 `layout_family`，并由 chat 模型给出 `image_prompt`（如未给出，会在 `content_agent.py:653-660` 用 `build_reference_prompt_by_mode` 兜底拼出）。

---

### Stage 2 — Reference Generation（逐页生图，stage1 并发）

| 项 | 值 |
|---|---|
| 触发 | `run_job_pipeline:503 update_stage("reference_generation", running)` |
| 并发 | `stage1_concurrency`，默认 1（`config["stage1_concurrency"]`） |
| 每页行为 | 调 `image_provider.generate(prompt=...)`，`prompt = build_compact_reference_prompt(page, ..., language=generation_options.language, ...)` |
| 目录 | `01_reference_pages/page_NN_reference.{png,prompt.txt}` |
| 异常 | 单页异常写到 `state.error`，不打断其他页 |
| 结束 | `:775 update_stage(reference_generation, completed)` |

**reference_generation 在做什么**：用 chat 模型给出的 `image_prompt` 调图像 API，每页出一张 2K（按 `image_preset`）原稿图。这张图就是用户在前端能看到的"带文字原稿图"。

---

### (Optional) HD & Narration

| 项 | 值 |
|---|---|
| 入口 | `generation_options.narration_enabled / narration_length`；HD 导出在 reference 完成后 `:880` |
| 行为 | `build_export_options(active_config)` + `export_pipeline.hd_export` → `export/hd_narration.zip`, `export/hd_images.zip`, `export/narration.docx` |
| 用途 | 给"图片 PPT"或"解说词 docx"做高清备份 |

---

### Stage 3 — Elements Generation（去文字背景 + 抠元素图）

| 项 | 值 |
|---|---|
| 触发 | `:526 update_stage("elements_generation", running)` |
| 并发 | `stage2_concurrency`（默认 1） |
| 子阶段 A — 去文字背景 | `03_textless_background_pages/page_NN_textless_background.{jpg,prompt.txt}`；用图像 API + "无文字背景"指令 |
| 子阶段 B — 抠元素图 | `02_elements_pages/page_NN_elements.{png,prompt.txt}`；输入是去文字背景，`prompt = build_elements_prompt(...)` |
| 同步产物 | `04_master_background/master_background.{png,jpg}`（整本共用） |
| 结束 | `:950 update_stage(elements_generation, completed, …, N 张元素 + M 张背景图)` |

**elements_generation 在做什么**：原稿图有"前景视觉元素 + 文字"。这一阶段**先做去文字背景**（保留背景色），**再抠出纯视觉元素**（无文字的图标 / 卡片 / 箭头 / 产品图），给后续"可编辑 PPT"用——这样文字可以作为可编辑层叠在元素之上。

---

### Stage 4 — Editable Generation（元素资源与文字脚本）

| 项 | 值 |
|---|---|
| 触发 | `:960 update_stage(...)` summary "正在生成可编辑元素资源与文字脚本" |
| 目录 | `03_ppt_build/page_NN/assets/*`, `editable_delivery.bundle.json`, `generated_text_layout.py` |
| 产出 | 每页结构化的"图层资源"+ 文本布局脚本（pymupdf 类，根据 `layout_family` 把元素和文字摆位） |
| 结束 | `:1075 update_stage(...)` "可继续导出可编辑PPT" |

**editable generation 在做什么**：把前面抠出的视觉元素 + chat 模型给出的 `title / summary / bullets` 合并成"图层 + 文字"双层模型——这一层是可编辑 PPT 的关键（用户后续编辑 PPT 时，文字框可重写而元素不变）。

---

### Stage 5 — PPTX Export（最终交付）

| 项 | 值 |
|---|---|
| 主调 | `export_pipeline.export_web_job_to_pptx(...)` / `export_editable_delivery(...)` (`ppt_system/export/`) |
| 产物 | `result.reference_only.pptx`（每页一张高清单图）<br/>`result.editable.overlay.pptx`（带文字层）<br/>`result.editable.separate_slides.pptx`（元素与文字分图层） |
| Delivery | `editable_delivery.bundle.json` 含有 `delivery_key`（`EDITABLE_PPT_DELIVERY_KEY` / `HD_NARRATION_DELIVERY_KEY` / `REFERENCE_PPT_DELIVERY_KEY`）决定向用户暴露哪几种交付 |

---

## 3. 状态与数据存储

```
output/<job_id>/
├── 01_reference_pages/page_NN_reference.{png,prompt.txt}
├── 02_elements_pages/page_NN_elements.{png,prompt.txt}
├── 03_textless_background_pages/page_NN_textless_background.{jpg,prompt.txt}
├── 03_ppt_build/
│   └── page_NN/{assets/, editable_delivery.bundle.json, generated_text_layout.py}
├── 04_master_background/master_background.{png,jpg}
├── 04_image_edits/page_NN/...   （用户手工编辑产物，单页操作可见）
├── config.snapshot.json
├── export/{hd_*.zip, narration.docx}
├── job.json
├── project.generated.json
├── result.editable.overlay.pptx
├── result.editable.separate_slides.pptx
├── result.reference_only.pptx
├── state.json            ← 一级状态
├── status.json           ← 前端 SSE 用的状态镜像
└── thumbnails/...

output/jobs.sqlite3       ← 全局任务清单 / request payload
```

`status.json` 是前端通过 `/runs/<job_id>/status.json` / `/api/jobs/<id>` 长轮询（或 SSE）的源头；前端轮到这个文件时显示进度条与阶段名。

---

## 4. 接口契约（用户在前端的视角）

| 阶段 | UI 反馈 | 主要 API |
|---|---|---|
| 提交 | 任务 ID + 计划页数 | `POST /api/jobs` → 202 |
| planning | 进度条："正在拆页并生成提示词" | `GET /api/jobs/<id>` (`status`) |
| reference | 缩略图逐一出现 | `GET /runs/<id>/01_reference_pages/...` |
| elements | 进度："去文字背景 / 元素图" | `GET /api/jobs/<id>` |
| editable | "可编辑元素生成完成" | `GET /api/jobs/<id>` |
| export | "PPTX 已就绪" + 下载链接 | `GET /runs/<id>/result.<key>.pptx` |

---

## 5. 关键约束 / 注意点（与语言 bug 相关）

| 约束点 | 位置 | 当前实现 | 影响 |
|---|---|---|---|
| system prompt 按 language 切 | `content_agent.py:82-100` | ✅ | 图文规划可用英文思维 |
| image prompt 模板标签 | `generation_prompts.py:212/280 …` | ✅ 修复后 212 / 280 已 inline 加 `if language == "en"` 追加禁中文句；其他 6 个字段标签（"页面主题"/"核心表达"/"必须体现的要点"/…）仍是中文硬编码 | 提示给图像模型的 prompt 第二段仍是中文 |
| reference 图 prompt 来源 | `page["image_prompt"]` 由 chat 模型产出（`content_agent.py:653`） | chat 模型 prompt 里有 "All prompts must be translated into English" 但同时有 "do not rewrite categories"——会保留原文（中文）row label | **图片里仍可能含中文标签** |
| elements prompt | `generation_prompts.py:401 build_elements_prompt` | ❌ 整段硬编码中文；无 `language` 参数 | en 任务仍发中文 prompt 给抠图模型 |

> 这就是为什么仅把 `generation_prompts.py:212`/`280` 改成"禁止中文"还不够根治——上游的 chat 模型仍按中文原文产出字段值，下游的 prompt 模板里仍有中文标签。要根除需要再加 1 步——整文翻译原文（方案 B）。

---

## 6. 相关文件指引

| 文件 | 作用 |
|---|---|
| `main.py` | 服务入口、Flask `app.run` |
| `ppt_system/web/app.py` | Flask `create_app()`、路由注册 |
| `ppt_system/web/blueprints/jobs_api.py` | POST /api/jobs 等路由 |
| `ppt_system/web/services/jobs_api_service.py` | `api_create_job` 校验 + 创建 state |
| `ppt_system/web/services/job_pipeline_runner.py` | **`run_job_pipeline` 主调度**（上面所有阶段的真身） |
| `ppt_system/generation/content_agent.py` | `build_content_plan`（planning 阶段核心） |
| `ppt_system/generation/generation_prompts.py` | `build_compact_reference_prompt` / `build_elements_prompt` |
| `ppt_system/export/export_pipeline.py` | PPTX 装配 (`export_web_job_to_pptx`) |
| `ppt_system/web/services/plan_version_store.py` | plan snapshot 持久化，支持重试/编辑 |
| `ppt_system/web/services/job_operations_service.py` | 单页编辑 / interrupt / 介入 |
