本文引用的文件
本项目是一个基于 Electron + ee-core 的桌面客户端,前端工程使用 Vite + Vue 3 + Ant Design Vue + TypeScript。主进程通过 ee-core.ElectronEgg 管理应用生命周期、窗口、托盘与安全服务;渲染进程负责主题、国际化、水印、授权、地图和 SSE Agent 对话。后端 ai-server 独立部署,客户端在运行时动态绑定其地址,并可选择加载本地 china.pmtiles 离线地图。
桌面端代码主要位于 ai-electron 下,可划分为三层:
| 层级 | 目录 | 职责 |
|---|---|---|
| 主进程层 | electron/main.ts、electron/preload/*、electron/config/*、electron/controller/*、electron/service/* |
应用生命周期、窗口控制、系统托盘、安全策略、IPC 控制器 |
| 渲染进程层 | frontend/src/* |
Vue 3 应用、路由、状态、UI、SSE Agent、离线地图、设置 |
| 构建与打包层 | package.json、cmd/builder.json、frontend/vite.config.ts |
开发脚本、Vite 配置、Electron Builder 产物 |
graph TB
subgraph "主进程"
M["main.ts<br/>注册生命周期与 preload"]
L["preload/lifecycle.ts<br/>ready / windowReady / beforeClose"]
P["preload/index.ts<br/>创建托盘与安全服务"]
C["controller/windowCtl.ts<br/>跨窗口 IPC"]
W["config/windowOptions.ts<br/>webPreferences"]
D["config/config.default.ts<br/>默认窗口与静态资源路径"]
end
subgraph "渲染进程"
FMain["frontend/src/main.ts<br/>启动引导、健康检查、路由守卫"]
AppVue["App.vue<br/>主题/国际化/水印/授权单元"]
ServerCfg["serverSetting.ts<br/>动态绑定 ai-server 地址"]
OfflineMap["offlineMap.ts<br/>china.pmtiles 离线地图"]
ChatStore["chatStream.ts<br/>会话与流式状态机"]
Sse["sse.ts<br/>SSE 解析与 fetch 流读取"]
ChatApi["chatApi.ts<br/>非流式聊天接口"]
end
M --> L
M --> P
M --> W
M --> D
M --> C
FMain --> AppVue
FMain --> ServerCfg
FMain --> ChatStore
ChatStore --> ChatApi
ChatStore --> Sse
AppVue --> ServerCfg
AppVue --> OfflineMap
图示来源
章节来源
本节聚焦主进程与渲染进程的职责边界。
main.ts 是 Electron 入口,使用 ee-core.ElectronEgg 创建应用实例,注册 ready、electron-app-ready、window-ready、before-close 生命周期钩子,并注册 preload。它不直接管理业务逻辑,而是把能力交给生命周期类和预加载模块。
关键点:
new ElectronEgg() 后调用 app.run()。Lifecycle 类实现,集中处理窗口居中、显示策略、二次启动激活等。preload 模块在窗口创建前执行,负责创建系统托盘和安全服务。章节来源
preload/index.ts 是框架级预加载入口,用于初始化托盘与安全服务;preload/bridge.ts 则在启用上下文隔离时通过 contextBridge.exposeInMainWorld('electron', ...) 向渲染进程暴露 ipcRenderer。当前窗口配置关闭了上下文隔离,因此渲染进程可直接访问 Node/Electron API,但 bridge 仍保留为扩展点。
安全模型要点:
contextIsolation: false、nodeIntegration: true,便于现有渲染代码直接使用 Electron API。contextBridge 暴露最小化 API,而不是直接把全部 Electron API 抛给渲染进程。WindowCtl 统一转发,避免渲染进程直接枚举所有窗口。章节来源
frontend/src/main.ts 是 Vue 应用的 bootstrap 入口,承担以下职责:
智能工作台窗口与普通主窗口的行为分支。
flowchart TD
Start["渲染进程启动"] --> Init["初始化 Store、路由、国际化、全局组件"]
Init --> CheckServer{"是否已绑定 ai-server 地址?"}
CheckServer -->|否| GoAuth["跳转到授权/服务地址页"]
CheckServer -->|是| Probe["健康检查:最多 10 次 × 3 秒"]
Probe --> Ready{"后端就绪?"}
Ready -->|否| ShowReconnect["显示重连与服务地址操作"]
Ready -->|是| AuthCheck["校验授权与登录态"]
AuthCheck --> Route["计算最终路由并挂载 App"]
图示来源
章节来源
App.vue 是根组件,负责:
StyleProvider 和 ConfigProvider 注入主题与语言。useLocale 获取中文语言包。useRootSetting 获取深色模式与主题色。useWatermark 根据授权单元设置页面水印。注意:如果尚未绑定服务端地址,或 /sys/info 失败,会清空水印,避免展示过期或错误的授权单元。
章节来源
下图展示从 Electron 主进程到前端 AI Agent 请求的整体调用链。
sequenceDiagram
participant EE as "ElectronEgg"
participant Main as "main.ts"
participant Life as "lifecycle.ts"
participant Pre as "preload/index.ts"
participant Win as "BrowserWindow"
participant Vue as "frontend main.ts"
participant Api as "serverSetting.ts"
participant SSE as "sse.ts"
participant Stream as "chatStream.ts"
participant Server as "ai-server"
EE->>Main: 创建应用
Main->>Life: 注册 ready / electron-app-ready / window-ready / before-close
Main->>Pre: 注册 preload
Main->>EE: run()
EE->>Win: 创建主窗口
Pre->>Pre: 创建托盘与安全服务
Life->>Win: 设置窗口大小与显示时机
Vue->>Api: getApiOrigin()
Vue->>Vue: waitForBackendReady()
Vue->>Server: GET /js/a/sys/health
Server-->>Vue: 健康响应
Vue->>Vue: resolveStartupRoute()
Vue->>Stream: 初始化会话与消息状态
Stream->>SSE: streamChat(url, params, handlers)
SSE->>Server: POST 事件流请求
Server-->>SSE: text/event-stream
SSE-->>Stream: intent / thinking / tool_call / token / done / error
图示来源
Lifecycle 类封装了四个关键阶段:
ready:ee-core 应用已加载。electronAppReady:处理双击图标时恢复已打开窗口,优先拉回智能工作台窗口。windowReady:主窗口按屏幕工作区尺寸按比例居中,并根据配置延迟显示。beforeClose:应用关闭前的清理钩子。窗口共享配置集中在 sharedWebPreferences,包含 contextIsolation: false 和 nodeIntegration: true,确保主窗口与智能工作台窗口行为一致。
章节来源
windowCtl.ts 提供统一的窗口管理 IPC:
openSmartWindow:打开或聚焦智能工作台窗口。closeSmartWindow:关闭智能工作台窗口。isSmartWindowOpen:查询智能窗口是否打开。focusMainWindow:登录失效等场景下把主窗口拉到前台。getWindowRole:让渲染进程知道自己属于主窗口还是智能工作台。syncState:向除发送者外的所有窗口广播语义事件,通道名为 app:state。该控制器返回 { success, data|message },失败时记录日志但不抛出到 Electron 层,便于渲染进程统一处理。
章节来源
serverSetting.ts 是客户端与后端地址绑定的核心:
SERVER_BASE_URL__ 中,不参与 AES 快照,客户端升级后复用。SERVER_BASE_URL_HISTORY__,最多保留 5 条。normalizeServerOrigin 只接受协议、主机和端口,拒绝携带 /js、/a 等路径。getApiOrigin 优先取运行时绑定地址,其次取构建期环境变量。buildHealthCheckUrl 构造 /js/a/sys/health 健康检查地址。testServerConnection 使用原生 fetch 测试连接,超时、跨域、HTTP 非 200 都会给出明确错误信息。reloadToAppRoot 切换地址后回到应用根路径并重新加载。
flowchart TD
Input["用户输入服务地址"] --> Normalize["normalizeServerOrigin 规范化"]
Normalize --> Valid{"格式合法?"}
Valid -->|否| Error["返回错误信息"]
Valid -->|是| Save["保存到 SERVER_BASE_URL__"]
Save --> Remember["加入最近使用历史"]
Remember --> Health["testServerConnection 健康检查"]
Health --> Ok{"连接成功?"}
Ok -->|否| ShowError["提示网络、端口、跨域或服务问题"]
Ok -->|是| Reload["reloadToAppRoot 回到根路径并重载"]
图示来源
章节来源
offlineMap.ts 提供两种 PMTiles 数据源:
ElectronPmtilesSource:通过主进程 IPC 读取本机绝对路径下的 china.pmtiles,按需分块读取二进制范围。HttpPmtilesSource:对内置静态资源 resource/map/china.pmtiles 使用 HTTP Range 请求。离线地图目录保存在 OFFLINE_MAP_DIR__,支持 Windows 盘符、UNC 路径、POSIX 绝对路径和本地协议。样式 JSON 也可从本机路径读取,否则回退到内置样式。
classDiagram
class ElectronPmtilesSource {
+string filePath
+getKey() string
+getBytes(offset, length) ArrayBuffer
}
class HttpPmtilesSource {
+string url
+getKey() string
+getBytes(offset, length) ArrayBuffer
}
class OfflineMap {
+getOfflineMapDir() string
+getOfflineMapPmtilesPath() string
+resolveOfflinePmtilesSources() Array
+readOfflineMapStyleText() Promise~string~
+isValidOfflineMapPath(value) boolean
+setOfflineMapDir(path) void
}
OfflineMap --> ElectronPmtilesSource : "本地路径时使用"
OfflineMap --> HttpPmtilesSource : "内置静态资源时使用"
图示来源
章节来源
Agent 对话分为两层:
chatApi.ts:封装非流式聊天接口,如会话列表、消息列表、发送消息等。sse.ts:通用 SSE 解析器与 streamSse 流式请求实现,支持 intent、thinking、tool_call、tool_input、tool_result、token、done、error 等事件。chatStream.ts:AI 对话流式状态机,维护会话、消息、工具步骤、思考过程、意图与建议项,并做增量解析节流。
sequenceDiagram
participant UI as "AI 对话界面"
participant Store as "chatStream.ts"
participant Api as "chatApi.ts"
participant SSE as "sse.ts"
participant Server as "ai-server"
UI->>Store: 发送消息
Store->>Api: createSession / listMessages / sendMessage
Store->>SSE: streamChat(url, params, handlers)
SSE->>Server: POST 带 x-token 的事件流请求
Server-->>SSE: intent / thinking / tool_call / token / done
SSE-->>Store: 分发事件
Store->>Store: 更新消息、工具步骤、思考文本、建议项
Store-->>UI: 增量渲染
图示来源
章节来源
前端工程位于 ai-electron/frontend,采用:
vite.config.ts 中:
loadEnv 加载环境配置。base、define、plugins、server、esbuild、build、css、resolve.alias。@ 指向 src,@build 指向 build。章节来源
graph LR
Package["package.json<br/>ee-core / electron-updater"]
Main["electron/main.ts"]
Lifecycle["electron/preload/lifecycle.ts"]
Preload["electron/preload/index.ts"]
Bridge["electron/preload/bridge.ts"]
WindowOpts["electron/config/windowOptions.ts"]
DefaultCfg["electron/config/config.default.ts"]
WindowCtl["electron/controller/windowCtl.ts"]
FrontMain["frontend/src/main.ts"]
AppVue["frontend/src/App.vue"]
ServerCfg["frontend/src/core/settings/serverSetting.ts"]
OfflineMap["frontend/src/core/utils/map/offlineMap.ts"]
SSE["frontend/src/ai/api/sse.ts"]
ChatStore["frontend/src/ai/store/chatStream.ts"]
ChatApi["frontend/src/ai/api/chatApi.ts"]
Package --> Main
Main --> Lifecycle
Main --> Preload
Main --> Bridge
DefaultCfg --> WindowOpts
Main --> WindowCtl
FrontMain --> AppVue
FrontMain --> ServerCfg
AppVue --> ServerCfg
AppVue --> OfflineMap
ChatStore --> ChatApi
ChatStore --> SSE
图示来源
章节来源
[本节为通用优化说明,不直接分析具体代码片段]
| 现象 | 可能原因 | 排查位置 |
|---|---|---|
| 启动后一直显示“后端服务连接失败” | ai-server 未启动、地址绑定错误、跨域被拦截、防火墙阻止 | frontend/src/main.ts 的健康检查逻辑 |
| 授权页显示服务不可达 | 绑定地址后首次健康检查失败 | frontend/src/core/settings/serverSetting.ts 的 testServerConnection |
| 地图不显示或瓦片空白 | 本机离线地图目录无效、china.pmtiles 不存在、Range 请求失败 |
frontend/src/core/utils/map/offlineMap.ts |
| Agent 没有流式输出 | 后端未返回 text/event-stream、token 失效、请求被代理或网关拦截 |
frontend/src/ai/api/sse.ts |
| 登录后跳转异常 | 入口路由处理、hash/history 模式差异、来源路由丢失 | frontend/src/main.ts 的路由解析与登录门禁 |
| 智能工作台窗口行为异常 | 窗口角色识别错误或 IPC 事件未正确转发 | electron/controller/windowCtl.ts |
| 打包后找不到静态资源 | Vite base、Electron 静态资源路径、asar 打包影响 |
frontend/vite.config.ts、electron/config/config.default.ts |
章节来源
以下路径描述最小可运行流程,不包含完整 UI 交互细节:
Electron 主进程启动:
main.ts。preload。app.run() 创建主窗口。预加载模块初始化:
bridge.ts 暴露 ipcRenderer。渲染进程引导:
授权与登录:
发起 Agent 请求:
chatStream.ts 调用 chatApi.ts 创建或查询会话。sse.ts 使用 fetch 发起事件流请求,携带 x-token。intent、thinking、tool_call、token、done、error 等事件。更新流式消息并渲染到界面。
flowchart TD
A["Electron 主进程启动"] --> B["注册生命周期与 preload"]
B --> C["创建主窗口"]
C --> D["渲染进程 main.ts 初始化"]
D --> E["检查 ai-server 地址"]
E --> F["健康检查后端"]
F --> G["授权与登录校验"]
G --> H["AI 对话界面发送消息"]
H --> I["chatStream.ts 调用 chatApi.ts"]
I --> J["sse.ts 发起 SSE 流式请求"]
J --> K["解析事件并更新消息"]
图示来源
本桌面客户端以 ee-core.ElectronEgg 为主进程骨架,通过生命周期钩子和预加载模块组织启动流程;渲染进程以 Vue 3 为核心,完成主题、国际化、授权、地图与 SSE Agent 对话。ai-server 地址在运行时绑定,离线地图通过 PMTiles 和本机路径读取;跨窗口状态通过主进程统一转发。整体架构清晰分离主进程与渲染进程职责,适合继续扩展安全加固、自动更新与更多桌面能力。
[本节为总结性内容,不直接分析具体文件]