diff --git a/dist-electron/main/main.js b/dist-electron/main/main.js index 86f81e5..a8c04dd 100644 --- a/dist-electron/main/main.js +++ b/dist-electron/main/main.js @@ -1,7 +1,2 @@ -"use strict"; -require("electron"); -require("./main-B0AKNiSn.js"); -require("electron-squirrel-startup"); -require("electron-log"); -require("bytenode"); -require("axios"); +require('bytenode') +require('./main.jsc') \ No newline at end of file diff --git a/dist-electron/preload/preload.js b/dist-electron/preload/preload.js index c4f6639..956f473 100644 --- a/dist-electron/preload/preload.js +++ b/dist-electron/preload/preload.js @@ -1,137 +1 @@ -"use strict"; -const electron = require("electron"); -var IPC_EVENTS = /* @__PURE__ */ ((IPC_EVENTS2) => { - IPC_EVENTS2["EXTERNAL_OPEN"] = "external-open"; - IPC_EVENTS2["APP_SET_FRAMELESS"] = "app:set-frameless"; - IPC_EVENTS2["APP_LOAD_PAGE"] = "app:load-page"; - IPC_EVENTS2["TAB_CREATE"] = "tab:create"; - IPC_EVENTS2["TAB_LIST"] = "tab:list"; - IPC_EVENTS2["TAB_NAVIGATE"] = "tab:navigate"; - IPC_EVENTS2["TAB_RELOAD"] = "tab:reload"; - IPC_EVENTS2["TAB_BACK"] = "tab:back"; - IPC_EVENTS2["TAB_FORWARD"] = "tab:forward"; - IPC_EVENTS2["TAB_SWITCH"] = "tab:switch"; - IPC_EVENTS2["TAB_CLOSE"] = "tab:close"; - IPC_EVENTS2["LOG_TO_MAIN"] = "log-to-main"; - IPC_EVENTS2["READ_FILE"] = "read-file"; - IPC_EVENTS2["INVOKE"] = "ipc:invoke"; - IPC_EVENTS2["INVOKE_ASYNC"] = "ipc:invokeAsync"; - IPC_EVENTS2["APP_MINIMIZE"] = "app:minimize"; - IPC_EVENTS2["APP_MAXIMIZE"] = "app:maximize"; - IPC_EVENTS2["APP_QUIT"] = "app:quit"; - IPC_EVENTS2["FILE_READ"] = "file:read"; - IPC_EVENTS2["FILE_WRITE"] = "file:write"; - IPC_EVENTS2["GET_WINDOW_ID"] = "get-window-id"; - IPC_EVENTS2["CUSTOM_EVENT"] = "custom:event"; - IPC_EVENTS2["TIME_UPDATE"] = "time:update"; - IPC_EVENTS2["RENDERER_IS_READY"] = "renderer-ready"; - IPC_EVENTS2["SHOW_CONTEXT_MENU"] = "show-context-menu"; - IPC_EVENTS2["START_A_DIALOGUE"] = "start-a-dialogue"; - IPC_EVENTS2["OPEN_WINDOW"] = "open-window"; - IPC_EVENTS2["LOG_DEBUG"] = "log-debug"; - IPC_EVENTS2["LOG_INFO"] = "log-info"; - IPC_EVENTS2["LOG_WARN"] = "log-warn"; - IPC_EVENTS2["LOG_ERROR"] = "log-error"; - IPC_EVENTS2["CONFIG_UPDATED"] = "config-updated"; - IPC_EVENTS2["SET_CONFIG"] = "set-config"; - IPC_EVENTS2["GET_CONFIG"] = "get-config"; - IPC_EVENTS2["UPDATE_CONFIG"] = "update-config"; - IPC_EVENTS2["SET_THEME_MODE"] = "set-theme-mode"; - IPC_EVENTS2["GET_THEME_MODE"] = "get-theme-mode"; - IPC_EVENTS2["IS_DARK_THEME"] = "is-dark-theme"; - IPC_EVENTS2["THEME_MODE_UPDATED"] = "theme-mode-updated"; - IPC_EVENTS2["EXECUTE_SCRIPT"] = "execute-script"; - IPC_EVENTS2["TASK_PROGRESS"] = "task:progress"; - IPC_EVENTS2["TASK_STARTED"] = "task:started"; - IPC_EVENTS2["TASK_COMPLETED"] = "task:completed"; - IPC_EVENTS2["OPEN_CHANNEL"] = "open-channel"; - IPC_EVENTS2["SCRIPT_LIST"] = "script:list"; - IPC_EVENTS2["SCRIPT_GET"] = "script:get"; - IPC_EVENTS2["SCRIPT_SAVE"] = "script:save"; - IPC_EVENTS2["SCRIPT_DELETE"] = "script:delete"; - IPC_EVENTS2["SCRIPT_TOGGLE"] = "script:toggle"; - IPC_EVENTS2["SCRIPT_RUN"] = "script:run"; - IPC_EVENTS2["SCRIPT_RECORD_START"] = "script:record-start"; - IPC_EVENTS2["SCRIPT_RECORD_STOP"] = "script:record-stop"; - IPC_EVENTS2["SCRIPT_CODEGEN"] = "script:codegen"; - IPC_EVENTS2["GATEWAY_RPC"] = "gateway:rpc"; - IPC_EVENTS2["GATEWAY_EVENT"] = "gateway:event"; - IPC_EVENTS2["UPDATE_CHECK"] = "update:check"; - IPC_EVENTS2["UPDATE_DOWNLOAD"] = "update:download"; - IPC_EVENTS2["UPDATE_INSTALL"] = "update:install"; - IPC_EVENTS2["UPDATE_VERSION"] = "update:version"; - IPC_EVENTS2["UPDATE_STATUS_CHANGED"] = "update:status-changed"; - return IPC_EVENTS2; -})(IPC_EVENTS || {}); -const api = { - versions: process.versions, - external: { - open: (url) => electron.ipcRenderer.invoke("external-open", url) - }, - platform: process.platform, - windowMinimize: () => electron.ipcRenderer.invoke("window:minimize"), - windowMaximize: () => electron.ipcRenderer.invoke("window:maximize"), - windowClose: () => electron.ipcRenderer.invoke("window:close"), - windowIsMaximized: () => electron.ipcRenderer.invoke("window:isMaximized"), - viewIsReady: () => electron.ipcRenderer.send(IPC_EVENTS.RENDERER_IS_READY), - app: { - setFrameless: (route) => electron.ipcRenderer.invoke(IPC_EVENTS.APP_SET_FRAMELESS, route), - loadPage: (page) => electron.ipcRenderer.invoke(IPC_EVENTS.APP_LOAD_PAGE, page) - }, - // 通过 IPC 调用主进程 - readFile: (filePath) => electron.ipcRenderer.invoke(IPC_EVENTS.READ_FILE, filePath), - // 异步调用(映射为 electron 的 invoke) - invoke: (channel, ...args) => electron.ipcRenderer.invoke(channel, ...args), - // 异步调用(为了兼容老代码) - invokeAsync: (channel, ...args) => electron.ipcRenderer.invoke(channel, ...args), - // 监听主进程消息 - on: (event, callback) => { - const subscription = (_event, ...args) => callback(...args); - electron.ipcRenderer.on(event, subscription); - return () => electron.ipcRenderer.removeListener(event, subscription); - }, - // 发送消息到主进程 - send: (channel, ...args) => electron.ipcRenderer.send(channel, ...args), - // 获取窗口ID - getCurrentWindowId: () => electron.ipcRenderer.sendSync(IPC_EVENTS.GET_WINDOW_ID), - // 发送日志 - logger: { - debug: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_DEBUG, message, ...meta), - info: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_INFO, message, ...meta), - warn: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_WARN, message, ...meta), - error: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_ERROR, message, ...meta) - }, - // 执行脚本 - executeScript: (params) => electron.ipcRenderer.invoke(IPC_EVENTS.EXECUTE_SCRIPT, params), - // 任务事件 - onTaskProgress: (cb) => { - const subscription = (_event, payload) => cb(payload); - electron.ipcRenderer.on(IPC_EVENTS.TASK_PROGRESS, subscription); - return () => electron.ipcRenderer.removeListener(IPC_EVENTS.TASK_PROGRESS, subscription); - }, - onTaskStarted: (cb) => { - const subscription = (_event, payload) => cb(payload); - electron.ipcRenderer.on(IPC_EVENTS.TASK_STARTED, subscription); - return () => electron.ipcRenderer.removeListener(IPC_EVENTS.TASK_STARTED, subscription); - }, - onTaskCompleted: (cb) => { - const subscription = (_event, payload) => cb(payload); - electron.ipcRenderer.on(IPC_EVENTS.TASK_COMPLETED, subscription); - return () => electron.ipcRenderer.removeListener(IPC_EVENTS.TASK_COMPLETED, subscription); - }, - // 打开渠道 - openChannel: (channels) => electron.ipcRenderer.invoke(IPC_EVENTS.OPEN_CHANNEL, channels), - // 脚本管理 - scriptApi: { - list: () => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_LIST), - get: (id) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_GET, id), - save: (input) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_SAVE, input), - delete: (id) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_DELETE, id), - toggle: (id, enabled) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_TOGGLE, id, enabled), - run: (id) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RUN, id), - startRecording: (url) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RECORD_START, url), - stopRecording: () => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RECORD_STOP), - codegen: (id, url) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_CODEGEN, id, url) - } -}; -electron.contextBridge.exposeInMainWorld("api", api); +"use strict";const r=require("electron");var i=(e=>(e.EXTERNAL_OPEN="external-open",e.APP_SET_FRAMELESS="app:set-frameless",e.APP_LOAD_PAGE="app:load-page",e.TAB_CREATE="tab:create",e.TAB_LIST="tab:list",e.TAB_NAVIGATE="tab:navigate",e.TAB_RELOAD="tab:reload",e.TAB_BACK="tab:back",e.TAB_FORWARD="tab:forward",e.TAB_SWITCH="tab:switch",e.TAB_CLOSE="tab:close",e.LOG_TO_MAIN="log-to-main",e.READ_FILE="read-file",e.INVOKE="ipc:invoke",e.INVOKE_ASYNC="ipc:invokeAsync",e.APP_MINIMIZE="app:minimize",e.APP_MAXIMIZE="app:maximize",e.APP_QUIT="app:quit",e.FILE_READ="file:read",e.FILE_WRITE="file:write",e.GET_WINDOW_ID="get-window-id",e.CUSTOM_EVENT="custom:event",e.TIME_UPDATE="time:update",e.RENDERER_IS_READY="renderer-ready",e.SHOW_CONTEXT_MENU="show-context-menu",e.START_A_DIALOGUE="start-a-dialogue",e.OPEN_WINDOW="open-window",e.LOG_DEBUG="log-debug",e.LOG_INFO="log-info",e.LOG_WARN="log-warn",e.LOG_ERROR="log-error",e.CONFIG_UPDATED="config-updated",e.SET_CONFIG="set-config",e.GET_CONFIG="get-config",e.UPDATE_CONFIG="update-config",e.SET_THEME_MODE="set-theme-mode",e.GET_THEME_MODE="get-theme-mode",e.IS_DARK_THEME="is-dark-theme",e.THEME_MODE_UPDATED="theme-mode-updated",e.EXECUTE_SCRIPT="execute-script",e.TASK_PROGRESS="task:progress",e.TASK_STARTED="task:started",e.TASK_COMPLETED="task:completed",e.OPEN_CHANNEL="open-channel",e.SCRIPT_LIST="script:list",e.SCRIPT_GET="script:get",e.SCRIPT_SAVE="script:save",e.SCRIPT_DELETE="script:delete",e.SCRIPT_TOGGLE="script:toggle",e.SCRIPT_RUN="script:run",e.SCRIPT_RECORD_START="script:record-start",e.SCRIPT_RECORD_STOP="script:record-stop",e.SCRIPT_CODEGEN="script:codegen",e.GATEWAY_RPC="gateway:rpc",e.GATEWAY_EVENT="gateway:event",e.UPDATE_CHECK="update:check",e.UPDATE_DOWNLOAD="update:download",e.UPDATE_INSTALL="update:install",e.UPDATE_VERSION="update:version",e.UPDATE_STATUS_CHANGED="update:status-changed",e))(i||{});const d={versions:process.versions,external:{open:e=>r.ipcRenderer.invoke("external-open",e)},platform:process.platform,windowMinimize:()=>r.ipcRenderer.invoke("window:minimize"),windowMaximize:()=>r.ipcRenderer.invoke("window:maximize"),windowClose:()=>r.ipcRenderer.invoke("window:close"),windowIsMaximized:()=>r.ipcRenderer.invoke("window:isMaximized"),viewIsReady:()=>r.ipcRenderer.send(i.RENDERER_IS_READY),app:{setFrameless:e=>r.ipcRenderer.invoke(i.APP_SET_FRAMELESS,e),loadPage:e=>r.ipcRenderer.invoke(i.APP_LOAD_PAGE,e)},readFile:e=>r.ipcRenderer.invoke(i.READ_FILE,e),invoke:(e,...n)=>r.ipcRenderer.invoke(e,...n),invokeAsync:(e,...n)=>r.ipcRenderer.invoke(e,...n),on:(e,n)=>{const t=(o,...R)=>n(...R);return r.ipcRenderer.on(e,t),()=>r.ipcRenderer.removeListener(e,t)},send:(e,...n)=>r.ipcRenderer.send(e,...n),getCurrentWindowId:()=>r.ipcRenderer.sendSync(i.GET_WINDOW_ID),logger:{debug:(e,...n)=>r.ipcRenderer.send(i.LOG_DEBUG,e,...n),info:(e,...n)=>r.ipcRenderer.send(i.LOG_INFO,e,...n),warn:(e,...n)=>r.ipcRenderer.send(i.LOG_WARN,e,...n),error:(e,...n)=>r.ipcRenderer.send(i.LOG_ERROR,e,...n)},executeScript:e=>r.ipcRenderer.invoke(i.EXECUTE_SCRIPT,e),onTaskProgress:e=>{const n=(t,o)=>e(o);return r.ipcRenderer.on(i.TASK_PROGRESS,n),()=>r.ipcRenderer.removeListener(i.TASK_PROGRESS,n)},onTaskStarted:e=>{const n=(t,o)=>e(o);return r.ipcRenderer.on(i.TASK_STARTED,n),()=>r.ipcRenderer.removeListener(i.TASK_STARTED,n)},onTaskCompleted:e=>{const n=(t,o)=>e(o);return r.ipcRenderer.on(i.TASK_COMPLETED,n),()=>r.ipcRenderer.removeListener(i.TASK_COMPLETED,n)},openChannel:e=>r.ipcRenderer.invoke(i.OPEN_CHANNEL,e),scriptApi:{list:()=>r.ipcRenderer.invoke(i.SCRIPT_LIST),get:e=>r.ipcRenderer.invoke(i.SCRIPT_GET,e),save:e=>r.ipcRenderer.invoke(i.SCRIPT_SAVE,e),delete:e=>r.ipcRenderer.invoke(i.SCRIPT_DELETE,e),toggle:(e,n)=>r.ipcRenderer.invoke(i.SCRIPT_TOGGLE,e,n),run:e=>r.ipcRenderer.invoke(i.SCRIPT_RUN,e),startRecording:e=>r.ipcRenderer.invoke(i.SCRIPT_RECORD_START,e),stopRecording:()=>r.ipcRenderer.invoke(i.SCRIPT_RECORD_STOP),codegen:(e,n)=>r.ipcRenderer.invoke(i.SCRIPT_CODEGEN,e,n)}};r.contextBridge.exposeInMainWorld("api",d); diff --git a/dist/index.html b/dist/index.html index a7a7ccb..c19a495 100644 --- a/dist/index.html +++ b/dist/index.html @@ -8,8 +8,8 @@ http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: http://8.138.234.141 https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn" /> - - + +
diff --git a/docs/OpenClaw-Chat-Alignment-Plan.md b/docs/OpenClaw-Chat-Alignment-Plan.md index fc35dc2..92767dc 100644 --- a/docs/OpenClaw-Chat-Alignment-Plan.md +++ b/docs/OpenClaw-Chat-Alignment-Plan.md @@ -1,64 +1,91 @@ # zn-ai 对齐 ClawX 对话能力的 OpenClaw 集成与迁移计划 -## 1. 结论摘要 +## 1. 当前结论 -- `zn-ai` 当前已经具备一套可工作的本地聊天骨架:`gateway:rpc`、`gateway:event`、`providerApiService`、流式渲染、会话列表、附件发送、`thinking/tool_use/tool_result` 数据结构都已存在。 -- `ClawX` 的核心优势不在单个聊天组件,而在于一整套“`Electron Main` 托管 `OpenClaw Gateway` + `Host API` 统一入口 + `~/.openclaw` 运行时配置 + Agent/Session/Transcript`”的闭环。 -- 如果目标是“对齐 ClawX 的对话能力”,推荐采用 **ClawX 原生方案对齐**,而不是继续把 `zn-ai` 现有轻量 gateway 做成长期主架构。 -- 推荐实施路径是:**先保持 `zn-ai` 现有 `gateway:rpc` / `gateway:event` 契约不变,逐步把底层执行面替换成 OpenClaw**。这样可以最大化复用 Vue Renderer,降低一次性重写风险。 +- `zn-ai` 已完成 `React` 平替,当前 Renderer 主链路已经是 `src/App.tsx` + `src/router/index.tsx` + `src/pages/Home/index.tsx`,不再按 `Vue` 页面作为迁移前提。 +- 这意味着接下来的 `OpenClaw` 对齐重点不再是“技术栈迁移”,而是 `Host API / Gateway Lifecycle / Session / Transcript / Agent Routing / Runtime Packaging` 这几个能力闭环。 +- 推荐继续保留 `zn-ai` 现有的 `gateway:rpc` 与 `hostApiFetch()` 契约,逐步把底层执行面替换成 `ClawX + OpenClaw` 风格,而不是直接推翻现有 React 聊天页。 +- 第一批集成开发已经开始:本地 `Host API` 路由骨架已从 `electron/main.ts` 拆出,新增了 `Gateway / Sessions / Files / Providers` 四类本地路由,为后续 `OpenClaw` 接入打底。 -## 2. ClawX 集成 OpenClaw 的实现思路 +## 2. React 平替后的新前提 -| 层级 | ClawX 做法 | 关键文件 | 对 zn-ai 的启发 | -| --- | --- | --- | --- | -| OpenClaw 运行时打包 | 把 `openclaw` 包和全部运行时依赖打进应用资源目录,同时额外打包 `uv`、Windows Node 二进制 | `ClawX/scripts/bundle-openclaw.mjs` `ClawX/scripts/download-bundled-uv.mjs` `ClawX/electron/utils/paths.ts` | `zn-ai` 不能只复用接口,必须把 OpenClaw runtime 一起嵌入,才能真正对齐 ClawX 的行为与目录结构 | -| Gateway 生命周期 | `Electron utilityProcess` 启动 `openclaw.mjs`,主进程负责 start/stop/restart/health/reconnect/orphan cleanup | `ClawX/electron/gateway/manager.ts` `ClawX/electron/gateway/process-launcher.ts` `ClawX/electron/gateway/supervisor.ts` | `zn-ai` 现有 in-process gateway 可作为过渡层,但目标应切到独立 OpenClaw 进程托管 | -| Host API 统一接入 | 主进程启动本地 Host API Server,Renderer 统一通过 `hostApiFetch()` 与各类 `/api/*` 路由通信 | `ClawX/electron/api/server.ts` `ClawX/electron/api/routes/gateway.ts` `ClawX/electron/api/routes/sessions.ts` `ClawX/src/lib/host-api.ts` | `zn-ai` 目前 `hostapi:fetch` 同时承担“本地 provider”与“远端业务后端”两套职责,后续需要把 OpenClaw 相关能力彻底本地化 | -| Renderer 与 Gateway 通信 | Renderer 通过 `gateway:rpc` 和 `/api/app/gateway-info` 建立统一通信链路,不直接知道 OpenClaw 进程细节 | `ClawX/src/lib/gateway-client.ts` `ClawX/electron/api/routes/gateway.ts` | `zn-ai` 已经有相同方向的封装,这部分最值得保留 | -| 对话状态机 | Chat Store 负责流式消息、历史回灌、错误恢复、附件缓存、tool/thinking 归并、会话切换 | `ClawX/src/stores/chat.ts` `ClawX/src/stores/chat/runtime-send-actions.ts` `ClawX/src/stores/chat/runtime-event-handlers.ts` | `zn-ai` 现有 `src/stores/chat.ts` 已经很接近,可以按契约对齐而不必推倒重来 | -| 对话展示能力 | Markdown、thinking 展示、tool cards、图片/文件附件、`@agent` 路由、执行图、sub-agent transcript | `ClawX/src/pages/Chat/index.tsx` `ClawX/src/pages/Chat/ChatInput.tsx` `ClawX/src/pages/Chat/ChatMessage.tsx` | `zn-ai` 需要补的是“真实能力闭环”,而不是单纯补 UI 外观 | -| 子任务/转录读取 | 通过 Session Transcript API 读取子 agent 的 `jsonl` 转录,驱动执行图 | `ClawX/electron/api/routes/sessions.ts` `ClawX/src/pages/Chat/index.tsx` | 这是 `zn-ai` 当前缺失最明显的一块,也是对齐 ClawX 对话体验的关键差异点 | +### 2.1 当前 React 主链路 -## 3. zn-ai 当前基础与差距 +| 模块 | 当前文件 | 说明 | +| --- | --- | --- | +| React App 入口 | `src/App.tsx` | 已由 React Router 托管 | +| 路由 | `src/router/index.tsx` | 主页面已经走 React 路由 | +| 主聊天页 | `src/pages/Home/index.tsx` | 当前对话、任务中心、渠道入口都在这里 | +| 聊天组件 | `src/components/chat/*` | 会话列表、消息列表、输入框、任务板 | +| Chat Store | `src/stores/chat.ts` | 已支持流式输出、历史回灌、附件 staging、session 删除 | +| Task/Channel Store | `src/stores/task.ts` `src/stores/channel.ts` | 已有任务与渠道配置的 React 状态层 | -### 3.1 已有基础 +### 2.2 已完成或已落地的基础能力 -| 能力 | 当前实现 | 关键文件 | 评估 | -| --- | --- | --- | --- | -| 主进程聊天入口 | 已有 `gateway:rpc` 与 `hostapi:fetch` | `zn-ai/electron/main.ts` | 可复用 | -| 本地 gateway 骨架 | 已有 `GatewayManager`、`chat.send/history/abort/session.list` | `zn-ai/electron/gateway/manager.ts` `zn-ai/electron/gateway/handlers/chat.ts` `zn-ai/electron/gateway/types.ts` | 适合做过渡层 | -| Provider 本地存储 | Provider 账户、默认账户、API Key 已本地化 | `zn-ai/electron/service/provider-api-service/index.ts` | 可作为 OpenClaw 配置同步源 | -| Renderer chat 状态机 | 已支持流式更新、错误恢复、历史回读、附件、tool status | `zn-ai/src/stores/chat.ts` | 高复用价值 | -| 消息模型 | 已定义 `thinking/tool_use/tool_result/image` | `zn-ai/src/pages/home/model/ChatModel.ts` | 结构已接近 ClawX | -| 聊天 UI | 已有消息列表、输入框、附件、Markdown、会话侧栏 | `zn-ai/src/pages/home/ChatBox.vue` `zn-ai/src/pages/home/ChatHistory.vue` `zn-ai/src/pages/home/components/chat/*` | 可迭代增强 | +| 能力 | 当前状态 | 关键文件 | +| --- | --- | --- | +| Renderer 已 React 化 | 已完成 | `src/App.tsx` `src/router/index.tsx` | +| Home/Chat 主链路 | 已完成 React 化 | `src/pages/Home/index.tsx` `src/components/chat/*` | +| gateway:rpc / gateway:event | 已可用 | `electron/gateway/manager.ts` `src/lib/gateway-client.ts` | +| hostApiFetch | 已可用 | `src/lib/host-api.ts` `electron/main.ts` | +| Transcript 写入 | 已有 jsonl 写入 | `electron/gateway/handlers/chat.ts` `electron/utils/token-usage-writer.ts` | +| Provider 本地化 | 已可用 | `electron/service/provider-api-service/index.ts` | +| 本地 Session Key | 已初步收敛到 `agent:*` | `src/stores/chat.ts` | -### 3.2 主要差距 +### 2.3 仍未对齐 ClawX 的关键差距 | 差距 | 当前状态 | 对齐目标 | | --- | --- | --- | -| OpenClaw runtime | 还没有嵌入 `openclaw` 包、`utilityProcess` 启动链路、打包脚本 | 与 ClawX 一样由主进程托管 OpenClaw Gateway | -| Host API 本地闭环 | `hostapi:fetch` 仍混合本地代理和远端服务转发 | OpenClaw 相关 `/api/*` 统一落在本地主进程 Host API | -| Session key 约定 | 仍会生成 `local:{defaultAccountId}:{uuid}` 本地会话键 | 统一收敛到 ClawX 风格的 `agent:{agentId}:{sessionId}` | -| `@agent` 直达路由 | 输入框已有 Agent chip,但当前是占位能力,未接入真实 Agent 列表和主会话映射 | 像 ClawX 一样把下一条消息直接送到目标 Agent 的主会话 | -| Agent/Session/Transcript | 缺少完整的 Agent 配置、main session 映射、子 agent transcript 加载 | 对齐 ClawX 的 Agent/Session/Transcript API | -| 执行图与子任务闭环 | 当前没有基于 transcript 的执行图、handoff、sub-agent 展示 | 对齐 ClawX 的任务执行可视化能力 | -| Gateway 生命周期治理 | 当前 gateway 只是主进程 service,没有 OpenClaw 进程治理、健康检查、重连策略 | 对齐 ClawX 的启动、重启、健康、孤儿进程治理逻辑 | +| OpenClaw runtime 打包 | 还没有 `openclaw` / `uv` / resource bundling | 像 ClawX 一样由主进程打包并启动 | +| Gateway 生命周期治理 | 当前仍是 in-process manager | 对齐 `start / stop / restart / health / supervisor` | +| Host API 路由层 | 刚开始拆分,本地路由还不是完整体系 | 对齐 `ClawX/electron/api/routes/*` 风格 | +| Session Transcript API | 刚开始补 `/api/sessions/transcript` | 对齐 transcript 读取、删除、后续执行图基础 | +| Agent/Session 主会话映射 | 仍未有完整 `agents` 本地接口 | 对齐 `@agent` 路由与主会话映射 | +| 执行图 / 子任务展示 | 尚未接入 UI | 对齐 `ClawX` 的 task visualizer / transcript tree | -## 4. 推荐目标架构 +## 3. ClawX 最小闭环对齐点 + +当前不需要一次搬运整个 `ClawX`。根据已完成分析,最值得先对齐的是这 5 个契约: + +1. `hostApiFetch` 所依赖的本地 `Host API` transport 契约。 +2. `gateway.status / gateway.health / gateway.ready` 生命周期契约。 +3. `sessions.list / sessions.delete / sessions.transcript` 会话目录与 transcript 契约。 +4. `chat.history` 历史回灌契约。 +5. `chat.send / chat.abort / chat:delta / chat:final / chat:error` 流式发送契约。 + +对应的 `ClawX` 关键参考文件: + +- `ClawX/electron/api/routes/gateway.ts` +- `ClawX/electron/api/routes/sessions.ts` +- `ClawX/electron/gateway/manager.ts` +- `ClawX/electron/gateway/process-launcher.ts` +- `ClawX/src/lib/host-api.ts` +- `ClawX/src/stores/chat.ts` + +当前建议只迁“最小闭环”,不一次性搬全: + +- 先做 `gateway-info / gateway-status / sessions-transcript / stage-buffer`。 +- 再做 `OpenClaw runtime` 打包与进程托管。 +- 最后才做 `Agent Routing / Execution Graph / Control UI / Doctor`。 + +## 4. 目标架构 ```text -Vue Renderer +React Renderer + ├─ src/pages/Home/index.tsx ├─ src/stores/chat.ts + ├─ src/stores/task.ts + ├─ src/stores/channel.ts ├─ src/lib/gateway-client.ts └─ src/lib/host-api.ts │ - │ IPC + Host API + │ IPC + Local Host API ▼ Electron Main - ├─ GatewayManager (OpenClaw process owner) - ├─ Host API Server (/api/gateway /api/providers /api/agents /api/sessions ...) - ├─ Provider/Agent/Session sync layer - └─ file stage / upload / local config bridge + ├─ GatewayManager (过渡期 in-process,目标是 OpenClaw process owner) + ├─ Host API Router (/api/providers /api/gateway /api/sessions /api/files ...) + ├─ Provider / Agent / Session config sync + └─ Runtime / packaging / paths │ │ utilityProcess + ~/.openclaw ▼ @@ -66,164 +93,135 @@ OpenClaw Gateway ├─ providers / auth profiles ├─ agents / sessions / transcripts ├─ tool use / thinking / attachments - └─ channel / cron / skills / model routing + └─ sub-agent / execution graph / control ui ``` -### 架构原则 +## 5. 分阶段迁移计划 -1. Renderer 尽量不直接感知 OpenClaw 进程细节,只认 `gateway:rpc` 和 `hostApiFetch`。 -2. `zn-ai` 已有聊天 UI 保留,主要做协议对齐和能力补全,不追求把 React 页面逐组件翻译成 Vue。 -3. `providerApiService` 在过渡期作为配置真源,后续逐步演进为 “zn-ai 配置面板 <-> OpenClaw runtime config” 的双向同步桥。 -4. 第一阶段只聚焦“对齐 ClawX 对话能力”,不把 `ClawHub`、完整 Channel UI、完整 Skills UI 一次性并入首个里程碑。 - -## 5. 目标对齐范围 - -### 5.1 P0:必须对齐的对话核心能力 - -- 多会话创建、切换、历史加载、删除、重命名 -- 流式输出、Markdown 渲染、错误恢复、Abort -- thinking 展示开关 -- 工具调用状态与工具结果归并 -- 图片/文件附件发送与展示 -- 默认模型选择生效 -- 主进程 Gateway 自动启动、状态展示、重启 - -### 5.2 P1:推荐在首轮迁移后立即补齐 - -- `@agent` 直接路由到目标 Agent 主会话 -- Agent 级 provider/model override -- Session Transcript API -- 执行图卡片与子任务可视化 -- 历史轮询与 history fallback 逻辑对齐 - -### 5.3 P2:对话增强能力 - -- sub-agent transcript 树展示 -- channel 会话历史与对话页面联动 -- OpenClaw doctor / Control UI / 调试入口 -- 更完整的 tool_result 文件回灌与 message-ref 展示 - -## 6. 分阶段迁移计划 - -| 阶段 | 目标 | 主要改动路径 | 退出标准 | +| 阶段 | 状态 | 目标 | 主要文件 | | --- | --- | --- | --- | -| Phase 0:契约冻结 | 冻结 Chat/Gateway/Session/Agent 的目标契约,避免边做边漂移 | 新增本计划文档;整理 `gateway:rpc`、`GatewayEvent`、`RawMessage`、`sessionKey` 约定 | 所有 sub-agent 使用同一套消息模型与接口名 | -| Phase 1:OpenClaw 运行时嵌入 | 在 `zn-ai` 中打进 `openclaw`、`uv`、必要运行时,能由主进程拉起 Gateway | `zn-ai/package.json` `zn-ai/scripts/*` `zn-ai/electron/gateway/**` `zn-ai/electron/utils/paths.ts` | 开发态与打包态都能启动 OpenClaw Gateway,并能返回 health/status | -| Phase 2:Host API 与配置同步 | 把 Provider/Agent/Session/Gateway 相关接口统一落到本地主进程;建立 `zn-ai -> ~/.openclaw` 配置同步 | `zn-ai/electron/main.ts` `zn-ai/electron/api/**` `zn-ai/electron/service/provider-api-service/**` | Renderer 可从本地拉到 gateway-info、providers、agents、sessions、transcripts | -| Phase 3:Renderer Chat Store 对齐 | 保留 Vue UI,改造 chat store 与 OpenClaw 事件/历史契约对齐 | `zn-ai/src/stores/chat.ts` `zn-ai/src/lib/gateway-client.ts` `zn-ai/src/lib/host-api.ts` `zn-ai/src/pages/home/model/ChatModel.ts` | 流式聊天、历史回灌、Abort、错误恢复、附件发送全链路稳定 | -| Phase 4:对话 UI 对齐 | 补齐当前 Agent、thinking、tool cards、session sidebar、gateway 状态等体验 | `zn-ai/src/pages/home/ChatBox.vue` `zn-ai/src/pages/home/ChatHistory.vue` `zn-ai/src/pages/home/components/chat/*` | 视觉与交互能力对齐 ClawX 聊天页的核心功能 | -| Phase 5:Agent 直达与执行图 | 接入 Agent 主会话映射、transcript 读取、执行图、sub-agent 展示 | `zn-ai/electron/api/routes/sessions.ts` `zn-ai/src/pages/home/components/*` 新增执行图组件 | 至少支持一个子任务 transcript 展示与执行过程可视化 | -| Phase 6:验收与回归 | 做端到端验收与回归检查,避免对脚本、任务、模型管理页面造成回归 | `zn-ai` 测试脚本、调试脚本、文档 | 通过验收清单,具备对外演示条件 | +| Phase 0:React 基线收口 | 已完成 | 让聊天页、任务中心、渠道入口都运行在 React 主链路 | `src/App.tsx` `src/router/index.tsx` `src/pages/Home/index.tsx` | +| Phase 1:Host API 路由层重构 | 进行中 | 把本地 Provider/Gateway/Sessions/Files 从 `electron/main.ts` 中拆成独立 Host API 路由 | `electron/api/*` `electron/main.ts` | +| Phase 2:OpenClaw Runtime/Packaging | 待开始 | 引入 `openclaw`、`uv`、runtime path 与 dev/package 双态启动能力 | `package.json` `scripts/*` `electron/utils/paths.ts` `electron/gateway/*` | +| Phase 3:Gateway Lifecycle + Config Sync | 待开始 | 让 `GatewayManager` 从 in-process 过渡到 OpenClaw 进程 owner,并同步 provider/agent/session 配置 | `electron/gateway/*` `electron/service/provider-api-service/*` | +| Phase 4:React Chat Store 对齐 | 待开始 | 让 React Chat Store 对齐 ClawX 的 session/history/send/abort/transcript 契约 | `src/stores/chat.ts` `src/lib/host-api.ts` `src/lib/gateway-client.ts` | +| Phase 5:Agent Routing + Execution Graph | 待开始 | 接入 `@agent` 主会话映射、transcript 读取、执行图和子任务展示 | `src/pages/Home/*` `src/components/chat/*` `electron/api/routes/sessions.ts` | +| Phase 6:验收与收口 | 待开始 | 做 E2E 与回归,清理过渡实现,准备进入 OpenClaw 主架构 | 文档、测试、构建脚本 | + +## 6. 第一批已开工内容 + +这轮已经开始落地的文件: + +- `electron/api/router.ts` +- `electron/api/context.ts` +- `electron/api/route-utils.ts` +- `electron/api/routes/providers.ts` +- `electron/api/routes/gateway.ts` +- `electron/api/routes/files.ts` +- `electron/api/routes/sessions.ts` +- `electron/gateway/manager.ts` +- `electron/main.ts` + +当前已完成的第一批能力: + +- 本地 `Host API` 路由分发骨架已经建立。 +- `Provider` 本地接口已从 `electron/main.ts` 的大 switch 逻辑中拆分。 +- 新增 `Gateway` 本地路由:`/api/app/gateway-info`、`/api/gateway/status`、`/api/gateway/health`。 +- 新增 `Sessions` 本地路由:`/api/sessions/transcript`、`/api/sessions/delete`。 +- 新增 `Files` 本地路由:`/api/files/stage-buffer`、`/api/files/stage-paths`。 +- `GatewayManager` 已补充最小 `status / health / start / stop / restart` 接口,作为向 `OpenClaw process owner` 演进的过渡层。 ## 7. sub-agent 数量估算 ### 7.1 推荐编制 -- 分析集成 sub-agent:`2` -- 功能迁移 sub-agent:`5` +- 分析 sub-agent:`2` +- 迁移开发 sub-agent:`5` - 集成验收 sub-agent:`1` - 推荐总数:`8` ### 7.2 最小可行编制 -- 分析集成 sub-agent:`2` -- 功能迁移 sub-agent:`4` -- 集成验收由主协调 agent 兼任 +- 分析 sub-agent:`2` +- 迁移开发 sub-agent:`4` +- 验收由主协调 agent 兼任 - 最小总数:`6` -### 7.3 为什么推荐 2 + 5 + 1 +### 7.3 为什么推荐 8 个 -- `ClawX` 与 `zn-ai` 的主要差距横跨“打包、主进程、Host API、配置同步、Renderer 状态机、UI、转录/执行图”七个面。 -- 其中 `OpenClaw runtime 嵌入` 和 `Renderer 对话能力对齐` 之间依赖明确,但文件改动面基本可分离,适合并行。 -- 如果迁移 sub-agent 少于 `4`,会出现“主进程迁移完成但 Renderer 无法及时接入”或“UI 已变更但 transcript/API 尚未可用”的串行瓶颈。 +- `React` 技术栈迁移已经完成,所以不再需要专门的“Vue/React 双栈迁移” sub-agent。 +- 但 `OpenClaw` 对齐仍横跨 `Runtime Packaging`、`Gateway Lifecycle`、`Host API`、`Config Sync`、`React Chat Store`、`Transcript / Execution Graph` 六条线。 +- 如果迁移 sub-agent 少于 `5`,很容易出现“主进程基础已经就绪,但 React 聊天页迟迟接不上”或“Transcript API 已有但 UI 不可见”的串行瓶颈。 -## 8. 功能迁移 sub-agent 分工方案 +## 8. sub-agent 分工与开工安排 -| 角色 | 数量 | 负责范围 | 建议文件所有权 | +### 8.1 分工方案 + +| 角色 | 数量 | 负责范围 | 文件所有权 | | --- | --- | --- | --- | -| 分析 A1:ClawX/OpenClaw 契约分析 | 1 | 梳理 ClawX 的 OpenClaw runtime、Gateway、Host API、Session Transcript 契约 | 只读分析,不改文件 | -| 分析 A2:zn-ai 落点映射 | 1 | 梳理 `zn-ai` 当前可复用模块、差距、替换顺序 | 只读分析,不改文件 | -| 迁移 M1:Runtime/Packaging | 1 | 嵌入 `openclaw`、迁移启动脚本、补 `paths`、补打包资源 | `package.json` `scripts/*` `electron/gateway/**` `electron/utils/paths.ts` | -| 迁移 M2:Host API/Config Sync | 1 | 新建/迁移 Host API Server、Gateway routes、Provider/Agent/Session 同步 | `electron/main.ts` `electron/api/**` `electron/service/provider-api-service/**` | -| 迁移 M3:Renderer 协议与 Chat Store | 1 | 让 Vue chat store 对齐 ClawX 的 `chat.send/history/abort/session.list` 与事件状态机 | `src/stores/chat.ts` `src/lib/gateway-client.ts` `src/lib/host-api.ts` `src/pages/home/model/ChatModel.ts` | -| 迁移 M4:聊天 UI/侧栏/输入框 | 1 | 侧栏会话管理、Agent picker、gateway 状态、thinking/tool/file 展示对齐 | `src/pages/home/ChatBox.vue` `src/pages/home/ChatHistory.vue` `src/pages/home/components/chat/*` | -| 迁移 M5:Transcript/Execution Graph | 1 | transcript API、执行图、sub-agent 展示、验收脚本 | `electron/api/routes/sessions.ts` 新增执行图组件与测试/调试脚本 | -| 验收 I1:集成收口 | 1 | 联调、回归、验收 checklist、发布说明 | 测试与文档,不抢占前面文件所有权 | +| A1:ClawX/OpenClaw 契约分析 | 1 | 提炼 ClawX 的最小闭环契约、关键文件与最小迁移子集 | 只读分析 | +| A2:zn-ai 差距映射 | 1 | 盘点当前 React/Gateway/Host API 的实际状态与缺口 | 只读分析 | +| M1:Host API / Gateway Foundation | 1 | 拆本地 Host API 路由,补 Gateway info/status/health,建立本地接口框架 | `electron/api/*` `electron/main.ts` `electron/gateway/manager.ts` | +| M2:OpenClaw Runtime / Packaging | 1 | 迁移 `openclaw` 打包、`uv`、runtime 路径和 dev/package 启动链路 | `package.json` `scripts/*` `electron/utils/paths.ts` `electron/gateway/*` | +| M3:Config Sync / Providers / Agents / Sessions | 1 | 把 provider 配置逐步收敛成 OpenClaw 可消费格式,并补本地 agent/session 接口 | `electron/service/provider-api-service/*` `electron/api/routes/*` | +| M4:React Chat Store / History / Send | 1 | 对齐 `chat.send/history/abort/session.list` 与 transcript fallback | `src/stores/chat.ts` `src/lib/host-api.ts` `src/lib/gateway-client.ts` | +| M5:React Chat UI / Agent Routing / Execution Graph | 1 | 接 `@agent`、thinking/tool cards、transcript 展示与执行图入口 | `src/pages/Home/*` `src/components/chat/*` | +| I1:集成验收与收口 | 1 | 联调、回归、验收 checklist、发布说明 | 测试与文档 | -### 8.1 并行施工波次 +### 8.2 当前已启动的角色 -| 波次 | 并行 sub-agent | 说明 | +| 角色 | 当前状态 | 备注 | | --- | --- | --- | -| Wave 1 | A1 + A2 | 只读分析,同步冻结契约与边界 | -| Wave 2 | M1 + M2 | 一边搭 OpenClaw runtime,一边准备 Host API 与配置同步 | -| Wave 3 | M3 + M4 | Runtime 和 API 基础稳定后,同时推进 store 与 UI | -| Wave 4 | M5 + I1 | 接入 transcript/执行图,并完成端到端验收 | +| A1 | 已启动并完成首轮分析 | 已明确最小闭环是 `transport -> gateway lifecycle -> sessions -> history -> send/stream` | +| A2 | 已启动但中途断流 | 不阻塞主线,已由主协调 agent 接管现状映射 | +| M1 | 已启动 | 第一批 `Host API` 路由骨架已落地 | +| 主协调 agent | 已启动 | 正在更新总计划文档并推进 Phase 1 基建 | -### 8.2 依赖关系 +### 8.3 建议并行波次 -1. `M3` 依赖 `M2` 提供稳定的 Host API/Gateway 契约。 -2. `M4` 依赖 `M3` 暴露真实的 Agent/session/tool/thinking 状态。 -3. `M5` 依赖 `M2` 的 session/transcript API 和 `M3` 的消息模型归一。 -4. `I1` 在 `M1-M5` 基本完成后统一收口。 - -## 9. 推荐实施细节 - -### 9.1 优先保留的 zn-ai 资产 - -- `src/stores/chat.ts` -- `src/pages/home/model/ChatModel.ts` -- `src/pages/home/components/chat/ChatMessage.vue` -- `src/pages/home/ChatHistory.vue` -- `src/lib/gateway-client.ts` -- `electron/service/provider-api-service/index.ts` - -这些模块的价值在于: - -- 已经具备较成熟的流式状态机,不需要重新从零写一套 Vue Chat Store。 -- 已经定义了与 ClawX 接近的结构化消息模型。 -- 已经把 Provider 配置和默认模型选择本地化,可以作为 OpenClaw 配置同步源。 - -### 9.2 建议替换或重构的部分 - -- 当前 `electron/gateway/manager.ts` 及 `handlers/chat.ts` -- 当前 `hostapi:fetch` 里“远端后端代理优先”的混合职责 -- 当前 `local:{accountId}:{uuid}` 会话键体系 -- 当前只做占位的 Agent mention 逻辑 - -### 9.3 首个里程碑不建议纳入的范围 - -- ClawHub 全量迁移 -- Skills 页面全量迁移 -- Channels 页面全量迁移 -- Cron 与对话的深度联动 - -原因是这几个模块都依赖 OpenClaw runtime 完整稳定,但不属于“先对齐 ClawX 对话能力”的最小闭环。 - -## 10. 风险与决策点 - -| 风险 | 说明 | 建议 | +| 波次 | 并行角色 | 目标 | | --- | --- | --- | -| 远端 Host API 与本地 Host API 混用 | 迁移期间容易出现一部分接口走远端、一部分走本地,导致行为不一致 | 明确约定:OpenClaw 相关接口全部本地化,其余业务接口保留远端 | -| Session key 不统一 | `local:*` 与 `agent:*` 并存会导致历史、侧栏、Agent 路由混乱 | Phase 0 就冻结统一命名规则 | -| Provider 配置双写 | `zn-ai` 本地配置与 `~/.openclaw` 运行时配置如果不同步,会出现“设置页一套、对话页一套” | Phase 2 建立单向真源和同步策略 | -| Vue/React 页面一比一翻译冲动 | 会导致迁移周期失控 | 只对齐能力,不对齐组件实现细节 | -| 打包态差异 | `OpenClaw` 在开发态和打包态的路径、资源、uv/node 依赖不同 | M1 单独负责 dev/package 双态验证 | +| Wave 1 | A1 + A2 + 主协调 agent | 冻结契约与当前状态,确认迁移边界 | +| Wave 2 | M1 + M2 | 一边拆 Host API,一边准备 OpenClaw runtime 打包与路径层 | +| Wave 3 | M3 + M4 | 配置同步与 React Chat Store 并行接入 | +| Wave 4 | M5 + I1 | 执行图 / Agent Routing / UI 收口与联调验收 | -## 11. 完成验收标准 +## 9. 接下来的直接开发任务 -- `zn-ai` 可以在开发态和打包态拉起 OpenClaw Gateway。 -- 聊天页默认使用本地默认模型配置,且修改默认模型后下一轮对话生效。 -- 会话列表支持新建、切换、加载、删除、重命名。 -- 聊天页支持流式文本、Markdown、thinking、tool cards、图片/文件附件。 -- `Abort`、错误恢复、历史回灌稳定可用。 -- `@agent` 能把消息送到目标 Agent 主会话,而不是仅显示占位 chip。 -- 至少一个子任务 transcript 能被加载并展示为执行图或子任务明细。 -- 不影响 `Scripts`、`Tasks`、模型管理页的现有主流程。 +按优先级建议这样推进: -## 12. 与现有文档的关系 +1. 完成 `Phase 1`: + - 继续把 `hostapi:fetch` 中本地可处理接口迁入 `electron/api/routes/*`。 + - 明确 `gateway-info` 与 `session-transcript` 的返回结构,冻结给前端使用。 +2. 启动 `Phase 2`: + - 新增 `electron/utils/paths.ts`,对齐 `ClawX` 的 runtime 路径管理。 + - 迁移 `openclaw` bundling 脚本与 `uv` 下载脚本。 +3. 启动 `Phase 3`: + - 增加 `agents` 本地接口与 `main session` 映射。 + - 让当前 `DEFAULT_AGENT_ID / DEFAULT_SESSION_KEY` 不再硬编码。 +4. 启动 `Phase 4`: + - 让 React Chat Store 能读取 `Host API` 的 `gateway-info` 与 `session-transcript`。 + - 把当前 history/session 逻辑从“本地过渡实现”逐步切向 `OpenClaw` 兼容契约。 + +## 10. 风险与约束 + +| 风险 | 说明 | 当前策略 | +| --- | --- | --- | +| Host API 本地/远端混用 | 迁移期容易出现一部分接口本地、一部分接口远端,行为不一致 | 所有 `OpenClaw` 相关接口逐步收敛到本地 Host API,业务后台接口继续远端代理 | +| Gateway 仍是 in-process | 当前还不是真正的 OpenClaw 进程托管 | 先补生命周期接口与路由层,再替换运行时 | +| Agent 路由尚未真实接入 | 当前 `sessionKey` 虽是 `agent:*` 风格,但主会话映射仍不完整 | 放在 M3 / M5 联合推进 | +| Transcript 能力有后端无前端 | 当前先补 Host API 路由,不代表 UI 已展示 | 通过 M4 / M5 分批接前端 | +| 仓库已有 TS 历史债 | 当前 `tsc` 仍存在一批既有错误 | 先以构建通过和新增链路可用为准,不在本轮一口气清旧债 | + +## 11. 当前验收口径 + +当前这一轮不以“OpenClaw 全部接通”为验收,而是以以下条件作为 Phase 1 开工完成标准: + +- `zn-ai` 仍能正常构建。 +- 本地 `Host API` 路由骨架已经独立存在,不再全部堆在 `electron/main.ts`。 +- 已有 `gateway-info / gateway-status / session-transcript / file-stage` 这类 `ClawX` 风格本地路由。 +- 文档中的 sub-agent 分工和实际代码开工状态一致。 + +本轮之后,正式进入 `Phase 2-4` 的并行迁移。 -- `docs/model-chat-migration-plan.md`:更偏“模型对话重构”的早期版本,可继续作为 Renderer/Gateway 迁移参考。 -- `docs/ChatPageMigrationPlan.md`:更偏聊天页面结构与 UI 迁移。 -- `docs/agents.md`:更偏 Agent 系统分析。 -本文件是上述文档的 **OpenClaw 对话能力总计划整合版**,适合作为后续 sub-agent 排活与里程碑跟踪的主文档。 diff --git a/docs/Vue-Exit-Checklist.md b/docs/Vue-Exit-Checklist.md deleted file mode 100644 index 2aa5762..0000000 --- a/docs/Vue-Exit-Checklist.md +++ /dev/null @@ -1,106 +0,0 @@ -# zn-ai Vue Exit Checklist - -Goal: move `zn-ai` from "React renderer plus leftover Vue-era assets/config" to a truly `React-only` repo, while keeping build and typecheck green at each step. - -## Current Status - -- Renderer bootstrap is React-only. - - `index.html` - - `src/main.tsx` -- `src/**` is now the active React renderer tree and no longer reaches back into the removed Vue code. -- Electron/shared runtime no longer imports from `src/lib/*`, `src/shared/*`, or `src/i18n/locales/*`. - - active runtime callers now use `@runtime/* -> runtime-shared/*` -- Legacy compatibility/config cleanup is complete. - - removed `@vitejs/plugin-vue` - - removed `unplugin-auto-import` - - removed Vue module declarations from `global.d.ts` - - removed stale `src/**` path aliases from Vite and TypeScript - - moved `openapi-ts-request` output to `src/api` - - added `src/api/request.ts` as the React-side generated request bridge -- The old Vue tree is gone end-to-end. - - deleted `src/main-vue.ts` - - deleted `src/router/index.ts` - - deleted `src/permission.ts` - - deleted `src/App.vue` - - deleted the remaining `src/**` directory, including `src/main.ts` and `src/framework.ts` -- Vue-era runtime dependencies are gone from `package.json`. - - removed `vue` - - removed `pinia` - - removed `vue-router` - - removed `element-plus` - - removed `vue-i18n` - - removed `@remixicon/vue` - - removed `@lucide/vue` - - removed `vue-codemirror` - - removed `vue-markdown-render` - - removed `@vueuse/core` - -## What This Means - -- The old Vue runtime path is no longer present in the source tree. -- `src/**` is no longer a live runtime or config boundary. -- The remaining follow-up is mostly housekeeping, not migration-blocking runtime work. - - stale historical references in docs - - optional cleanup of `package-lock.json` if the repo standardizes on `pnpm` only - - the known bytecode warning when the local Electron binary is missing - -## File Plan - -### 1. `package.json` - -#### Done - -- removed the Vue-era runtime/UI dependencies from the manifest -- refreshed `pnpm-lock.yaml` - -### 2. `global.d.ts` - -#### Done - -- removed `declare module "@stores/*";` -- removed `declare module "@service/*";` -- removed `declare module "@utils/*";` -- removed `declare module "@constant/*";` -- removed `declare module "vue-router";` -- removed `declare module "*.vue"` -- removed `declare module "@remixicon/vue"` -- removed `declare module "@assets/images/*"` - -### 3. `tsconfig.app.json` - -#### Done - -- removed explicit include for `src/permission.ts` -- removed `include: "src/**/*.vue"` -- removed the remaining dead `src/**` path aliases - -### 4. `vite.config.ts` - -#### Done - -- active renderer build is React-only -- Electron shared runtime uses `@runtime/*` -- removed the legacy `src/**` alias surface from the Vite config - -## Recommended Execution Order - -1. Done: remove low-risk compatibility items from `package.json` and `global.d.ts` -2. Done: remove the dead Vue bootstrap chain -3. Done: remove the dead Vue page/component/store/request tree -4. Done: delete `src/main.ts`, `src/framework.ts`, and the remaining dead `src/**` files -5. Done: trim stale TypeScript/Vite/OpenAPI alias surface that pointed into deleted legacy areas -6. Done: remove the remaining Vue-era dependencies from `package.json` - -## Residual Notes - -- Keep `@runtime/* -> runtime-shared/*`, which is now the active Electron/shared-runtime boundary. -- `package-lock.json` still contains historical Vue-era entries, but the active workspace lockfile is `pnpm-lock.yaml`. - -## Next Gate - -Vue exit is effectively complete for source/runtime once all of the following remain true: - -1. no source file imports any Vue-era runtime package -2. `package.json` stays free of the Vue-era dependencies and `pnpm-lock.yaml` stays refreshed -3. Vite/TypeScript/OpenAPI config stays off `src/**` -4. `pnpm typecheck` and `pnpm build:vite` stay green diff --git a/docs/Vue-to-React-Replacement-Plan.md b/docs/Vue-to-React-Replacement-Plan.md deleted file mode 100644 index 7725b8d..0000000 --- a/docs/Vue-to-React-Replacement-Plan.md +++ /dev/null @@ -1,472 +0,0 @@ -# zn-ai 对齐 ClawX 的 Vue -> React 平替迁移计划 - -## 1. 目标与结论 - -- `zn-ai` 当前前端是完整的 `Vue 3 + Pinia + Vue Router + Element Plus + Tailwind` 体系,不是少量页面级 Vue 组件。 -- `ClawX` 当前前端是完整的 `React 19 + React Router + Zustand + Radix/shadcn + Tailwind` 体系。 -- 如果目标是“对齐 ClawX 使用 React”,建议迁移目标不只是“把 `.vue` 改成 `.tsx`”,而是 **整体对齐前端工程组织方式**: - - 路由层对齐 `React Router` - - 状态层对齐 `Zustand` - - 组件层逐步摆脱 `Element Plus` - - 页面层按业务模块分波次平替 -- 最终目标是 **React-only**:Vue 只允许作为短期过渡层存在,迁移完成后必须移除。 -- 推荐路径是 **短期双栈过渡、按路由/页面壳分批平替、迁完即收口**,不建议一次性大爆炸重写。 - -## 2. 当前现状 - -### 2.1 zn-ai 当前 Vue 技术栈 - -| 维度 | 当前实现 | 关键路径 | -| --- | --- | --- | -| 应用入口 | React-only 兼容引导(`src/main.ts -> src-react/main.tsx`) | `zn-ai/src/main.ts` | -| 根组件 | `App.tsx + HashRouter + AppRouter` | `zn-ai/src-react/App.tsx` | -| 路由 | `react-router-dom` | `zn-ai/src-react/router/index.tsx` | -| 状态管理 | `React stores + shared runtime modules` | `zn-ai/src-react/stores/*` | -| UI 组件体系 | `React 组件 + Tailwind` | `zn-ai/src-react/components/*` | -| 页面目录 | React 页面位于 `src-react/pages/*`,旧 Vue 页面树已移除 | `zn-ai/src-react/pages/*` | -| Electron 交互 | `window.api + hostapi:fetch + gateway:rpc` | `zn-ai/electron/*` `zn-ai/src/lib/*` | -| 样式 | `Tailwind v4 + 全局 CSS + Element Plus 主题` | `zn-ai/src/styles/*` | - -### 2.2 ClawX 当前 React 参照栈 - -| 维度 | ClawX 做法 | 关键路径 | -| --- | --- | --- | -| 应用入口 | `main.tsx` + `HashRouter` | `ClawX/src/main.tsx` | -| 根组件 | `App.tsx` + `Routes/Route` | `ClawX/src/App.tsx` | -| 布局 | `MainLayout + Sidebar + TitleBar` | `ClawX/src/components/layout/*` | -| 状态管理 | `Zustand` | `ClawX/src/stores/*` | -| UI 组件体系 | `Radix + 自建 ui 组件 + Tailwind` | `ClawX/src/components/ui/*` | -| 页面目录 | 按页面模块划分 | `ClawX/src/pages/*` | -| API/Gateway | `host-api.ts` / `gateway-client.ts` / typed stores | `ClawX/src/lib/*` | -| 样式 | `globals.css + CSS variables + Tailwind utilities` | `ClawX/src/styles/globals.css` | - -## 3. 迁移目标 - -### 3.1 技术目标 - -1. `zn-ai` Renderer 入口从 Vue 根应用切换为 React 根应用。 -2. 路由系统从 `vue-router` 切换为 `react-router-dom`。 -3. 核心状态逐步从 `Pinia` 迁到 `Zustand`。 -4. 页面与通用组件逐步从 `.vue` 平替为 `.tsx`。 -5. UI 层逐步减少 `Element Plus` 依赖,最终以 Tailwind + React 组件为主。 -6. Electron 主进程、preload、IPC、Host API 相关逻辑尽量保持不动,避免把“框架迁移”和“桌面能力迁移”绑死在同一波次。 - -### 3.2 业务目标 - -- 保持 `Home/Chat`、`Agents`、`Skills`、`Cron`、`Scripts`、`Setting`、`Login` 可持续可用。 -- 在迁移过程中不影响 Electron 打包、启动、窗口控制、IPC 调用。 -- React 平替后,后续功能开发可以直接复用 ClawX 的 React 组织方式与组件思想。 - -## 4. 推荐迁移策略 - -## 4.1 不推荐:大爆炸式重写 - -特点: - -- 一次性删除 Vue 根应用 -- 一次性切到 React -- 所有页面同步重写 - -问题: - -- 风险最高 -- 回归面最大 -- 业务停滞时间最长 -- 很难快速验证 Electron 集成链路是否稳定 - -## 4.2 推荐:短期双栈过渡、分波次平替 - -特点: - -- 在同一仓库临时同时支持 `@vitejs/plugin-vue` 和 `@vitejs/plugin-react` -- 先把 React 根应用、路由、布局、状态基建搭起来 -- 再按页面分批把 Vue 页面平替成 React 页面 -- React 接管默认入口后,立即进入 Vue 清退阶段 -- 最后删除 Vue 页面、Vue store、Vue Router、Element Plus 与 Vue 构建链 - -优点: - -- 风险可控 -- 可以边迁移边交付 -- 页面出现问题时回滚范围小 -- 更适合 `zn-ai` 这种 Electron 桌面应用 - -边界要求: - -- 双栈只用于迁移窗口期,不作为长期架构存在 -- Phase 2 之后默认 Renderer 入口必须是 React -- Phase 3 起禁止新增 Vue 页面、Vue store、Element Plus 依赖 -- Phase 5 必须完成 Vue 依赖与 Vue 代码清退,否则不算迁移完成 - -### 4.3 推荐迁移粒度 - -建议按这个粒度迁移: - -1. 应用壳:入口、路由、主布局、标题栏、侧边栏 -2. 核心页面:`Home/Chat` -3. 平台页面:`Agents`、`Setting` -4. 工具页面:`Skills`、`Cron`、`Scripts` -5. 低频或独立页面:`Knowledge`、`Login` -6. 最后移除 Vue 运行时与旧依赖 - -不建议按“单个小组件岛”零散迁移,因为: - -- `zn-ai` 的页面级状态和路由耦合很重 -- `Layout + SideMenus + 页面容器 + store` 是一整套 -- 组件岛太细会让 Vue/React 互相嵌套,维护复杂度更高 - -### 4.4 终态约束 - -最终态不是 “Vue/React 双栈长期共存”,而是: - -- Renderer 只保留 React 入口 -- 路由只保留 `react-router-dom` -- 状态层只保留 React 可用的 store 体系 -- UI 只保留当前视觉风格对应的 React 组件实现 -- `vue`、`vue-router`、`pinia`、`element-plus`、`@vitejs/plugin-vue` 从生产依赖与构建链中移除 - -## 5. 工程平替方案 - -### 5.1 目录策略 - -推荐迁移期间采用双目录: - -```text -src/ # 保留现有 Vue 代码 -src-react/ # 新增 React 代码 -``` - -或者更偏最终态的方式: - -```text -src/ - react/ - electron/ - shared/ -``` - -更推荐第一种,原因是: - -- 对当前工程侵入更小 -- 平替期间边界更清晰 -- 方便按页面对照迁移 - -但这个目录策略只服务于迁移期。最终收口时应执行以下动作之一: - -- 将 `src-react/` 合并回最终 Renderer 目录 -- 或者删除旧 `src/` 中仅服务 Vue 的前端代码,只保留共享与 Electron 代码 - -### 5.2 构建策略 - -建议分两步: - -1. 先在 `vite.config.ts` 里加入 `@vitejs/plugin-react` -2. 保留 `plugin-vue` 一段时间,直到最后一个 Vue 页面被移除 - -迁移初期目标不是删 Vue,而是: - -- 先让 React 页面可以在 Electron Renderer 中跑起来 -- 再让 React 尽快接手默认入口 - -构建收口要求: - -- 默认启动入口一旦切到 React,就不再新增任何 Vue 侧能力开发 -- 最后一阶段必须移除 `@vitejs/plugin-vue` -- 打包与开发脚本最终只保留 React 所需链路 - -### 5.3 状态管理策略 - -| 当前 | 目标 | 建议 | -| --- | --- | --- | -| `Pinia` | `Zustand` | 新 React 页面只写 Zustand,不再新增 Pinia store | -| 旧 Pinia store | 过渡兼容 | 迁移期间允许 React 通过桥接层复用部分现有逻辑 | -| 领域状态 | 切分迁移 | 优先迁移 `chat/providers/theme/locale/settings` | - -推荐做法: - -- 对每个核心领域新增 React 版 store -- 在短期内允许 Pinia 与 React store 并存 -- 页面迁走后,再删除对应 Pinia store -- 从 Phase 3 开始禁止新增 Pinia 代码 - -### 5.4 UI 组件策略 - -| 当前 | 目标 | 建议 | -| --- | --- | --- | -| `Element Plus` | `Tailwind + React 组件` | 不直接一比一平替 Element Plus 组件 API | -| `Vue 单文件组件` | `TSX 组件` | 先迁页面壳,再抽通用组件 | -| Tailwind 样式 | 保留 | 继续沿用颜色、间距、布局 token | - -建议: - -- 不要把 `Element Plus` 在 React 中“硬套”一层兼容壳 -- 更适合借鉴 ClawX 的做法,逐步建设 `src-react/components/ui/*` -- 先做基础组件:Button、Input、Dialog、Tabs、Tooltip、Dropdown、Toast - -### 5.5 共享能力策略 - -以下模块尽量不重写,只做前端适配: - -- `electron/main.ts` -- `electron/preload/*` -- `electron/gateway/*` -- `src/lib/host-api.ts` 的底层契约 -- `src/lib/gateway-client.ts` 的底层契约 -- 绝大部分 `electron/service/*` - -换句话说: - -- 框架迁移主要发生在 Renderer -- 主进程和 Electron 基础设施尽量保持稳定 - -## 6. 页面迁移优先级 - -### 6.1 P0:先迁基础壳 - -- `src/main.ts` -- `src-react/main.tsx` -- `src-react/App.tsx` -- `src-react/router/*` -- `src-react/components/layout/MainLayout.tsx` -- `src-react/components/layout/Sidebar.tsx` -- `src-react/components/layout/TitleBar.tsx` - -原因: - -- 这是 React 页面承接的运行底座 -- 不先迁应用壳,后面页面只能继续挂在 Vue 下 - -### 6.2 P1:高价值页面 - -- `src/pages/home/index.vue` -- `src/pages/home/ChatBox.vue` -- `src/pages/home/ChatHistory.vue` -- `src/pages/home/components/chat/*` -- `src/stores/chat.ts` -- `src/stores/providers.ts` - -原因: - -- 首页/对话页是使用频率最高的模块 -- 同时它也是最能直接复用 ClawX React 经验的地方 - -### 6.3 P2:管理页面 - -- `src/pages/agents/index.vue` -- `src/pages/setting/index.vue` -- `src/pages/skills/index.vue` -- `src/pages/cron/index.vue` -- `src/pages/scripts/index.vue` - -### 6.4 P3:低频与收尾页面 - -- `src/pages/knowledge/index.vue` -- `src/pages/login/index.vue` -- 零散公用组件 -- Vue/Pinia/Element Plus 清理 - -## 7. 分阶段实施计划 - -| 阶段 | 目标 | 主要产出 | 退出标准 | -| --- | --- | --- | --- | -| Phase 0 | 冻结迁移边界 | 迁移文档、路由清单、页面资产清单、Vue 退场清单 | 团队对范围、命名、目录、状态策略与 Vue 退场标准达成一致 | -| Phase 1 | 建短期双栈基建 | React 依赖、React 入口、临时双栈 Vite 配置 | Electron 中可启动 React 根页面 | -| Phase 2 | React 接管应用壳 | React `App`、React Router、MainLayout、Sidebar、TitleBar、默认入口切换 | Vue 根壳退出默认路径,React 成为默认 Renderer | -| Phase 3 | 平替核心页 | Home/Chat 与核心 store 迁到 React | 主路径用户完全走 React,Vue 不再承接核心流程 | -| Phase 4 | 平替剩余页面 | Agents/Setting/Skills/Cron/Scripts/Knowledge/Login 迁到 React | 所有用户可见主页面全部脱离 Vue | -| Phase 5 | Vue 退场与依赖清理 | 删除 `.vue` 页面、Pinia/Vue Router/Element Plus 依赖与构建链 | 项目代码库不再依赖 Vue 运行时与 Vue 构建插件 | -| Phase 6 | 回归与交付 | 回归测试、打包验证、文档更新 | Dev/Build/Package 全链路稳定,React-only 终态成立 | - -## 8. sub-agent 数量估算 - -### 8.1 标准推荐编制 - -- 架构收口 sub-agent:`1` -- 功能迁移 sub-agent:`5` -- Vue 清退 sub-agent:`1` -- 集成验收 sub-agent:`1` -- 标准总数:`8` - -这是最适合当前 `zn-ai` 体量的推荐方案,能覆盖: - -- React-only 架构冻结 -- 应用壳迁移 -- 状态与通信层迁移 -- 视觉样式与通用组件平替 -- 高复杂页面迁移 -- Vue 退场 -- 联调验收 - -### 8.2 扩展并行编制 - -- 架构收口 sub-agent:`1` -- 功能迁移 sub-agent:`6` -- Vue 清退 sub-agent:`1` -- 集成验收 sub-agent:`1` -- 扩展总数:`9` - -适用于你希望更快并行推进、并把页面迁移与 Vue 清退准备拆得更细的情况。 - -### 8.3 最小可行编制 - -- 架构收口 sub-agent:`1` -- 功能迁移 sub-agent:`4` -- Vue 清退与集成验收由主协调 agent 兼任 -- 最小总数:`5` - -这个方案能做,但节奏会更紧,回归与清尾风险更高。 - -### 8.4 为什么比“只迁聊天页”需要更多 sub-agent - -这次不是单一页面迁移,而是: - -- 前端框架替换 -- 路由替换 -- 状态管理替换 -- UI 体系替换 -- 页面级别平替 -- Electron Renderer 构建链调整 - -因此复杂度明显高于单一业务模块迁移。 - -## 9. 推荐 sub-agent 分工 - -### 9.1 标准 8-agent 分工 - -| 角色 | 数量 | 负责范围 | 建议文件所有权 | -| --- | --- | --- | --- | -| A1:React-only 架构收口 | 1 | 冻结迁移边界、禁止新增 Vue 扩面、维护 Vue 退场清单 | 只读分析,不改文件 | -| M1:构建与入口切换 | 1 | React 依赖、Vite、Renderer 入口切换、保留短期双栈兜底 | `package.json` `vite.config.ts` `tsconfig*` `src/main.ts` `src/framework.ts` | -| M2:应用壳与路由 | 1 | React `App`、React Router、布局、标题栏、侧栏,并保持当前视觉风格 | `src-react/App.tsx` `src-react/router/*` `src-react/components/layout/*` | -| M3:共享基础设施 | 1 | i18n、theme、host-api、gateway-client、window api 类型桥接 | `src-react/lib/*` `src-react/stores/*` `src-react/i18n/*` `src-react/types/*` | -| M4:Home/Chat 主链路 | 1 | Home、Chat、ChatHistory、聊天 store、对话组件,保持当前 UI 布局与交互 | `src-react/pages/Home/*` `src-react/components/chat/*` `src-react/stores/chat/*` | -| M5:非聊天页面平替 | 1 | Agents、Setting、Skills、Cron、Scripts、Knowledge、Login 等页面平替,视觉沿用当前实现 | `src-react/pages/Agents/*` `src-react/pages/Setting/*` `src-react/pages/Skills/*` `src-react/pages/Cron/*` `src-react/pages/Scripts/*` `src-react/pages/Knowledge/*` `src-react/pages/Login/*` | -| C1:Vue 清退与依赖下线 | 1 | 删除 `.vue` 页面、Pinia/Vue Router/Element Plus、清理 Vue 构建链与过渡文件 | `src/*.vue` `src/router/*` `src/stores/*` `vite.config.ts` `package.json` | -| I1:联调与验收 | 1 | 回归、打包验证、迁移文档回填、React-only 验收 | 测试脚本、验收记录、迁移记录 | - -### 9.2 扩展 9-agent 分工 - -如果采用扩展并行编制,可以把标准方案里的 `M5` 再拆成两组: - -- 迁移 M5:平台与设置页 -- 迁移 M6:技能与工具页 - -这样页面并行度更高,更适合在 React 接管默认入口后快速清空剩余 Vue 页面。 - -### 9.3 当前阶段继续执行的推荐编组 - -如果从当前阶段继续往下推进,推荐立即投入这 `4` 个执行向 sub-agent,再由主协调 agent 持续整合: - -- `Gibbs`:负责 `M1`,继续把默认入口、构建配置和 Vue 兜底边界收紧,禁止双栈长期化。 -- `Mendel`:负责 `M2 + M5`,优先把应用壳、侧栏、标题栏和非聊天页面按当前视觉样式迁到 React。 -- `Dalton`:负责 `M3`,继续打通 React 侧 i18n、theme、host-api、gateway-client 与 settings store。 -- 新增 `C1/M4` 组合 worker:优先迁 `Home/Chat` 主链路,并同步维护 Vue 退场清单,避免聊天页迁完后又回头返工。 - -## 10. 推荐并行波次 - -| 波次 | 并行 sub-agent | 说明 | -| --- | --- | --- | -| Wave 1 | A1 | 先冻结 React-only 边界、Vue 退场标准与视觉复用策略 | -| Wave 2 | M1 + M2 + M3 | 一边搭建 React 运行底座,一边准备共享桥接层,并让 React 接手默认入口 | -| Wave 3 | M4 + M5 | 核心页与剩余页面并行平替,要求沿用当前视觉样式 | -| Wave 4 | C1 + I1 | 集中执行 Vue 清退、构建链收口、联调与验收 | - -如果采用扩展 9-agent 方案,则 `Wave 3` 可改为: - -- `M4 + M5 + M6` 并行 -- `C1` 提前介入维护 Vue 删除清单 -- `I1` 提前介入做持续验收 - -## 11. 文件级拆分建议 - -### 11.1 M1:构建与短期双栈基建 - -- `zn-ai/package.json` -- `zn-ai/vite.config.ts` -- `zn-ai/tsconfig.app.json` -- `zn-ai/tsconfig.json` - -### 11.2 M2:应用壳与路由 - -- `zn-ai/src-react/main.tsx` -- `zn-ai/src-react/App.tsx` -- `zn-ai/src-react/router/*` -- `zn-ai/src-react/components/layout/*` - -### 11.3 M3:共享基础设施 - -- `zn-ai/src-react/lib/host-api.ts` -- `zn-ai/src-react/lib/gateway-client.ts` -- `zn-ai/src-react/lib/*` -- `zn-ai/src-react/i18n/*` -- `zn-ai/src-react/stores/settings.ts` - -### 11.4 M4:Home/Chat - -- `zn-ai/src-react/pages/Home/*` -- `zn-ai/src-react/components/chat/*` -- `zn-ai/src-react/stores/chat/*` - -### 11.5 M5:非聊天页面平替 - -- `zn-ai/src-react/pages/Agents/*` -- `zn-ai/src-react/pages/Setting/*` -- `zn-ai/src-react/pages/Login/*` -- `zn-ai/src-react/pages/Skills/*` -- `zn-ai/src-react/pages/Cron/*` -- `zn-ai/src-react/pages/Scripts/*` -- `zn-ai/src-react/pages/Knowledge/*` - -### 11.6 C1:Vue 清退与依赖下线 - -- 已删除:`zn-ai/src/main-vue.ts` -- 已删除:`zn-ai/src/router/index.ts` -- 已删除:`zn-ai/src/permission.ts` -- 已删除:`zn-ai/src/App.vue` -- `zn-ai/src/stores/*` -- `zn-ai/src/components/**/*.vue` -- `zn-ai/src/pages/**/*.vue` -- `zn-ai/package.json` -- `zn-ai/vite.config.ts` - -### 11.7 扩展方案 M6:技能与工具页 - -- `zn-ai/src-react/pages/Skills/*` -- `zn-ai/src-react/pages/Cron/*` -- `zn-ai/src-react/pages/Scripts/*` -- `zn-ai/src-react/pages/Knowledge/*` - -## 12. 主要风险 - -| 风险 | 说明 | 建议 | -| --- | --- | --- | -| Vue/React 双栈期间复杂度上升 | 两套路由、样式、状态会短期并存 | 严格限定过渡期目录与职责边界,并对双栈设置明确退出时间点 | -| Element Plus 替换成本被低估 | 不是只替换组件,还会影响交互与样式结构 | 先迁页面壳和逻辑,再逐步建设 React UI 库 | -| Pinia 逻辑复制导致状态漂移 | 同一业务状态在 Vue/React 两边各维护一套容易分叉 | 核心领域先做桥接层,明确单一真源 | -| 页面迁移顺序不当 | 先迁低价值页会拉长工期,主路径收益低 | 先壳后首页,再平台页 | -| 打包链路回归 | Electron Renderer 切换入口后容易影响 dev/build/package | M1 和 I1 都要覆盖打包验证 | - -## 13. 验收标准 - -- React 入口可以作为默认 Renderer 启动入口。 -- 主布局、路由、标题栏、侧边导航全部由 React 承接。 -- `Home/Chat`、`Agents`、`Setting`、`Skills`、`Cron`、`Scripts` 至少完成 React 版主路径迁移。 -- Electron IPC、Host API、Gateway 调用在 React 页面中保持可用。 -- 迁移完成后,项目必须移除 `vue`、`pinia`、`vue-router`、`element-plus` 与 `@vitejs/plugin-vue`。 -- `pnpm dev`、`pnpm build`、打包主流程可正常运行。 - -## 14. 最终建议 - -- **推荐方案**:以 `React-only` 为唯一终态,短期双栈仅作为过渡。 -- **标准推荐 sub-agent 数量**:`8` 个。 -- **扩展并行数量**:`9` 个。 -- **最小可行数量**:`5` 个。 -- **最重要的执行原则**:先让 React 接管默认入口,再迁高价值页面,随后立即清退 Vue。 - -从投入产出比来看,最值得优先平替的是: - -1. 应用壳与路由 -2. Home/Chat -3. Agents/Setting - -只要这三块完成,`zn-ai` 就已经从“Vue 项目”实质性切到“React 项目”了;剩余工作不再是“是否保留 Vue”的讨论,而是明确执行 Vue 清退与 React-only 收口。 diff --git a/docs/WindowControlsMigrationPlan.md b/docs/WindowControlsMigrationPlan.md deleted file mode 100644 index 9b12458..0000000 --- a/docs/WindowControlsMigrationPlan.md +++ /dev/null @@ -1,423 +0,0 @@ -# Window Controls 迁移计划(对齐 ClawX) - -> 目标:将 zn-ai 的窗口控制体系**全面对齐 ClawX**,废弃 zn-ai 现有相关代码,按平台重建跨平台标题栏方案。UI 视觉延用 zn-ai 设计,图标库统一使用 `@lucide/vue`。 - ---- - -## 1. 现状分析 - -### 1.1 ClawX 实现思路 - -| 平台 | 方案 | 关键配置 | -|------|------|----------| -| **macOS** | 原生 traffic lights | `titleBarStyle: 'hiddenInset'` | -| **Windows** | 自定义 React 标题栏 | `titleBarStyle: 'hidden'` + `frame: false` | -| **Linux** | 保留原生窗口边框 | `frame: true`(默认) | - -- **macOS**:前端仅渲染一个 `drag-region`(高度 40px),红绿灯由系统原生绘制。 -- **Windows**:前端自定义最小化、最大化/恢复、关闭三个按钮,通过 `invoke` IPC 调用主进程 `BrowserWindow` API。 -- **Linux**:出于 IME 兼容性考虑,不隐藏原生边框,前端不渲染任何自定义标题栏。 - -### 1.2 zn-ai 现状(待废弃) - -| 项 | 现状 | 决策 | -|----|------|------| -| 主进程窗口配置 | 所有平台统一 `frame: false` + `titleBarStyle: 'hidden'` | **废弃,重写** | -| 前端组件 | `HeaderBar/index.vue` 同时包含 macOS 自定义按钮 + Windows 自定义按钮 | **废弃,删除** | -| 窗口管理 Hook | `useWinManager.ts` 封装 `ref` + `onMounted` 状态管理 | **废弃,删除** | -| 拖拽区域组件 | `DragRegion/index.vue` 独立组件 | **废弃,删除**(直接内联到 TitleBar) | -| Layout 集成 | `Layout/index.vue` 嵌套 `header-bar` + `drag-region` | **重写** | -| IPC 通信 | `WINDOW_CLOSE` / `WINDOW_MINIMIZE` / `WINDOW_MAXIMIZE` / `IS_WINDOW_MAXIMIZED`(`send`/`on` 混合) | **废弃,改为 `invoke/handle` 对齐 ClawX** | -| 图标库 | `@iconify/vue` + `@iconify-json/material-symbols` | **废弃,替换为 `@lucide/vue`** | - -### 1.3 核心差异 - -1. **macOS**:ClawX 使用原生 traffic lights;zn-ai 使用自定义按钮(将原生按钮移出可视区域),行为不完整。 -2. **Linux**:ClawX 保留原生边框;zn-ai 隐藏了原生边框,存在 IME 兼容性风险。 -3. **IPC 模式**:ClawX 使用 `ipcMain.handle` + `invokeIpc`(请求-响应),zn-ai 使用 `ipcMain.on` + `ipcRenderer.send`(事件驱动),模式不统一。 -4. **组件层级**:ClawX 的 `TitleBar` 是一个自包含组件,内部自带 `drag-region`/`no-drag` 逻辑;zn-ai 将其拆成了 `HeaderBar` + `DragRegion` + `useWinManager` 三个文件,过度拆分。 - ---- - -## 2. 迁移方案 - -### 2.1 设计原则 - -- **全面对齐 ClawX**:架构、IPC 模式、平台分支逻辑直接复刻 ClawX 实现。 -- **zn-ai 既有代码可抛弃**:`HeaderBar`、`DragRegion`、`useWinManager` 及相关 IPC 直接删除,不用做兼容改造。 -- **UI 视觉延用 zn-ai**:Windows 按钮的 hover 颜色、尺寸保持 zn-ai 现有设计(`#999` / `#ff0000`)。 -- **图标统一使用 `@lucide/vue`**:废弃 `@iconify/vue`。 - -### 2.2 废弃清单(Delete List) - -| 文件/目录 | 说明 | -|-----------|------| -| `src/components/HeaderBar/index.vue` | 既有自定义按钮组件,完全废弃 | -| `src/components/DragRegion/index.vue` | 拖拽区域组件,逻辑内联到新的 `TitleBar` | -| `src/hooks/useWinManager.ts` | 窗口管理 Hook,逻辑内联到新的 `TitleBar` | -| `src/main.ts` 中 `HeaderBar` / `DragRegion` 的全局注册 | 删除全局组件注册 | - -### 2.3 废弃的 IPC 与常量 - -zn-ai 现有窗口控制 IPC(基于 `send`/`on`): -```ts -// constants.ts 中废弃以下事件 -WINDOW_MINIMIZE = 'window-minimize' -WINDOW_MAXIMIZE = 'window-maximize' -WINDOW_CLOSE = 'window-close' -IS_WINDOW_MAXIMIZED = 'is-window-maximized' -``` - -**废弃后替换为 ClawX 风格的 `invoke` channel**: -```ts -// 新增(对齐 ClawX) -'window:minimize' -'window:maximize' -'window:close' -'window:isMaximized' -``` - -> 若项目中其他模块也使用了旧的 `WINDOW_MINIMIZE` 等常量,需要一并迁移。经排查,旧常量仅在 `HeaderBar` / `useWinManager` / `window-service` / `preload` 中使用,可随本次重构一并删除。 - -### 2.4 新建/重写清单(Create/Rewrite List) - -| 新建/重写项 | 说明 | -|-------------|------| -| `src/components/layout/TitleBar/index.vue` | **新建**。对齐 ClawX `TitleBar.tsx`,Vue 实现。 | -| `src/components/layout/Layout/index.vue` | **重写**。移除 `header-bar`/`drag-region` 引用,改用 `TitleBar`。 | -| `electron/service/window-service/index.ts` | **重写 `SHARED_WINDOW_OPTIONS`**。按平台设置 `frame`/`titleBarStyle`/`trafficLightPosition`。 | -| `electron/main.ts` 或新建 `electron/main/ipc-handlers.ts` | **新增/重写**。注册 `window:*` 的 `ipcMain.handle`。 | -| `electron/preload/index.ts` | **重写**。移除旧 `send/on` API,新增 `invoke('window:*')` 和 `platform`。 | -| `src/lib/api-client.ts`(或类似文件) | **新增 `invokeIpc` 辅助函数**。对齐 ClawX 的 `invokeIpc` 调用风格。 | -| `src/styles/index.css` | 保留 `.drag-region` / `.no-drag`,供 `TitleBar` 使用。 | - -### 2.5 主进程窗口配置改造 - -**目标代码(对齐 ClawX)**: -```ts -// electron/service/window-service/index.ts -const isMac = process.platform === 'darwin'; -const isWindows = process.platform === 'win32'; -const useCustomTitleBar = isWindows; - -const SHARED_WINDOW_OPTIONS = { - frame: isMac || !useCustomTitleBar, - titleBarStyle: isMac ? 'hiddenInset' : useCustomTitleBar ? 'hidden' : 'default', - trafficLightPosition: isMac ? { x: 16, y: 16 } : undefined, - show: false, - title: 'NIANXX', - darkTheme: themeManager.isDark, - backgroundColor: themeManager.isDark ? '#2C2C2C' : '#FFFFFF', - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - sandbox: true, - backgroundThrottling: false, - preload: MAIN_WINDOW_VITE_DEV_SERVER_URL - ? path.join(process.cwd(), 'dist-electron/preload/preload.js') - : path.join(__dirname, 'preload.js'), - }, -} as BrowserWindowConstructorOptions; -``` - -**注意**: -- 删除旧的 `trafficLightPosition: { x: -100, y: -100 }`。 -- macOS 使用 `hiddenInset` 后,红绿灯由系统原生绘制,前端无需硬编码左侧占位。 - -### 2.6 新增 IPC Handlers(对齐 ClawX) - -新建或复用文件(建议新建 `electron/ipc/window-handlers.ts`,方便模块化): - -```ts -import { ipcMain, BrowserWindow } from 'electron'; - -export function registerWindowHandlers(mainWindow: BrowserWindow): void { - ipcMain.handle('window:minimize', () => { - mainWindow.minimize(); - }); - - ipcMain.handle('window:maximize', () => { - if (mainWindow.isMaximized()) { - mainWindow.unmaximize(); - } else { - mainWindow.maximize(); - } - }); - - ipcMain.handle('window:close', () => { - mainWindow.close(); - }); - - ipcMain.handle('window:isMaximized', () => { - return mainWindow.isMaximized(); - }); -} -``` - -在 `electron/main.ts` 或 `electron/wins/index.ts` 的适当时机调用 `registerWindowHandlers(mainWindow)`。 - -### 2.7 Preload 改造(对齐 ClawX `invokeIpc` 风格) - -```ts -// electron/preload/index.ts -const api: WindowApi = { - // ... 保留既有 API - - platform: process.platform, - - // 窗口控制 — 对齐 ClawX 的 invoke 风格 - windowMinimize: () => ipcRenderer.invoke('window:minimize'), - windowMaximize: () => ipcRenderer.invoke('window:maximize'), - windowClose: () => ipcRenderer.invoke('window:close'), - windowIsMaximized: () => ipcRenderer.invoke('window:isMaximized'), - - // 移除旧的 minimizeWindow / maximizeWindow / closeWindow / onWindowMaximized / isWindowMaximized -} -``` - -### 2.8 前端 `invokeIpc` 辅助函数(可选,强烈建议) - -ClawX 使用统一的 `invokeIpc` 函数调用主进程,zn-ai 建议新增该辅助函数以保持一致风格: - -```ts -// src/lib/api-client.ts -export function invokeIpc(channel: string, ...args: any[]): Promise { - return window.api.invoke(channel, ...args); -} -``` - -> 如果 zn-ai 已有类似封装(如 `window.api.invoke`),可直接使用,无需额外文件。 - -### 2.9 新建 TitleBar 组件(对齐 ClawX) - -**新建文件**:`src/components/layout/TitleBar/index.vue` - -```vue -