406 lines
18 KiB
Markdown
406 lines
18 KiB
Markdown
---
|
||
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 动态 baseURL、api/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 + main),main 初始 `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` 命令:发送 SIGTERM(Unix)或 taskkill(Windows)
|
||
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 Release(draft)
|
||
|
||
**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
|
||
|
||
- 用户机器上有 WebView2(Windows 10+ 自带)、WebKit(macOS 自带)、WebKitGTK(Linux 需安装)
|
||
- Python 后端不需要 GPU 加速或其他特殊硬件
|
||
- 单用户本地使用场景,不需要多用户认证
|
||
- 首个版本不需要应用签名和公证
|
||
|
||
## Open Questions
|
||
|
||
- 自动更新服务器的部署方案(S3 / GitHub Releases / 自建)— 延迟到后续迭代
|
||
- 是否需要支持 Linux ARM64 — 视用户需求决定
|
||
- PyInstaller 产物体积优化(是否排除不必要的依赖)— 实现时评估
|