From b1dea9a5c2a3ade2064915be0353dc76508c361d Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Fri, 17 Apr 2026 07:09:56 +0800 Subject: [PATCH] feat: implement task management store with IPC integration - Added a new task store in `src-react/stores/task.ts` to manage tasks and their statuses. - Implemented functions for creating, executing, and retrying tasks, along with handling task progress and completion. - Introduced persistence for tasks using IPC. - Created utility functions for normalizing room types and building subtasks. - Added a new CSS file for global styles in `src-react/styles.css`. - Created runtime types in `src-react/types/runtime.ts` and exported them. - Updated the main entry points for Vue and React applications to support dynamic framework loading. - Refactored chat model interfaces and utility functions into `src/shared/chat-model.ts`. - Updated TypeScript configuration to include paths for React components and types. - Enhanced Vite configuration to support both Vue and React frameworks. --- dist-electron/main/main.js | 8 +- dist-electron/preload/preload.js | 138 +-- dist/index.html | 33 +- docs/OpenClaw-Chat-Alignment-Plan.md | 229 +++++ docs/Vue-Exit-Checklist.md | 136 +++ docs/Vue-to-React-Replacement-Plan.md | 469 +++++++++++ electron/gateway/handlers/chat.ts | 9 +- electron/gateway/manager.ts | 2 + electron/gateway/session-store.ts | 2 +- electron/gateway/types.ts | 6 +- electron/process/runTaskOperationService.ts | 20 +- package.json | 6 + pnpm-lock.yaml | 365 +++++++- src-react/App.tsx | 16 + src-react/components/chat/ChatComposer.tsx | 125 +++ .../components/chat/ChatHistoryPanel.tsx | 108 +++ src-react/components/chat/ChatMessageList.tsx | 92 ++ src-react/components/chat/TaskBoard.tsx | 114 +++ src-react/components/chat/index.ts | 5 + src-react/components/chat/types.ts | 33 + src-react/components/layout/MainLayout.tsx | 23 + src-react/components/layout/Sidebar.tsx | 67 ++ src-react/components/layout/TitleBar.tsx | 71 ++ src-react/components/layout/index.ts | 3 + src-react/i18n/constants.ts | 15 + src-react/i18n/index.ts | 107 +++ src-react/i18n/messages.ts | 112 +++ src-react/i18n/resolver.ts | 24 + src-react/lib/constants.ts | 31 + src-react/lib/gateway-client.ts | 12 + src-react/lib/host-api.ts | 142 ++++ src-react/lib/index.ts | 52 ++ src-react/lib/runtime.ts | 76 ++ src-react/lib/theme.ts | 51 ++ src-react/main.tsx | 18 + src-react/pages/Agents/index.tsx | 16 + src-react/pages/Cron/index.tsx | 16 + .../Home/components/AddChannelDialog.tsx | 174 ++++ .../pages/Home/components/DialogSurface.tsx | 56 ++ .../Home/components/TaskOperationDialog.tsx | 203 +++++ src-react/pages/Home/components/index.ts | 2 + src-react/pages/Home/index.tsx | 446 ++++++++++ src-react/pages/Knowledge/index.tsx | 16 + src-react/pages/Login/index.tsx | 96 +++ src-react/pages/PagePlaceholder.tsx | 73 ++ src-react/pages/Scripts/index.tsx | 16 + src-react/pages/Setting/index.tsx | 16 + src-react/pages/Skills/index.tsx | 16 + src-react/router/index.tsx | 32 + src-react/router/routes.ts | 44 + src-react/stores/channel.ts | 202 +++++ src-react/stores/chat.ts | 788 ++++++++++++++++++ src-react/stores/index.ts | 15 + src-react/stores/settings.ts | 374 +++++++++ src-react/stores/task.ts | 367 ++++++++ src-react/styles.css | 27 + src-react/types/index.ts | 1 + src-react/types/runtime.ts | 104 +++ src/framework.ts | 24 + src/lib/types.ts | 9 +- src/main-vue.ts | 35 + src/main.ts | 43 +- src/pages/home/model/ChatModel.ts | 168 +--- src/shared/chat-model.ts | 173 ++++ src/stores/chat.ts | 4 +- tsconfig.app.json | 7 +- tsconfig.json | 2 + vite.config.ts | 22 +- 68 files changed, 5910 insertions(+), 397 deletions(-) create mode 100644 docs/OpenClaw-Chat-Alignment-Plan.md create mode 100644 docs/Vue-Exit-Checklist.md create mode 100644 docs/Vue-to-React-Replacement-Plan.md create mode 100644 src-react/App.tsx create mode 100644 src-react/components/chat/ChatComposer.tsx create mode 100644 src-react/components/chat/ChatHistoryPanel.tsx create mode 100644 src-react/components/chat/ChatMessageList.tsx create mode 100644 src-react/components/chat/TaskBoard.tsx create mode 100644 src-react/components/chat/index.ts create mode 100644 src-react/components/chat/types.ts create mode 100644 src-react/components/layout/MainLayout.tsx create mode 100644 src-react/components/layout/Sidebar.tsx create mode 100644 src-react/components/layout/TitleBar.tsx create mode 100644 src-react/components/layout/index.ts create mode 100644 src-react/i18n/constants.ts create mode 100644 src-react/i18n/index.ts create mode 100644 src-react/i18n/messages.ts create mode 100644 src-react/i18n/resolver.ts create mode 100644 src-react/lib/constants.ts create mode 100644 src-react/lib/gateway-client.ts create mode 100644 src-react/lib/host-api.ts create mode 100644 src-react/lib/index.ts create mode 100644 src-react/lib/runtime.ts create mode 100644 src-react/lib/theme.ts create mode 100644 src-react/main.tsx create mode 100644 src-react/pages/Agents/index.tsx create mode 100644 src-react/pages/Cron/index.tsx create mode 100644 src-react/pages/Home/components/AddChannelDialog.tsx create mode 100644 src-react/pages/Home/components/DialogSurface.tsx create mode 100644 src-react/pages/Home/components/TaskOperationDialog.tsx create mode 100644 src-react/pages/Home/components/index.ts create mode 100644 src-react/pages/Home/index.tsx create mode 100644 src-react/pages/Knowledge/index.tsx create mode 100644 src-react/pages/Login/index.tsx create mode 100644 src-react/pages/PagePlaceholder.tsx create mode 100644 src-react/pages/Scripts/index.tsx create mode 100644 src-react/pages/Setting/index.tsx create mode 100644 src-react/pages/Skills/index.tsx create mode 100644 src-react/router/index.tsx create mode 100644 src-react/router/routes.ts create mode 100644 src-react/stores/channel.ts create mode 100644 src-react/stores/chat.ts create mode 100644 src-react/stores/index.ts create mode 100644 src-react/stores/settings.ts create mode 100644 src-react/stores/task.ts create mode 100644 src-react/styles.css create mode 100644 src-react/types/index.ts create mode 100644 src-react/types/runtime.ts create mode 100644 src/framework.ts create mode 100644 src/main-vue.ts create mode 100644 src/shared/chat-model.ts diff --git a/dist-electron/main/main.js b/dist-electron/main/main.js index 48b2026..b4e89e9 100644 --- a/dist-electron/main/main.js +++ b/dist-electron/main/main.js @@ -1,7 +1 @@ -"use strict"; -require("electron"); -require("./main-ByCp1zrw.js"); -require("electron-squirrel-startup"); -require("electron-log"); -require("bytenode"); -require("axios"); +"use strict";require("electron");require("./main-BEhicpdt.js");require("electron-squirrel-startup");require("electron-log");require("bytenode");require("axios"); 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 28380ff..fd3af34 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,17 +1,16 @@ - - - - - NIANXX - - - - - - -
- - + + + + + NIANXX + + + + + +
+ + diff --git a/docs/OpenClaw-Chat-Alignment-Plan.md b/docs/OpenClaw-Chat-Alignment-Plan.md new file mode 100644 index 0000000..fc35dc2 --- /dev/null +++ b/docs/OpenClaw-Chat-Alignment-Plan.md @@ -0,0 +1,229 @@ +# zn-ai 对齐 ClawX 对话能力的 OpenClaw 集成与迁移计划 + +## 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,降低一次性重写风险。 + +## 2. ClawX 集成 OpenClaw 的实现思路 + +| 层级 | 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 对话体验的关键差异点 | + +## 3. zn-ai 当前基础与差距 + +### 3.1 已有基础 + +| 能力 | 当前实现 | 关键文件 | 评估 | +| --- | --- | --- | --- | +| 主进程聊天入口 | 已有 `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/*` | 可迭代增强 | + +### 3.2 主要差距 + +| 差距 | 当前状态 | 对齐目标 | +| --- | --- | --- | +| 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 的启动、重启、健康、孤儿进程治理逻辑 | + +## 4. 推荐目标架构 + +```text +Vue Renderer + ├─ src/stores/chat.ts + ├─ src/lib/gateway-client.ts + └─ src/lib/host-api.ts + │ + │ IPC + 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 + │ + │ utilityProcess + ~/.openclaw + ▼ +OpenClaw Gateway + ├─ providers / auth profiles + ├─ agents / sessions / transcripts + ├─ tool use / thinking / attachments + └─ channel / cron / skills / model routing +``` + +### 架构原则 + +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` 测试脚本、调试脚本、文档 | 通过验收清单,具备对外演示条件 | + +## 7. sub-agent 数量估算 + +### 7.1 推荐编制 + +- 分析集成 sub-agent:`2` +- 功能迁移 sub-agent:`5` +- 集成验收 sub-agent:`1` +- 推荐总数:`8` + +### 7.2 最小可行编制 + +- 分析集成 sub-agent:`2` +- 功能迁移 sub-agent:`4` +- 集成验收由主协调 agent 兼任 +- 最小总数:`6` + +### 7.3 为什么推荐 2 + 5 + 1 + +- `ClawX` 与 `zn-ai` 的主要差距横跨“打包、主进程、Host API、配置同步、Renderer 状态机、UI、转录/执行图”七个面。 +- 其中 `OpenClaw runtime 嵌入` 和 `Renderer 对话能力对齐` 之间依赖明确,但文件改动面基本可分离,适合并行。 +- 如果迁移 sub-agent 少于 `4`,会出现“主进程迁移完成但 Renderer 无法及时接入”或“UI 已变更但 transcript/API 尚未可用”的串行瓶颈。 + +## 8. 功能迁移 sub-agent 分工方案 + +| 角色 | 数量 | 负责范围 | 建议文件所有权 | +| --- | --- | --- | --- | +| 分析 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、发布说明 | 测试与文档,不抢占前面文件所有权 | + +### 8.1 并行施工波次 + +| 波次 | 并行 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/执行图,并完成端到端验收 | + +### 8.2 依赖关系 + +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 双态验证 | + +## 11. 完成验收标准 + +- `zn-ai` 可以在开发态和打包态拉起 OpenClaw Gateway。 +- 聊天页默认使用本地默认模型配置,且修改默认模型后下一轮对话生效。 +- 会话列表支持新建、切换、加载、删除、重命名。 +- 聊天页支持流式文本、Markdown、thinking、tool cards、图片/文件附件。 +- `Abort`、错误恢复、历史回灌稳定可用。 +- `@agent` 能把消息送到目标 Agent 主会话,而不是仅显示占位 chip。 +- 至少一个子任务 transcript 能被加载并展示为执行图或子任务明细。 +- 不影响 `Scripts`、`Tasks`、模型管理页的现有主流程。 + +## 12. 与现有文档的关系 + +- `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 new file mode 100644 index 0000000..592c8e2 --- /dev/null +++ b/docs/Vue-Exit-Checklist.md @@ -0,0 +1,136 @@ +# zn-ai Vue 退场清单 + +目标:在 React 接管默认入口后,按清单删除所有仅服务 Vue 的资产,最终收敛为 `React-only`。 + +## 当前状态 + +- `Home` 的 React 主链路已经接入真实 `chat/task/channel` store、真实 IPC,以及 `TaskOperationDialog / AddChannelDialog`。 +- `Home` 这批旧 Vue 文件目前仍被 `src/router/index.ts -> src/pages/home/index.vue` 的 Vue fallback 引用,因此本轮不能直接删除。 +- 其余主页面虽然已有 `src-react/pages/*` 路由壳,但当前大多还是占位页,尚不满足“删除 Vue 路由与页面”的退场条件。 + +当前删除策略: + +1. 先停止新增对 `src/pages/home/**`、`src/stores/chat.ts`、`src/stores/task.ts`、`src/stores/channel.ts` 的 React 侧依赖。 +2. 等 `Agents / Knowledge / Skills / Cron / Scripts / Setting / Login` 的 React 页面完成真实迁移后,再统一删除 `src/router/*`、`.vue` 页面和旧 Pinia store。 +3. 在此之前,只做“替换引用”和“收口依赖”,不做会破坏 Vue fallback 的物理删除。 + +## 1. 入口与切换层 + +- `src/main-vue.ts` +- `src/framework.ts` +- `src/main.ts` +- `src/App.vue` +- `src/permission.ts` + +退场标准: + +- `src/main.ts` 不再分流 Vue 入口。 +- `src/framework.ts` 不再保留 `vue` 分支。 +- `src/main-vue.ts` 删除。 +- `src/App.vue` 和 `src/permission.ts` 只要还依赖 Vue 生命周期或 Router 守卫,就必须迁入 React 后再删除旧文件。 + +## 2. Vue 页面 + +以下页面都属于 Vue 退场范围,React 版本完成后删除: + +- `src/pages/home/index.vue` +- `src/pages/home/ChatBox.vue` +- `src/pages/home/ChatHistory.vue` +- `src/pages/home/TaskCenter.vue` +- `src/pages/home/components/*.vue` +- `src/pages/login/index.vue` +- `src/pages/agents/index.vue` +- `src/pages/knowledge/index.vue` +- `src/pages/skills/index.vue` +- `src/pages/skills/components/*.vue` +- `src/pages/cron/index.vue` +- `src/pages/cron/components/*.vue` +- `src/pages/setting/index.vue` +- `src/pages/setting/components/*.vue` + +建议删除顺序: + +1. 先迁 `home`,再迁其余业务页。 +2. 先删纯展示/表单页,再删聊天主链路。 +3. 最后统一清理页面下的 Vue 子组件目录。 + +## 3. Vue Store + +以下 `Pinia` store 是 Vue 体系的核心状态,React 完成对等实现后删除: + +- `src/stores/chat.ts` +- `src/stores/providers.ts` +- `src/stores/channel.ts` +- `src/stores/cron.ts` +- `src/stores/script.ts` +- `src/stores/skills.ts` +- `src/stores/task.ts` +- `src/stores/theme.ts` +- `src/stores/locale.ts` +- `src/stores/update.ts` +- `src/stores/userinfo.ts` +- `src/stores/sharedStore.ts` + +退场标准: + +- React 侧已具备对等 store 后,旧 Pinia store 不再被任何路由或组件引用。 +- `src/stores/*` 不再承担主流程状态。 + +## 4. Router 与守卫 + +需要替换或删除的 Vue 路由资产: + +- `src/router/index.ts` +- `src/constant/menus.ts` +- `src/permission.ts` +- `src/components/SideMenus/index.vue` + +退场标准: + +- 路由切换完全由 React Router 接管。 +- 菜单配置不再依赖 Vue 路由表。 +- 登录守卫迁到 React 侧后,旧守卫文件删除。 + +## 5. 仅服务 Vue 的共享组件 + +这些组件如果只被 Vue 页面使用,应在 React 对等实现后删除: + +- `src/components/Layout/index.vue` +- `src/components/Layout/TitleBar/index.vue` +- `src/components/NativeTooltip/index.vue` +- `src/components/Pagination/index.vue` +- `src/components/TitleSection/index.vue` + +## 6. Vue 依赖与构建项 + +`package.json` 中后续需要删除或替换的 Vue 依赖: + +- `vue` +- `vue-router` +- `pinia` +- `vue-i18n` +- `element-plus` +- `@vueuse/core` +- `@vitejs/plugin-vue` +- `@lucide/vue` +- `@remixicon/vue` +- `vue-codemirror` +- `vue-markdown-render` + +`vite.config.ts` 中后续需要清理的 Vue 构建项: + +- `vue()` 插件 +- `unplugin-auto-import` 的 Vue 自动导入配置 +- 与 Vue 页面强绑定的 `src/auto-imports.d.ts` 生成链路 +- 所有仅为 Vue 入口保留的分支判断 + +## 7. 最终删除判定 + +只有在以下条件都满足后,才允许进入最终清理提交: + +- 默认入口只剩 React。 +- 所有主页面都已迁到 `src-react/`。 +- `src/pages/**` 中不再存在可执行的 Vue 页面。 +- `src/stores/**` 中不再存在被运行时调用的 Vue 状态。 +- `package.json` 和 `vite.config.ts` 中不再保留 Vue 构建依赖。 +- 仓库中不再需要 `VITE_UI_FRAMEWORK=vue` 作为运行开关。 diff --git a/docs/Vue-to-React-Replacement-Plan.md b/docs/Vue-to-React-Replacement-Plan.md new file mode 100644 index 0000000..3f7bf40 --- /dev/null +++ b/docs/Vue-to-React-Replacement-Plan.md @@ -0,0 +1,469 @@ +# 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 技术栈 + +| 维度 | 当前实现 | 关键路径 | +| --- | --- | --- | +| 应用入口 | Vue 根应用 | `zn-ai/src/main.ts` | +| 根组件 | `App.vue + router-view + keep-alive` | `zn-ai/src/App.vue` | +| 路由 | `vue-router` | `zn-ai/src/router/index.ts` | +| 状态管理 | `Pinia` | `zn-ai/src/stores/*` | +| UI 组件体系 | `Element Plus + 自定义 Vue 组件 + Tailwind` | `zn-ai/src/components/*` | +| 页面目录 | Vue 页面分散在 `src/pages/*` | `zn-ai/src/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:先迁基础壳 + +- `App.vue` +- `src/main.ts` +- `src/router/index.ts` +- `src/components/Layout/index.vue` +- `src/components/SideMenus/index.vue` +- `src/components/Layout/TitleBar/index.vue` + +原因: + +- 这是 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/*` +- `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/electron/gateway/handlers/chat.ts b/electron/gateway/handlers/chat.ts index 73d5a38..c34dad9 100644 --- a/electron/gateway/handlers/chat.ts +++ b/electron/gateway/handlers/chat.ts @@ -3,7 +3,7 @@ import { createProvider } from '@electron/providers'; import type { BaseProvider } from '@electron/providers/BaseProvider'; import { providerApiService } from '@electron/service/provider-api-service'; import logManager from '@electron/service/logger'; -import type { RawMessage } from '@src/pages/home/model/ChatModel'; +import type { RawMessage } from '@shared/chat-model'; import { sessionStore } from '../session-store'; import type { GatewayEvent, GatewayRpcParams, GatewayRpcReturns } from '../types'; import { appendTranscriptLine } from '@electron/utils/token-usage-writer'; @@ -197,3 +197,10 @@ export function handleChatAbort( export function handleSessionList(): GatewayRpcReturns['session.list'] { return sessionStore.getAllKeys(); } + +export function handleSessionDelete( + params: GatewayRpcParams['session.delete'] +): GatewayRpcReturns['session.delete'] { + sessionStore.deleteSession(params.sessionKey); + return { success: true }; +} diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index 4938501..6b748f9 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -31,6 +31,8 @@ class GatewayManager { return chatHandlers.handleChatAbort(params, (event) => this.broadcast(event)); case 'session.list': return chatHandlers.handleSessionList(); + case 'session.delete': + return chatHandlers.handleSessionDelete(params); case 'provider.list': return providerHandlers.handleProviderList(); case 'provider.getDefault': diff --git a/electron/gateway/session-store.ts b/electron/gateway/session-store.ts index aca0508..dba0983 100644 --- a/electron/gateway/session-store.ts +++ b/electron/gateway/session-store.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { app } from 'electron'; import logManager from '@electron/service/logger'; -import type { RawMessage } from '@src/pages/home/model/ChatModel'; +import type { RawMessage } from '@shared/chat-model'; let sessionsFilePath: string | null = null; diff --git a/electron/gateway/types.ts b/electron/gateway/types.ts index cbca474..d2b8abc 100644 --- a/electron/gateway/types.ts +++ b/electron/gateway/types.ts @@ -1,4 +1,4 @@ -import type { RawMessage } from '@src/pages/home/model/ChatModel'; +import type { RawMessage } from '@shared/chat-model'; /// Gateway 向 Renderer 推送的事件类型 export type GatewayEvent = @@ -47,6 +47,9 @@ export interface GatewayRpcParams { sessionKey: string; }; 'session.list': Record; + 'session.delete': { + sessionKey: string; + }; 'provider.list': Record; 'provider.getDefault': Record; } @@ -57,6 +60,7 @@ export interface GatewayRpcReturns { 'chat.history': RawMessage[]; 'chat.abort': void; 'session.list': string[]; + 'session.delete': { success: boolean }; 'provider.list': { accounts: any[]; defaultAccountId: string | null }; 'provider.getDefault': { accountId: string | null }; } diff --git a/electron/process/runTaskOperationService.ts b/electron/process/runTaskOperationService.ts index b16f7e7..02d4e7e 100644 --- a/electron/process/runTaskOperationService.ts +++ b/electron/process/runTaskOperationService.ts @@ -230,7 +230,14 @@ export function runTaskOperationService() { ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options: any) => { try { const taskId = options.taskId || randomUUID(); - const roomType = options.roomList.find((item: any) => item.id === options.roomType); + const roomTypeRaw = options.roomList.find((item: any) => item.id === options.roomType); + const roomType = roomTypeRaw + ? { + ...roomTypeRaw, + dyHotSpringName: roomTypeRaw.dyHotSpringName ?? roomTypeRaw.dyHotSrpingName, + dyHotSrpingName: roomTypeRaw.dyHotSrpingName ?? roomTypeRaw.dyHotSpringName, + } + : null; const pairs: Array<[string, string]> = [ ['fzName', 'fg_trace.js'], @@ -238,7 +245,12 @@ export function runTaskOperationService() { ['dyHotelName', 'dy_hotel_trace.js'], ['dyHotSpringName', 'dy_hot_spring_trace.js'] ] - const scriptEntries = pairs.filter(([prop]) => roomType?.[prop]) + const scriptEntries = pairs.filter(([prop]) => { + if (prop === 'dyHotSpringName') { + return roomType?.dyHotSpringName || roomType?.dyHotSrpingName + } + return roomType?.[prop] + }) const scriptsDir = getScriptsDir() @@ -301,7 +313,9 @@ export function runTaskOperationService() { const result = await executeScriptServiceInstance.executeScript( item.scriptPath, { - roomType: roomType[item.channel], + roomType: item.channel === 'dyHotSpringName' + ? (roomType.dyHotSpringName || roomType.dyHotSrpingName) + : roomType[item.channel], startTime: options.startTime, endTime: options.endTime, operation: options.operation, diff --git a/package.json b/package.json index fbe451c..634ba8f 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,11 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/lodash-es": "^4.17.12", "@types/node": "^25.3.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-vue": "^6.0.3", + "@vitejs/plugin-react": "^5.0.0", "electron": "^40.8.5", "electron-builder": "^26.8.1", "esbuild": "^0.27.4", @@ -97,6 +100,9 @@ "openai": "^6.14.0", "pinia": "^2.3.1", "playwright": "^1.58.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.13.0", "ts-node": "^10.9.2", "uuid": "^13.0.0", "vue": "^3.5.22", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e8f6ef..674be1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,7 @@ importers: version: 1.14.0 browser-use-sdk: specifier: ^2.0.12 - version: 2.0.15(react@19.2.4)(zod@3.25.76) + version: 2.0.15(react@18.3.1)(zod@3.25.76) bytenode: specifier: ^1.5.7 version: 1.5.7 @@ -104,6 +104,15 @@ importers: playwright: specifier: ^1.58.2 version: 1.59.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^7.13.0 + version: 7.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@25.5.2)(typescript@5.9.3) @@ -147,9 +156,18 @@ importers: '@types/node': specifier: ^25.3.0 version: 25.5.2 + '@types/react': + specifier: ^18.3.12 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.7(@types/react@18.3.28) '@typescript-eslint/parser': specifier: ^5.62.0 version: 5.62.0(eslint@8.57.1)(typescript@5.9.3) + '@vitejs/plugin-react': + specifier: ^5.0.0 + version: 5.2.0(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)) '@vitejs/plugin-vue': specifier: ^6.0.3 version: 6.0.5(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0))(vue@3.5.32(typescript@5.9.3)) @@ -202,14 +220,40 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -218,11 +262,31 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -752,6 +816,9 @@ packages: '@rolldown/pluginutils@1.0.0-rc.2': resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rollup/rollup-android-arm-eabi@4.60.1': resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} cpu: [arm] @@ -1031,6 +1098,18 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} @@ -1076,6 +1155,17 @@ packages: '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -1125,6 +1215,12 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@vitejs/plugin-vue@6.0.5': resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1314,6 +1410,11 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.19: + resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==} + engines: {node: '>=6.0.0'} + hasBin: true + bing-translate-api@4.2.0: resolution: {integrity: sha512-7a9yo1NbGcHPS8zXTdz8tCOymHZp2pvCuYOChCaXKjOX8EIwdV3SLd4D7RGIqZt1UhffypYBUcAV2gDcTgK0rA==} @@ -1345,6 +1446,11 @@ packages: react: ^18 || ^19 zod: ^4 + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1388,6 +1494,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001788: + resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1488,6 +1597,13 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -1692,6 +1808,9 @@ packages: resolution: {integrity: sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==} engines: {node: '>=20'} + electron-to-chromium@1.5.339: + resolution: {integrity: sha512-Is+0BBHJ4NrdpAYiperrmp53pLywG/yV/6lIMTAnhxvzj/Cmn5Q/ogSHC6AKe7X+8kPLxxFk0cs5oc/3j/fxIg==} + electron-updater@6.8.3: resolution: {integrity: sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==} @@ -1978,6 +2097,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2371,6 +2494,10 @@ packages: resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} engines: {node: '>=8.0'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -2378,6 +2505,9 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2573,6 +2703,9 @@ packages: node-readfiles@0.2.0: resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + nopt@8.1.0: resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} engines: {node: ^18.17.0 || >=20.5.0} @@ -2816,8 +2949,34 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.14.1: + resolution: {integrity: sha512-ZkrQuwwhGibjQLqH1eCdyiZyLWglPxzxdl5tgwgKEyCSGC76vmAjleGocRe3J/MLfzMUIKwaFJWpFVJhK3d2xA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.14.1: + resolution: {integrity: sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} read-binary-file-arch@1.0.6: @@ -2910,6 +3069,9 @@ packages: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -2933,6 +3095,9 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3238,6 +3403,12 @@ packages: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3404,6 +3575,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -3452,6 +3626,28 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.2 @@ -3460,16 +3656,59 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-globals@7.28.0': {} + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -4018,6 +4257,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.2': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rollup/rollup-android-arm-eabi@4.60.1': optional: true @@ -4202,6 +4443,27 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.2.0 @@ -4253,6 +4515,17 @@ snapshots: xmlbuilder: 15.1.1 optional: true + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + '@types/responselike@1.0.3': dependencies: '@types/node': 25.5.2 @@ -4309,6 +4582,18 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react@5.2.0(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-vue@6.0.5(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0))(vue@3.5.32(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 @@ -4539,6 +4824,8 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.10.19: {} + bing-translate-api@4.2.0: dependencies: got: 11.8.6 @@ -4569,12 +4856,20 @@ snapshots: dependencies: fill-range: 7.1.1 - browser-use-sdk@2.0.15(react@19.2.4)(zod@3.25.76): + browser-use-sdk@2.0.15(react@18.3.1)(zod@3.25.76): dependencies: fast-json-stable-stringify: 2.1.0 - react: 19.2.4 + react: 18.3.1 zod: 3.25.76 + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.19 + caniuse-lite: 1.0.30001788 + electron-to-chromium: 1.5.339 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-crc32@0.2.13: {} buffer-from@1.1.2: {} @@ -4650,6 +4945,8 @@ snapshots: callsites@3.1.0: {} + caniuse-lite@1.0.30001788: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -4752,6 +5049,10 @@ snapshots: confbox@0.2.4: {} + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + core-util-is@1.0.2: optional: true @@ -4981,6 +5282,8 @@ snapshots: conf: 15.1.0 type-fest: 5.5.0 + electron-to-chromium@1.5.339: {} + electron-updater@6.8.3: dependencies: builder-util-runtime: 9.5.1 @@ -5369,6 +5672,8 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -5738,10 +6043,18 @@ snapshots: transitivePeerDependencies: - supports-color + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -5949,6 +6262,8 @@ snapshots: dependencies: es6-promise: 3.3.1 + node-releases@2.0.37: {} + nopt@8.1.0: dependencies: abbrev: 3.0.1 @@ -6210,7 +6525,31 @@ snapshots: quick-lru@5.1.1: {} - react@19.2.4: {} + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.18.0: {} + + react-router-dom@7.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + cookie: 1.1.1 + react: 18.3.1 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 read-binary-file-arch@1.0.6: dependencies: @@ -6322,6 +6661,10 @@ snapshots: sax@1.6.0: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + scule@1.3.0: {} semver-compare@1.0.0: @@ -6338,6 +6681,8 @@ snapshots: type-fest: 0.13.1 optional: true + set-cookie-parser@2.7.2: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -6693,6 +7038,12 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6820,6 +7171,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@4.0.0: {} yallist@5.0.0: {} diff --git a/src-react/App.tsx b/src-react/App.tsx new file mode 100644 index 0000000..ed0af8a --- /dev/null +++ b/src-react/App.tsx @@ -0,0 +1,16 @@ +import { useEffect } from 'react'; +import { HashRouter } from 'react-router-dom'; +import { AppRouter } from './router'; +import { initSettingsStore } from './stores'; + +export default function App() { + useEffect(() => { + void initSettingsStore(); + }, []); + + return ( + + + + ); +} diff --git a/src-react/components/chat/ChatComposer.tsx b/src-react/components/chat/ChatComposer.tsx new file mode 100644 index 0000000..28e4448 --- /dev/null +++ b/src-react/components/chat/ChatComposer.tsx @@ -0,0 +1,125 @@ +import { useRef } from 'react'; +import type { AttachedFileMeta } from '@shared/chat-model'; + +type ChatComposerProps = { + value: string; + isSending: boolean; + attachments: AttachedFileMeta[]; + error?: string | null; + onChange: (value: string) => void; + onSend: () => void; + onStop: () => void; + onAttach: (files: File[]) => void | Promise; + onRemoveAttachment: (index: number) => void; + onDismissError?: () => void; +}; + +export default function ChatComposer({ + value, + isSending, + attachments, + error, + onChange, + onSend, + onStop, + onAttach, + onRemoveAttachment, + onDismissError, +}: ChatComposerProps) { + const fileInputRef = useRef(null); + + return ( +
+
+
+
+ AI +
+
+ {error ? ( +
+ {error} + {onDismissError ? ( + + ) : null} +
+ ) : null} +