# 系统详细设计 > 项目:PPT 自动生成 Agent · 赛道一:Agent 开发实战赛 · 新规项目 > 配套文档:PRD v1.2(`项目资料/需求与思路/02-需求文档/产品需求文档_PRD.md`)、设计文档(`项目资料/对外资料/设计文档.md`) > 版本:v0.4(2026-08-25 二次同步:UI 全语言收尾 + 网络配图管线;新增 6.6,更新 5.1/6.5/十章) > 前版:v0.3(2026-08-25 状态同步:Sprint 2 对话修改落地、在线预览上线、LLM 零Key 接入;新增 4.7 预览设计) > 前版:v0.2(2026-08-24 引擎切换改版:第5/6章改为 aura-ppt 方案,散点同步) > 初版:v0.1(第1批 · 骨架,2026-08-17) > 配套选型依据:`项目资料/事前调查/引擎选型对比_ppt-master_vs_aura-ppt.md` --- ## 一、总体架构与分层 ### 1.1 分层架构图 ``` 前端(HTML + JS) │ HTTP / JSON ▼ Flask API 层(路由 / 鉴权 / 文件上传 / 静态资源) │ ▼ Agent 核心(感知 → 规划 → 行动 → 记忆) │ ① 参数映射(标题/内容/配色/页数 → plan.json 页面规划) │ ② 状态机(生成任务调度 / 进度回传 / 失败重试) ▼ aura-ppt 渲染引擎(项目内副本:ppt-agent/engine/aura-ppt/) │ plan.json → 确定性渲染 PPTX → 内容保真验证 │ (辅助:ppt-master 仅复用文档解析/搜图脚本,Sprint 3 接入) ▼ SQLite(users / preferences / ppt_records / versions / chat_messages) ``` ### 1.2 模块职责 | 模块 | 目录 | 职责 | |------|------|------| | Web 前端 | `src/web/static/` | 4 页面(登录/工作台/历史/设置)+ 样式 + JS | | Flask API | `src/web/app.py` | 路由注册、Session 鉴权、请求校验、错误统一处理 | | Agent 核心 | `src/agent/` | perception / planning / action / memory 四子模块 + chat(对话修改)/ llm(LLM 客户端) | | 引擎桥接 | `src/engine_bridge/` | 调用 aura-ppt CLI(渲染 / 保真验证)、预览图导出(COM);ppt-master 解析脚本已接入(upload.py),搜图待接 | | 配置 | `config.py` | 引擎路径、上传限制、数据库路径、DeepSeek API 等 | | 数据层 | `src/db/` | SQLite 连接管理、表结构 DDL | ### 1.3 关键设计约束 | 约束 | 说明 | |------|------| | 引擎路径可配置 | `config.AURA_ENGINE_PATH` 指向项目内 aura-ppt 副本(渲染),`config.PPT_MASTER_PATH` 指向 ppt-master 副本(解析/搜图),不硬编码全局路径 | | 引擎只经 CLI 调用 | Agent 不 import 引擎内部模块,渲染统一走 `engine.py --plan/--verify` + subprocess | | 生成异步化 | PPT 生成耗时长,采用后台任务 + 前端轮询进度(对应 US-2.7a 进度提示、US-2.11 跨页面状态)| | 状态机驱动 | 每个生成任务有任务 ID,状态迁移:pending → analyzing → planning → rendering → done / failed | ### 1.4 中日文切换设计 系统支持**双语言**,且拆分为两个**相互独立**的维度: | 维度 | 含义 | 切换入口 | 存储 | |------|------|---------|------| | **UI 语言** | 界面文案(按钮/标签/提示)整体切换 | 顶栏语言下拉 + 设置页 | localStorage(未登录)→ preferences(登录后同步) | | **生成语言** | 生成的 PPT 内容与演讲备注的语言 | 工作台「生成配置」面板下拉 | 每次生成独立选择,默认取偏好值 | **多语言实现**: - 前端 i18n 字典:`src/web/static/lang/zh.json` 与 `ja.json`,键值一一对应,支持动态切换无需刷新 - 引擎侧:生成语言=ja 时,Agent 感知层输出的源 Markdown、执行器产生的演讲备注全部用日文,字体栈换为 Meiryo(参照试跑项目 ai-basics-jp) - 偏好默认值:`default_language` 默认 `zh`,用户可改 **语言洁癖约束**:任一语言环境下界面与生成内容均保持单一语言,禁止中日文混排。 --- ## 二、REST API 规范 ### 2.1 通用约定 | 项 | 约定 | |----|------| | 基础路径 | `/api` | | 内容类型 | 请求/响应均为 `application/json`(文件上传除外) | | 认证 | 除登录/注册外,所有接口需带 `session cookie`;未登录返回 `401` | | 响应结构 | 成功:`{"code":0,"data":{...}}`;失败:`{"code":<错误码>,"message":"中文提示"}` | | 错误码前缀 | `10xx` 认证 / `20xx` 生成 / `30xx` 上传解析 / `40xx` 历史 / `50xx` 设置 / `60xx` 引擎 | ### 2.2 错误码表 | 错误码 | 含义 | 前端提示文案 | |:--:|------|------| | 1001 | 用户名已存在 | "用户名已被使用" | | 1002 | 用户名或密码错误 | "用户名或密码错误" | | 1003 | 两次密码不一致 | "两次输入的密码不一致" | | 1004 | 未登录或会话过期 | "请先登录" | | 2001 | 内容字数不足 20 | "内容至少需要 20 个字" | | 2002 | 页数范围非法(min>max) | "最少页数不能大于最多页数" | | 2003 | 引擎繁忙 | "引擎繁忙,请稍后重试" | | 2004 | 生成失败(引擎侧) | "生成失败,请重试" | | 3001 | 不支持的文件类型 | "不支持的文件类型" | | 3002 | 文件超过 20MB | "文件大小不能超过 20MB" | | 3003 | 文件损坏无法读取 | "文件损坏,无法读取" | | 3004 | 解析内容为空 | "文件内容为空" | | 4001 | 历史记录不存在 | "该记录不存在或已被删除" | | 5001 | 原密码错误 | "原密码不正确" | | 6001 | 引擎内部错误 | "引擎内部异常,请联系管理员" | ### 2.3 接口清单 #### Epic 1 · 认证 | # | 方法 | 路径 | 说明 | 对应故事 | |---|------|------|------|---------| | 1 | POST | `/api/register` | 注册 | US-1.1 | | 2 | POST | `/api/login` | 登录 | US-1.2 | | 3 | POST | `/api/logout` | 退出登录 | US-1.3 | | 4 | GET | `/api/me` | 获取当前用户信息 | US-1.2 | #### Epic 2 · PPT 生成 | # | 方法 | 路径 | 说明 | 对应故事 | |---|------|------|------|---------| | 5 | POST | `/api/generate` | 触发生成,返回任务 ID | US-2.7a | | 6 | GET | `/api/generate//status` | 查询生成进度 | US-2.7a / US-2.11 | | 7 | GET | `/api/generate//result` | 生成完成后取结果 | US-2.7b | | 8 | POST | `/api/title/suggest` | 智能生成标题 | US-2.4 | | 9 | GET | `/api/files//download` | 下载 PPTX | US-2.8 | | 10 | GET | `/api/files//preview` | 在线预览页数据(PNG 按版本缓存) | US-2.9 | > 预览页图实际加载走 `GET /api/preview-img//`(单页 PNG,归属校验 + 文件名白名单,见 4.7)。 #### Epic 3 · 上传解析 | # | 方法 | 路径 | 说明 | 对应故事 | |---|------|------|------|---------| | 11 | POST | `/api/upload` | 上传文档(multipart) | US-2.3a | | 12 | POST | `/api/parse` | 解析上传文件为文本 | US-2.3b | #### Epic 4 · 对话修改 | # | 方法 | 路径 | 说明 | 对应故事 | |---|------|------|------|---------| | 13 | POST | `/api/chat` | 发送修改指令 | US-3.1 | | 14 | GET | `/api/chat//messages` | 获取本轮对话记录 | US-3.2 | #### Epic 5 · 历史记录 | # | 方法 | 路径 | 说明 | 对应故事 | |---|------|------|------|---------| | 15 | GET | `/api/records` | 历史列表(搜索/筛选/分页) | US-4.1 / 4.2 / 4.3 | | 16 | GET | `/api/records/` | 历史详情(含版本) | US-4.4 | | 17 | DELETE | `/api/records/` | 删除记录 | US-4.6 | #### Epic 6 · 个人设置 | # | 方法 | 路径 | 说明 | 对应故事 | |---|------|------|------|---------| | 18 | PUT | `/api/settings/username` | 修改用户名 | US-5.1 | | 19 | PUT | `/api/settings/password` | 修改密码 | US-5.2 | | 20 | PUT | `/api/settings/preferences` | 保存生成偏好 | US-5.3 | | 21 | GET | `/api/settings` | 获取设置与偏好 | US-5.1 / 5.3 | ### 2.4 关键接口详细定义 #### POST /api/generate **请求体:** ```json { "title": "七月项目周报", "content": "本月完成模块A开发……(≥20字)", "scene": "report", // report=社内汇报 / training=社内培训 "language": "zh", // zh=中文 PPT / ja=日文 PPT "canvas": "ppt169", // ppt169=16:9(默认)/ ppt43=4:3 "image_strategy": "web", // web=网络搜图(默认)/ ai=AI生成 / off=不用图 "color_scheme": "blue", // blue/green/purple/orange/gray "page_min": 10, "page_max": 15 } ``` **成功响应:** ```json { "code": 0, "data": { "task_id": "gen_20260817_1015_3f8a", "status": "analyzing" } } ``` **状态迁移(US-2.7a 进度提示映射):** | 状态 | 前端提示 | 对应引擎阶段 | |------|---------|-------------| | `analyzing` | "正在分析内容…" | 源内容处理 / 策略师分析 | | `planning` | "正在规划结构…" | 策略师八大确认解析 | | `rendering` | "正在排版渲染…" | 执行器 SVG 逐页生成 + 导出 | | `done` | 生成完成 | 导出完成 | | `failed` | 按错误码提示 | 任一环节失败 | #### GET /api/generate//status ```json { "code": 0, "data": { "status": "rendering", "progress": 68, // 0-100,渲染阶段按已生成页/总页估算 "message": "正在排版渲染…" } } ``` #### GET /api/files//download `Content-Disposition: attachment; filename="标题_20260817_1015.pptx"`,返回二进制流。 --- ## 三、SQLite 数据库设计 ### 3.1 ER 概览 ``` users 1 ─── 1 preferences │ └── 1 ─── n ppt_records 1 ─── 0..n versions │ └── 1 ─── n chat_messages ``` ### 3.2 表结构 #### users(用户表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 | | username | TEXT | UNIQUE NOT NULL | 用户名 | | password_hash | TEXT | NOT NULL | bcrypt 哈希,不存明文 | | created_at | TEXT | NOT NULL | 创建时间(ISO8601) | #### preferences(偏好表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 | | user_id | INTEGER | NOT NULL, FOREIGN KEY→users.id | 所属用户 | | default_scene | TEXT | DEFAULT 'training' | 默认场景 | | default_color | TEXT | DEFAULT 'blue' | 默认配色 | | default_language | TEXT | DEFAULT 'zh' | 默认生成语言(zh/ja) | | default_canvas | TEXT | DEFAULT 'ppt169' | 默认画布(ppt169/ppt43) | | default_image_strategy | TEXT | DEFAULT 'web' | 默认配图策略(web/ai/off) | | default_page_min | INTEGER | DEFAULT 10 | 默认页数下限 | | default_page_max | INTEGER | DEFAULT 15 | 默认页数上限 | #### ppt_records(PPT 生成记录表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 | | user_id | INTEGER | NOT NULL, FOREIGN KEY→users.id | 所属用户(索引) | | title | TEXT | NOT NULL | PPT 标题 | | content | TEXT | NOT NULL | 用户输入的源内容 | | scene | TEXT | NOT NULL | report / training | | language | TEXT | NOT NULL DEFAULT 'zh' | 生成语言(zh/ja),历史筛选依据 | | color_scheme | TEXT | NOT NULL | blue/green/purple/orange/gray | | page_min | INTEGER | NOT NULL | 页数下限 | | page_max | INTEGER | NOT NULL | 页数上限 | | status | TEXT | NOT NULL DEFAULT 'done' | 生成状态 | | file_size | INTEGER | NULL | PPTX 文件大小(字节) | | page_count | INTEGER | NULL | 实际生成页数 | | generate_duration | INTEGER | NULL | 生成耗时(秒) | | created_at | TEXT | NOT NULL | 生成时间(列表倒序排序依据) | #### versions(版本表,对应 US-4.4 两版本策略) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 | | record_id | INTEGER | NOT NULL, FOREIGN KEY→ppt_records.id | 所属记录 | | version_no | INTEGER | NOT NULL | 版本号(1=第一版,2=修改后) | | file_path | TEXT | NOT NULL | PPTX 文件相对路径 | | svg_dir | TEXT | NULL | SVG 源目录相对路径 | | change_note | TEXT | NULL | 本次修改说明(对话修改时记录) | | created_at | TEXT | NOT NULL | 版本生成时间 | > 两版本策略:**当前版本 + 上一版本**(决策日志 2026-07-20)。生成新版本时,旧"上一版"删除,原"当前版"降为"上一版"。 #### chat_messages(对话消息表) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | id | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 | | record_id | INTEGER | NOT NULL, FOREIGN KEY→ppt_records.id | 关联记录 | | role | TEXT | NOT NULL | user / assistant | | content | TEXT | NOT NULL | 消息内容 | | created_at | TEXT | NOT NULL | 时间戳 | ### 3.3 索引 | 表 | 索引 | 用途 | |----|------|------| | users | UNIQUE (username) | 登录查询、注册唯一性 | | ppt_records | (user_id, created_at DESC) | 历史列表按时间倒序 | | ppt_records | (user_id, scene) | 场景筛选(US-4.3)| | versions | (record_id) | 版本回查(US-4.4)| | chat_messages | (record_id, created_at) | 对话历史按时间顺序 | ### 3.4 SQLite 配置 ```sql PRAGMA journal_mode = WAL; -- 读写并发优化 PRAGMA foreign_keys = ON; -- 外键约束 PRAGMA busy_timeout = 5000; -- 写入冲突等待 5s ``` --- ## 四、Agent 核心模块设计 > 对应设计文档「感知→规划→行动→记忆」四层架构,每个模块对应一个 Python 子包。 ### 4.1 模块划分与文件结构 ``` src/agent/ ├── __init__.py ├── perception.py # 感知层:输入解析 + 场景/语言/偏好识别 ├── planning.py # 规划层:结构匹配 + 页数分配 + 配色应用 + 参数映射 ├── action.py # 行动层:引擎编排 + 状态机调度 ├── chat.py # 对话修改:意图解析(LLM 优先/规则兜底)+ plan.json 变更应用 ├── llm.py # LLM 客户端:opencode serve / OpenAI 兼容双后端 ├── memory.py # 记忆层:读写 SQLite + 两版本策略 └── orchestrator.py # 总调度:串联四层,生成/对话/解析三条流程入口 ``` ### 4.2 感知层(perception.py) | 方法 | 输入 | 输出 | 职责 | |------|------|------|------| | `parse_input(title, content)` | 标题 + 正文 | 结构化内容对象 | 清洗文本、截断超长、提取行数/字数 | | `resolve_config(payload, user_id)` | 用户请求 | 完整生成配置 | 合并请求参数 + 用户偏好(未显式提供的项用偏好默认值) | | `detect_lang_override()` | — | 语言参数 | 生成语言显式传参,否则取偏好 `default_language` | **校验规则**(对齐 US-2.2 / US-2.6):内容 ≥ 20 字、页数 min ≤ max,不满足回传相应错误码(2001/2002)。 ### 4.3 规划层(planning.py) **职责**:把用户配置映射为引擎可执行的方案,是"包装引擎"的核心。 | 方法 | 职责 | |------|------| | `build_plan(cfg)` | 配置 + 内容 → plan.json(Markdown 标题切块、页数预算合并/拆分、五色板/日式规格选择,详见第五章)| ### 4.4 行动层(action.py) **职责**:编排生成任务,管理状态机,调用引擎。 ``` 生成流程(on /api/generate): 1. 校验通过 → 占并发槽位(满则 2003)→ 返回 task_id 2. 后台线程: a. 规划层 build_plan 产出 plan.json 并写盘(状态: planning) b. 引擎桥接 render_plan 渲染 + verify_output 保真验证(rendering) c. 完成后写 versions 表 + 记录 file_size/page_count/duration → status=done d. 失败 → 记录失败原因 → status=failed 3. 前端通过 status 接口轮询(1~2s 间隔) ``` ### 4.5 记忆层(memory.py) | 方法 | 职责 | |------|------| | `create_record(...)` / `list_records(...)` | 历史记录增查(含搜索/筛选/分页)| | `save_version(record_id, file_path, change_note)` | 两版本策略写盘 | | `load_history(record_id)` | 版本回查(当前+上一版)| | `record_chat(record_id, role, content)` | 对话消息落库 | | `read_preferences(user_id)` / `save_preferences(...)` | 偏好读写 | > 两版本策略规则(决策日志 7-20):新版本生成时,删除旧"上一版",原"当前版"降级为"上一版",新文件成为"当前版"。 ### 4.6 对话修改流程(chat.py,Sprint 2 已实现) ``` 用户消息(POST /api/chat) → 意图解析(chat.parse_intent:LLM 优先解析任意自然语言并自动定位页码,规则句式中/日双语兜底;降级链见 5.6) → 应用变更到 plan.json(chat._apply_to_plan)并写回磁盘 → aura 全量重渲(秒级)+ 内容保真验证 → 记忆层:save_version 两版本策略(新版本=1,旧版本降为2)+ 对话落库 → 返回确认回复(含新版本号与变更明细) ``` | 意图类型 | 指令示例(中/日) | 动作 | |---------|------------------|------| | change_color | 换成绿色 / カラーを緑に変更 | 切换 corp-* 设计系统重渲(ja 固定 report-jp,返回不支持提示) | | change_title | 把标题改成XX / タイトルを「XX」に変更 | 更新 plan.title + 封面页 + ppt_records.title | | edit_page | 第2页把A改成B / 第3页删除"xx" / 第2页添加"yy" | 定位 slides[n] 增删改要点 | | unknown | 其余 | 返回帮助文案,不重渲染 | > **实现说明**:设计原则为"只重新生成受影响内容";aura-ppt 引擎全量重渲仅数秒,等效满足体验目标,且保证整册风格一致性(如换色必须全册生效)。plan.json 是唯一演进基准(见 6.3)。 ### 4.7 在线预览设计(2026-08-25 上线) ``` 预览请求(前端弹层) → GET /api/files//preview # 校验记录归属当前用户 → 返回版本列表与页面清单 → bridge.export_preview_images # PowerPoint COM 导出每页 PNG(WPS 兜底),按版本缓存 # 落盘 projects//preview/<版本>/Slide.PNG(文件名归一化) → GET /api/preview-img// # 单页图片读取:归属校验 + 文件名白名单 ``` | 设计点 | 说明 | |--------|------| | 渲染方式 | PowerPoint COM 自动化导出(本机 Office,WPS 兜底),零额外服务依赖 | | 缓存策略 | 按版本缓存 PNG,同版本二次打开零等待;新版本自动增量导出 | | 安全 | 两路由均校验 record 归属当前用户;文件名白名单(Slide.PNG),防路径穿越 | | 前端 | 工作台结果卡片「预览」按钮弹出翻页弹层,默认展示对话修改后的最新版本 | --- ## 五、Agent 参数映射规则 > 2026-08-24 引擎切换后改版:原「八大确认自动应答」方案废弃,改为 **用户配置 → aura-ppt plan.json** 的确定性映射。规划层不再模拟人工确认,直接产出引擎可执行的结构化页面规划。 ### 5.1 配置 → plan.json 映射总表 | 用户配置 | plan.json 落点 | 映射规则 | |---------|---------------|---------| | title / content | `title` + slides 结构 | content 按 Markdown 标题切块分页(见 5.3) | | language=ja | `design: report-jp` | 日语固定走日式报告规格(法人蓝 #0B3D91,与本部长报告会母版对齐) | | language=zh + color_scheme | `design: corp-*` | 五色板设计系统(见 5.2),默认 corp-blue | | canvas | 画布尺寸 | 当前仅支持 ppt169(16:9);ppt43 自动回退并写入任务告警(引擎待验证项) | | page_min ~ page_max | slides 数量预算 | 扣除封面/目次/结尾 3 页后,对内容块做合并/拆分(见 5.3) | | scene | ppt_records 落库字段 | v1 分页算法与场景无关,场景值用于历史筛选,Sprint 2 增强为骨架模板 | | image_strategy | plan.slides[].image | web=内容页配图(见 6.6,失败降级无图);ai/off=落库字段,暂不配图 | ### 5.2 五色板设计系统(对应 US-2.5) 在引擎 `DESIGN_SYSTEMS` 注册表扩展(继承 business-report 浅色骨架,覆写颜色令牌): | 方案名 | design 键 | 主色 | 辅色 | 卡片底 | 说明 | |--------|-----------|:--:|:--:|------|------| | 商务蓝(默认) | corp-blue | #1565C0 | #2196F3 | #E3F2FD | 产品默认 | | 清新绿 | corp-green | #2E7D32 | #34A853 | #E8F5E9 | | | 科技紫 | corp-purple | #5B21B6 | #7C3AED | #EDE9FE | | | 活力橙 | corp-orange | #D97706 | #F59E0B | #FEF3C7 | | | 深灰 | corp-gray | #374151 | #6B7280 | #F3F4F6 | 图表灰阶+蓝红点睛 | > 日语生成走 report-jp(#0B3D91/#1976D2),即公司确认的强调色 R11 G61 B145 / R25 G118 B210。 ### 5.3 内容分页算法(planning.build_plan) ``` 输入 content(Markdown 文本) 1. 逐行扫描:#/##/### 标题行 → 新建页面块;"-/*"行 → 当前块要点;普通行 → 要点 2. 页数预算 = clamp(page_min,page_max) - 3(封面/目次/结尾) 3. 块数 > 预算上限:小块合并(无标题且合计 ≤5 条优先并给前一有题块) 4. 块数 < 预算下限:最大块对半拆分(续页标题追加「(续)/(続き)」) 5. 单块要点 >10 条:按每页 5 条拆分为多页 输出 slides = [cover] + [toc(≥2 页时)] + [content × N] + [end] ``` - 无标题块回退标题:中文「要点 n」/日文「ポイント n」 - 目次条目即各 content 页标题;语言文案包内置 zh/ja 两套(目录/致谢页等) ### 5.4 公司母版继承策略(--template 模式) | 配置 | 默认 | 说明 | |------|------|------| | `COMPANY_TEMPLATE` | `engine/aura-ppt/assets/公司模板.pptx` | 项目资产副本(技能原版只读) | | `COMPANY_TEMPLATE_MODE` | `ja` | always=全部用模板 / ja=仅日语生成用 / off=不用 | | 生效行为 | — | 继承母版背景/Logo/页脚,以「白紙」版式建页,引擎元素叠加;页面尺寸随母版(10.83×7.5in) | ### 5.5 语言映射 | 维度 | zh | ja | |------|----|----| | design | corp-{color} | report-jp(固定) | | 回退标题 | 要点 n | ポイント n | | 目录/结尾 | 目录 / 谢谢聆听 | 目次 / ご清聴ありがとうございました | | 续页标记 | (续) | (続き) | ### 5.6 LLM 智能规划模式(2026-08-25 接入,08-26 改版为无降级) **后端**:默认复用 opencode serve(`opencode serve --port 4096`,零 Key,模型 mimo-v2.5-free 经基准选定);预留 OpenAI 兼容接口(DeepSeek 等,配 Key 即切)。配置项:`LLM_BACKEND / OPENCODE_SERVE / OPENCODE_MODEL / OPENAI_API_* / PLAN_CHUNK_CHARS / PLAN_MAX_CHUNKS`。 **产品决策(2026-08-26)**:**取消规则降级**——要么产出总结版,要么明确报生成失败(含具体原因),绝不静默产出照搬版。规划失败抛 `PlanningError` → 任务 failed → 前端 toast 透传原因。 **能力与流程**: | 环节 | 实现 | 失败处理 | |------|------|---------| | 长文档两级规划 | >6000 字先分段通读(Map:每段提炼小节摘要+关键数据)→ 汇总产出大纲(Reduce);全文覆盖,不再只读前3000字 | 单段调用重试2次,仍失败报错 | | 短文档直通 | ≤6000 字直接出大纲 | 同上 | | 页数硬约束 | `_validate_slides` 裁剪保证最终页数不超上限;每轮尝试均为 LLM 规划,共两轮 | 两轮均败→任务失败 | | 智能标题 | /api/title/suggest 返回 3 个候选 | 首行截断 | | 对话意图解析 | parse_intent_llm + 封面替换规范化为改标题;规则句式兜底 | 返回帮助文案 | | 数字保真 | 提示词约束 → 数字消毒器 → 引擎 --verify 门禁 | 保真不过→下一轮重新规划→仍败则报错 | **质量保障**:LLM 产出经 `_validate_slides` 严格校验(类型白名单/长度截断/首尾页规范/页数硬上限)+ `_sanitize_fidelity` 数字消毒;引擎 `--verify` 内容保真为最终门禁。 ## 六、aura-ppt 集成方式 > 2026-08-24 选型切换(对比依据:`项目资料/事前调查/引擎选型对比_ppt-master_vs_aura-ppt.md`)。渲染出口统一为 aura-ppt;ppt-master 降级为辅助引擎,仅复用其文档解析(source_to_md)与网络搜图(image_search)独立脚本。 ### 6.1 渲染调用链(action.run_generation) | 步骤 | 操作 | 实现 | 状态机 | |------|------|------|:--:| | ① 规划 | GenerateConfig → plan dict | `planning.build_plan(cfg)`(第五章算法) | planning | | ② 写盘 | `projects/_/` 建 sources/source.md + plan.json | action 直接写文件 | planning | | ③ 渲染 | `engine.py --plan plan.json --output out.pptx [--template 公司模板.pptx]` | `bridge.render_plan()` subprocess | rendering | | ④ 验证 | `engine.py --verify out.pptx --plan plan.json` | `bridge.verify_output()`,失败重试 1 次 | rendering | | ⑤ 落库 | ppt_records + versions(version_no=1) | memory.save_generated_record | done | ### 6.2 调用实现要点 | 要点 | 说明 | |------|------| | CLI 调用 | `subprocess.run([python, engine.py, ...], cwd=AURA_ENGINE_PATH)`,不 import 引擎内部模块 | | 编码 | 子进程强制 `PYTHONIOENCODING=utf-8` 语义(bridge 以 utf-8 解码),规避 GBK 控制台报错 | | 成功判据 | 渲染:退出码 0 且输出文件存在;验证:stdout 含「内容保真验证通过」且无「保真失败」 | | 失败处理 | EngineError → 任务 failed(错误码 2004);保真失败重试 1 次仍失败即终止 | | 并发控制 | 进程内信号量(MAX_CONCURRENT_GENERATION),占满返回 2003 引擎繁忙 | | 超时 | 渲染 600s / 验证 300s,超时抛 EngineError | | 模板模式 | 日语生成默认继承公司母版(5.4);模板模式页面尺寸随母版 | ### 6.3 生成目录结构 ``` src/web/data/projects/<标题slug>_<时间戳>/ ├── sources/ │ └── source.md # 用户原始内容(留档/复盘) ├── plan.json # 本次生成的完整页面规划(可独立重放渲染) └── exports/ └── _<时间戳>.pptx # 成品文件(versions.file_path 相对引用) ``` > plan.json 即「执行合同」:下载、预览、对话修改均以它为基准做增量演进(Sprint 2 局部重渲染)。 ### 6.4 双引擎分工与配置 | 引擎 | 角色 | 路径配置 | 当前接入点 | |------|------|---------|-----------| | aura-ppt | 主渲染引擎 | `AURA_ENGINE_PATH` / `AURA_ENGINE_SCRIPT` | bridge.render_plan / verify_output | | ppt-master | 辅助:文档解析 + 网络搜图 | `PPT_MASTER_PATH` | 文档解析已接入(agent/upload.py 调 source_to_md 五脚本);配图(image_search)待接 | ### 6.5 上传解析链路(2026-08-25 落地) ``` 前端选择文件/粘贴URL → POST /api/upload(扩展名白名单 + 20MB + 24h 过期清理,存 UPLOAD_DIR) → POST /api/parse → agent/upload.py 按扩展名分派: .md/.txt 直读;.pdf/.docx/.doc/.html/.xlsx/.xlsm/.pptx 经 subprocess 调 ppt-master scripts/source_to_md/{pdf,doc,excel,ppt}_to_md.py;URL 调 web_to_md.py → 产物清洗(去转换器横幅/内部文件名引用/图片与资源引用、Markdown 反转义、压空行、3万字截断) → 回传 {text, chars},前端自动切回文本页并回填正文框 错误映射:3001 类型不支持 / 3002 超 20MB / 3003 解析失败 / 3004 内容为空 ### 6.6 网络配图链路(2026-08-25 落地) ``` image_strategy=web → action 挂载 imaging.attach_images(plan.slides, project_dir) → 取前 MAX_IMAGE_SLIDES(4) 个内容页,以页面标题为关键词 → IMAGE_SEARCH_MODE: web = subprocess 调 ppt-master scripts/image_search.py (openverse/wikimedia 零配置;pexels/pixabay 配 Key 即启用) test = 本地样例图通道(IMAGE_SEARCH_TEST_DIR,e2e 确定性) off = 禁用 → 成图落 projects//images/slide_x.jpg,路径写入 plan.slides[].image 并随 plan.json 落盘 → aura-ppt 渲染:content 页左文右图(文本区收窄 58%,图片等比缩放/垂直居中/细边框) 容错:单页失败仅记任务告警,全部失败降级为无图版式;verify 保真检查跳过 image 键。 现状:本机网络 openverse/wikimedia 不可达(超时/重置),管线与降级已验证, 真实搜图待更换网络环境或配置 pexels/pixabay Key 即自动启用。 ### 6.5 引擎扩展点(已落地与待移植) | 项 | 状态 | |----|------| | 五色板设计系统注册(corp-*) | ✅ 已落地(engine.py DESIGN_SYSTEMS) | | 公司模板 assets 副本 | ✅ 已拷入 engine/aura-ppt/assets/ | | KPI 卡片 / 数据源脚注 / 阴影开关 / 精选图标 | ⏳ 移植第一批(见选型对比文档第五章) | | 幻灯片插图片(content 页右图布局) | ✅ 已落地(_place_image,见 6.6) | | 4:3 画布 | ⏳ 待试跑验证后实现 | | 动画 | ✕ 放弃(python-pptx 限制) | --- ## 七、异常与错误码体系 > 对齐 PRD 4.3「错误提示以中文展示,不显示原始异常栈」。前端直接展示 `message` 字段,后端日志记录完整堆栈。 ### 7.1 错误码全景表 | 错误码 | 触发场景 | 后端处理 | 前端提示文案 | |:--:|------|------|------| | **1001** | 注册用户名已存在 | 唯一索引冲突捕获 | 用户名已被使用 | | **1002** | 登录用户名或密码错误 | 哈希比对失败 | 用户名或密码错误 | | **1003** | 注册两次密码不一致 | 请求参数校验 | 两次输入的密码不一致 | | **1004** | 未登录访问受限接口 | Session 校验拦截 | 请先登录 | | **2001** | 生成内容不足 20 字 | 校验拦截 | 内容至少需要 20 个字 | | **2002** | 页数 min > max | 校验拦截 | 最少页数不能大于最多页数 | | **2003** | 引擎繁忙(并发上限)| 任务队列拒绝 | 引擎繁忙,请稍后重试 | | **2004** | 生成失败(引擎已重试)| 任务标记 failed | 生成失败,请重试 | | **2005** | 生成中断(服务重启/超时)| 任务标记 failed | 生成中断,请重新生成 | | **3001** | 上传文件类型不在白名单 | 扩展名校验拦截 | 不支持的文件类型 | | **3002** | 上传文件超过 20MB | 大小校验拦截 | 文件大小不能超过 20MB | | **3003** | 文档解析失败(损坏)| 解析器异常捕获 | 文件损坏,无法读取 | | **3004** | 解析结果为空 | 解析后空文本检测 | 文件内容为空 | | **3005** | 文件上传失败 | 存储异常捕获 | 上传失败,请重试 | | **4001** | 历史记录不存在/已被删除 | 查询无结果 | 该记录不存在或已被删除 | | **4002** | 下载文件不存在 | 文件系统检查 | 文件已丢失,请重新生成 | | **5001** | 修改密码原密码错误 | 哈希比对失败 | 原密码不正确 | | **5002** | 用户名/密码格式非法 | 正则校验 | 用户名 3-20 位,密码至少 6 位 | | **6001** | 引擎进程非零退出 | subprocess stderr 解析 | 引擎内部异常,请联系管理员 | | **6002** | 引擎配置文件缺失 | 启动自检 | 系统配置异常,请稍后重试 | ### 7.2 统一错误响应结构 ```json { "code": 2001, "message": "内容至少需要 20 个字", "detail": null } ``` > `detail` 仅后端调试日志输出,**永不返回前端**。 ### 7.3 引擎失败分类与重试策略 | 分类 | 判定 | 动作 | 前端码 | |------|------|------|:--:| | 可恢复(网络/限速/图片源超时)| 退出码非零 + stderr 匹配网络关键字 | 重试 1 次(指数退避 2s→4s)| 重试成功继续,失败 2004 | | 不可恢复(脚本缺参/SVG 质检 error 无法修复)| 退出码非零 + 逻辑错误 | 直接失败 | 2004 / 6001 | | 资源性(同时任务超限)| 任务队列满载 | 拒绝入队 | 2003 | | 中断性(服务重启)| 任务表 status=processing 且服务重启 | 标记 failed | 2005 | ### 7.4 前端错误处理约定 | 场景 | 行为 | |------|------| | 字段校验错误 | Toast 提示对应文案,不清空用户输入 | | 接口 401 | 跳转登录页 | | 联网失败(fetch 异常)| 提示"网络异常,请检查网络后重试",保留页面状态 | | 生成中失败 | 结果卡片红色区域展示错误文案 +「重新生成」按钮(US-2.7b)| --- ## 八、安全设计 > 对齐 PRD 4.2。本项目为内网自用系统,采用适度防护,不做过度设计。 | 维度 | 措施 | 实现 | |------|------|------| | **密码存储** | bcrypt 哈希,不存明文 | `werkzeug.security.generate_password_hash`,成本因子 12 | | **会话管理** | Flask Session + 签名 Cookie | `SECRET_KEY` 存于 `config.SECRET_KEY`(环境变量注入,不进仓库)| | **CSRF 防护** | 同源校验 | 所有写接口校验 `Origin`/`Referer` 头 + Session 内 CSRF Token | | **文件上传** | 扩展名白名单 + 大小限制 | 8 种格式白名单,≤20MB,存储到非 web 根目录 | | **SQL 注入防护** | 参数化查询 | 全部 SQL 用 `?` 占位符,禁止字符串拼接 | | **路径穿越** | 下载路径校验 | record_id 反查数据库后拼接相对路径,禁止直接拼接用户输入文件名 | | **日志脱敏** | 不记录密码/令牌 | 统一日志过滤器屏蔽敏感字段 | | **内网安全** | 仅监听 127.0.0.1 | Flask 运行绑定本机回环地址,不开放公网 | --- ## 九、目录结构与部署 ### 9.1 项目目录树 ``` ppt-agent/ ├── src/ │ ├── web/ │ │ ├── app.py # Flask 入口 + 路由 + 中间件 │ │ ├── static/ │ │ │ ├── css/ │ │ │ ├── js/ │ │ │ └── lang/ # zh.json / ja.json(UI 语言包) │ │ ├── templates/ # 4 页面 Jinja2 模板 │ │ └── data/ │ │ ├── ppt.db # SQLite │ │ └── projects/ # 生成任务输出目录 │ ├── agent/ # Agent 核心(第4章) │ ├── engine_bridge/ # 引擎 CLI 调用封装 │ └── db/ # 连接管理 + DDL ├── engine/ │ ├── aura-ppt/ # ⚠️ 主渲染引擎副本(真值,优化在此) │ │ └── assets/公司模板.pptx │ └── ppt-master/ # 辅助引擎副本(解析/搜图脚本复用) ├── tests/ # 单元/集成测试 ├── scripts/ # 辅助脚本(DB 初始化等) ├── requirements.txt ├── config.py # 常量与配置 ├── .gitignore └── README.md ``` ### 9.2 运行方式 ``` # 安装依赖 pip install -r requirements.txt # 初始化数据库(幂等,可重复执行) python -m src.db.init # 启动 Web 服务(默认绑定 127.0.0.1:5000) python src/web/app.py ``` ### 9.3 配置项(config.py) | 配置 | 默认值 | 说明 | |------|--------|------| | `AURA_ENGINE_PATH` | `engine/aura-ppt/` | 主渲染引擎目录(可配置)| | `PPT_MASTER_PATH` | `engine/ppt-master/` | 辅助引擎目录(解析/搜图)| | `COMPANY_TEMPLATE` | `engine/aura-ppt/assets/公司模板.pptx` | 公司母版(--template 模式)| | `COMPANY_TEMPLATE_MODE` | ja | 模板模式策略:always/ja/off | | `PROJECTS_PATH` | `src/web/data/projects/` | 生成任务输出根目录 | | `DB_PATH` | `src/web/data/ppt.db` | SQLite 文件路径 | | `SECRET_KEY` | 环境变量 | Flask Session 密钥 | | `MAX_UPLOAD_MB` | 20 | 上传大小上限 | | `ALLOWED_EXT` | 8 种格式 | 扩展名白名单 | | `ALLOWED_SCENE` | report/training | 场景枚举 | | `ALLOWED_COLOR` | blue/green/purple/orange/gray | 配色枚举 | | `ALLOWED_LANG` | zh/ja | 生成语言枚举 | | `ALLOWED_CANVAS` | ppt169/ppt43 | 画布枚举 | | `ALLOWED_IMAGE` | web/ai/off | 配图策略枚举 | | `LLM_BACKEND` | opencode | 大模型后端:opencode(免费零 Key)/ openai(兼容接口) | | `OPENCODE_SERVE` | http://127.0.0.1:4096 | opencode serve 地址(--port 4096 常驻) | | `OPENCODE_MODEL` | opencode/mimo-v2.5-free | 免费模型(三模型基准选定,44s/大纲) | | `OPENAI_API_BASE` / `OPENAI_API_KEY` / `OPENAI_MODEL` | 环境变量 | DeepSeek 等 OpenAI 兼容后端切换入口(配 Key 即切) | | `LLM_TIMEOUT` | 180 | LLM 请求超时(秒) | --- ## 十、里程碑映射 > 对齐 PRD「五、用户故事与开发阶段对应」的 3 个 Sprint,补充到接口/数据表/模块粒度。 ### 10.1 Sprint 1(MVP:登录→生成→下载)✅ 已完成(2026-08-24) **目标**:跑通"登录 → 输入文字 → 配置 → 生成 → 下载"完整链路。 | 内容 | 明细 | |------|------| | 接口 | 1-9 号(认证 4 + 生成 6)| | 数据表 | users + preferences + ppt_records + versions(versions 先建表,Sprint 2 启用回退)| | Agent 模块 | perception(基础解析)+ action(生成编排)+ orchestrator(生成流程)| | 引擎集成 | 7 步全流程 + branding 模板(blue)+ 质检门禁 | | 语言 | zh / ja 生成 + UI zh/ja 双语言包(UI 语言切换第一期启用)| ### 10.2 Sprint 2(对话修改 + 历史闭环)✅ 已完成(2026-08-25) **目标**:生成→修改→回退 完整闭环 + 在线预览。 | 内容 | 明细 | |------|------| | 接口 | 10、13-17 号(预览 + 对话 + 历史 CRUD)| | 数据表 | chat_messages 启用、versions 回退逻辑启用 | | Agent 模块 | planning(修改意图解析)+ memory(版本/对话读写)| | 引擎集成 | 只重生成受影响页面(局部渲染)| | 前端 | 对话弹窗接入真实轮询 + 历史两版本展示 | > **完成情况与偏差**:接口 13/14 号、chat_messages 落库、两版本回退均已落地,e2e_chat 16 项断言全绿;「只重生成受影响页面」调整为 plan.json 变更 + aura 全量重渲(秒级,等效满足体验且保证整册风格一致,见 4.6);在线预览以 COM 导出 PNG 实现(见 4.7);智能标题(原 Sprint 3 内容)随 LLM 接入提前完成。 ### 10.3 Sprint 3(体验完善) **目标**:上传解析 + 智能标题 + 偏好 + UI 全语言。 | 内容 | 明细 | |------|------| | 接口 | 11-12、18-21 号(上传解析 + 设置)| | Agent 模块 | memory 偏好读写、upload 解析链路 | | 引擎集成 | `source_to_md/*.py` 8 格式转换 + 智能标题 DeepSeek 调用 | | 前端 | 设置页生成偏好绑定 + 历史页语言筛选(预留,本期可选)| > **进度**:上传解析已提前落地(2026-08-25,见 6.5,e2e_upload 21 项断言全绿);智能标题已于 LLM 接入时完成;设置偏好与历史语言筛选已落地(e2e_settings 18 项断言全绿);UI 全语言四页收尾完成(login/history 补 i18n 与切换入口);网络配图管线落地(见 6.6,e2e_image 9 项断言全绿)。**Sprint 3 全部用户故事已覆盖**,余一项环境依赖:真实搜图源需换网络或配 Key。 ### 10.4 里程碑与成果物对齐 | 里程碑 | 关联成果物 | 验收口径 | |--------|-----------|---------| | Sprint 1 完成 | 03 源码(主体)| US-1.1~2.8 通过测试顾问验收 | | Sprint 2 完成 | 03 源码(完整)+ 04 实验报告起点 | 修改/回退闭环可演示 | | Sprint 3 完成 | 04 实验报告 | 全部 23 条用户故事覆盖 | | 全量定稿 | 05 AI使用日志 + 06 演示视频 | 整理 8 月日志 + 录制完整流程 | --- *全 10 章完成。v0.3 已同步 Sprint 2 落地状态(对话修改 / 在线预览 / LLM 零Key 接入),e2e_web、e2e_chat、e2e_llm 三套测试全绿。*