## Context 当前 `yaml2pptx.py` 是一个命令行工具,接收 YAML 文件作为输入,生成 PPTX 文件作为输出。脚本使用 uv 的 Inline script metadata 管理依赖(`python-pptx`、`pyyaml`),包含完整的 YAML 解析、模板渲染、元素渲染和 PPTX 生成逻辑。 现在需要在同一个脚本中添加预览模式,当用户使用 `--preview` 参数时,启动一个轻量级的 Web 服务器,提供浏览器实时预览功能。预览模式需要复用现有的 YAML 解析和模板渲染逻辑,但将输出目标从 PPTX 改为 HTML/CSS。 ## Goals / Non-Goals **Goals:** - 在 `yaml2pptx.py` 中添加预览模式,通过 `--preview` 参数启用 - 使用轻量级的 Web 框架(Flask)提供 HTTP 服务 - 使用 Server-Sent Events (SSE) 实现浏览器自动刷新 - 使用 watchdog 监听 YAML 文件变化 - 复用现有的 `Presentation` 类和 YAML 解析逻辑 - 将 YAML 元素(文本、形状、表格、图片)渲染为 HTML/CSS - 保持向后兼容,不影响现有的 PPTX 生成功能 **Non-Goals:** - 不追求 100% 像素级精确的预览效果(HTML/CSS 与 PPTX 渲染存在差异是可接受的) - 不支持交互功能(如点击元素查看属性、拖拽调整位置等) - 不支持导出为静态 HTML 文件 - 不支持多用户同时预览 - 不支持 PPTX 动画和过渡效果的预览 ## Decisions ### 决策 1: 集成到现有脚本 vs 独立脚本 **选择**: 集成到 `yaml2pptx.py` 中,通过 `--preview` 参数启用 **理由**: - 保持项目简洁,只有一个主脚本 - 用户体验更统一,无需记忆多个命令 - 更容易复用现有的解析逻辑和类定义 - 减少代码重复和维护成本 **替代方案**: 创建独立的 `preview.py` 脚本 - 优点:代码分离更清晰 - 缺点:需要重复导入或复制代码,增加维护成本 ### 决策 2: Web 框架选择 **选择**: Flask **理由**: - 轻量级,适合本地开发工具 - API 简单,易于实现 - 社区成熟,文档丰富 - 支持 Server-Sent Events **替代方案**: - FastAPI: 更现代,但对于简单的预览服务来说过于复杂 - Python 内置 http.server: 太简单,不支持动态路由和 SSE ### 决策 3: 实时刷新方案 **选择**: Server-Sent Events (SSE) **理由**: - 单向推送,符合需求(服务器通知浏览器刷新) - 浏览器原生支持,无需额外的 JavaScript 库 - 实现简单,约 10 行代码 - 无需 WebSocket 的复杂握手和双向通信 **替代方案**: - WebSocket: 过于复杂,需要 Flask-SocketIO 依赖 - 轮询: 延迟高,资源浪费 - Meta refresh: 固定时间刷新,无法响应文件变化 ### 决策 4: 文件监听方案 **选择**: watchdog **理由**: - 跨平台,支持 macOS、Linux、Windows - 成熟稳定,广泛使用 - API 简单,易于集成 - 可以监听整个目录(包括模板文件变化) **替代方案**: - 轮询文件修改时间: 延迟高,不够优雅 - 操作系统原生 API: 不跨平台,实现复杂 ### 决策 5: 单位转换策略 **选择**: 固定 DPI (96 DPI),幻灯片固定尺寸 960x540 像素 **理由**: - 实现简单,无需复杂的缩放计算 - 960x540 是合理的预览尺寸(16:9 比例) - 用户可以使用浏览器缩放功能(Cmd +/-)调整大小 - 与 PPTX 的英寸单位转换清晰:1 英寸 = 96 像素 **替代方案**: - 自适应缩放: 更灵活,但实现复杂,需要处理百分比定位 - CSS 英寸单位: 不同浏览器可能有差异 ### 决策 6: HTML 渲染策略 **选择**: 使用 `
` + 内联样式渲染元素 **理由**: - 简单直接,易于实现 - 使用绝对定位模拟 PPTX 的坐标系统 - 内联样式避免 CSS 类管理的复杂性 - 文本使用 `pt` 单位保持与 PPTX 一致 **元素映射**: - 文本元素: `
` + `font-size: Xpt` - 形状元素: `
` + `background-color` + `border-radius` - 表格元素: `` 标签 - 图片元素: `` 标签 **替代方案**: - SVG 渲染: 更精确,但实现复杂 - Canvas 渲染: 性能好,但无法选择文本,调试困难 ### 决策 7: 代码组织 **选择**: 在 `yaml2pptx.py` 末尾添加预览相关代码,使用条件判断分离两种模式 **结构**: ```python # 现有代码(PPTX 生成) class Presentation: ... class PptxGenerator: ... # 新增代码(预览模式) # HTML 渲染函数 def render_element_to_html(elem): ... # Flask 应用 app = Flask(__name__) @app.route('/'): ... @app.route('/events'): ... # 主函数修改 def main(): if args.preview: # 启动预览服务器 start_preview_server() else: # 生成 PPTX(现有逻辑) generate_pptx() ``` **理由**: - 清晰分离两种模式的逻辑 - 不影响现有代码的可读性 - 易于维护和测试 ## Risks / Trade-offs ### 风险 1: HTML/CSS 渲染与 PPTX 渲染存在差异 **风险**: 预览效果与最终 PPTX 可能不完全一致(字体渲染、间距、换行等) **缓解措施**: - 在文档中明确说明预览仅供参考 - 使用相同的单位系统(pt、英寸)尽量保持一致 - 提供快速生成 PPTX 的方式进行最终确认 ### 风险 2: 依赖增加 **风险**: 新增 `flask` 和 `watchdog` 依赖,增加脚本启动时间和依赖管理复杂度 **缓解措施**: - 使用 uv 的 Inline script metadata 自动管理依赖 - 仅在预览模式下导入 Flask 和 watchdog(延迟导入) - 依赖都是轻量级库,影响较小 ### 风险 3: 端口冲突 **风险**: 默认端口 5000 可能被其他服务占用 **缓解措施**: - 提供 `--port` 参数允许用户指定端口 - 捕获端口占用错误,提示用户更换端口 ### 风险 4: 文件监听性能 **风险**: 监听大型目录可能影响性能 **缓解措施**: - 仅监听 YAML 文件所在目录,不递归监听 - 使用 watchdog 的高效事件机制 - 添加防抖逻辑,避免短时间内多次刷新 ### 权衡 1: 简单性 vs 功能完整性 **权衡**: 选择简单的实现方案,牺牲一些高级功能(如元素交互、网格线、标尺等) **理由**: 预览功能的核心价值是快速反馈,简单实现可以更快交付,后续可根据需求迭代 ### 权衡 2: 精确性 vs 速度 **权衡**: 使用 HTML/CSS 渲染而不是生成 PPTX 再转图片,牺牲精确性换取速度 **理由**: 实时预览的核心是速度,用户可以随时生成 PPTX 进行精确确认 ## Migration Plan ### 部署步骤 1. 在 `yaml2pptx.py` 的 Inline script metadata 中添加依赖: ```python # dependencies = [ # "python-pptx", # "pyyaml", # "flask", # 新增 # "watchdog", # 新增 # ] ``` 2. 添加预览相关代码(约 200 行) 3. 修改 `parse_args()` 函数,添加 `--preview` 和 `--port` 参数 4. 修改 `main()` 函数,根据参数选择模式 5. 更新 README.md,添加预览功能的使用说明 ### 回滚策略 - 如果预览功能有问题,用户仍可使用原有的 PPTX 生成功能(完全向后兼容) - 可以通过 git revert 回滚到之前的版本 - 预览功能是可选的,不影响核心功能 ### 测试计划 1. 单元测试:测试 HTML 渲染函数的正确性 2. 集成测试:测试预览服务器的启动和文件监听 3. 手动测试:在不同浏览器中测试预览效果 4. 兼容性测试:确保不影响现有的 PPTX 生成功能 ## Open Questions 1. 是否需要支持多幻灯片的缩略图导航? - 当前方案:垂直排列显示所有幻灯片 - 可选方案:添加左侧缩略图导航栏 2. 是否需要支持深色模式? - 当前方案:仅支持浅色背景 - 可选方案:添加深色模式切换 3. 是否需要显示元素边框和尺寸标注? - 当前方案:不显示辅助信息 - 可选方案:添加调试模式,显示元素边框和尺寸 这些问题可以在实现后根据用户反馈决定是否添加。