# https://xybcloud.online/chat/ 任务来源 + 两种生成方式完整业务流程

> 分析日期：2026-08-27
> 涉及项目：`/var/www/html/chat/`、`/var/www/cutebot/`、`/root/ai-ppt-maker/`
> 核心问题：chat 页作为任务入口，"从文本生成"和"从原稿图继续"两条链路的全链路差异

---

## 0. 三层架构总览

```
┌──────────────────────────────────────────────────────────────────┐
│ Layer 1 · 任务来源（前端对话）  https://xybcloud.online/chat/    │
│   /var/www/html/chat/index.php                                   │
│   - 用户输入文本 + 可选上传图片附件                             │
│   - POST /api/send  (Bearer token)                            │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ Layer 2 · 对话 Agent（执行调度）  /var/www/cutebot/cutebot.py   │
│   - handle_send 解析前端消息                                   │
│   - ask_claude() → bash 调 `claude --output-format stream-json`│
│   - Claude 带 MCP 工具自主决定如何生成 PPT                     │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ Layer 3 · 任务引擎（产出 PPT）  /root/ai-ppt-maker/             │
│   Flask + React/Vite · POST /api/jobs · 共享 4 阶段流水线      │
│   - 产出落到 /var/www/html/share/<file>.pptx                  │
└──────────────────────────────────────────────────────────────────┘
```

---

## 1. 任务来源：chat 前端 → cutebot 后端

文件：`/var/www/html/chat/index.php`

- `API_BASE = location.origin`（1189 行）→ 同源请求 `https://xybcloud.online/chat/api/...`
- 文件上传：`POST ?action=upload`（21–89 行）→ 落到 `/var/www/html/data/<safe_name>`，返回 `{ ok, url }`
- 发送消息：`POST /api/send`（1914 行），body `{message, model_id, session_id, new}`
- 上传文件被附加到消息末尾：`@<url>` 形式（1882–1884 行）
- 关键：chat 前端 **不会** 直接调 ai-ppt-maker；它只是把"文本+图片链接"扔给 Claude

文件：`/var/www/cutebot/cutebot.py`

- `handle_send(request)`（1170 行）→ 解析 message，调 `ask_claude()`
- `ask_claude()`（643 行）：`bash -c "... && claude --allow-dangerously-skip-permissions --output-format stream-json --verbose -- <question>"`，拿流式 JSON 实时回调 on_progress 给前端

---

## 2. 关键岔路口：两种 source_mode

ai-ppt-maker 后端（`ppt_system/web/services/jobs_api_service.py:49`）用 `request.form.get("source_mode")` 区分两种创建路径：

| `source_mode` | 入口函数 | 业务含义 |
|---|---|---|
| **空 / 缺省** | `_api_create_job` 主分支（52–183 行） | 从文本生成 PPT |
| `"external_reference"` | `_api_create_external_reference_job` 分支（186–260 行） | 从原稿图继续生成 PPT |

`EXTERNAL_REFERENCE_SOURCE_MODE = "external_reference"` 定义在 `external_reference_job.py:19`

---

## 3. 完整业务流程图

### 3.1 方式 A · 从文本生成（用户只输入 prompt）

