mirror of
https://github.com/cs-lazy-tools/ChatGPT-On-CS.git
synced 2026-08-28 18:17:40 +08:00
feat: 移除企业微信支持
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
<a href="./README.md">简体中文</a>
|
||||
</p>
|
||||
|
||||
懒人客服是一个基于 LLM 大语言模型的知识库的集成客服系统,提供开箱即用的智能客服解决方案,支持微信、企业微信、千牛、哔哩哔哩、抖音企业号、抖音、抖店、拼多多、微博聊天、小红书专业号运营、小红书、知乎等平台接入,支持文本、语音和图片,通过插件访问操作系统和互联网等外部资源,支持基于自有知识库定制企业 AI 应用.
|
||||
懒人客服是一个基于 LLM 大语言模型的知识库的集成客服系统,提供开箱即用的智能客服解决方案,支持微信、千牛、哔哩哔哩、抖音企业号、抖音、抖店、拼多多、微博聊天、小红书专业号运营、小红书、知乎等平台接入,支持文本、语音和图片,通过插件访问操作系统和互联网等外部资源,支持基于自有知识库定制企业 AI 应用.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
* [Gitee] https://gitee.com/alsritter/ChatGPT-On-CS · (国内用户推荐)
|
||||
|
||||
## 主要功能
|
||||
- [x] 多平台支持:当前支持微信、企业微信、千牛、哔哩哔哩、抖音企业号、抖音、抖店、微博聊天、小红书专业号运营、小红书、知乎等平台,未来将不断扩展支持更多社交媒体平台.
|
||||
- [x] 多平台支持:当前支持微信、千牛、哔哩哔哩、抖音企业号、抖音、抖店、微博聊天、小红书专业号运营、小红书、知乎等平台,未来将不断扩展支持更多社交媒体平台.
|
||||
- [x] 预设回复内容:允许用户设置自定义回复,以应对常见问题,提高回复效率.
|
||||
- [x] 接入ChatGPT接口,根据客户的咨询内容智能生成回复,适用于处理复杂或者个性化的客户咨询.
|
||||
- [x] 发送图片和二进制文件:支持发送图片等二进制文件,满足多样化的客户服务需求.
|
||||
@@ -79,15 +79,12 @@ https://down.wizgadg.top/chatgpt-on-cs
|
||||
- [x] 优化千牛的对接模式
|
||||
- [x] 优化微信平台的回复速度
|
||||
- [x] 添加中文路径的支持
|
||||
- [x] 支持企业微信外部群聊天功能
|
||||
- [x] 支持自动检测人类操作后自动暂停项目
|
||||
- [ ] 新增 wx 群聊需要使用 @ 机器人才能回复的功能
|
||||
- [x] 支持浏览器多开功能
|
||||
- [x] 支持导出聊天记录到 Excel
|
||||
- [ ] 支持关键词匹配测试功能
|
||||
- [x] 实现小红书回复后,自动私聊的功能
|
||||
- [x] 支持企业微信自动识别内部群聊
|
||||
- [ ] 支持企业微信自动加人功能
|
||||
- [ ] 支持抖音直播平台自动回复功能
|
||||
- [ ] 支持拼多多
|
||||
- [ ] 支持多平台文章自动发送功能
|
||||
|
||||
Generated
+1015
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ import { loader } from '@monaco-editor/react';
|
||||
import { HashRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import GeneralSettings from './components/Settings/GeneralSettings';
|
||||
import LLMSettings from './components/Settings/LLMSettings';
|
||||
import LLMSettings from './components/LLMSettings';
|
||||
import PluginPage from './pages/Plugin';
|
||||
import PluginEditPage from './pages/PluginEdit';
|
||||
import AboutPage from './components/About';
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Select,
|
||||
Input,
|
||||
Highlight,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Button,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||
import { LLMConfig } from '../../../common/services/platform/platform';
|
||||
import { ModelList, LLMTypeList } from '../../../common/utils/constants';
|
||||
|
||||
interface ThirdPartyInterfaceProps {
|
||||
config: LLMConfig;
|
||||
handleUpdateConfig: (newConfig: Partial<LLMConfig>) => void;
|
||||
handleBaseURLChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
handleCheckHealth: () => void;
|
||||
reply: string;
|
||||
show: boolean;
|
||||
setShow: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const ThirdPartyInterface: React.FC<ThirdPartyInterfaceProps> = ({
|
||||
config,
|
||||
handleUpdateConfig,
|
||||
handleBaseURLChange,
|
||||
handleCheckHealth,
|
||||
reply,
|
||||
show,
|
||||
setShow,
|
||||
}) => (
|
||||
<>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="llmType">选择大模型类型</FormLabel>
|
||||
<Select
|
||||
id="llmType"
|
||||
placeholder="选择大模型类型"
|
||||
value={config.llmType}
|
||||
onChange={(e) => handleUpdateConfig({ llmType: e.target.value })}
|
||||
>
|
||||
{LLMTypeList.map((type) => (
|
||||
<option key={type.key} value={type.key}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="model">选择或输入模型</FormLabel>
|
||||
<InputGroup>
|
||||
<Input
|
||||
id="model"
|
||||
placeholder="选择或输入模型"
|
||||
value={config.model}
|
||||
onChange={(e) => handleUpdateConfig({ model: e.target.value })}
|
||||
list="models"
|
||||
/>
|
||||
<datalist id="models">
|
||||
{ModelList.map((model) => (
|
||||
<option key={model.key} value={model.key}>
|
||||
{model.name}
|
||||
</option>
|
||||
))}
|
||||
</datalist>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="gptAddress" mt="8px">
|
||||
<Highlight query="/v1" styles={{ px: '1', py: '1', bg: 'orange.100' }}>
|
||||
API 地址设置(尾部需要加上 /v1)
|
||||
</Highlight>
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
ml="4"
|
||||
loadingText="检查中"
|
||||
onClick={handleCheckHealth}
|
||||
>
|
||||
检查连接
|
||||
</Button>
|
||||
</FormLabel>
|
||||
<InputGroup size="sm">
|
||||
<Input
|
||||
id="gptAddress"
|
||||
value={config.baseUrl}
|
||||
placeholder="输入站点地址"
|
||||
onChange={handleBaseURLChange}
|
||||
/>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="apiKey">API Key</FormLabel>
|
||||
<InputGroup size="md">
|
||||
<Input
|
||||
id="apiKey"
|
||||
pr="4.5rem"
|
||||
type={show ? 'text' : 'password'}
|
||||
placeholder="Enter password"
|
||||
value={config.key}
|
||||
onChange={(e) => handleUpdateConfig({ key: e.target.value })}
|
||||
/>
|
||||
<InputRightElement width="4.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
{show ? <ViewIcon /> : <ViewOffIcon />}
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
{reply && (
|
||||
<>
|
||||
<Text>回复内容</Text>
|
||||
<Text>{reply}</Text>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
export default ThirdPartyInterface;
|
||||
@@ -0,0 +1,186 @@
|
||||
import React, { useState, useEffect, ChangeEvent } from 'react';
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Select,
|
||||
VStack,
|
||||
Text,
|
||||
useToast,
|
||||
Stack,
|
||||
Skeleton,
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
getConfig,
|
||||
updateConfig,
|
||||
checkGptHealth,
|
||||
} from '../../../common/services/platform/controller';
|
||||
import { LLMConfig } from '../../../common/services/platform/platform';
|
||||
import ThirdPartyInterface from './ThirdParty';
|
||||
|
||||
const LazybonesToolbox: React.FC = () => <Text>懒人百宝箱接入的内容</Text>;
|
||||
|
||||
const UseCoze: React.FC = () => <Text>使用 Coze 的内容</Text>;
|
||||
|
||||
interface LLMSettingsProps {
|
||||
appId?: string;
|
||||
instanceId?: string;
|
||||
}
|
||||
|
||||
const LLMSettings: React.FC<LLMSettingsProps> = ({ appId, instanceId }) => {
|
||||
const toast = useToast();
|
||||
const [show, setShow] = useState(false);
|
||||
const [reply, setReply] = useState('');
|
||||
const [selectedOption, setSelectedOption] = useState('thirdParty');
|
||||
|
||||
const { data, isLoading } = useQuery(
|
||||
['config', 'llm', appId, instanceId],
|
||||
async () => {
|
||||
try {
|
||||
const resp = await getConfig({
|
||||
appId,
|
||||
instanceId,
|
||||
type: 'llm',
|
||||
});
|
||||
return resp;
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '获取配置失败',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const [config, setConfig] = useState<LLMConfig>({
|
||||
appId: '',
|
||||
instanceId: '',
|
||||
llmType: '',
|
||||
model: '',
|
||||
baseUrl: '',
|
||||
key: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const obj = data.data as LLMConfig;
|
||||
setConfig(obj);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleUpdateConfig = async (newConfig: Partial<LLMConfig>) => {
|
||||
const updatedConfig = { ...config, ...newConfig };
|
||||
setConfig(updatedConfig);
|
||||
try {
|
||||
await updateConfig({
|
||||
appId,
|
||||
instanceId,
|
||||
type: 'llm',
|
||||
cfg: updatedConfig,
|
||||
});
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '更新配置失败',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBaseURLChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
let { value } = e.target;
|
||||
if (!value.startsWith('http://') && !value.startsWith('https://')) {
|
||||
value = `https://${value}`;
|
||||
}
|
||||
|
||||
handleUpdateConfig({ baseUrl: value });
|
||||
};
|
||||
|
||||
const handleCheckHealth = async () => {
|
||||
try {
|
||||
if (!config) return;
|
||||
const resp = await checkGptHealth(config);
|
||||
if (!resp.status) {
|
||||
throw new Error(resp.message);
|
||||
}
|
||||
|
||||
setReply(resp.message);
|
||||
|
||||
toast({
|
||||
title: '连接成功',
|
||||
position: 'top',
|
||||
description: 'GPT 连接成功',
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '连接失败',
|
||||
position: 'top',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton height="20px" />
|
||||
<Skeleton height="20px" />
|
||||
<Skeleton height="20px" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack spacing="4" align="start">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="option">选择选项</FormLabel>
|
||||
<Select
|
||||
id="option"
|
||||
value={selectedOption}
|
||||
onChange={(e) => setSelectedOption(e.target.value)}
|
||||
>
|
||||
<option value="lazybonesToolbox">懒人百宝箱接入</option>
|
||||
<option value="useCoze">使用 Coze</option>
|
||||
<option value="thirdParty">三方接口接入</option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{selectedOption === 'thirdParty' && (
|
||||
<ThirdPartyInterface
|
||||
config={config}
|
||||
handleUpdateConfig={handleUpdateConfig}
|
||||
handleBaseURLChange={handleBaseURLChange}
|
||||
handleCheckHealth={handleCheckHealth}
|
||||
reply={reply}
|
||||
show={show}
|
||||
setShow={setShow}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedOption === 'lazybonesToolbox' && <LazybonesToolbox />}
|
||||
|
||||
{selectedOption === 'useCoze' && <UseCoze />}
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default LLMSettings;
|
||||
@@ -1,259 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Select,
|
||||
Input,
|
||||
Highlight,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Button,
|
||||
VStack,
|
||||
Text,
|
||||
useToast,
|
||||
Stack,
|
||||
Skeleton,
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
getConfig,
|
||||
updateConfig,
|
||||
checkGptHealth,
|
||||
} from '../../../common/services/platform/controller';
|
||||
import { LLMConfig } from '../../../common/services/platform/platform.d';
|
||||
import { ModelList, LLMTypeList } from '../../../common/utils/constants';
|
||||
|
||||
const LLMSettings = ({
|
||||
appId,
|
||||
instanceId,
|
||||
}: {
|
||||
appId?: string;
|
||||
instanceId?: string;
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
const [show, setShow] = useState(false);
|
||||
const [reply, setReply] = useState(''); // 新增一个状态用于展示回复内容
|
||||
|
||||
const { data, isLoading } = useQuery(
|
||||
['config', 'llm', appId, instanceId],
|
||||
async () => {
|
||||
try {
|
||||
const resp = await getConfig({
|
||||
appId,
|
||||
instanceId,
|
||||
type: 'llm',
|
||||
});
|
||||
return resp;
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '获取配置失败',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const [config, setConfig] = useState<LLMConfig>({
|
||||
appId: '',
|
||||
instanceId: '',
|
||||
llmType: '',
|
||||
model: '',
|
||||
baseUrl: '',
|
||||
key: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const obj = data.data as LLMConfig;
|
||||
setConfig(obj);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleUpdateConfig = async (newConfig: Partial<LLMConfig>) => {
|
||||
const updatedConfig = { ...config, ...newConfig };
|
||||
setConfig(updatedConfig);
|
||||
try {
|
||||
await updateConfig({
|
||||
appId,
|
||||
instanceId,
|
||||
type: 'llm',
|
||||
cfg: updatedConfig,
|
||||
});
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '更新配置失败',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBaseURLChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
let { value } = e.target;
|
||||
if (!value.startsWith('http://') && !value.startsWith('https://')) {
|
||||
value = `https://${value}`;
|
||||
}
|
||||
|
||||
handleUpdateConfig({ baseUrl: value });
|
||||
};
|
||||
|
||||
const handleCheckHealth = async () => {
|
||||
try {
|
||||
if (!config) return;
|
||||
const resp = await checkGptHealth(config);
|
||||
if (!resp.status) {
|
||||
throw new Error(resp.message);
|
||||
}
|
||||
|
||||
setReply(resp.message); // 设置回复内容
|
||||
|
||||
toast({
|
||||
title: '连接成功',
|
||||
position: 'top',
|
||||
description: 'GPT 连接成功',
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const errormsg =
|
||||
error instanceof Error ? error.message : JSON.stringify(error);
|
||||
toast({
|
||||
title: '连接失败',
|
||||
position: 'top',
|
||||
description: errormsg,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton height="20px" />
|
||||
<Skeleton height="20px" />
|
||||
<Skeleton height="20px" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack spacing="4" align="start">
|
||||
<>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="llmType">选择大模型类型</FormLabel>
|
||||
<Select
|
||||
id="llmType"
|
||||
placeholder="选择大模型类型"
|
||||
value={config.llmType}
|
||||
onChange={(e) => handleUpdateConfig({ llmType: e.target.value })}
|
||||
>
|
||||
{LLMTypeList.map((type) => (
|
||||
<option key={type.key} value={type.key}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="model">选择或输入模型</FormLabel>
|
||||
<InputGroup>
|
||||
<Input
|
||||
id="model"
|
||||
placeholder="选择或输入模型"
|
||||
value={config.model}
|
||||
onChange={(e) => handleUpdateConfig({ model: e.target.value })}
|
||||
list="models"
|
||||
/>
|
||||
<datalist id="models">
|
||||
{ModelList.map((model) => (
|
||||
<option key={model.key} value={model.key}>
|
||||
{model.name}
|
||||
</option>
|
||||
))}
|
||||
</datalist>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="gptAddress" mt="8px">
|
||||
<Highlight
|
||||
query="/v1"
|
||||
styles={{ px: '1', py: '1', bg: 'orange.100' }}
|
||||
>
|
||||
API 地址设置(尾部需要加上 /v1)
|
||||
</Highlight>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
ml="4"
|
||||
loadingText="检查中"
|
||||
onClick={handleCheckHealth}
|
||||
>
|
||||
检查连接
|
||||
</Button>
|
||||
</FormLabel>
|
||||
<InputGroup size="sm">
|
||||
<Input
|
||||
id="gptAddress"
|
||||
value={config.baseUrl}
|
||||
placeholder="输入站点地址"
|
||||
onChange={handleBaseURLChange}
|
||||
/>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="apiKey">API Key</FormLabel>
|
||||
<InputGroup size="md">
|
||||
<Input
|
||||
id="apiKey"
|
||||
pr="4.5rem"
|
||||
type={show ? 'text' : 'password'}
|
||||
placeholder="Enter password"
|
||||
value={config.key}
|
||||
onChange={(e) => handleUpdateConfig({ key: e.target.value })}
|
||||
/>
|
||||
<InputRightElement width="4.5rem">
|
||||
<Button
|
||||
h="1.75rem"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
{show ? <ViewIcon /> : <ViewOffIcon />}
|
||||
</Button>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</FormControl>
|
||||
|
||||
{/* 新增一个展示回复内容的块 */}
|
||||
{reply && (
|
||||
<>
|
||||
<Text>回复内容</Text>
|
||||
<Text>{reply}</Text>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default LLMSettings;
|
||||
@@ -1,187 +0,0 @@
|
||||
import React, { useEffect, useState, useCallback } 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 '../Settings/GeneralSettings';
|
||||
import LLMSettings from '../Settings/LLMSettings';
|
||||
import PluginSettings from '../Settings/PluginSettings';
|
||||
import {
|
||||
activeConfig,
|
||||
checkConfigActive,
|
||||
} from '../../../common/services/platform/controller';
|
||||
// import { useAppManager } from './AppManagerContext';
|
||||
|
||||
const SettingsModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
appId,
|
||||
instanceId,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
appId?: string;
|
||||
instanceId?: string;
|
||||
}) => {
|
||||
// const { data: appInfo } = useAppManager();
|
||||
const [appName, setAppName] = useState<string | undefined>(undefined);
|
||||
|
||||
const toast = useToast();
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
const [data, setData] = useState<{
|
||||
active: boolean;
|
||||
} | null>(null);
|
||||
|
||||
const fetchConfigActive = useCallback(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);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appId, instanceId]);
|
||||
|
||||
useEffect(() => {
|
||||
// setAppName(appInfo?.data.find((app) => app.id === appId)?.name);
|
||||
fetchConfigActive();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appId, instanceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setIsActive(data.active);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleCheckboxChange = useCallback(
|
||||
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,
|
||||
});
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[appId, instanceId],
|
||||
);
|
||||
|
||||
return (
|
||||
<ChakraProvider>
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
设置
|
||||
<Checkbox
|
||||
ml={4}
|
||||
isChecked={isActive}
|
||||
onChange={handleCheckboxChange}
|
||||
>
|
||||
激活{' '}
|
||||
{instanceId ? `客服 ${instanceId} 设置` : `应用 ${appName} 设置`}
|
||||
</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);
|
||||
Reference in New Issue
Block a user