OpenBrep 瘦身实录:删除 79 个文件、8000 行代码,用 Tauri 包裹 Python 桌面入口


七十九个文件,记录着 OpenBrep 在摸索中走过的弯路。
01 七十九个文件,有多少真正在做工
OpenBrep 最初基于 Streamlit 构建。每一次按钮交互、编译触发、预览刷新,都挂载在 ui/app.py 这一个文件上。项目幼年时,Streamlit 足够直接、够快。然而随着功能增多,ui/ 目录膨胀至 79 个文件:视图、状态、预览、聊天、工程文件解析、知识加载……诸多逻辑杂糅在一起,难以测试,更难复用。从 v0.8 起,React 工作台被设为默认入口,Streamlit 沦为了遗留物。保留它,意味着每次启动都要加载这一大包,每次修改配置都得考虑它,每次 lint 都要刻意绕过。
清理审计开始了。79 个文件中,有 3 个值得特别注意:它们没有 Streamlit 依赖,却被工作台服务层悄悄引用——view_models.py 负责将 LLM 输出拆分到对应的 GDL 脚本路径;three_preview.py 把三维网格转换为 Three.js 可用的 JSON 载荷;local_file_dialog.py 调用 macOS 的 osascript 弹出文件选择对话框。这三个承载了领域逻辑,本就不该活在 UI 层。剩下的 76 个文件,在执行 git rm 时只用了不到五秒。
02 把三个文件挪到该去的地方
迁移远非简单的复制粘贴。classify_code_blocks() 被安置进 openbrep/workbench/view_models.py,随后逐一找到引用它的服务文件,将旧的 import 路径更新。preview_3d_to_three_payload() 则落进 openbrep/workbench/three_preview.py。local_file_dialog 被移至项目根目录下的 openbrep/,因为它作为运行时工具,不属于 workbench 层。每修改一处 import,就跑一轮测试。126 个测试用例,最终全部通过。这本身就说明了关键:只要领域逻辑与 UI 层彻底分离,迁移只是地址变更,不是重写。
pyproject.toml 的细节:删除 ui/ 后,hatch 构建时抛出 FileNotFoundError,原来 force-include 中仍残留着 “ui” = “ui”。构建系统比你更记得你删掉了什么。Streamlit 相关的依赖——streamlit、streamlit-ace、plotly——也从 dev 和 ui 组中同步移除。
03 一个 Rust 外壳,包裹一颗 Python 心脏
Streamlit 退役后,下一步是为 OpenBrep 打造一个真正的桌面入口。我们选择了 Tauri v2——轻量,依靠系统原生 webview,打包后无需捆绑浏览器。src-tauri/ 目录从一组 Cargo.toml 和 tauri.conf.json 中生长出骨架。main.rs 的核心任务只有一件:spawn 一个 Python 子进程,读取其标准输出,直到出现 OBR7_READY_URL=http://… 这样一行,随后将那 URL 传递给 WebviewWindowBuilder 打开窗口。窗口关闭时,向 Python 后端发送 POST /api/shutdown,等待 800 毫秒后若进程仍在运行,便 kill 掉。只靠几行 stdout 通信,这就是两个进程之间的全部协议。
编译过程中遇到一个小插曲:macOS 上同时安装了 Homebrew 的 rustc 1.72 和 rustup 管理的最新 stable 1.96。Tauri 要求 Rust 版本 ≥ 1.83,1.72 拒绝编译。解决办法是在 src-tauri/ 中放置一个 rust-toolchain.toml 文件,指定 channel = “stable”,这样 rustup 将使用自己管理的工具链,不受 PATH 中 Homebrew 版本的影响。
图标同样有讲究。Tauri 在编译时,tauri::generate_context!() 宏展开期间会尝试打开 icons/ 目录下的 PNG 文件,并验证它们为 RGBA 四通道格式。若图标缺失,或者为 RGB 三通道而非 RGBA,宏展开阶段就会触发 panic。用几十行 Python 可以生成占位图标,但前提是必须知道需要四通道。
Python 后端有两处配合改动。workbench_api.py 新增 –static-dir 参数。在 Tauri 模式下,Python 既要响应 API,也负责将 frontend/dist/ 中的静态文件服务出去。不以 /api/ 开头的 GET 请求将匹配静态文件路径,找不到文件时回退到 index.html 以实现 SPA 路由。为防止路径遍历,候选路径必须位于静态目录之下。obr7.py 新增 –tauri 标志。Tauri 模式下不启动 vite dev server,不打开浏览器,仅运行 API 进程。服务就绪后向 stdout 打印 OBR7_READY_URL 和 OBR7_API_URL,Rust 端读取到这些信息后才显示窗口。
cargo check 全部通过。Rust 1.96,零错误,一个 warning。
删除完那七十九个文件后,目录安静得超乎想象。一个项目到底有多少东西是真正必须的,只有在你敢于清理时才能看清。Tauri 的壳还没有正式图标,还没有安装包,也还没在真实设备上运行起来。但 cargo check 通过了,Python 后端正等待着被唤醒。轮廓已经浮现,旅程还在继续。