```
[用户]
  │ 输入文本："给我做一份关于量子计算的 PPT"
  ▼
[chat.php 前端]
  │ POST /api/send  body={message:"量子计算..."}
  │ （无 uploadedFiles，无 @url）
  ▼
[cutebot/cutebot.py · handle_send]
  │ ask_claude(message)
  ▼
[Claude CLI + MCP]
  │ 自主判断：高质量需求 → 调 ai-ppt-maker /api/jobs
  │ curl POST /api/jobs   form:
  │   source_mode       = (空)
  │   content           = "量子计算..."
  │   page_count        = 12
  │   image_preset      = "landscape_2k"
  │   image_quality     = "medium"
  │   style_notes       = ""
  │   style_images[]    = (可选) 风格参考图
  ▼
[ai-ppt-maker · _api_create_job]  jobs_api_service.py:52
  │ 1) uuid → job_id，建 job_dir/style_refs/01_reference_pages/02_elements_pages
  │ 2) save style_images → style_refs/
  │ 3) runtime.create_job_record(JOBS_DB_PATH, ...) 持久化
  │ 4) runtime.JOB_EXECUTOR.submit(run_job_pipeline, ...)
  ▼
[ai-ppt-maker · 4 阶段流水线]   job_pipeline_runner.py:171
  │
  │ ┌──────── 阶段 1: planning ────────┐
  │ │ - 加载对话/生图模型配置           │
  │ │ - 调 chat 模型解析 prompt →     │
  │ │   style_guide + 逐页规划        │
  │ │ - page_evaluator 评估 → 重试    │
  │ └──────────────────────────────────┘
  │         ↓
  │ ┌──────── 阶段 2: reference_generation ────────┐
  │ │ - page_image_pipeline.run_page_image_pipeline│
  │ │ - 并发生成 N 张原稿图（带文字完整视觉稿）    │
  │ │ - 落 01_reference_pages/NN.png              │
  │ └──────────────────────────────────────────────┘
  │         ↓
  │ ┌──────── 阶段 3: elements_generation ────────┐
  │ │ - 对每页原稿图去文字 → 生成纯元素背景图     │
  │ │ - 透明化 / 连通域分割                       │
  │ │ - 落 02_elements_pages/NN.png              │
  │ └─────────────────────────────────────────────┘
  │         ↓
  │ ┌──────── 阶段 4: ppt_export ────────┐
  │ │ - 调 chat 模型：原稿图+元素图 → 文字层布局脚本│
  │ │ - 真实 PPT 渲染 → 再调模型回看修正文字位置 │
  │ │ - 输出可编辑分层 PPTX                │
  │ │   (元素资源页 + 文本框页)           │
  │ └─────────────────────────────────────┘
  │
  ▼
[ai-ppt-maker · /api/jobs/<id>/deliver 或直接落盘]
  │ 落到 /root/ai-ppt-maker/output/<job_id>/*.pptx
  │ （前端可通过 /runs/<job_id>/... 静态访问）
  ▼
[Claude 把 PPT 路径回贴给用户]
  │ https://xybcloud.online/runs/<job_id>/final.pptx
  ▼
[用户下载 / 预览]
```

### 3.2 方式 B · 从原稿图继续（用户上传 1 张原稿图）

```
[用户]
  │ 上传一张 PPT 截图 + 输入："按这个风格继续生成 5 页"
  ▼
[chat.php 前端]
  │ 1) 触发 triggerUpload() → uploadFile(file)
  │ 2) POST ?action=upload → 存 /var/www/html/data/<safe>.png
  │    返回 {url:"https://xybcloud.online/data/<safe>.png"}
  │ 3) 消息文本末尾追加 '@<url>'（1882 行）
  │ 4) POST /api/send  body.message = "按这个风格继续生成 5 页\n@https://.../<safe>.png"
  ▼
[cutebot/cutebot.py · handle_send]
  │ ask_claude(message)
  ▼
[Claude CLI + MCP]
  │ 1) Read 工具读图（@url 是 MCP 可识别的图片标记）
  │ 2) 识别原稿结构 / 风格 / 页码 → 决策：
  │    - 若希望保留原稿风格继续生成：调 ai-ppt-maker /api/jobs
  │      curl POST /api/jobs   form:
  │        source_mode                 = "external_reference"
  │        reference_images[]          = <原稿图文件>
  │        content                     = "按这个风格继续生成 5 页"
  │        page_title                  = (可选)
  │        external_reference_resize_mode = "stretch" | "contain" | "cover"
  │        external_reference_background  = "#FFFFFF"
  │        external_reference_create_only = false
  │        job_target = "editable_ppt"  （或 "reference_only" 只登记）
  │      若用户图本身已托管可 URL 访问，也可先
  │      POST /api/jobs/reference-image-from-url  拿到字节流
  │      再带进 reference_images
  ▼
[ai-ppt-maker · _api_create_external_reference_job]  jobs_api_service.py:186
  │ 1) 校验图片格式（SUPPORTED_IMAGE_SUFFIXES）和数量
  │ 2) 落盘 <job_dir>/external_reference_uploads/NN_<name>
  │ 3) create_external_reference_job(...)：
  │    - 按 resize_mode 调整画幅（拉伸/等比留白/等比裁切）
  │    - 把调整后图登记为"阶段 1 原稿图" → 写入 01_reference_pages/
  │    - 【跳过 reference_generation 阶段】原稿图已经在了
  │ 4) 若 create_only=false：
  │    JOB_EXECUTOR.submit(run_job_pipeline, ...)  从 elements_generation 起
  ▼
[ai-ppt-maker · 流水线（跳过阶段 2）】
  │
  │ ✓ planning           仍然跑（基于 content + 原稿图做规划）
  │ ✗ reference_generation   【跳过】原稿图已就绪
  │ ↓
  │ ┌──────── elements_generation ────────┐
  │ │ - 对每页原稿图去文字 → 纯元素背景图 │
  │ │ - 透明化 / 连通域分割                │
  │ └─────────────────────────────────────┘
  │ ↓
  │ ┌──────── ppt_export ─────────────────┐
  │ │ - 与方式 A 阶段 4 完全一致         │
  │ └─────────────────────────────────────┘
  │
  ▼
[同方式 A 末尾：交付 PPTX → Claude 回贴给用户]
```

