mirror of
https://github.com/cs-lazy-tools/ChatGPT-On-CS.git
synced 2026-08-28 10:00:27 +08:00
fix: 修复日志同步,add: 添加自动暂停功能
This commit is contained in:
@@ -2,5 +2,4 @@ PY_HOSTNAME=localhost
|
||||
PY_PORT=9999
|
||||
VAR=1234
|
||||
BKEXE_PATH=./backend/__main__.exe
|
||||
PKG_VERSION=0.0.1
|
||||
DEBUG=true
|
||||
PKG_VERSION=0.0.1
|
||||
@@ -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",
|
||||
|
||||
Generated
-3
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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<boolean> {
|
||||
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
|
||||
> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -70,3 +70,10 @@ export interface PluginConfig {
|
||||
usePlugin: boolean;
|
||||
pluginCode: string;
|
||||
}
|
||||
|
||||
export interface DriverConfig {
|
||||
hasPaused: boolean;
|
||||
hasKeywordMatch: boolean;
|
||||
hasUseGpt: boolean;
|
||||
hasMouseClose: boolean;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
// 启动服务器
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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<string>('');
|
||||
@@ -27,13 +26,12 @@ const SystemCheck = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const cancelRef = React.useRef<any>();
|
||||
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;
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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' }}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@@ -21,7 +21,7 @@ const LogBox = () => {
|
||||
|
||||
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;
|
||||
|
||||
@@ -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<DriverConfig>({
|
||||
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<DriverConfig>) => {
|
||||
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 = () => {
|
||||
<HStack width="full" alignItems="center">
|
||||
<Checkbox
|
||||
mr={4}
|
||||
isChecked={driverSettings.isPaused}
|
||||
onChange={handleFormChange('isPaused')}
|
||||
isChecked={driverSettings.hasPaused}
|
||||
onChange={(e) =>
|
||||
handleUpdateConfig({ hasPaused: e.target.checked })
|
||||
}
|
||||
>
|
||||
<Tooltip label="暂停软件后,将不再自动回复消息">暂停软件</Tooltip>
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
isChecked={driverSettings.isKeywordMatch}
|
||||
onChange={handleFormChange('isKeywordMatch')}
|
||||
isChecked={driverSettings.hasKeywordMatch}
|
||||
onChange={(e) =>
|
||||
handleUpdateConfig({ hasKeywordMatch: e.target.checked })
|
||||
}
|
||||
>
|
||||
<Tooltip label="将优先匹配关键词,未匹配的才去调用 GPT 接口">
|
||||
关键词匹配
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
isChecked={driverSettings.isUseGpt}
|
||||
onChange={handleFormChange('isUseGpt')}
|
||||
isChecked={driverSettings.hasUseGpt}
|
||||
onChange={(e) =>
|
||||
handleUpdateConfig({ hasUseGpt: e.target.checked })
|
||||
}
|
||||
>
|
||||
<Tooltip label="是否开启 GPT 回复,关闭后只会使用关键词回复">
|
||||
GPT 回复
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
</HStack>
|
||||
<HStack width="full" alignItems="center">
|
||||
<Checkbox
|
||||
isChecked={driverSettings.hasMouseClose}
|
||||
onChange={(e) =>
|
||||
handleUpdateConfig({ hasMouseClose: e.target.checked })
|
||||
}
|
||||
>
|
||||
<Tooltip label="是否开启鼠标移动时,自动暂停自动客服">
|
||||
鼠标移动自动暂停
|
||||
</Tooltip>
|
||||
</Checkbox>
|
||||
</HStack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -70,3 +70,10 @@ export interface PluginConfig {
|
||||
usePlugin: boolean;
|
||||
pluginCode: string;
|
||||
}
|
||||
|
||||
export interface DriverConfig {
|
||||
hasPaused: boolean;
|
||||
hasKeywordMatch: boolean;
|
||||
hasUseGpt: boolean;
|
||||
hasMouseClose: boolean;
|
||||
}
|
||||
|
||||
@@ -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<State>()(
|
||||
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<State>()(
|
||||
storage: createJSONStorage(() => electronStore),
|
||||
partialize: (state) => ({
|
||||
context: state.context,
|
||||
driverSettings: state.driverSettings,
|
||||
messages: state.messages,
|
||||
}),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user