From 2a4927d8ee1efbc0e1d3aa7bc215206897e7a6b4 Mon Sep 17 00:00:00 2001 From: lrhh123 Date: Sat, 15 Jun 2024 00:46:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=EF=BC=8Cadd:=20=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9A=82=E5=81=9C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 3 +- package.json | 1 - pnpm-lock.yaml | 3 - src/main/backend/backend.ts | 26 +-- src/main/backend/constants/index.ts | 4 +- .../backend/controllers/configController.ts | 82 ++++++++- src/main/backend/entities/config.ts | 29 +++ src/main/backend/services/appService.ts | 6 + src/main/backend/services/dispatchService.ts | 91 ++++++++-- src/main/backend/services/pluginService.ts | 4 + src/main/backend/types/index.ts | 7 + src/main/cron.ts | 45 +++++ src/main/gptproxy/dify/chat/completions.ts | 2 + src/main/ipcHandlers.ts | 17 -- src/main/main.ts | 2 + src/main/system/backend.ts | 13 -- src/renderer/components/SystemCheck/index.tsx | 11 +- src/renderer/hooks/useBroadcastContext.tsx | 4 +- .../Home/AppManager/AppManagerContext.tsx | 6 +- .../Home/AppManager/InstanceListComponent.tsx | 19 +- src/renderer/pages/Home/LogBox/index.tsx | 2 +- src/renderer/pages/Home/Panels/index.tsx | 170 +++++++++++------- src/renderer/services/platform/controller.ts | 22 +-- src/renderer/services/platform/platform.d.ts | 7 + src/renderer/stores/useSystemStore.ts | 26 +-- 25 files changed, 405 insertions(+), 197 deletions(-) create mode 100644 src/main/cron.ts diff --git a/.env b/.env index 99edf75..9662778 100644 --- a/.env +++ b/.env @@ -2,5 +2,4 @@ PY_HOSTNAME=localhost PY_PORT=9999 VAR=1234 BKEXE_PATH=./backend/__main__.exe -PKG_VERSION=0.0.1 -DEBUG=true \ No newline at end of file +PKG_VERSION=0.0.1 \ No newline at end of file diff --git a/package.json b/package.json index ea7523c..38a06f5 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,6 @@ "ms": "^2.1.3", "net": "^1.0.2", "node-cron": "^3.0.3", - "node-fetch": "2.6.7", "openai": "^4.38.3", "pg-connection-string": "^2.6.4", "pg-hstore": "^2.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c185a3a..8f109a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,9 +104,6 @@ dependencies: node-cron: specifier: ^3.0.3 version: 3.0.3 - node-fetch: - specifier: 2.6.7 - version: 2.6.7 openai: specifier: ^4.38.3 version: 4.38.5 diff --git a/src/main/backend/backend.ts b/src/main/backend/backend.ts index a1e4184..c632266 100644 --- a/src/main/backend/backend.ts +++ b/src/main/backend/backend.ts @@ -6,7 +6,6 @@ import http from 'http'; import { Server } from 'socket.io'; import { BrowserWindow, shell } from 'electron'; import { sequelize } from './ormconfig'; -import { StrategyServiceStatusEnum } from './types'; import { ConfigController } from './controllers/configController'; import { MessageController } from './controllers/messageController'; import { KeywordReplyController } from './controllers/keywordReplyController'; @@ -196,7 +195,7 @@ class BKServer { const data = { appId: appId ? String(appId) : undefined, instanceId: instanceId ? String(instanceId) : undefined, - type: type ? String(type) : 'generic', + type: type ? String(type) : ('generic' as any), }; const obj = await configController.getConfigByType(data); @@ -225,30 +224,15 @@ class BKServer { }; await configController.updateConfigByType(data); + await this.dispatchService.syncConfig(); res.json({ success: true }); }), ); // Endpoint to update runner status based on incoming configuration - this.app.post('/api/v1/base/runner', async (req, res) => { - const { - is_paused: isPaused, - is_keyword_match: isKeywordMatch, - is_use_gpt: isUseGptReply, - } = req.body; + this.app.post('/api/v1/base/sync', async (req, res) => { try { - if (isPaused) { - await this.dispatchService.updateStatus( - StrategyServiceStatusEnum.STOPPED, - ); - } else { - await this.dispatchService.updateStatus( - StrategyServiceStatusEnum.RUNNING, - ); - } - - this.messageService.updateKeywordMatch(isKeywordMatch, isUseGptReply); - + await this.dispatchService.syncConfig(); res.json({ success: true }); } catch (error) { if (error instanceof Error) { @@ -432,9 +416,9 @@ class BKServer { data: task, }); } catch (error) { - console.error(error); res.json({ success: false, + error: error instanceof Error ? error.message : String(error), data: null, }); } diff --git a/src/main/backend/constants/index.ts b/src/main/backend/constants/index.ts index cbac2dc..6ac66f9 100644 --- a/src/main/backend/constants/index.ts +++ b/src/main/backend/constants/index.ts @@ -109,13 +109,13 @@ async function main(ctx, messages) { }); // 再检查是否使用关键词匹配 - if (rp.isKeywordMatch) { + if (cfg.has_keyword_match) { const data = await rp.matchKeyword(ctx, lastUserMsg); if (data) return data; } // 最后检查是否使用 GPT 生成回复 - if (rp.isUseGptReply) { + if (cfg.has_use_gpt) { const data = await rp.getLLMResponse(cfg, ctx, messages); if (data) return data; } diff --git a/src/main/backend/controllers/configController.ts b/src/main/backend/controllers/configController.ts index 95ba448..c8fdddc 100644 --- a/src/main/backend/controllers/configController.ts +++ b/src/main/backend/controllers/configController.ts @@ -6,6 +6,7 @@ import { LLMConfig, AccountConfig, PluginConfig, + DriverConfig, } from '../types'; import { CTX_APP_ID, CTX_INSTANCE_ID } from '../constants'; @@ -41,6 +42,18 @@ export class ConfigController { config = await Config.create({ global: true, }); + } else { + const globalConfig = await Config.findOne({ + where: { global: true }, + }); + + // 这三个配置项是全局配置,需要合并到实例配置中 + if (globalConfig) { + config.has_keyword_match = globalConfig.has_keyword_match; + config.has_paused = globalConfig.has_paused; + config.has_use_gpt = globalConfig.has_use_gpt; + config.has_mouse_close = globalConfig.has_mouse_close; + } } return config; @@ -156,9 +169,14 @@ export class ConfigController { }: { appId: string | undefined; instanceId: string | undefined; - type: string; + type: 'generic' | 'llm' | 'plugin' | 'driver' | 'account'; }): Promise< - GenericConfig | LLMConfig | AccountConfig | PluginConfig | undefined + | GenericConfig + | LLMConfig + | AccountConfig + | PluginConfig + | DriverConfig + | undefined > { let config = null; if (instanceId) { @@ -206,6 +224,7 @@ export class ConfigController { defaultReply: config?.default_reply || '', }; } + if (type === 'llm') { return { appId: config?.platform_id || '', @@ -216,6 +235,7 @@ export class ConfigController { model: config?.model || 'gpt-3.5-turbo', }; } + if (type === 'plugin') { let pluginCode = ''; @@ -232,6 +252,15 @@ export class ConfigController { }; } + if (type === 'driver') { + return { + hasPaused: config?.has_paused || false, + hasKeywordMatch: config?.has_keyword_match || false, + hasUseGpt: config?.has_use_gpt || false, + hasMouseClose: config?.has_mouse_close || false, + }; + } + return { activationCode: config?.activation_code || '', }; @@ -250,7 +279,12 @@ export class ConfigController { appId: string | undefined; instanceId: string | undefined; type: string; - cfg: GenericConfig | LLMConfig | AccountConfig | PluginConfig; + cfg: + | GenericConfig + | LLMConfig + | AccountConfig + | PluginConfig + | DriverConfig; }) { let dbConfig = null; if (instanceId) { @@ -328,6 +362,21 @@ export class ConfigController { use_plugin: config.usePlugin, plugin_id: pluginId, }); + } else if (type === 'driver') { + // TODO: 目前只有全局配置,后续再实现实例配置 + const config = cfg as DriverConfig; + dbConfig = await Config.findOne({ + where: { global: true }, + }); + if (!dbConfig) { + throw new Error('Driver config not found'); + } + await dbConfig.update({ + has_paused: config.hasPaused, + has_keyword_match: config.hasKeywordMatch, + has_use_gpt: config.hasUseGpt, + has_mouse_close: config.hasMouseClose, + }); } else { const config = cfg as AccountConfig; await dbConfig.update({ @@ -335,4 +384,31 @@ export class ConfigController { }); } } + + /** + * 更新配置 + * @param + */ + public async moveMouseHandler(): Promise { + const dbConfig = await Config.findOne({ + where: { global: true }, + }); + + if (!dbConfig) { + return false; + } + + // 检查是否开启了鼠标移动自动暂停功能 + if (dbConfig.has_mouse_close) { + if (!dbConfig.has_paused) { + await dbConfig.update({ + has_paused: true, + }); + + return true; + } + } + + return false; + } } diff --git a/src/main/backend/entities/config.ts b/src/main/backend/entities/config.ts index 9cabdc2..27dae80 100644 --- a/src/main/backend/entities/config.ts +++ b/src/main/backend/entities/config.ts @@ -45,6 +45,14 @@ export class Config extends Model { declare activation_code: string; declare version: string; + + declare has_paused: boolean; + + declare has_keyword_match: boolean; + + declare has_use_gpt: boolean; + + declare has_mouse_close: boolean; // 鼠标移动时是否自动关闭 } export function initConfig(sequelize: Sequelize) { @@ -111,6 +119,7 @@ export function initConfig(sequelize: Sequelize) { default_reply: { type: DataTypes.STRING, allowNull: true, + defaultValue: '当前消息有点多,我稍后再回复你', }, context_count: { type: DataTypes.FLOAT, @@ -147,6 +156,26 @@ export function initConfig(sequelize: Sequelize) { defaultValue: '1.0.0', allowNull: true, }, + has_paused: { + type: DataTypes.BOOLEAN, + defaultValue: true, + allowNull: true, + }, + has_keyword_match: { + type: DataTypes.BOOLEAN, + defaultValue: true, + allowNull: true, + }, + has_use_gpt: { + type: DataTypes.BOOLEAN, + defaultValue: true, + allowNull: true, + }, + has_mouse_close: { + type: DataTypes.BOOLEAN, + defaultValue: true, + allowNull: true, + }, }, { sequelize, diff --git a/src/main/backend/services/appService.ts b/src/main/backend/services/appService.ts index 08eb8e7..31bc5dd 100644 --- a/src/main/backend/services/appService.ts +++ b/src/main/backend/services/appService.ts @@ -60,6 +60,12 @@ export class AppService { throw new Error('Failed to update tasks'); } + // 遍历 result 检查,判断是否存在 error 属性 + const err_target = result.find((task) => task.error); + if (err_target) { + throw new Error(err_target.error); + } + const target = result.find( (task) => task.task_id === String(instance.id), ); diff --git a/src/main/backend/services/dispatchService.ts b/src/main/backend/services/dispatchService.ts index 14a8163..9ea73b8 100644 --- a/src/main/backend/services/dispatchService.ts +++ b/src/main/backend/services/dispatchService.ts @@ -25,7 +25,7 @@ export class DispatchService { constructor( mainWindow: BrowserWindow, io: socketIo.Server, - configService: ConfigController, + configController: ConfigController, messageService: MessageService, messageController: MessageController, pluginService: PluginService, @@ -34,17 +34,28 @@ export class DispatchService { this.mainWindow = mainWindow; this.messageService = messageService; this.messageController = messageController; - this.configController = configService; + this.configController = configController; this.pluginService = pluginService; } public registerHandlers(socket: socketIo.Socket): void { - socket.on('messageService-broadcast', (msg: any, callback) => { - const { event_id: eventId, message } = msg; - this.receiveBroadcast(msg); + socket.on('messageService-broadcast', async (msg: any, callback) => { + const { event, data } = msg; + if (event === 'mouse_move') { + const change = await this.configController.moveMouseHandler(); + if (change) { + this.receiveBroadcast({ + event: 'has_paused', + data: {}, + }); + } + } else { + this.receiveBroadcast(msg); + } + callback({ - event_id: eventId, - event_type: message, + event, + data, }); }); @@ -60,18 +71,27 @@ export class DispatchService { // 检查是否使用插件 const cfg = await this.configController.get(ctxMap); await this.messageService.extractMsgInfo(cfg, ctxMap, messages); - if (cfg.use_plugin && cfg.plugin_id) { - reply = await this.pluginService.executePlugin( - cfg.plugin_id, - ctx, - messages, - ); - } else { - reply = await this.pluginService.executePluginCode( - PluginDefaultRunCode, - ctxMap, - messages, - ); + + try { + if (cfg.use_plugin && cfg.plugin_id) { + reply = await this.pluginService.executePlugin( + cfg.plugin_id, + ctx, + messages, + ); + } else { + reply = await this.pluginService.executePluginCode( + PluginDefaultRunCode, + ctxMap, + messages, + ); + } + } catch (error) { + console.error('Failed to execute plugin', error); + reply = { + content: cfg.default_reply || 'Failed to execute plugin', + type: 'TEXT', + }; } callback(reply); @@ -94,10 +114,43 @@ export class DispatchService { } } + public async syncConfig(): Promise { + try { + const cfg = await this.configController.getConfigByType({ + appId: undefined, + instanceId: undefined, + type: 'driver', + }); + + if (!cfg) { + return false; + } + + let hasPaused = false; + if ('hasPaused' in cfg) { + hasPaused = cfg.hasPaused || false; + } + + await emitAndWait(this.io, 'strategyService-updateStatus', { + status: hasPaused + ? StrategyServiceStatusEnum.STOPPED + : StrategyServiceStatusEnum.RUNNING, + }); + + const instances = await Instance.findAll(); + await this.updateTasks(instances); + return true; + } catch (error) { + console.error('Failed to sync config', error); + return false; + } + } + public async updateTasks(tasks: Instance[]): Promise< | { task_id: string; env_id: string; + error?: string; }[] | null > { diff --git a/src/main/backend/services/pluginService.ts b/src/main/backend/services/pluginService.ts index eda03c7..6496308 100644 --- a/src/main/backend/services/pluginService.ts +++ b/src/main/backend/services/pluginService.ts @@ -185,6 +185,10 @@ export class PluginService { throw new Error('Plugin does not export a function'); } + if (!messages || messages.length === 0) { + throw new Error('No messages provided to the plugin'); + } + let data; if (sandbox.module.exports[Symbol.toStringTag] === 'AsyncFunction') { data = await sandbox.module.exports(ctx, messages); diff --git a/src/main/backend/types/index.ts b/src/main/backend/types/index.ts index 739590f..cfd3c2b 100644 --- a/src/main/backend/types/index.ts +++ b/src/main/backend/types/index.ts @@ -70,3 +70,10 @@ export interface PluginConfig { usePlugin: boolean; pluginCode: string; } + +export interface DriverConfig { + hasPaused: boolean; + hasKeywordMatch: boolean; + hasUseGpt: boolean; + hasMouseClose: boolean; +} diff --git a/src/main/cron.ts b/src/main/cron.ts new file mode 100644 index 0000000..12a5e2b --- /dev/null +++ b/src/main/cron.ts @@ -0,0 +1,45 @@ +import axios from 'axios'; +import { BrowserWindow } from 'electron'; +import { setCron } from './system/cron'; +import type BackendServiceManager from './system/backend'; + +const setupCron = (mainWindow: BrowserWindow, bsm: BackendServiceManager) => { + const baseURL = (url: string) => { + return `http://127.0.0.1:${bsm.getPort()}/${url}`; + }; + + // 每隔 5 秒执行一次,通知和渲染进程刷新配置 + setCron('*/5 * * * * *', () => { + mainWindow.webContents.send('refresh-config'); + }); + + // 每隔 5 秒执行一次检查后端服务是否健康 + setCron('*/5 * * * * *', async () => { + if (!bsm) { + console.error('BackendServiceManager not found'); + return; + } + + const { + data: { data }, + } = await axios.get(baseURL(`api/v1/base/health`)); + mainWindow.webContents.send('check-health', data); + }); + + // 每隔 5 秒同步一次 Backend 服务的状态 + setCron('*/20 * * * * *', async () => { + if (!bsm) { + console.error('BackendServiceManager not found'); + return; + } + + // 为了避免依赖麻烦,这里直接通过 axios 发送请求 + try { + await axios.post(baseURL('api/v1/base/sync'), {}); + } catch (error) { + console.error('Error syncing backend service status:', error); + } + }); +}; + +export default setupCron; diff --git a/src/main/gptproxy/dify/chat/completions.ts b/src/main/gptproxy/dify/chat/completions.ts index 7c2fa0b..7ecb4c5 100644 --- a/src/main/gptproxy/dify/chat/completions.ts +++ b/src/main/gptproxy/dify/chat/completions.ts @@ -41,6 +41,7 @@ export class Completions extends APIResource { // return this.afterSSEResponse(response, controller); return Completions.fromOpenAIStream( 'your-model-id', + // @ts-ignore Stream.fromSSEResponse(response, controller), controller, ); @@ -49,6 +50,7 @@ export class Completions extends APIResource { return this.afterResponse( Completions.fromOpenAIStream( 'your-model-id', + // @ts-ignore Stream.fromSSEResponse(response, controller), controller, ), diff --git a/src/main/ipcHandlers.ts b/src/main/ipcHandlers.ts index 60ac0d5..49141e8 100644 --- a/src/main/ipcHandlers.ts +++ b/src/main/ipcHandlers.ts @@ -9,7 +9,6 @@ import { import Store from 'electron-store'; import os from 'os'; import path from 'path'; -import { setCron } from './system/cron'; import type BackendServiceManager from './system/backend'; import { getBrowserVersionFromOS } from './system/chrome'; @@ -92,22 +91,6 @@ const setupIpcHandlers = ( }; new Notification(notification).show(); }); - - // 每隔 5 秒执行一次 - setCron('*/5 * * * * *', () => { - mainWindow.webContents.send('refresh-config'); - }); - - // 每隔 5 秒执行一次检查后端服务是否健康 - setCron('*/5 * * * * *', async () => { - if (!bsm) { - console.error('BackendServiceManager not found'); - return; - } - - const isHealthy = await bsm.check_health(); - mainWindow.webContents.send('check-health', isHealthy); - }); }; export default setupIpcHandlers; diff --git a/src/main/main.ts b/src/main/main.ts index 5fa40d5..bb29e01 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -15,6 +15,7 @@ import { app, BrowserWindow, shell } from 'electron'; import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; import setupIpcHandlers from './ipcHandlers'; +import setupCron from './cron'; import BackendServiceManager from './system/backend'; import Server from './backend/backend'; @@ -114,6 +115,7 @@ const createWindow = async () => { } setupIpcHandlers(mainWindow, backendServiceManager); + setupCron(mainWindow, backendServiceManager); const server = new Server(backendServiceManager.getPort(), mainWindow); // 启动服务器 diff --git a/src/main/system/backend.ts b/src/main/system/backend.ts index bea954c..20adb58 100644 --- a/src/main/system/backend.ts +++ b/src/main/system/backend.ts @@ -1,6 +1,5 @@ import { spawn, exec } from 'child_process'; import { createServer } from 'net'; -import axios from 'axios'; import fs from 'fs'; import path from 'path'; import { getTempPath } from '../utils'; @@ -126,18 +125,6 @@ class BackendServiceManager { }); } - async check_health() { - // 发送 HTTP 请求检查服务是否健康 - try { - const { - data: { data }, - } = await axios.get(`http://127.0.0.1:${this.port}/api/v1/base/health`); - return data; - } catch (error) { - return false; - } - } - stop() { // 修改停止方法以标记不自动重启 return new Promise((resolve, reject) => { diff --git a/src/renderer/components/SystemCheck/index.tsx b/src/renderer/components/SystemCheck/index.tsx index 0d87221..7f3610c 100644 --- a/src/renderer/components/SystemCheck/index.tsx +++ b/src/renderer/components/SystemCheck/index.tsx @@ -19,7 +19,6 @@ import { useDisclosure, } from '@chakra-ui/react'; import { useWebSocketContext } from '../../hooks/useBroadcastContext'; -import { useSystemStore } from '../../stores/useSystemStore'; const SystemCheck = () => { const [humanTaskMsg, setHumanTaskMsg] = useState(''); @@ -27,13 +26,12 @@ const SystemCheck = () => { const [isModalOpen, setIsModalOpen] = useState(false); const cancelRef = React.useRef(); const { registerEventHandler } = useWebSocketContext(); - const { setDriverSettings, driverSettings } = useSystemStore(); useEffect(() => { const unregister = registerEventHandler((message) => { - if (message.message === 'chrome_download') { + if (message.event === 'chrome_download') { setIsModalOpen(true); - } else if (message.message === 'human_task') { + } else if (message.event === 'human_task') { if (!message.data) { return; } @@ -44,11 +42,6 @@ const SystemCheck = () => { '有需要人工处理的消息,请手动处理,注意处理完成后请取消暂停勾选。', ); - setDriverSettings({ - ...driverSettings, - isPaused: true, - }); - const data = message.data as { message: string; value: string; diff --git a/src/renderer/hooks/useBroadcastContext.tsx b/src/renderer/hooks/useBroadcastContext.tsx index ece3b75..459d5e9 100644 --- a/src/renderer/hooks/useBroadcastContext.tsx +++ b/src/renderer/hooks/useBroadcastContext.tsx @@ -9,9 +9,8 @@ import React, { // 定义 Broadcast 消息类型 interface BroadcastMessage { - message: string; data: any; - event_id: string; + event: string; } // 定义上下文类型 @@ -44,6 +43,7 @@ export const BroadcastProvider = ({ children }: { children: ReactNode }) => { window.electron.ipcRenderer.on('broadcast', (msg) => { const message = msg as BroadcastMessage; + console.log('Received broadcast', message); eventHandlers.forEach((handler) => handler(message)); }); diff --git a/src/renderer/pages/Home/AppManager/AppManagerContext.tsx b/src/renderer/pages/Home/AppManager/AppManagerContext.tsx index 9fc6f25..31bdfb7 100644 --- a/src/renderer/pages/Home/AppManager/AppManagerContext.tsx +++ b/src/renderer/pages/Home/AppManager/AppManagerContext.tsx @@ -181,7 +181,11 @@ export const AppManagerProvider = ({ children }: AppManagerProviderProps) => { if (selectedAppId) { setIsTasksLoading(true); try { - await addTask(selectedAppId); + const { error } = await addTask(selectedAppId); + if (error) { + throw new Error(error); + } + await refetchTasks(); } finally { setIsTasksLoading(false); diff --git a/src/renderer/pages/Home/AppManager/InstanceListComponent.tsx b/src/renderer/pages/Home/AppManager/InstanceListComponent.tsx index 5bdc24b..6df7118 100644 --- a/src/renderer/pages/Home/AppManager/InstanceListComponent.tsx +++ b/src/renderer/pages/Home/AppManager/InstanceListComponent.tsx @@ -7,6 +7,7 @@ import { IconButton, Tooltip, Spinner, + useToast, } from '@chakra-ui/react'; import { AddIcon } from '@chakra-ui/icons'; import InstanceCardComponent from './InstanceCardComponent'; @@ -26,6 +27,7 @@ const InstanceListComponent = () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [currentAppId, setCurrentAppId] = useState(selectedAppId); + const toast = useToast(); useEffect(() => { console.log('selectedAppId', selectedAppId, filteredInstances); @@ -33,6 +35,21 @@ const InstanceListComponent = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedAppId]); + const handleAddTaskWrapper = async () => { + try { + await handleAddTask(); + } catch (error) { + toast({ + title: '添加失败', + description: (error as Error).message || '未知错误', + position: 'top', + status: 'error', + duration: 5000, + isClosable: true, + }); + } + }; + let content; if (!selectedAppId) { @@ -83,7 +100,7 @@ const InstanceListComponent = () => { p={3} justify="center" cursor="pointer" - onClick={handleAddTask} + onClick={handleAddTaskWrapper} _hover={{ bg: 'gray.200' }} > { useEffect(() => { const unregister = registerEventHandler((message) => { - if (message.message === 'log_show') { + if (message.event === 'log_show') { if (message.data) { const log = message.data as { time: string; diff --git a/src/renderer/pages/Home/Panels/index.tsx b/src/renderer/pages/Home/Panels/index.tsx index f35db7c..7eba075 100644 --- a/src/renderer/pages/Home/Panels/index.tsx +++ b/src/renderer/pages/Home/Panels/index.tsx @@ -1,77 +1,97 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { Checkbox, Stack, HStack, Tooltip } from '@chakra-ui/react'; -import { updateRunner } from '../../../services/platform/controller'; -import { useSystemStore } from '../../../stores/useSystemStore'; +import { useQuery } from '@tanstack/react-query'; import { useToast } from '../../../hooks/useToast'; +import { getConfig, updateConfig } from '../../../services/platform/controller'; +import { DriverConfig } from '../../../services/platform/platform.d'; +import { useWebSocketContext } from '../../../hooks/useBroadcastContext'; const Panels = () => { const { toast } = useToast(); - const { driverSettings, setDriverSettings } = useSystemStore(); + const { registerEventHandler } = useWebSocketContext(); + const [driverSettings, setDriverSettings] = useState({ + hasPaused: true, + hasKeywordMatch: false, + hasUseGpt: false, + hasMouseClose: true, + }); + + const { data } = useQuery(['config', 'driver'], async () => { + try { + const resp = await getConfig({ + type: 'driver', + }); + return resp; + } catch (error) { + toast({ + title: '获取配置失败', + description: error instanceof Error ? error.message : String(error), + status: 'error', + }); + + return null; + } + }); useEffect(() => { - window.electron.ipcRenderer.on('refresh-config', () => { - const { isPaused, isKeywordMatch, isUseGpt } = driverSettings; - - (async () => { - try { - await updateRunner({ - is_paused: isPaused, - is_keyword_match: isKeywordMatch, - is_use_gpt: isUseGpt, - }); - } catch (error: any) { - console.error(error); - } - })(); - }); - - return () => { - window.electron.ipcRenderer.remove('refresh-config'); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const handleFormChange = (field: string) => (event: any) => { - const { value, checked, type } = event.target; - - const newDriverSettings = { - ...driverSettings, - [field]: type === 'checkbox' ? checked : value, - }; - - setDriverSettings(newDriverSettings); - - if (field === 'isPaused') { - if (checked) { - toast({ - position: 'top', - title: '已经暂停自动回复功能', - status: 'warning', + const unregister = registerEventHandler((message) => { + if (message.event === 'has_paused') { + setDriverSettings({ + ...driverSettings, + hasPaused: true, }); - } else { + toast({ + title: '自动回复已暂停', + status: 'info', position: 'top', - title: '已经开启自动回复功能', - status: 'success', + duration: 5000, + isClosable: true, }); } + }); - updateRunner({ - is_paused: checked, - is_keyword_match: driverSettings.isKeywordMatch, - is_use_gpt: driverSettings.isUseGpt, + // 组件卸载时注销事件处理器 + return () => unregister(); + }, [registerEventHandler]); // eslint-disable-line + + useEffect(() => { + if (data) { + const obj = data.data as DriverConfig; + setDriverSettings(obj); + } + }, [data]); + + const handleUpdateConfig = async (newConfig: Partial) => { + const updatedConfig = { ...driverSettings, ...newConfig }; + setDriverSettings(updatedConfig); + try { + await updateConfig({ + type: 'driver', + cfg: updatedConfig, }); - } else if (field === 'isKeywordMatch') { - updateRunner({ - is_paused: driverSettings.isPaused, - is_keyword_match: checked, - is_use_gpt: driverSettings.isUseGpt, - }); - } else if (field === 'isUseGpt') { - updateRunner({ - is_paused: driverSettings.isPaused, - is_keyword_match: driverSettings.isKeywordMatch, - is_use_gpt: checked, + + // 检查是否是 hasPaused 变更 + if ('hasPaused' in newConfig) { + toast({ + title: '更新配置成功', + description: newConfig.hasPaused + ? '已经暂停自动回复功能' + : '已经开启自动回复功能', + status: 'success', + duration: 5000, + isClosable: true, + }); + } + } catch (error) { + const errormsg = + error instanceof Error ? error.message : JSON.stringify(error); + toast({ + title: '更新配置失败', + description: errormsg, + status: 'error', + duration: 5000, + isClosable: true, }); } }; @@ -82,28 +102,46 @@ const Panels = () => { + handleUpdateConfig({ hasPaused: e.target.checked }) + } > 暂停软件 + handleUpdateConfig({ hasKeywordMatch: e.target.checked }) + } > 关键词匹配 + handleUpdateConfig({ hasUseGpt: e.target.checked }) + } > GPT 回复 + + + handleUpdateConfig({ hasMouseClose: e.target.checked }) + } + > + + 鼠标移动自动暂停 + + + ); diff --git a/src/renderer/services/platform/controller.ts b/src/renderer/services/platform/controller.ts index c508df6..89b87d6 100644 --- a/src/renderer/services/platform/controller.ts +++ b/src/renderer/services/platform/controller.ts @@ -6,6 +6,7 @@ import { LLMConfig, AccountConfig, PluginConfig, + DriverConfig, Message, } from './platform'; import { GET, POST } from '../common/api/request'; @@ -22,14 +23,6 @@ export async function updatePlatform(ids: string[]) { await POST('/api/v1/base/platform', ids); } -export async function updateRunner(data: { - is_paused: boolean; - is_keyword_match: boolean; - is_use_gpt: boolean; -}) { - await POST('/api/v1/base/runner', data); -} - export async function getReplyList({ page, pageSize, @@ -113,7 +106,12 @@ export async function getConfig({ instanceId?: string; }) { const data = await GET<{ - data: GenericConfig | LLMConfig | AccountConfig | PluginConfig; + data: + | GenericConfig + | LLMConfig + | AccountConfig + | PluginConfig + | DriverConfig; }>('/api/v1/base/setting', { appId, instanceId, @@ -131,7 +129,7 @@ export async function updateConfig({ type: string; appId?: string; instanceId?: string; - cfg: GenericConfig | LLMConfig | AccountConfig | PluginConfig; + cfg: GenericConfig | LLMConfig | AccountConfig | PluginConfig | DriverConfig; }) { await POST('/api/v1/base/setting', { appId, @@ -217,7 +215,9 @@ export async function getTasks() { } export async function addTask(appId: string) { - const data = await POST(`/api/v1/strategy/tasks`, { + const data = await POST<{ + error?: string; + }>(`/api/v1/strategy/tasks`, { appId, }); return data; diff --git a/src/renderer/services/platform/platform.d.ts b/src/renderer/services/platform/platform.d.ts index f35e55c..362c4a6 100644 --- a/src/renderer/services/platform/platform.d.ts +++ b/src/renderer/services/platform/platform.d.ts @@ -70,3 +70,10 @@ export interface PluginConfig { usePlugin: boolean; pluginCode: string; } + +export interface DriverConfig { + hasPaused: boolean; + hasKeywordMatch: boolean; + hasUseGpt: boolean; + hasMouseClose: boolean; +} diff --git a/src/renderer/stores/useSystemStore.ts b/src/renderer/stores/useSystemStore.ts index 12b94cd..0bd1710 100644 --- a/src/renderer/stores/useSystemStore.ts +++ b/src/renderer/stores/useSystemStore.ts @@ -29,36 +29,13 @@ type MessageState = { removeMessage: (index: number) => void; }; -type DriverState = { - driverSettings: { - isPaused: boolean; - isKeywordMatch: boolean; - isUseGpt: boolean; - }; - setDriverSettings: (settings: { - isPaused: boolean; - isKeywordMatch: boolean; - isUseGpt: boolean; - }) => void; -}; - -type State = MessageState & DriverState; +type State = MessageState; // Create Zustand store export const useSystemStore = create()( devtools( persist( immer((set) => ({ - driverSettings: { - isPaused: true, - isUseGpt: true, - isKeywordMatch: true, - }, - setDriverSettings: (settings) => { - set((state) => { - state.driverSettings = settings; - }); - }, context: {}, setContext: (key, value) => set((state) => { @@ -79,7 +56,6 @@ export const useSystemStore = create()( storage: createJSONStorage(() => electronStore), partialize: (state) => ({ context: state.context, - driverSettings: state.driverSettings, messages: state.messages, }), },