docs(design): 加 §8.3 PPTX 前端在线预览方案(LibreOffice → PDF)
前端 preview.js 已用 iframe 原生渲 PDF,故方案极简:后端 pptx→PDF,前端 复用现有 _showPdf,前端改动近零。要点: - 与 pptx_preview.py(HTML→Chrome→PNG,agent 自检用)分工:本方案是 前端面向用户的高保真预览(LibreOffice→PDF,像素级,任意 pptx) - 后端 web/pptx_render.py(soffice 转换+哈希缓存+独立 profile 绕并发锁+超时) + GET /v1/files/preview_pdf(复用 files 鉴权与 _safe_path) - 前端 preview.js 加 ppt 类,pptx 走转换端点拿 PDF 喂 _showPdf - 依赖:服务器装 libreoffice-impress + fonts-noto-cjk;无新 pip 依赖 - 风险:冷启动/上传 pptx 跑 LibreOffice 安全/字体保真;分两阶段落地 status=design,未实施。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b2b4a29ad3
commit
f2c6f2e2f4
52
DESIGN.md
52
DESIGN.md
|
|
@ -604,6 +604,58 @@ zcbot-sandbox image 已 ~1.5G(python deps + chromium + nodejs + mermaid-cli),后
|
||||||
- 文献采集 / 论文写作 / PPT 三类长任务仍能复查原文路径,不会因摘要丢失用户确认过的规格。
|
- 文献采集 / 论文写作 / PPT 三类长任务仍能复查原文路径,不会因摘要丢失用户确认过的规格。
|
||||||
- 增加 focused tests 覆盖 usage detail 提取、成本兜底、工具结果裁剪、上下文压缩协议完整性。
|
- 增加 focused tests 覆盖 usage detail 提取、成本兜底、工具结果裁剪、上下文压缩协议完整性。
|
||||||
|
|
||||||
|
### 8.3 PPTX 前端在线预览(LibreOffice → PDF,2026-06-09)
|
||||||
|
|
||||||
|
**动机**:ppt skill 现在能产出观感不错的 `.pptx`,但前端对话/文件区里 `.pptx` 只能**下载**(`preview.js` 的 `_categorize` 把它归为 `fallback` → "暂不支持在线预览,请下载查看")。用户要在浏览器里直接翻看 deck,不必下载 + 开 PowerPoint。需求扩展到**任意 pptx**(含用户上传的),不只 skill 自产。
|
||||||
|
|
||||||
|
**关键洞察(决定了方案极简)**:前端 `preview.js` 已有完整预览弹框,且 **PDF 已用 `<iframe src=blob:application/pdf>` 浏览器原生渲染**(`_showPdf`)。所以只要把 pptx **在后端转成 PDF**,前端复用现成的 PDF iframe 路径即可 —— **不需要 pdf.js、不需要 PNG 栅格化、前端改动近乎为零**。
|
||||||
|
|
||||||
|
**与 `pptx_preview.py` 的分工(两个工具,两个用途,别混)**:
|
||||||
|
- `scripts/pptx_preview.py`(pptx→HTML→Chrome→PNG):**agent / skill 自检**用,在 deck 生成阶段让模型用 `read` 看 PNG 验观感;近似渲染(只覆盖 skill 用的形状子集,chevron/椭圆会失真),但零服务器依赖、能在生成现场跑。
|
||||||
|
- 本方案(pptx→LibreOffice→PDF):**前端面向用户**的高保真在线预览;像素级真实、支持任意 pptx,代价是服务器 LibreOffice 依赖。
|
||||||
|
|
||||||
|
**选型(为何 LibreOffice→PDF)**:
|
||||||
|
| 方案 | 保真 | 前端工作 | 服务器依赖 | 取舍 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 轻量 HTML(复用 pptx_preview 的 HTML,前端 iframe 渲) | 近似(复杂 pptx 失真) | 中(新渲染组件) | 无 | 够预览自产 deck,但用户上传的复杂 pptx 会失真,不满足"任意 pptx" |
|
||||||
|
| **LibreOffice→PDF + 复用 _showPdf** ✅ | 像素级 | **近零**(复用现有 PDF iframe) | LibreOffice + CJK 字体 | 选它:保真 + 通用 + 前端几乎不动;成本是服务器装 LibreOffice |
|
||||||
|
| LibreOffice→PDF→PNG(pymupdf/poppler) | 像素级 | 小(<img>) | LibreOffice + 栅格库 | 多一层栅格化且失去矢量缩放,无收益 —— 浏览器能原生渲 PDF |
|
||||||
|
|
||||||
|
**架构**:转换跑在 **backend host(`web/app.py` 进程)**,**不进执行沙盒**(沙盒无 LibreOffice 也不该有;本预览面向 user_root 下任意 pptx,与 deck 生成解耦)。按需触发(首访转换 + 缓存),不在 deck 生成时 eager 转(对上传文件也通用)。
|
||||||
|
|
||||||
|
**后端改动**:
|
||||||
|
1. 新模块 `web/pptx_render.py`:`pptx_to_pdf(pptx_path) -> pdf_path`(同步、可缓存)。
|
||||||
|
- 调 `soffice --headless --convert-to pdf --outdir <tmp> <pptx>`;**每次用独立 `-env:UserInstallation=file://<临时 profile>`** 绕开 LibreOffice 单 profile 锁(否则并发转换互斥);`subprocess` 设超时(~60s)+ 超时 kill。
|
||||||
|
- soffice 路径发现复用 `render_bg.py` 的思路(查常见安装路径 + PATH);缺失则抛清晰错误(端点回 501 + "服务器未装 LibreOffice")。
|
||||||
|
2. 新端点 `GET /v1/files/preview_pdf?path=<rel.pptx>`:复用 files API 的鉴权 + `_safe_path`(路径必须落在 user_root 内,防穿越);转换后 `FileResponse(application/pdf)`。阻塞转换走 `run_in_executor` 不堵事件循环。
|
||||||
|
3. **缓存**:PDF 落同目录隐藏 `.preview/<stem>.<hash>.pdf`,`hash` 由源 pptx 的 `mtime+size`(或内容哈希)派生;命中且新鲜直接返回。`.preview/` 是 dotdir,`_enumerate_files` 已跳过 dotfile,不会污染文件列表。失效靠 hash 换名 + 现有磁盘配额扫描兜底清理。
|
||||||
|
4. **并发**:按解析后的 pptx 绝对路径加 `asyncio.Lock`,避免同一文件并发重复转换。
|
||||||
|
|
||||||
|
**前端改动**(`preview.js`,极小):
|
||||||
|
1. `_EXT_GROUPS` 加 `ppt: new Set(["pptx","ppt"])`。
|
||||||
|
2. `openFilePreview` / `openMiniFilePreview`:`cat === "ppt"` 时,不 `download` 原文件,改 `fetch("/v1/files/preview_pdf?path=...")` 拿 PDF blob → 复用 `_showPdf(blob)`;加"由 PPT 转换 · 首次稍候"提示 + loading 态;转换失败回 `_showFallback`(已带"下载原文件"按钮,`fp-download` 仍下载 .pptx)。
|
||||||
|
|
||||||
|
**依赖与部署**:
|
||||||
|
- **LibreOffice**(`soffice`):backend host + 部署镜像装(Dockerfile `apt-get install -y --no-install-recommends libreoffice-impress`)。dev(Windows)用 `winget install TheDocumentFoundation.LibreOffice` 装来测。
|
||||||
|
- **CJK 字体**:Linux 服务器装 `fonts-noto-cjk`。**保真边界**:我们的 deck 用「微软雅黑」,Linux 上 LibreOffice 会替换成 Noto Sans CJK,字形度量与 Windows PowerPoint 略有差异(可接受);要完全一致需在服务器装 YaHei(版权另议)。
|
||||||
|
- **无新 pip 依赖**(不引 pymupdf/pdf.js)。
|
||||||
|
|
||||||
|
**边界 / 风险**:
|
||||||
|
- **冷启动延迟**:soffice 首次转换 ~2-4s(冷启 + 转换),缓存后即时。可选优化:常驻 soffice listener(`--headless --invisible` socket 模式)消冷启 —— 留作 Stage 2。
|
||||||
|
- **安全**:本预览会对**用户上传的任意 pptx**跑 LibreOffice(历史有 EPS/宏类 CVE)。缓解:`--convert-to` 模式默认不执行文档宏;额外 pin 宏安全到 high、禁网络、限资源;只处理鉴权用户自己 user_root 内的文件。
|
||||||
|
- **超时 / 损坏文件**:转换超时或失败 → 端点 500 + 前端 fallback 到下载,不挂起。
|
||||||
|
- **大 deck**:页数多则转换久,靠超时 + 缓存兜。
|
||||||
|
|
||||||
|
**分阶段落地**:
|
||||||
|
- **Stage 1(本期)**:`web/pptx_render.py`(soffice 转换 + 缓存 + 锁)+ `/v1/files/preview_pdf` 端点 + `preview.js` 路由 pptx→PDF;部署侧装 LibreOffice + Noto CJK。出可用功能。
|
||||||
|
- **Stage 2(可选)**:常驻 soffice listener 消冷启;deck 生成后 eager 预转换;缩略图条 / 翻页导航增强。
|
||||||
|
|
||||||
|
**验收指标**:
|
||||||
|
- 对话/文件区点 `.pptx` → 弹框内直接翻页预览(PDF iframe),版面/字体/图与 PowerPoint 基本一致;首次 ~数秒、再次即时(命中缓存)。
|
||||||
|
- 路径穿越被 `_safe_path` 挡;非 pptx / 损坏文件优雅回退到下载。
|
||||||
|
- 服务器未装 LibreOffice 时端点回明确错误、前端提示下载,不白转/不挂起。
|
||||||
|
- 新增 focused tests:转换成功落 PDF + 缓存命中、路径越界拒绝、soffice 缺失降级、并发同文件只转一次。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 附录:DeepSeek V4 关键事实(2026-04-24)
|
## 附录:DeepSeek V4 关键事实(2026-04-24)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue