add: 添加配置项

This commit is contained in:
lrhh123
2024-06-10 09:39:16 +08:00
parent ac7aa86f0d
commit 23e89496ea
9 changed files with 481 additions and 261 deletions
+87 -65
View File
@@ -145,6 +145,40 @@ class BKServer {
}),
);
// 取得平台是否激活
this.app.get(
'/api/v1/base/platform/active',
asyncHandler(async (req, res) => {
const { appId, instanceId } = req.query;
const active = await configController.checkConfigActive({
appId: appId ? String(appId) : undefined,
instanceId: instanceId ? String(instanceId) : undefined,
});
res.json({
success: true,
data: {
active,
},
});
}),
);
// 更新平台激活状态
this.app.post(
'/api/v1/base/platform/active',
asyncHandler(async (req, res) => {
const { appId, instanceId, active } = req.body;
await configController.activeConfig({
appId: appId ? String(appId) : undefined,
instanceId: instanceId ? String(instanceId) : undefined,
active,
});
res.json({
success: true,
});
}),
);
// 获取配置
this.app.get(
'/api/v1/base/setting',
@@ -214,69 +248,6 @@ class BKServer {
}
});
// // Endpoint to retrieve configuration settings
// this.app.get('/api/v1/base/settings', async (req, res) => {
// try {
// const config = await configController.getConfig();
// // @ts-ignore 兼容性处理
// config.reply_speed = [config.reply_speed, config.reply_random_speed];
// res.json({ success: true, data: config });
// } catch (error) {
// if (error instanceof Error) {
// res.status(500).json({ success: false, message: error.message });
// }
// }
// });
// // Endpoint to update configuration settings
// this.app.post('/api/v1/base/settings', async (req, res) => {
// try {
// const cfg = req.body as {
// extract_phone: boolean; // 提取手机号
// extract_product: boolean; // 提取商品
// save_path?: string; // 保存路径
// default_reply?: string; // 默认回复
// reply_speed: number[]; // 回复速度
// context_count: number; // 合并消息数量
// wait_humans_time: number; // 等待人工时间
// gpt_base_url?: string; // GPT服务地址
// gpt_key?: string; // GPT服务key
// gpt_model?: string; // GPT服务模型
// gpt_temperature?: number; // GPT服务温度
// gpt_top_p?: number; // GPT服务top_p
// stream?: boolean; // 是否开启stream
// use_dify?: boolean; // 是否使用 Dify 百宝箱
// };
// if (!cfg.reply_speed || cfg.reply_speed.length !== 2) {
// cfg.reply_speed = [0, 0];
// }
// await configController.updateConfig(1, {
// extract_phone: cfg.extract_phone,
// extract_product: cfg.extract_product,
// default_reply: cfg.default_reply,
// save_path: cfg.save_path,
// reply_speed: cfg.reply_speed[0],
// reply_random_speed: cfg.reply_speed[1],
// context_count: cfg.context_count,
// wait_humans_time: cfg.wait_humans_time,
// gpt_base_url: cfg.gpt_base_url,
// gpt_key: cfg.gpt_key,
// gpt_model: cfg.gpt_model,
// gpt_temperature: cfg.gpt_temperature,
// gpt_top_p: cfg.gpt_top_p,
// stream: cfg.stream,
// use_dify: cfg.use_dify,
// });
// res.json({ success: true });
// } catch (error) {
// if (error instanceof Error) {
// res.status(500).json({ success: false, message: error.message });
// }
// }
// });
this.app.get('/api/v1/reply/list', async (req, res) => {
const { page = 1, page_size: pageSize, ptf_id: platformId } = req.query;
@@ -391,8 +362,7 @@ class BKServer {
// 检查 GPT 链接是否正常
this.app.get('/api/v1/base/gpt/health', async (req, res) => {
const { base_url: gptBaseUrl, key, use_dify: useDify, model } = req.query;
// const { base_url: gptBaseUrl, key, use_dify: useDify, model } = req.query;
// try {
// const { status, message } = await this.messageService.checkApiHealth({
// baseUrl: String(gptBaseUrl),
@@ -412,6 +382,58 @@ class BKServer {
// });
// }
});
// 获取任务列表
this.app.get('/api/v1/strategy/tasks', async (req, res) => {
const { appId } = req.query;
try {
const tasks = await this.dispatchService.getTasks(String(appId));
res.json({
success: true,
data: tasks,
});
} catch (error) {
console.error(error);
res.json({
success: false,
data: null,
});
}
});
// 添加任务
this.app.post('/api/v1/strategy/tasks', async (req, res) => {
const { appId } = req.body;
try {
const task = await this.dispatchService.addTask(String(appId));
res.json({
success: true,
data: task,
});
} catch (error) {
console.error(error);
res.json({
success: false,
data: null,
});
}
});
// 删除任务
this.app.post('/api/v1/strategy/task/remove', async (req, res) => {
const { taskId } = req.body;
try {
await this.dispatchService.removeTask(String(taskId));
res.json({
success: true,
});
} catch (error) {
console.error(error);
res.json({
success: false,
});
}
});
}
// 启动服务器的方法
@@ -75,12 +75,23 @@ export class ConfigController {
config = await Config.findOne({
where: { instance_id: instanceId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
instance_id: instanceId,
});
}
}
if (!config && appId) {
config = await Config.findOne({
where: { platform_id: appId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
});
}
}
if (!config) {
@@ -112,19 +123,21 @@ export class ConfigController {
config = await Config.findOne({
where: { instance_id: instanceId },
});
return config?.active || false;
}
if (!config && appId) {
if (appId) {
config = await Config.findOne({
where: { platform_id: appId },
});
return config?.active || false;
}
if (!config) {
config = await Config.findOne({
where: { global: true },
});
}
config = await Config.findOne({
where: { global: true },
});
return config?.active || false;
}
@@ -152,12 +165,25 @@ export class ConfigController {
config = await Config.findOne({
where: { instance_id: instanceId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
instance_id: instanceId,
});
}
}
if (!config && appId) {
config = await Config.findOne({
where: { platform_id: appId },
});
if (!config) {
config = await Config.create({
platform_id: appId,
});
}
}
if (!config) {
@@ -284,10 +310,7 @@ export class ConfigController {
let pluginId = null;
const config = cfg as PluginConfig;
if (dbConfig.use_plugin) {
let plugin = await Plugin.findOne({
where: { code: config.pluginCode },
});
let plugin = await Plugin.findByPk(dbConfig.plugin_id);
if (!plugin) {
plugin = await Plugin.create({
code: config.pluginCode,
@@ -83,6 +83,39 @@ export class DispatchService {
}
}
public async getTasks(appId: string): Promise<any> {
try {
return await emitAndWait(this.io, 'strategyService-getTasks', {
app_id: appId,
});
} catch (error) {
console.error('Failed to get tasks', error);
return null;
}
}
public async addTask(appId: string): Promise<any> {
try {
return await emitAndWait(this.io, 'strategyService-addTask', {
app_id: appId,
});
} catch (error) {
console.error('Failed to add task', error);
return null;
}
}
public async removeTask(taskId: string): Promise<any> {
try {
return await emitAndWait(this.io, 'strategyService-removeTask', {
task_id: taskId,
});
} catch (error) {
console.error('Failed to remove task', error);
return null;
}
}
public async updateStatus(status: StrategyServiceStatusEnum): Promise<any> {
try {
return await emitAndWait(this.io, 'strategyService-updateStatus', {
+1 -9
View File
@@ -11,7 +11,6 @@ import SettingsPage from './pages/Settings';
import AboutPage from './pages/About';
import MsgList from './pages/MsgList';
import FullScreenLoader from './pages/FullScreenLoader';
import { SettingsProvider } from './components/Settings/SettingsContext';
import Updater from './components/Updater';
import SystemCheck from './components/SystemCheck';
import { BroadcastProvider } from './hooks/useBroadcastContext';
@@ -61,14 +60,7 @@ function App() {
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/msg" element={<MsgList />} />
<Route
path="/settings"
element={
<SettingsProvider>
<SettingsPage />
</SettingsProvider>
}
/>
<Route path="/settings" element={<SettingsPage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Box>
+1 -1
View File
@@ -63,7 +63,7 @@ const AboutPage: React.FC = () => {
本项目是基于大模型的智能对话客服工具,支持哔哩哔哩、抖音企业号、抖音、抖店、微博聊天、小红书专业号运营、小红书、知乎等平台接入,可选择 GPT3.5/GPT4.0,能处理文本、语音和图片,通过插件访问操作系统和互联网等外部资源,支持基于自有知识库定制企业 AI 应用。
## 使用说明
项目文档: [懒人百宝箱使用说明](https://gitee.com/alsritter/ChatGPT-On-CS)
项目文档: [懒人百宝箱使用说明](https://doc.lazaytools.top/)
## 演示视频
[哔哩哔哩](https://www.bilibili.com/video/BV1qz421Q73S)
@@ -1,165 +0,0 @@
import React, { useEffect, useState } from 'react';
import {
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
Tooltip,
Checkbox,
Stack,
Image,
Box,
Skeleton,
CheckboxGroup,
IconButton,
VStack,
Grid,
} from '@chakra-ui/react';
import { SettingsIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query';
import {
getPlatformList,
updatePlatform,
} from '../../../services/platform/controller';
import { Platform } from '../../../services/platform/platform';
import PlatformSettings from '../../../components/PlatformConfigs';
import {
PlatformTypeMap,
PlatformTypeEnum,
} from '../../../services/platform/constant';
import { useSystemStore } from '../../../stores/useSystemStore';
import defaultPlatformIcon from '../../../../assets/base/default-platform-icon.png';
import windowsIcon from '../../../../assets/base/windows.png';
import { trackCheckboxChange } from '../../../services/analytics';
const PlatformTabs = () => {
const { data, isLoading } = useQuery(['platformList'], getPlatformList);
const [firstLoad, setFirstLoad] = useState<boolean>(true);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [selectedPlatformId, setSelectedPlatformId] = useState<string | null>(
null,
);
const { selectedPlatforms, setSelectedPlatforms } = useSystemStore();
// 处理数据加载完毕后的平台分组
const groupedPlatforms = data?.data.reduce(
(acc, platform) => {
const type = String(platform.type) || 'other';
if (!acc[type]) acc[type] = [];
acc[type].push(platform);
return acc;
},
{} as Record<string, Platform[]>,
);
// 第一次加载时需要更新一次后端
useEffect(() => {
if (selectedPlatforms && firstLoad) {
updatePlatform(selectedPlatforms);
setFirstLoad(false);
}
}, [firstLoad, setFirstLoad, selectedPlatforms]);
const handleCheckboxChange = async (selectedIds: string[]) => {
setSelectedPlatforms(selectedIds);
await updatePlatform(selectedIds);
trackCheckboxChange('platforms', selectedIds);
};
if (isLoading) {
return (
<Stack>
<Skeleton height="20px" />
<Skeleton height="20px" />
<Skeleton height="20px" />
</Stack>
);
}
const renderTabPanels = () => {
const types = Object.keys(groupedPlatforms || {});
return types.map((type) => (
<TabPanel key={type}>
<CheckboxGroup
colorScheme="green"
value={selectedPlatforms}
onChange={handleCheckboxChange}
>
<Grid templateColumns="repeat(2, 1fr)" gap={4}>
{groupedPlatforms?.[type]?.map((platform) => (
<Box key={platform.id} display="flex" alignItems="center">
{/* 显示平台图标,如果没有则显示默认图标 */}
<Image
src={platform.avatar || defaultPlatformIcon}
fallbackSrc={defaultPlatformIcon}
boxSize="25px"
marginRight="12px"
/>
<Checkbox value={platform.id} isDisabled={!platform.impl}>
{platform.name}
</Checkbox>
<VStack>
{platform.impl && (
<Tooltip label={`设置 ${platform.name} 平台`}>
<IconButton
variant="borderless"
aria-label={`设置 ${platform.name} 平台`}
fontSize="12px"
w={2}
h={2}
icon={<SettingsIcon />}
onClick={() => {
setSelectedPlatformId(platform.id);
setIsSettingsOpen(true);
}}
/>
</Tooltip>
)}
{
// 如果 id 前缀有 win_ 则显示一个小图标标识
platform.id.startsWith('win_') && (
<Tooltip label="客户端应用,需要先手动打开该应用">
<Image
src={windowsIcon}
boxSize="10px"
marginLeft="4px"
alt="windows"
/>
</Tooltip>
)
}
</VStack>
</Box>
))}
</Grid>
</CheckboxGroup>
</TabPanel>
));
};
return (
<>
<Tabs>
<TabList>
{Object.keys(groupedPlatforms || {}).map((type) => (
<Tab key={type}>{PlatformTypeMap[type as PlatformTypeEnum]}</Tab>
))}
</TabList>
<TabPanels>{renderTabPanels()}</TabPanels>
</Tabs>
{selectedPlatformId && (
<PlatformSettings
platformId={selectedPlatformId}
isOpen={isSettingsOpen}
onClose={() => setIsSettingsOpen(false)}
/>
)}
</>
);
};
export default PlatformTabs;
@@ -0,0 +1,179 @@
import React, { useEffect, useState } from 'react';
import {
ChakraProvider,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
Checkbox,
Box,
Flex,
Text,
useToast,
} from '@chakra-ui/react';
import GeneralSettings from '../../../components/Settings/GeneralSettings';
import LLMSettings from '../../../components/Settings/LLMSettings';
import PluginSettings from '../../../components/Settings/PluginSettings';
import {
activeConfig,
checkConfigActive,
} from '../../../services/platform/controller';
const SettingsModal = ({
isOpen,
onClose,
appId,
instanceId,
}: {
isOpen: boolean;
onClose: () => void;
appId?: string;
instanceId?: string;
}) => {
const toast = useToast();
const [isActive, setIsActive] = useState(false);
const [data, setData] = useState<{
active: boolean;
} | null>(null);
const fetchConfigActive = async () => {
try {
const resp = await checkConfigActive({ appId, instanceId });
// @ts-ignore
setData(resp.data);
} catch (error) {
const errormsg =
error instanceof Error ? error.message : JSON.stringify(error);
toast({
title: '获取配置失败',
description: errormsg,
status: 'error',
duration: 5000,
isClosable: true,
});
setData(null);
}
};
useEffect(() => {
fetchConfigActive();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appId, instanceId]);
useEffect(() => {
if (data) {
setIsActive(data.active);
}
}, [data]);
const handleCheckboxChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
if (!data) {
return;
}
try {
setIsActive(event.target.checked);
await activeConfig({
active: event.target.checked,
appId,
instanceId,
});
toast({
title: '更新配置成功',
position: 'top',
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,
});
}
};
return (
<ChakraProvider>
<Modal isOpen={isOpen} onClose={onClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Checkbox
ml={4}
isChecked={isActive}
onChange={handleCheckboxChange}
>
{' '}
{instanceId ? `客服 ${instanceId} 设置` : `应用 ${appId} 设置`}
</Checkbox>
<Text color="gray.500" fontSize="sm">
</Text>
</ModalHeader>
<ModalCloseButton />
<ModalBody position="relative" overflowY="auto" maxHeight="60vh">
{!isActive && (
<Flex
position="absolute"
top="0"
left="0"
right="0"
bottom="0"
backgroundColor="rgba(0, 0, 0, 0.5)"
justifyContent="center"
alignItems="center"
zIndex="1"
>
<Text color="white" fontSize="lg">
</Text>
</Flex>
)}
<Box
opacity={isActive ? 1 : 0.4}
pointerEvents={isActive ? 'auto' : 'none'}
>
<Tabs variant="enclosed" isFitted>
<TabList>
<Tab></Tab>
<Tab>AI </Tab>
<Tab></Tab>
</TabList>
<TabPanels>
<TabPanel>
<GeneralSettings appId={appId} instanceId={instanceId} />
</TabPanel>
<TabPanel>
<LLMSettings appId={appId} instanceId={instanceId} />
</TabPanel>
<TabPanel>
<PluginSettings appId={appId} instanceId={instanceId} />
</TabPanel>
</TabPanels>
</Tabs>
</Box>
</ModalBody>
</ModalContent>
</Modal>
</ChakraProvider>
);
};
export default React.memo(SettingsModal);
+88 -9
View File
@@ -14,6 +14,7 @@ import {
IconButton,
Stack,
Skeleton,
useToast,
} from '@chakra-ui/react';
import {
SearchIcon,
@@ -24,12 +25,24 @@ import {
import { useQuery } from '@tanstack/react-query';
import defaultPlatformIcon from '../../../../../assets/base/default-platform-icon.png';
import windowsIcon from '../../../../../assets/base/windows.png';
import { getPlatformList } from '../../../services/platform/controller';
import {
getPlatformList,
getTasks,
removeTask,
addTask,
} from '../../../services/platform/controller';
import SettingsModal from './SettingsModal';
const AppManagerComponent = () => {
const toast = useToast();
const { data, isLoading } = useQuery(['platformList'], getPlatformList);
const [selectedApp, setSelectedApp] = useState<number | null>(null);
const [selectedInstance, setSelectedInstance] = useState<number | null>(null);
const [selectedAppId, setSelectedAppId] = useState<string | null>(null);
const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(
null,
);
const [filteredInstances, setFilteredInstances] = useState<
{
task_id: string;
@@ -38,6 +51,10 @@ const AppManagerComponent = () => {
avatar: string;
}[]
>([]);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const openSettings = () => setIsSettingsOpen(true);
const closeSettings = () => setIsSettingsOpen(false);
const high = '42vh'; // 调整为合适的全局高度
const instances = [
@@ -59,11 +76,19 @@ const AppManagerComponent = () => {
{ task_id: '3136613131', app_id: 'zhihu', env_id: '2' },
];
const getTasksQuery = useQuery(
['tasks', selectedAppId],
() => getTasks(selectedAppId || ''),
{
enabled: selectedAppId !== null,
},
);
useEffect(() => {
if (selectedApp !== null && data && data.data) {
const selectedAppId = data.data[selectedApp]?.id;
const said = data.data[selectedApp]?.id;
const matchedInstances = instances.filter(
(instance) => instance.app_id === selectedAppId,
(instance) => instance.app_id === said,
);
setFilteredInstances(
matchedInstances.map((x) => {
@@ -73,12 +98,36 @@ const AppManagerComponent = () => {
};
}),
);
const appId = data.data[selectedApp]?.id;
setSelectedAppId(appId || null);
if (appId) {
getTasksQuery.refetch();
}
} else {
setFilteredInstances([]);
setSelectedAppId(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedApp, data]);
useEffect(() => {
if (selectedInstance !== null) {
setSelectedInstanceId(
filteredInstances[selectedInstance]?.task_id || null,
);
} else {
setSelectedInstanceId(null);
}
}, [selectedInstance, filteredInstances]);
useEffect(() => {
if (getTasksQuery.data) {
console.log('getTasksQuery.data', getTasksQuery.data);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getTasksQuery.data]);
if (isLoading || !data || !data.data) {
return (
<Stack>
@@ -89,10 +138,32 @@ const AppManagerComponent = () => {
);
}
const handleDelete = (taskId: string) => {
setFilteredInstances(
filteredInstances.filter((instance) => instance.task_id !== taskId),
);
const handleDelete = async (taskId: string) => {
try {
await removeTask(taskId);
setFilteredInstances(
filteredInstances.filter((instance) => instance.task_id !== taskId),
);
} catch (error) {
const errormsg = error instanceof Error ? error.message : '未知错误';
toast({
title: '删除失败',
position: 'top',
description: errormsg,
status: 'error',
duration: 5000,
isClosable: true,
});
}
};
const handleAddTask = async () => {
if (!selectedAppId) {
return;
}
await addTask(selectedAppId);
getTasksQuery.refetch();
};
return (
@@ -194,7 +265,7 @@ const AppManagerComponent = () => {
w={4}
h={4}
icon={<SettingsIcon />}
onClick={() => {}}
onClick={openSettings}
/>
</Tooltip>
</Box>
@@ -238,7 +309,7 @@ const AppManagerComponent = () => {
fontSize="15px"
aria-label="Settings"
icon={<SettingsIcon />}
onClick={() => {}}
onClick={openSettings}
/>
<IconButton
color="red.500"
@@ -274,11 +345,19 @@ const AppManagerComponent = () => {
aria-label="Add instance"
variant="unstyled"
icon={<AddIcon />}
onClick={handleAddTask}
/>
</Flex>
</Tooltip>
</VStack>
</Box>
<SettingsModal
isOpen={isSettingsOpen}
onClose={closeSettings}
appId={selectedAppId || undefined}
instanceId={selectedInstanceId || undefined}
/>
</Flex>
);
};
+59 -2
View File
@@ -71,6 +71,38 @@ export async function exportReplyExcel() {
return data;
}
export async function activeConfig({
active,
appId,
instanceId,
}: {
active: boolean;
appId?: string;
instanceId?: string;
}) {
await POST('/api/v1/base/platform/active', {
active,
appId,
instanceId,
});
}
export async function checkConfigActive({
appId,
instanceId,
}: {
appId?: string;
instanceId?: string;
}) {
const data = await GET<{
active: boolean;
}>('/api/v1/base/platform/active', {
appId,
instanceId,
});
return data;
}
export async function getConfig({
type,
appId,
@@ -83,8 +115,8 @@ export async function getConfig({
const data = await GET<{
data: GenericConfig | LLMConfig | AccountConfig | PluginConfig;
}>('/api/v1/base/setting', {
app_id: appId,
instance_id: instanceId,
appId,
instanceId,
type,
});
return data;
@@ -154,3 +186,28 @@ export async function checkGptHealth(data: {
});
return resp;
}
export async function getTasks(appId: string) {
const data = await GET<{
data: {
[key: string]: any;
};
}>(`/api/v1/strategy/tasks`, {
appId,
});
return data;
}
export async function addTask(appId: string) {
const data = await POST(`/api/v1/strategy/tasks`, {
appId,
});
return data;
}
export async function removeTask(taskId: string) {
const data = await POST(`/api/v1/strategy/task/remove`, {
taskId,
});
return data;
}