feat: 移除企业微信支持

This commit is contained in:
lrhh123
2024-07-08 18:49:43 +08:00
parent 575b5b4458
commit ca412b1663
7 changed files with 1336 additions and 452 deletions
+2 -5
View File
@@ -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] 支持企业微信自动识别内部群聊
- [ ] 支持企业微信自动加人功能
- [ ] 支持抖音直播平台自动回复功能
- [ ] 支持拼多多
- [ ] 支持多平台文章自动发送功能
+1015
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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);