fischer-agentkit/docs/plans/2026-06-13-004-feat-tauri-d...

406 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: "2026-06-13"
status: active
depth: standard
origin: null
---
# feat: Tauri 2.x Desktop Client
## Summary
创建 Tauri 2.x 桌面客户端外壳,将现有 Vue 3 GUI 包装为跨平台桌面应用Windows/macOS/Linux。Tauri 管理窗口和 Python 后端进程生命周期,前端代码仅做最小适配(动态 API baseURL + Splash 启动流程),不修改任何 GUI 组件、布局、样式或业务逻辑。
## Problem Frame
当前 AgentKit GUI 只能通过浏览器访问(`agentkit gui` 启动后自动打开浏览器),用户需要:
1. 手动启动后端进程
2. 在浏览器中操作,缺乏原生体验(无系统托盘、无桌面集成、无自动更新)
3. 无法像桌面应用一样安装、启动、关闭
竞品Cursor、Devin均提供原生桌面客户端。AgentKit 需要对齐这一体验标准。
## Key Technical Decisions
**KTD-1: Tauri 2.x 作为桌面壳。** Tauri 使用系统 WebView不打包 Chromium安装包 ~5-10MB + PyInstaller 产物 ~30-80MB总计远小于 Electron 方案(~200MB+)。启动快(窗口 <500ms内存低~110-200MB vs Electron ~300-500MB)。Tauri 2.x 插件化架构更灵活capabilities 权限模型更安全且是长期维护版本
**KTD-2: PyInstaller 打包 Python 后端为 sidecar。** PyInstaller Python 后端打包为独立单文件二进制Tauri 通过 `tauri-plugin-shell` sidecar API 管理其生命周期 Nuitka 兼容性更好社区更成熟
**KTD-3: 端口 0 + stdout 解析实现端口发现。** Python 后端以 `--port 0` 启动OS 分配随机可用端口uvicorn 启动后通过 stdout 输出 `AGENTKIT_PORT=XXXXX`Rust 侧解析后通过 Tauri IPC 传递给前端避免端口冲突无需用户配置
**KTD-4: 前端最小适配策略。** 仅修改 `api/base.ts`动态 baseURL `App.vue`Splash 启动流程新增 `api/tauri.ts`Tauri invoke 封装 `TitleBar.vue`自定义标题栏)。所有现有 GUI 组件布局样式业务逻辑不做任何修改
**KTD-5: 双窗口 Splash 方案。** Tauri 配置两个窗口splash小窗口加载动画 main主窗口初始隐藏)。后端就绪后 Rust 侧关闭 splash显示 main比前端内 Splash 更干净不侵入现有 App.vue 的布局逻辑
## High-Level Technical Design
```
┌──────────────────────────────────────────────────────┐
│ Tauri Application (Rust) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Splash │ │ Main Window │ │ System Tray │ │
│ │ (loading) │ │ (Vue 3 SPA) │ │ (minimize) │ │
│ └─────────────┘ └──────┬───────┘ └─────────────┘ │
│ │ HTTP/WS │
│ ┌───────────────────────▼──────────────────────────┐│
│ │ Python Backend (sidecar process) ││
│ │ FastAPI + uvicorn on random port ││
│ └──────────────────────────────────────────────────┘│
│ │
│ Rust Modules: │
│ - sidecar.rs: start/stop/health Python process │
│ - tray.rs: system tray menu │
│ - lib.rs: Tauri Builder + plugin registration │
└──────────────────────────────────────────────────────┘
```
**启动序列:**
```
1. 用户双击桌面图标 → Tauri 启动
2. Rust setup() → 创建系统托盘
3. Splash 窗口显示(加载动画)
4. 前端 invoke('start_backend')
5. Rust spawn sidecar: agentkit-server --port 0
6. Python 绑定随机端口 → stdout: AGENTKIT_PORT=XXXXX
7. Rust 解析端口 → 存入 BackendState → emit('backend-ready')
8. 前端设置 apiBaseURL → 健康检查通过
9. Rust 关闭 splash → 显示 main 窗口
```
**关闭序列:**
```
1. 用户点击关闭按钮 → 窗口隐藏到托盘(不退出)
2. 托盘菜单"退出" → Rust 发送 SIGTERM 给 Python 进程
3. Python 优雅关闭 → Tauri app.exit(0)
```
## Scope Boundaries
**在范围内:**
- Tauri 2.x 项目脚手架和配置
- Rust sidecar 进程管理启动/停止/健康检查/端口发现
- 前端最小适配api/base.ts 动态 baseURLapi/tauri.ts 封装App.vue Splash 流程TitleBar.vue
- 系统托盘显示/隐藏窗口退出
- 单实例锁
- 自定义标题栏无边框窗口
- PyInstaller 打包脚本
- 跨平台 CI 构建GitHub Actions
**延迟到后续迭代:**
- 自动更新器tauri-plugin-updater 集成 + 签名密钥 + 发布服务器
- 应用签名和公证macOS code signing / Windows Authenticode
- 多语言支持
- 离线模式
- 深度链接deep link / URL scheme 注册
- 文件关联
**不在范围内:**
- 修改现有 GUI 组件布局样式
- 修改后端 API 或业务逻辑
- 移动端适配Tauri 2.x 支持 iOS/Android但不在本次范围
- 多用户协作
## Implementation Units
### U1. Tauri 2.x 项目脚手架
**Goal:** 创建 Tauri 2.x 项目结构配置构建管线验证 dev 模式可运行
**Requirements:** KTD-1, KTD-4
**Dependencies:**
**Files:**
- `src-tauri/Cargo.toml` Rust 依赖tauri 2, tauri-plugin-shell, tauri-plugin-single-instance, serde, tokio, reqwest
- `src-tauri/tauri.conf.json` Tauri 核心配置窗口sidecar 声明bundle 设置
- `src-tauri/capabilities/default.json` 权限声明
- `src-tauri/src/main.rs` Rust 入口
- `src-tauri/src/lib.rs` Tauri Builder占位后续单元填充
- `src-tauri/build.rs` 构建脚本
- `src-tauri/icons/` 应用图标占位
- `package.json` 添加 Tauri CLI API 依赖
**Approach:**
1. 在项目根目录初始化 Tauri 2.x`npm create tauri-app@latest` 或手动创建 `src-tauri/` 目录
2. `tauri.conf.json` 配置双窗口splash + mainmain 初始 `visible: false`
3. `build.beforeDevCommand` 指向前端 dev 脚本`build.beforeBuildCommand` 指向前端 build 脚本
4. `build.frontendDist` 指向前端构建输出目录
5. `bundle.externalBin` 声明 sidecar 二进制名
6. 安装前端依赖`@tauri-apps/api@^2`, `@tauri-apps/plugin-shell@^2`
7. 验证 `npm run tauri dev` 能启动窗口
**Patterns to follow:** Tauri 2.x 官方项目结构
**Test scenarios:**
- `npm run tauri dev` 启动后显示 Tauri 窗口前端内容可能为空因为后端未启动
- `npm run tauri build` 能生成安装包可能因缺少 sidecar 二进制而失败此为预期
**Verification:** `npm run tauri dev` 能启动 Tauri 窗口并加载前端页面
---
### U2. Rust Sidecar 进程管理
**Goal:** 实现 Python 后端进程的启动停止健康检查和端口发现
**Requirements:** KTD-2, KTD-3
**Dependencies:** U1
**Files:**
- `src-tauri/src/sidecar.rs` sidecar 管理模块新建
- `src-tauri/src/lib.rs` 注册 invoke handler BackendState
- `src-tauri/Cargo.toml` 添加 `regex` 依赖
**Approach:**
1. 定义 `BackendState` 结构体`port: Mutex<Option<u16>>`, `pid: Mutex<Option<u32>>`
2. 实现 `start_backend` 命令:
- 通过 `app.shell().sidecar("agentkit-server")` 创建 sidecar 命令
- 传入 `--port 0``AGENTKIT_GUI_MODE=1` 环境变量
- spawn 后监听 stdout/stderr用正则解析 `AGENTKIT_PORT=(\d+)`
- 端口就绪后 emit `backend-ready` 事件
- 带超时等待30s
3. 实现 `get_backend_port` 命令:返回当前端口
4. 实现 `stop_backend` 命令:发送 SIGTERMUnix或 taskkillWindows
5. 实现 `check_backend_health` 命令HTTP GET `http://127.0.0.1:{port}/api/v1/health`
6.`lib.rs` 中注册所有命令和 `BackendState` managed state
**Technical design:**
```
start_backend() flow:
sidecar("agentkit-server").args(["--port", "0"]).env("AGENTKIT_GUI_MODE", "1")
→ spawn → tokio::spawn listen stdout/stderr
→ parse AGENTKIT_PORT=XXXXX → store in BackendState.port
→ emit("backend-ready", { port }) → return port
stop_backend() flow:
BackendState.pid → SIGTERM/taskkill → clear port & pid
```
**Patterns to follow:** `tauri-plugin-shell` sidecar API 文档
**Test scenarios:**
- 启动 sidecar 后 `get_backend_port` 返回有效端口号
- `check_backend_health` 在后端就绪后返回 true
- `stop_backend` 能成功终止 Python 进程
- 后端启动超时30s时返回错误
- 后端意外崩溃时 Rust 侧能检测到并清理状态
**Verification:** 手动测试 `invoke('start_backend')``invoke('get_backend_port')``invoke('check_backend_health')``invoke('stop_backend')` 全流程
---
### U3. 前端适配 — 动态 API baseURL + Splash 启动流程
**Goal:** 前端支持 Tauri 环境,动态设置 API baseURL添加 Splash 启动流程。
**Requirements:** KTD-3, KTD-4, KTD-5
**Dependencies:** U2
**Files:**
- `src/agentkit/server/frontend/src/api/base.ts` — 修改:支持 Tauri 环境动态 baseURL
- `src/agentkit/server/frontend/src/api/tauri.ts` — 新增Tauri invoke 封装
- `src/agentkit/server/frontend/src/App.vue` — 修改:添加 Splash 启动流程
- `src/agentkit/server/frontend/src/components/layout/SplashScreen.vue` — 新增Splash 加载组件
- `src/agentkit/server/frontend/src/components/layout/TitleBar.vue` — 新增:自定义标题栏
- `src/agentkit/server/frontend/src/views/AgentLayout.vue` — 修改:集成 TitleBar
- `src/agentkit/server/frontend/vite.config.ts` — 修改Tauri 环境适配
**Approach:**
1. **`api/tauri.ts`**:封装 Tauri invoke 调用
- `isTauri()` 检测是否在 Tauri 环境中(`'__TAURI_INTERNALS__' in window`
- `startBackend()``invoke('start_backend')`
- `getBackendPort()``invoke('get_backend_port')`
- `stopBackend()``invoke('stop_backend')`
- `checkBackendHealth()``invoke('check_backend_health')`
2. **`api/base.ts`**:动态 baseURL
- 新增 `initApiBaseURL()` 函数
- Tauri 环境:从 `getBackendPort()` 获取端口,设置 `apiBaseURL = http://127.0.0.1:{port}`
- 浏览器环境:`apiBaseURL = ''`(保持现有相对路径行为)
- `BaseApiClient``request()``createWebSocket()` 方法使用动态 baseURL
3. **`App.vue`**Splash 启动流程
- 新增 `loading``loadingProgress` 响应式状态
- `onMounted` 时:如果 `isTauri()`,调用 `startBackend()``initApiBaseURL()``checkBackendHealth()`
- 加载完成前显示 `SplashScreen`,完成后显示 `RouterView`
- 浏览器环境保持现有行为不变
4. **`SplashScreen.vue`**:加载动画组件
- 品牌 Logo + 加载进度条 + 状态文字("正在启动后端..."、"正在连接..."
- 使用现有 Design Token 变量
5. **`TitleBar.vue`**:自定义标题栏
- `data-tauri-drag-region` 使标题栏可拖拽
- 最小化/最大化/关闭按钮(关闭时隐藏到托盘)
- 仅在 Tauri 环境中显示
6. **`vite.config.ts`**Tauri 适配
- 读取 `TAURI_DEV_HOST` 环境变量
- `envPrefix` 添加 `TAURI_`
- `server.watch.ignored` 排除 `src-tauri/`
**Patterns to follow:** 现有 `api/base.ts``BaseApiClient` 模式;现有 Design Token 体系
**Test scenarios:**
- 浏览器模式:所有 API 调用正常工作(相对路径不变)
- Tauri 模式:`initApiBaseURL()` 设置正确的 `http://127.0.0.1:{port}`
- Tauri 模式WebSocket 连接到正确的 `ws://127.0.0.1:{port}/api/v1/...`
- Splash 在后端就绪前显示,就绪后消失
- TitleBar 拖拽移动窗口,按钮功能正确
- 后端启动失败时显示错误信息
**Verification:** `npm run tauri dev` 启动后Splash 显示 → 后端启动 → 主界面加载 → API 调用正常
---
### U4. 系统托盘 + 单实例 + 窗口管理
**Goal:** 实现系统托盘、单实例锁和窗口关闭行为(最小化到托盘)。
**Requirements:** KTD-1
**Dependencies:** U2
**Files:**
- `src-tauri/src/tray.rs` — 新增:系统托盘模块
- `src-tauri/src/lib.rs` — 修改:注册托盘、单实例插件、窗口事件
- `src-tauri/Cargo.toml` — 添加 `tauri-plugin-single-instance`
**Approach:**
1. **系统托盘**`tray.rs`
- 菜单项:显示主窗口 / 分隔线 / 退出
- 左键点击托盘图标:显示并聚焦主窗口
- 退出菜单项:先 `stop_backend()`,再 `app.exit(0)`
2. **单实例锁**
- 注册 `tauri-plugin-single-instance`
- 第二个实例启动时:聚焦已有主窗口
3. **窗口关闭行为**
- `CloseRequested` 事件中 `api.prevent_close()` + `window.hide()`
- 窗口隐藏到托盘而非退出
4. **Splash → Main 切换**
- `start_backend` 成功后Rust 侧关闭 splash 窗口、显示 main 窗口
**Patterns to follow:** Tauri 2.x tray API 文档
**Test scenarios:**
- 点击关闭按钮后窗口隐藏,托盘图标仍在
- 托盘"显示"菜单恢复窗口
- 托盘"退出"菜单停止后端并退出应用
- 左键点击托盘图标恢复窗口
- 启动第二个实例时聚焦已有窗口
**Verification:** 完整的窗口生命周期测试:启动 → 关闭到托盘 → 从托盘恢复 → 托盘退出
---
### U5. PyInstaller 打包脚本 + Python 侧适配
**Goal:** 创建 PyInstaller 打包脚本Python 后端支持 `--port 0` 并输出端口信息。
**Requirements:** KTD-2, KTD-3
**Dependencies:** U2
**Files:**
- `scripts/build-backend.sh` — 新增PyInstaller 打包脚本
- `scripts/build-backend.ps1` — 新增Windows 版打包脚本
- `src/agentkit/cli/main.py` — 修改:`gui` 命令支持 `--port 0`
- `src/agentkit/server/runner.py` — 修改uvicorn 启动后输出 `AGENTKIT_PORT=XXXXX`
**Approach:**
1. **Python 侧适配**
- `gui` 命令的 `--port` 参数支持 `0`(让 OS 分配随机端口)
- uvicorn 启动后,在 lifespan startup 中检测实际绑定端口
- 通过 stdout 输出 `AGENTKIT_PORT={port}`flush=True供 Rust 解析
-`--port 0`host 默认改为 `127.0.0.1`(仅本地访问,安全)
2. **PyInstaller 打包脚本**
- `--onefile` 模式打包
- `--name agentkit-server` 命名
- 包含必要的 `--hidden-import`uvicorn.logging, agentkit.server 等)
- `--add-data` 包含 agentkit.yaml 和 configs 目录
- 输出到 `src-tauri/binaries/` 并添加平台后缀
3. **平台后缀命名**
- macOS ARM: `agentkit-server-aarch64-apple-darwin`
- macOS Intel: `agentkit-server-x86_64-apple-darwin`
- Windows: `agentkit-server-x86_64-pc-windows-msvc.exe`
- Linux: `agentkit-server-x86_64-unknown-linux-gnu`
**Patterns to follow:** 现有 `agentkit gui` 命令的启动流程
**Test scenarios:**
- `python -m agentkit gui --port 0` 启动后 stdout 输出 `AGENTKIT_PORT=XXXXX`
- PyInstaller 打包的二进制能独立运行
- 打包的二进制支持 `--port 0``AGENTKIT_GUI_MODE=1`
- 打包的二进制在无 Python 环境的机器上能运行
**Verification:** 在当前平台执行 `scripts/build-backend.sh`,产物放入 `src-tauri/binaries/``npm run tauri dev` 能通过 sidecar 启动后端
---
### U6. 跨平台 CI 构建
**Goal:** 配置 GitHub Actions 自动构建 Windows/macOS/Linux 安装包。
**Requirements:** KTD-1
**Dependencies:** U1, U5
**Files:**
- `.github/workflows/release-desktop.yml` — 新增:桌面客户端发布工作流
**Approach:**
1. 触发条件:推送 `v*` tag
2. 构建矩阵macOS (aarch64 + x86_64)、Windows (x86_64)、Linux (x86_64)
3. 每个平台步骤:
- Setup Node.js 20 + Rust stable + Python 3.11
- 安装 Linux 系统依赖libwebkit2gtk-4.1-dev 等)
- `pip install pyinstaller` + 执行 `scripts/build-backend.sh`
- `npm ci` + `npm run tauri build`
4. 使用 `tauri-apps/tauri-action@v0` 构建和发布
5. 产物上传到 GitHub Releasedraft
**Patterns to follow:** Tauri 官方 GitHub Actions 模板
**Test scenarios:**
- 推送 tag 后工作流在所有 4 个目标上成功运行
- 产物正确上传到 GitHub Release
**Verification:** 手动触发工作流或推送 tag检查 GitHub Release 页面
---
## Risks & Dependencies
| 风险 | 影响 | 缓解 |
|------|------|------|
| PyInstaller 打包后部分依赖缺失 | 后端无法启动 | 逐步测试,添加 `--hidden-import` |
| Linux WebView 兼容性差异 | GUI 渲染异常 | 测试主流发行版Ubuntu 22.04+),文档说明依赖 |
| Python sidecar 启动慢3-5s | 用户等待时间长 | Splash 加载画面 + 进度反馈 |
| macOS 签名/公证缺失 | 用户安装时被 Gatekeeper 阻止 | 延迟到后续迭代,文档说明绕过方法 |
| Tauri 2.x API 变更 | 代码需调整 | 锁定 Tauri 2.x 稳定版 |
## Assumptions
- 用户机器上有 WebView2Windows 10+ 自带、WebKitmacOS 自带、WebKitGTKLinux 需安装)
- Python 后端不需要 GPU 加速或其他特殊硬件
- 单用户本地使用场景,不需要多用户认证
- 首个版本不需要应用签名和公证
## Open Questions
- 自动更新服务器的部署方案S3 / GitHub Releases / 自建)— 延迟到后续迭代
- 是否需要支持 Linux ARM64 — 视用户需求决定
- PyInstaller 产物体积优化(是否排除不必要的依赖)— 实现时评估