---

## 4. 两方式对比矩阵

| 阶段 | 方式 A 从文本生成 | 方式 B 从原稿图继续 | 备注 |
|---|---|---|---|
| 前端表单 `source_mode` | 空 | `"external_reference"` | 决定路由分支 |
| 上传文件 | 可选 `style_images` | 必传 `reference_images`（multipart） | |
| 原稿图来源 | AI 生成（`gpt-image-2`） | 用户上传（再 resize） | 唯一真正差异 |
| planning 规划 | ✓ | ✓ | 共享 |
| **reference_generation** | ✓（生成 N 页原稿图） | **✗ 跳过** | 决定差异 |
| elements_generation | ✓ | ✓ | 共享 |
| ppt_export | ✓ | ✓ | 共享 |
| 可注册 job_target | editable_ppt / hd_narration 等 | 多一个 `reference_only`（只登记不跑后续） | |
| 输出位置 | `output/<job_id>/*.pptx` | 同左 | |

---

## 5. 关键代码定位（速查）

| 关注点 | 路径 | 行号 |
|---|---|---|
| chat 前端 send | `chat/index.php` | 1914 |
| chat 前端 upload | `chat/index.php` | 21–89 |
| chat 前端附件→@url | `chat/index.php` | 1882–1884 |
| cutebot handle_send | `cutebot.py` | 1170 |
| cutebot ask_claude | `cutebot.py` | 643 |
| ai-ppt-maker 总入口（Flask） | `main.py` | 16, 220, 238 |
| ai-ppt-maker blueprints 注册 | `ppt_system/web/app.py` | 32–37 |
| ai-ppt-maker jobs_api 路由 | `blueprints/jobs_api.py` | 全文 |
| ai-ppt-maker create_job 主分支 | `jobs_api_service.py` | 49–183 |
| ai-ppt-maker 原稿图分支 | `jobs_api_service.py` | 186–260 |
| EXTERNAL_REFERENCE_SOURCE_MODE 常量 | `external_reference_job.py` | 19 |
| 4 阶段流水线 | `job_pipeline_runner.py` | 171 起 |
| 阶段名常量 | 同上 + `job_status_messages.py` | planning / reference_generation / elements_generation / ppt_export |
| 原稿图来源 URL 抓图 | `blueprints/reference_image_url_api.py` | 86–140 |
| 前端两种入口 UI | `web_ui/dist/index-Fi-urJWT.js` | —（SPA 打包后） |

---

## 6. 业务关键设计点

1. **chat 不直连 ai-ppt-maker**：任务来源是 Claude 对话，由 Claude 自主决定是否/如何触发 ai-ppt-maker。
   这是 cutebot 历史日志里看到"用 python-pptx 直接拼"和"调 ai-ppt-maker"两条分支同时存在的原因。

2. **共享后半段流水线**：从 elements_generation 起，两种方式完全等价；原稿图存在与否只决定要不要跑 reference_generation 阶段。

3. **断点续跑友好**：每个阶段都有 `should_run_stage` 检查，已存在的产出会被复用——切方式 A/B 时不会浪费之前的成果。

4. **`reference_only` 模式**：`job_target=reference_only` 或 `external_reference_create_only=true` 时，原稿图分支只登记不入队，适合"先把原稿图存档，之后手动续跑"。

5. **SSRF 防护**：`reference_image_url_api.py` 拒绝私有/回环地址，限制 25MB / 12s，避免抓图时被打内网。

---

## 7. 一次用户可观测的全过程

方式 A 示例（来自 `/var/www/cutebot/chat_history.json`）：

```
用户："读取 https://mp.weixin.qq.com/s/... 网页内容，生成PPT"
Claude：tavily_extract → 拿到文章 → 调 ai-ppt-maker /api/jobs (source_mode 空)
        → planning → reference_generation → elements_generation → ppt_export
回复：访问地址 https://xybcloud.online/runs/<id>/final.pptx
```

方式 B 示例（待补充：需用户在 chat 上传 1 张 PPT 截图，Claude 调 source_mode=external_reference 分支）。