Publish app - feishu and wecom (#2375)

* feat(app publish): feishu bot (#2290)

* feat: feishu publish channel fe

* feat: enable feishu fe,
feat: feishu token api

* feat: feishu bot

* chore: extract saveChat from projects/app

* chore: remove debug log output

* feat: Basic Info

* chore: feishu bot fe adjusting

* feat: feishu bot docs

* feat: new tmpData collection for all tmpdata

* chore: compress the image

* perf: feishu config

* feat: source name

* perf: text desc

* perf: load system plugins

* perf: chat source

* feat(publish): Wecom bot (#2343)

* chore: Wecom Config

* feat(fe): wecom config fe

* feat: wecom fe

* chore: uses the newest editmodal

* feat: update png; adjust the fe

* chore: adjust fe

* perf: publish app ui

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2024-08-13 21:52:18 +08:00
committed by GitHub
parent 7417de74da
commit 0f3418daf5
71 changed files with 1301 additions and 498 deletions

View File

@@ -75,9 +75,9 @@ const MyRadio = ({
{!!item.icon && (
<>
{item.icon.startsWith('/') ? (
<Image src={item.icon} mr={'14px'} w={iconSize} alt={''} />
<Image src={item.icon} mr={'14px'} w={iconSize} alt={''} fill={'primary.600'} />
) : (
<MyIcon mr={'14px'} name={item.icon as any} w={iconSize} />
<MyIcon mr={'14px'} name={item.icon as any} w={iconSize} fill={'primary.600'} />
)}
</>
)}

View File

@@ -25,7 +25,6 @@ import {
} from '@/web/support/openapi/api';
import type { EditApiKeyProps } from '@/global/support/openapi/api.d';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import dayjs from 'dayjs';
import { AddIcon } from '@chakra-ui/icons';
import { useCopyData } from '@/web/common/hooks/useCopyData';
@@ -53,7 +52,6 @@ const defaultEditData: EditProps = {
const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
const { t } = useTranslation();
const { Loading } = useLoading();
const theme = useTheme();
const { copyData } = useCopyData();
const { feConfigs } = useSystemStore();
@@ -82,7 +80,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
useEffect(() => {
setBaseUrl(feConfigs?.customApiDomain || `${location.origin}/api`);
}, []);
}, [feConfigs?.customApiDomain]);
return (
<MyBox

View File

@@ -2,7 +2,7 @@ import type { NextApiResponse } from 'next';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node.d';
import { NextAPI } from '@/service/middleware/entry';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register';
import { getSystemPlugins } from '@/service/core/app/plugin';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
@@ -24,7 +24,7 @@ async function handler(
const formatParentId = parentId || null;
return getSystemPluginTemplates().then((res) =>
return getSystemPlugins().then((res) =>
res
// Just show the active plugins
.filter((item) => item.isActive)

View File

@@ -1,7 +1,7 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register';
import { getSystemPlugins } from '@/service/core/app/plugin';
export type pathQuery = {
parentId: ParentIdType;
@@ -19,7 +19,7 @@ async function handler(
if (!parentId) return [];
const plugins = await getSystemPluginTemplates();
const plugins = await getSystemPlugins();
const plugin = plugins.find((item) => item.id === parentId);
if (!plugin) return [];

View File

@@ -0,0 +1,20 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { POST } from '@fastgpt/service/common/api/plusRequest';
export type OutLinkFeishuQuery = any;
export type OutLinkFeishuBody = any;
export type OutLinkFeishuResponse = {};
async function handler(
req: ApiRequestProps<OutLinkFeishuBody, OutLinkFeishuQuery>,
res: ApiResponseType<any>
): Promise<void> {
// send to pro
const { token } = req.query;
const result = await POST<any>(`support/outLink/feishu/${token}`, req.body, {
headers: req.headers as any
});
res.json(result);
}
export default handler;

View File

@@ -13,7 +13,7 @@ export type OutLinkUpdateResponse = {};
async function handler(
req: ApiRequestProps<OutLinkUpdateBody, OutLinkUpdateQuery>
): Promise<OutLinkUpdateResponse> {
const { _id, name, responseDetail, limit } = req.body;
const { _id, name, responseDetail, limit, app } = req.body;
if (!_id) {
return Promise.reject(CommonErrEnum.missingParams);
@@ -24,7 +24,8 @@ async function handler(
await MongoOutLink.findByIdAndUpdate(_id, {
name,
responseDetail,
limit
limit,
app
});
return {};
}

View File

@@ -0,0 +1,31 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { plusRequest } from '@fastgpt/service/common/api/plusRequest';
export type OutLinkWecomQuery = any;
export type OutLinkWecomBody = any;
export type OutLinkWecomResponse = {};
async function handler(
req: ApiRequestProps<OutLinkWecomBody, OutLinkWecomQuery>,
res: ApiResponseType<any>
): Promise<any> {
const { token, type } = req.query;
const result = await plusRequest({
url: `support/outLink/wecom/${token}`,
params: {
...req.query,
type
},
data: req.body
});
if (result.data?.data?.message) {
// chanllege
res.send(result.data.data.message);
res.end();
}
res.send('success');
res.end();
}
export default handler;

View File

@@ -21,7 +21,7 @@ import {
} from '@fastgpt/global/core/workflow/runtime/utils';
import { GPTMessages2Chats, chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { saveChat } from '@/service/utils/chat/saveChat';
import { saveChat } from '@fastgpt/service/core/chat/saveChat';
import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';

View File

@@ -71,35 +71,33 @@ const Logs = () => {
const [detailLogsId, setDetailLogsId] = useState<string>();
return (
<>
<Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}>
{isPc && (
<>
<Box fontWeight={'bold'} fontSize={['md', 'lg']} mb={2}>
{appT('chat_logs')}
<Flex flexDirection={'column'} h={'100%'}>
{isPc && (
<Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}>
<Box fontWeight={'bold'} fontSize={['md', 'lg']} mb={2}>
{appT('chat_logs')}
</Box>
<Box color={'myGray.500'} fontSize={'sm'}>
{appT('chat_logs_tips')},{' '}
<Box
as={'span'}
mr={2}
textDecoration={'underline'}
cursor={'pointer'}
onClick={onOpenMarkDesc}
>
{t('common:core.chat.Read Mark Description')}
</Box>
<Box color={'myGray.500'} fontSize={'sm'}>
{appT('chat_logs_tips')},{' '}
<Box
as={'span'}
mr={2}
textDecoration={'underline'}
cursor={'pointer'}
onClick={onOpenMarkDesc}
>
{t('common:core.chat.Read Mark Description')}
</Box>
</Box>
</>
)}
</Box>
</Box>
</Box>
)}
{/* table */}
<Flex
flexDirection={'column'}
{...cardStyles}
boxShadow={3.5}
mt={4}
mt={[0, 4]}
px={[4, 8]}
py={[4, 6]}
flex={'1 0 0'}
@@ -214,7 +212,7 @@ const Logs = () => {
>
<ModalBody whiteSpace={'pre-wrap'}>{t('common:core.chat.Mark Description')}</ModalBody>
</MyModal>
</>
</Flex>
);
};

View File

@@ -1,32 +1,35 @@
import React, { useMemo } from 'react';
import { Flex, Box, Button, ModalFooter, ModalBody, Input } from '@chakra-ui/react';
import React from 'react';
import { Flex, Box, Button, ModalFooter, ModalBody, Input, Link, Grid } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';
import dayjs from 'dayjs';
import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
import { useI18n } from '@/web/context/I18n';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import BasicInfo from '../components/BasicInfo';
import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
const FeiShuEditModal = ({
appId,
defaultData,
onClose,
onCreate,
onEdit
onEdit,
isEdit = false
}: {
appId: string;
defaultData: OutLinkEditType<FeishuType>;
defaultData: OutLinkEditType<FeishuAppType>;
onClose: () => void;
onCreate: (id: string) => void;
onEdit: () => void;
isEdit?: boolean;
}) => {
const { t } = useTranslation();
const { publishT } = useI18n();
const {
register,
setValue,
@@ -35,174 +38,101 @@ const FeiShuEditModal = ({
defaultValues: defaultData
});
const isEdit = useMemo(() => !!defaultData?._id, [defaultData]);
const { mutate: onclickCreate, isLoading: creating } = useRequest({
mutationFn: async (e: OutLinkEditType<FeishuType>) => {
const { runAsync: onclickCreate, loading: creating } = useRequest2(
(e) =>
createShareChat({
...e,
appId,
type: PublishChannelEnum.feishu
});
},
errorToast: t('common:common.Create Failed'),
onSuccess: onCreate
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: (e: OutLinkEditType<FeishuType>) => {
return updateShareChat(e);
},
}),
{
errorToast: t('common:common.Create Failed'),
successToast: t('common:common.Create Success'),
onSuccess: onCreate
}
);
const { runAsync: onclickUpdate, loading: updating } = useRequest2((e) => updateShareChat(e), {
errorToast: t('common:common.Update Failed'),
successToast: t('common:common.Update Success'),
onSuccess: onEdit
});
const { feConfigs } = useSystemStore();
const { isPc } = useSystem();
return (
<MyModal
isOpen={true}
iconSrc="/imgs/modal/shareFill.svg"
title={isEdit ? publishT('edit_link') : publishT('create_link')}
iconSrc="core/app/publish/lark"
title={isEdit ? t('publish:edit_feishu_bot') : t('publish:new_feishu_bot')}
minW={['auto', '60rem']}
>
<ModalBody>
<Flex alignItems={'center'}>
<Box flex={'0 0 90px'}>{t('common:Name')}</Box>
<Input
placeholder={publishT('feishu_name') || 'link_name'} // TODO: i18n
maxLength={20}
{...register('name', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
QPM
<QuestionTip ml={1} label={publishT('qpm_tips' || '')}></QuestionTip>
<ModalBody display={'grid'} gridTemplateColumns={['1fr', '1fr 1fr']} fontSize={'14px'} p={0}>
<Box p={8} h={['auto', '400px']} borderRight={'base'}>
<BasicInfo register={register} setValue={setValue} defaultData={defaultData} />
</Box>
<Flex p={8} h={['auto', '400px']} flexDirection="column" gap={6}>
<Flex alignItems="center">
<Box color="myGray.600">{t('publish:feishu_api')}</Box>
{feConfigs?.docUrl && (
<Link
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/feishu-bot')}
target={'_blank'}
ml={2}
color={'primary.500'}
fontSize={'sm'}
>
<Flex alignItems={'center'}>
<MyIcon name="book" mr="1" />
{t('common:common.Read document')}
</Flex>
</Link>
)}
</Flex>
<Input
max={1000}
{...register('limit.QPM', {
min: 0,
max: 1000,
valueAsNumber: true,
required: publishT('qpm_is_empty') || ''
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:support.outlink.Max usage points')}
<QuestionTip
ml={1}
label={t('common:support.outlink.Max usage points tip')}
></QuestionTip>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
App ID
</FormLabel>
<Input
placeholder={t('common:core.module.http.AppId')}
{...register('app.appId', {
required: true
})}
/>
</Flex>
<Input
{...register('limit.maxUsagePoints', {
min: -1,
max: 10000000,
valueAsNumber: true,
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:common.Expired Time')}
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
App Secret
</FormLabel>
<Input
placeholder={'App Secret'}
{...register('app.appSecret', {
required: true
})}
/>
</Flex>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:default_reply')}
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'}>Encrypt Key</FormLabel>
<Input placeholder="Encrypt Key" {...register('app.encryptKey')} />
</Flex>
<Input
placeholder={publishT('default_response') || 'link_name'}
maxLength={20}
{...register('defaultResponse', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common:reply_now')}
<Box flex={1}></Box>
<Flex justifyContent={'end'}>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) =>
isEdit ? onclickUpdate(data) : onclickCreate(data)
)}
>
{t('common:common.Confirm')}
</Button>
</Flex>
<Input
placeholder={publishT('default_response') || 'link_name'}
maxLength={20}
{...register('immediateResponse', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>{t('common:core.module.http.AppId')}</Box>
<Input
placeholder={t('common:core.module.http.AppId') || 'link_name'}
// maxLength={20}
{...register('app.appId', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>{t('common:core.module.http.AppSecret' as any)}</Box>
<Input
placeholder={'App Secret'}
// maxLength={20}
{...register('app.appSecret', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>Encrypt Key</Box>
<Input
placeholder="Encrypt Key"
// maxLength={20}
{...register('app.encryptKey', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>Verification Token</Box>
<Input
placeholder="Verification Token"
// maxLength={20}
{...register('app.verificationToken', {
required: t('common:common.name_is_empty') || 'name_is_empty'
})}
/>
</Flex>
{/* <Flex alignItems={'center'} mt={4}> */}
{/* <Flex flex={'0 0 90px'} alignItems={'center'}> */}
{/* 限制回复 */}
{/* </Flex> */}
{/* <Switch {...register('wecomConfig.ReplyLimit')} size={'lg'} /> */}
{/* </Flex> */}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import {
Flex,
Box,
@@ -9,61 +9,82 @@ import {
Tr,
Th,
Td,
Tbody
Tbody,
useDisclosure
} from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useQuery } from '@tanstack/react-query';
import { getShareChatList, delShareChatById } from '@/web/support/outLink/api';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { defaultFeishuOutLinkForm } from '@/web/core/app/constants';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import dayjs from 'dayjs';
import dynamic from 'next/dynamic';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const FeiShuEditModal = dynamic(() => import('./FeiShuEditModal'));
const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal'));
const FeiShu = ({ appId }: { appId: string }) => {
const { t } = useTranslation();
const { Loading, setIsLoading } = useLoading();
const { feConfigs } = useSystemStore();
const { copyData } = useCopyData();
const [editFeiShuLinkData, setEditFeiShuLinkData] = useState<OutLinkEditType<FeishuType>>();
const { toast } = useToast();
const {
isFetching,
data: shareChatList = [],
refetch: refetchShareChatList
} = useQuery(['initShareChatList', appId], () =>
getShareChatList<FeishuType>({ appId, type: PublishChannelEnum.feishu })
const [editFeiShuLinkData, setEditFeiShuLinkData] = useState<OutLinkEditType<FeishuAppType>>();
const [isEdit, setIsEdit] = useState<boolean>(false);
const baseUrl = useMemo(
() => feConfigs?.customApiDomain || `${location.origin}/api`,
[feConfigs?.customApiDomain]
);
const {
data: shareChatList = [],
loading: isFetching,
runAsync: refetchShareChatList
} = useRequest2(
() => getShareChatList<FeishuAppType>({ appId, type: PublishChannelEnum.feishu }),
{
manual: false
}
);
const {
onOpen: openShowShareLinkModal,
isOpen: showShareLinkModalOpen,
onClose: closeShowShareLinkModal
} = useDisclosure();
const [showShareLink, setShowShareLink] = useState<string | null>(null);
return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'}>
<Flex justifyContent={'space-between'} flexDirection="row">
<Box fontWeight={'bold'} fontSize={['md', 'lg']}>
{t('common:core.app.publish.Fei shu bot publish')}
</Box>
<Button
variant={'whitePrimary'}
variant={'primary'}
colorScheme={'blue'}
size={['sm', 'md']}
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />}
ml={3}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: t('common:core.app.share.Amount limit tip')
}
: {})}
onClick={() => setEditFeiShuLinkData(defaultFeishuOutLinkForm)}
onClick={() => {
setEditFeiShuLinkData(defaultFeishuOutLinkForm);
setIsEdit(false);
}}
>
{t('common:core.app.share.Create link')}
{t('common:add_new')}
</Button>
</Flex>
<TableContainer mt={3}>
@@ -112,11 +133,22 @@ const FeiShu = ({ appId }: { appId: string }) => {
: t('common:common.Un used')}
</Td>
<Td display={'flex'} alignItems={'center'}>
<Button
onClick={() => {
setShowShareLink(`${baseUrl}/support/outLink/feishu/${item.shareId}`);
openShowShareLinkModal();
}}
size={'sm'}
mr={3}
variant={'whitePrimary'}
>
{t('publish:request_address')}
</Button>
<MyMenu
Button={
<MyIcon
name={'more'}
_hover={{ bg: 'myGray.100 ' }}
_hover={{ bg: 'myGray.100' }}
cursor={'pointer'}
borderRadius={'md'}
w={'14px'}
@@ -129,7 +161,7 @@ const FeiShu = ({ appId }: { appId: string }) => {
{
label: t('common:common.Edit'),
icon: 'edit',
onClick: () =>
onClick: () => {
setEditFeiShuLinkData({
_id: item._id,
name: item.name,
@@ -138,7 +170,9 @@ const FeiShu = ({ appId }: { appId: string }) => {
responseDetail: item.responseDetail,
defaultResponse: item.defaultResponse,
immediateResponse: item.immediateResponse
})
});
setIsEdit(true);
}
},
{
label: t('common:common.Delete'),
@@ -167,27 +201,24 @@ const FeiShu = ({ appId }: { appId: string }) => {
{editFeiShuLinkData && (
<FeiShuEditModal
appId={appId}
// type={'feishu' as PublishChannelEnum}
defaultData={editFeiShuLinkData}
onCreate={(id) => {
refetchShareChatList();
setEditFeiShuLinkData(undefined);
}}
onEdit={() => {
toast({
status: 'success',
title: t('common:common.Update Successful')
});
refetchShareChatList();
setEditFeiShuLinkData(undefined);
}}
onCreate={() => Promise.all([refetchShareChatList(), setEditFeiShuLinkData(undefined)])}
onEdit={() => Promise.all([refetchShareChatList(), setEditFeiShuLinkData(undefined)])}
onClose={() => setEditFeiShuLinkData(undefined)}
isEdit={isEdit}
/>
)}
{shareChatList.length === 0 && !isFetching && (
<EmptyTip text={t('common:core.app.share.Not share link')}></EmptyTip>
)}
<Loading loading={isFetching} fixed={false} />
{showShareLinkModalOpen && (
<ShowShareLinkModal
shareLink={showShareLink ?? ''}
onClose={closeShowShareLinkModal}
img="/imgs/outlink/feishu-copylink-instruction.png"
/>
)}
</Box>
);
};

View File

@@ -0,0 +1,168 @@
import React from 'react';
import { Flex, Box, Button, ModalBody, Input, Link } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import BasicInfo from '../components/BasicInfo';
import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
const WecomEditModal = ({
appId,
defaultData,
onClose,
onCreate,
onEdit,
isEdit = false
}: {
appId: string;
defaultData: OutLinkEditType<WecomAppType>;
onClose: () => void;
onCreate: (id: string) => void;
onEdit: () => void;
isEdit?: boolean;
}) => {
const { t } = useTranslation();
const {
register,
setValue,
handleSubmit: submitShareChat
} = useForm({
defaultValues: defaultData
});
const { runAsync: onclickCreate, loading: creating } = useRequest2(
(e) =>
createShareChat({
...e,
appId,
type: PublishChannelEnum.wecom
}),
{
errorToast: t('common:common.Create Failed'),
successToast: t('common:common.Create Success'),
onSuccess: onCreate
}
);
const { runAsync: onclickUpdate, loading: updating } = useRequest2((e) => updateShareChat(e), {
errorToast: t('common:common.Update Failed'),
successToast: t('common:common.Update Success'),
onSuccess: onEdit
});
const { feConfigs } = useSystemStore();
return (
<MyModal
iconSrc="core/app/publish/wecom"
title={isEdit ? t('publish:wecom.edit_modal_title') : t('publish:wecom.create_modal_title')}
minW={['auto', '60rem']}
>
<ModalBody display={'grid'} gridTemplateColumns={['1fr', '1fr 1fr']} fontSize={'14px'} p={0}>
<Box p={8} minH={['auto', '400px']} borderRight={'base'}>
<BasicInfo register={register} setValue={setValue} defaultData={defaultData} />
</Box>
<Flex p={8} minH={['auto', '400px']} flexDirection="column" gap={6}>
<Flex alignItems="center">
<Box color="myGray.600">{t('publish:wecom.api')}</Box>
{feConfigs?.docUrl && (
<Link
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/wecom-bot')}
target={'_blank'}
ml={2}
color={'primary.500'}
fontSize={'sm'}
>
<Flex alignItems={'center'}>
<MyIcon name="book" mr="1" />
{t('common:common.Read document')}
</Flex>
</Link>
)}
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Corp ID
</FormLabel>
<Input
placeholder="Corp ID"
{...register('app.CorpId', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Agent ID
</FormLabel>
<Input
placeholder="Agent ID"
{...register('app.AgentId', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Secret
</FormLabel>
<Input
placeholder="Secret"
{...register('app.SuiteSecret', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
Token
</FormLabel>
<Input
placeholder="Token"
{...register('app.CallbackToken', {
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} required>
AES Key
</FormLabel>
<Input
placeholder="AES Key"
{...register('app.CallbackEncodingAesKey', {
required: true
})}
/>
</Flex>
<Box flex={1}></Box>
<Flex justifyContent={'end'}>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) =>
isEdit ? onclickUpdate(data) : onclickCreate(data)
)}
>
{t('common:common.Confirm')}
</Button>
</Flex>
</Flex>
</ModalBody>
</MyModal>
);
};
export default WecomEditModal;

View File

@@ -0,0 +1,223 @@
import React, { useMemo, useState } from 'react';
import {
Flex,
Box,
Button,
TableContainer,
Table,
Thead,
Tr,
Th,
Td,
Tbody,
useDisclosure
} from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { getShareChatList, delShareChatById } from '@/web/support/outLink/api';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { defaultOutLinkForm } from '@/web/core/app/constants';
import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import dayjs from 'dayjs';
import dynamic from 'next/dynamic';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const WecomEditModal = dynamic(() => import('./WecomEditModal'));
const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal'));
const Wecom = ({ appId }: { appId: string }) => {
const { t } = useTranslation();
const { Loading, setIsLoading } = useLoading();
const { feConfigs } = useSystemStore();
const [editWecomData, setEditWecomData] = useState<OutLinkEditType<WecomAppType>>();
const [isEdit, setIsEdit] = useState<boolean>(false);
const baseUrl = useMemo(
() => feConfigs?.customApiDomain || `${location.origin}/api`,
[feConfigs?.customApiDomain]
);
const {
data: shareChatList = [],
loading: isFetching,
runAsync: refetchShareChatList
} = useRequest2(() => getShareChatList<WecomAppType>({ appId, type: PublishChannelEnum.wecom }), {
manual: false
});
const {
onOpen: openShowShareLinkModal,
isOpen: showShareLinkModalOpen,
onClose: closeShowShareLinkModal
} = useDisclosure();
const [showShareLink, setShowShareLink] = useState<string | null>(null);
return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'} flexDirection="row">
<Box fontWeight={'bold'} fontSize={['md', 'lg']}>
{t('publish:wecom.title')}
</Box>
<Button
variant={'primary'}
colorScheme={'blue'}
size={['sm', 'md']}
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />}
ml={3}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: t('common:core.app.share.Amount limit tip')
}
: {})}
onClick={() => {
setEditWecomData(defaultOutLinkForm as any); // HACK
setIsEdit(false);
}}
>
{t('common:add_new')}
</Button>
</Flex>
<TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
<Thead>
<Tr>
<Th>{t('common:common.Name')} </Th>
<Th> {t('common:support.outlink.Usage points')} </Th>
{feConfigs?.isPlus && (
<>
<Th>{t('common:core.app.share.Ip limit title')} </Th>
<Th> {t('common:common.Expired Time')} </Th>
</>
)}
<Th>{t('common:common.Last use time')} </Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name} </Td>
<Td>
{Math.round(item.usagePoints)}
{feConfigs?.isPlus
? `${
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
? ` / ${item.limit.maxUsagePoints}`
: ` / ${t('common:common.Unlimited')}`
}`
: ''}
</Td>
{feConfigs?.isPlus && (
<>
<Td>{item?.limit?.QPM || '-'} </Td>
<Td>
{item?.limit?.expiredTime
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
: '-'}
</Td>
</>
)}
<Td>
{item.lastTime
? t(formatTimeToChatTime(item.lastTime) as any)
: t('common:common.Un used')}
</Td>
<Td display={'flex'} alignItems={'center'}>
<Button
onClick={() => {
setShowShareLink(`${baseUrl}/support/outLink/wecom/${item.shareId}`);
openShowShareLinkModal();
}}
size={'sm'}
mr={3}
variant={'whitePrimary'}
>
{t('publish:request_address')}
</Button>
<MyMenu
Button={
<MyIcon
name={'more'}
_hover={{ bg: 'myGray.100' }}
cursor={'pointer'}
borderRadius={'md'}
w={'14px'}
p={2}
/>
}
menuList={[
{
children: [
{
label: t('common:common.Edit'),
icon: 'edit',
onClick: () => {
setEditWecomData({
_id: item._id,
name: item.name,
limit: item.limit,
app: item.app,
responseDetail: item.responseDetail,
defaultResponse: item.defaultResponse,
immediateResponse: item.immediateResponse
});
setIsEdit(true);
}
},
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: async () => {
setIsLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setIsLoading(false);
}
}
]
}
]}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{editWecomData && (
<WecomEditModal
appId={appId}
defaultData={editWecomData}
onCreate={() => Promise.all([refetchShareChatList(), setEditWecomData(undefined)])}
onEdit={() => Promise.all([refetchShareChatList(), setEditWecomData(undefined)])}
onClose={() => setEditWecomData(undefined)}
isEdit={isEdit}
/>
)}
{shareChatList.length === 0 && !isFetching && (
<EmptyTip text={t('common:core.app.share.Not share link')}> </EmptyTip>
)}
<Loading loading={isFetching} fixed={false} />
{showShareLinkModalOpen && (
<ShowShareLinkModal
shareLink={showShareLink ?? ''}
onClose={closeShowShareLinkModal}
img="/imgs/outlink/wecom-copylink-instruction.png"
/>
)}
</Box>
);
};
export default React.memo(Wecom);

View File

@@ -0,0 +1,87 @@
import React from 'react';
import { Box, Flex, Input } from '@chakra-ui/react';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { UseFormRegister, UseFormSetValue } from 'react-hook-form';
import { OutLinkEditType } from '@fastgpt/global/support/outLink/type';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
function BasicInfo({
register,
setValue,
defaultData
}: {
register: UseFormRegister<OutLinkEditType<any>>;
setValue: UseFormSetValue<OutLinkEditType<any>>;
defaultData: OutLinkEditType<any>;
}) {
const { t } = useTranslation();
return (
<Flex flexDirection="column" gap={6}>
<Box color="myGray.600">{t('publish:basic_info')}</Box>
<Flex alignItems={'center'}>
<FormLabel required flex={'0 0 6.25rem'}>
{t('common:Name')}
</FormLabel>
<Input
placeholder={t('publish:publish_name')}
maxLength={20}
{...register('name', {
required: t('common:common.name_is_empty')
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} alignItems={'center'}>
QPM
<QuestionTip ml={1} label={t('publish:qpm_tips')}></QuestionTip>
</FormLabel>
<Input
max={1000}
{...register('limit.QPM', {
min: 0,
max: 1000,
valueAsNumber: true,
required: t('publish:qpm_is_empty')
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} alignItems={'center'}>
{t('common:support.outlink.Max usage points')}
<QuestionTip
ml={1}
label={t('common:support.outlink.Max usage points tip')}
></QuestionTip>
</FormLabel>
<Input
{...register('limit.maxUsagePoints', {
min: -1,
max: 10000000,
valueAsNumber: true,
required: true
})}
/>
</Flex>
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 6.25rem'} alignItems={'center'}>
{t('common:common.Expired Time')}
</FormLabel>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
</Flex>
);
}
export default BasicInfo;

View File

@@ -0,0 +1,50 @@
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { Box, Image, Flex, ModalBody } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'react-i18next';
export type ShowShareLinkModalProps = {
shareLink: string;
onClose: () => void;
img: string;
};
function ShowShareLinkModal({ shareLink, onClose, img }: ShowShareLinkModalProps) {
const { copyData } = useCopyData();
const { t } = useTranslation();
return (
<MyModal onClose={onClose} title={t('publish:show_share_link_modal_title')}>
<ModalBody>
<Box borderRadius={'md'} bg={'myGray.100'} overflow={'hidden'} fontSize={'sm'}>
<Flex
p={3}
bg={'myWhite.500'}
border="base"
borderTopLeftRadius={'md'}
borderTopRightRadius={'md'}
>
<Box flex={1}>{t('publish:copy_link_hint')}</Box>
<MyIcon
name={'copy'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{ color: 'primary.500' }}
onClick={() => copyData(shareLink)}
/>
</Flex>
<Box whiteSpace={'pre'} p={3} overflowX={'auto'}>
{shareLink}
</Box>
</Box>
<Box mt="4" borderRadius="0.5rem" border="1px" borderStyle="solid" borderColor="myGray.200">
<Image src={img} borderRadius="0.5rem" alt="" />
</Box>
</ModalBody>
</MyModal>
);
}
export default ShowShareLinkModal;

View File

@@ -1,5 +1,5 @@
import React, { useRef, useState } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import dynamic from 'next/dynamic';
@@ -10,14 +10,18 @@ import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { cardStyles } from '../constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import Link from './Link';
const Link = dynamic(() => import('./Link'));
const API = dynamic(() => import('./API'));
const FeiShu = dynamic(() => import('./FeiShu'));
const Wecom = dynamic(() => import('./Wecom'));
const OutLink = () => {
const { t } = useTranslation();
const theme = useTheme();
const { feConfigs } = useSystemStore();
const { toast } = useToast();
const appId = useContextSelector(AppContext, (v) => v.appId);
@@ -26,33 +30,65 @@ const OutLink = () => {
icon: '/imgs/modal/shareFill.svg',
title: t('common:core.app.Share link'),
desc: t('common:core.app.Share link desc'),
value: PublishChannelEnum.share
value: PublishChannelEnum.share,
isProFn: false
},
{
icon: 'support/outlink/apikeyFill',
title: t('common:core.app.Api request'),
desc: t('common:core.app.Api request desc'),
value: PublishChannelEnum.apikey
value: PublishChannelEnum.apikey,
isProFn: false
},
{
icon: 'core/app/publish/lark',
title: t('publish:feishu_bot'),
desc: t('publish:feishu_bot_desc'),
value: PublishChannelEnum.feishu,
isProFn: true
},
{
icon: 'core/app/publish/wecom',
title: t('publish:wecom.bot'),
desc: t('publish:wecom.bot_desc'),
value: PublishChannelEnum.wecom,
isProFn: true
}
// {
// icon: 'core/app/publish/lark',
// title: t('common:core.app.publish.Fei shu bot'),
// desc: t('common:core.app.publish.Fei Shu Bot Desc'),
// value: PublishChannelEnum.feishu
// }
]);
const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share);
return (
<>
<Box
display={['block', 'flex']}
overflowY={'auto'}
overflowX={'hidden'}
h={'100%'}
flexDirection={'column'}
>
<Box {...cardStyles} boxShadow={2} px={[4, 8]} py={[4, 6]}>
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 400px))']}
gridTemplateColumns={[
'repeat(1,1fr)',
'repeat(2, 1fr)',
'repeat(3, 1fr)',
'repeat(3, 1fr)',
'repeat(4, 1fr)'
]}
iconSize={'20px'}
list={publishList.current}
value={linkType}
onChange={(e) => setLinkType(e as PublishChannelEnum)}
onChange={(e) => {
const config = publishList.current.find((v) => v.value === e)!;
if (!feConfigs.isPlus && config.isProFn) {
toast({
status: 'warning',
title: t('common:common.system.Commercial version function')
});
} else {
setLinkType(e as PublishChannelEnum);
}
}}
/>
</Box>
@@ -63,15 +99,16 @@ const OutLink = () => {
mt={4}
px={[4, 8]}
py={[4, 6]}
flex={'1 0 0'}
flex={1}
>
{linkType === PublishChannelEnum.share && (
<Link appId={appId} type={PublishChannelEnum.share} />
)}
{linkType === PublishChannelEnum.apikey && <API appId={appId} />}
{linkType === PublishChannelEnum.feishu && <FeiShu appId={appId} />}
{linkType === PublishChannelEnum.wecom && <Wecom appId={appId} />}
</Flex>
</>
</Box>
);
};

View File

@@ -6,7 +6,7 @@ import Edit from './Edit';
import { useContextSelector } from 'use-context-selector';
import { AppContext, TabEnum } from '../context';
import dynamic from 'next/dynamic';
import { Flex } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import { useTranslation } from 'next-i18next';
@@ -29,10 +29,10 @@ const SimpleEdit = () => {
{currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} />
) : (
<Flex h={'100%'} flexDirection={'column'} mt={4}>
<Box flex={'1 0 0'} h={0} mt={4}>
{currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />}
</Flex>
</Box>
)}
</Flex>
);

View File

@@ -1,12 +1,12 @@
import { getSystemPlugins } from '@/service/core/app/plugin';
import { initSystemConfig } from '.';
import { createDatasetTrainingMongoWatch } from '@/service/core/dataset/training/utils';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register';
import { MongoSystemConfigs } from '@fastgpt/service/common/system/config/schema';
import { MongoSystemPluginSchema } from '@fastgpt/service/core/app/plugin/systemPluginSchema';
export const startMongoWatch = async () => {
reloadConfigWatch();
refetchSystemPlugin();
refetchSystemPlugins();
createDatasetTrainingMongoWatch();
};
@@ -23,12 +23,12 @@ const reloadConfigWatch = () => {
});
};
const refetchSystemPlugin = () => {
const refetchSystemPlugins = () => {
const changeStream = MongoSystemPluginSchema.watch();
changeStream.on('change', async (change) => {
try {
getSystemPluginTemplates(true);
getSystemPlugins(true);
} catch (error) {}
});
};

View File

@@ -0,0 +1,69 @@
import { FastGPTProUrl, isProduction } from '@fastgpt/service/common/system/constants';
import { cloneDeep } from 'lodash';
import { getCommunityCb, getCommunityPlugins } from '@fastgpt/plugins/register';
import { GET, POST } from '@fastgpt/service/common/api/plusRequest';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { addLog } from '@fastgpt/service/common/system/log';
import { SystemPluginResponseType } from '@fastgpt/plugins/type';
/* Get plugins */
const getCommercialPlugins = () => {
return GET<SystemPluginTemplateItemType[]>('/core/app/plugin/getSystemPlugins');
};
export const getSystemPlugins = async (refresh = false) => {
if (isProduction && global.systemPlugins && !refresh) return cloneDeep(global.systemPlugins);
try {
if (!global.systemPlugins) {
global.systemPlugins = [];
}
global.systemPlugins = FastGPTProUrl ? await getCommercialPlugins() : getCommunityPlugins();
addLog.info(`Load system plugin successfully: ${global.systemPlugins.length}`);
return cloneDeep(global.systemPlugins);
} catch (error) {
//@ts-ignore
global.systemPlugins = undefined;
return Promise.reject(error);
}
};
/* Get plugin callback */
const getCommercialCb = async () => {
const plugins = await getSystemPlugins();
const result = plugins.map((plugin) => {
const name = plugin.id.split('-')[1];
return {
name,
cb: (e: any) =>
POST<Record<string, any>>('/core/app/plugin/run', {
pluginName: name,
data: e
})
};
});
return result.reduce<Record<string, (e: any) => SystemPluginResponseType>>(
(acc, { name, cb }) => {
acc[name] = cb;
return acc;
},
{}
);
};
export const getSystemPluginCb = async () => {
if (isProduction && global.systemPluginCb) return global.systemPluginCb;
try {
global.systemPluginCb = {};
global.systemPluginCb = FastGPTProUrl ? await getCommercialCb() : await getCommunityCb();
return global.systemPluginCb;
} catch (error) {
//@ts-ignore
global.systemPluginCb = undefined;
return Promise.reject(error);
}
};

View File

@@ -12,6 +12,7 @@ import { startMongoWatch } from './common/system/volumnMongoWatch';
import { startTrainingQueue } from './core/dataset/training/utils';
import { systemStartCb } from '@fastgpt/service/common/system/tools';
import { addLog } from '@fastgpt/service/common/system/log';
import { getSystemPluginCb } from './core/app/plugin';
/**
* This function is equivalent to the entry to the service
@@ -31,7 +32,7 @@ export function connectToDatabase() {
systemStartCb();
//init system configinit vector databaseinit root user
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]);
await Promise.all([getInitConfig(), getSystemPluginCb(), initVectorStore(), initRootUser()]);
startMongoWatch();
// cron

View File

@@ -1,113 +0,0 @@
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { addLog } from '@fastgpt/service/common/system/log';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { getAppChatConfig, getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
type Props = {
chatId: string;
appId: string;
teamId: string;
tmbId: string;
nodes: StoreNodeItemType[];
appChatConfig?: AppChatConfigType;
variables?: Record<string, any>;
isUpdateUseTime: boolean;
newTitle: string;
source: `${ChatSourceEnum}`;
shareId?: string;
outLinkUid?: string;
content: [UserChatItemType & { dataId?: string }, AIChatItemType & { dataId?: string }];
metadata?: Record<string, any>;
};
export async function saveChat({
chatId,
appId,
teamId,
tmbId,
nodes,
appChatConfig,
variables,
isUpdateUseTime,
newTitle,
source,
shareId,
outLinkUid,
content,
metadata = {}
}: Props) {
try {
const chat = await MongoChat.findOne(
{
appId,
chatId
},
'_id metadata'
);
const metadataUpdate = {
...chat?.metadata,
...metadata
};
const { welcomeText, variables: variableList } = getAppChatConfig({
chatConfig: appChatConfig,
systemConfigNode: getGuideModule(nodes),
isPublicFetch: false
});
await mongoSessionRun(async (session) => {
await MongoChatItem.insertMany(
content.map((item) => ({
chatId,
teamId,
tmbId,
appId,
...item
})),
{ session }
);
await MongoChat.updateOne(
{
appId,
chatId
},
{
$set: {
teamId,
tmbId,
appId,
chatId,
variableList,
welcomeText,
variables: variables || {},
title: newTitle,
source,
shareId,
outLinkUid,
metadata: metadataUpdate,
updateTime: new Date()
}
},
{
session,
upsert: true
}
);
});
if (isUpdateUseTime) {
await MongoApp.findByIdAndUpdate(appId, {
updateTime: new Date()
});
}
} catch (error) {
addLog.error(`update chat history error`, error);
}
}

View File

@@ -1,6 +1,6 @@
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { NullPermission } from '@fastgpt/global/support/permission/constant';
import { i18nT } from '@fastgpt/web/i18n/utils';
@@ -32,30 +32,12 @@ export const defaultOutLinkForm: OutLinkEditType = {
}
};
// export const defaultWecomOutLinkForm: OutLinkConfigEditType = {
// name: '',
// wecomConfig: {
// ReplyLimit: false,
// defaultResponse: '',
// immediateResponse: false,
// WXWORK_TOKEN: '',
// WXWORK_AESKEY: '',
// WXWORK_SECRET: '',
// WXWORD_ID: ''
// },
// limit: {
// QPM: 100,
// maxUsagePoints: -1
// }
// };
export const defaultFeishuOutLinkForm: OutLinkEditType<FeishuType> = {
export const defaultFeishuOutLinkForm: OutLinkEditType<FeishuAppType> = {
name: '',
limit: {
QPM: 100,
maxUsagePoints: -1
},
responseDetail: false
}
};
export enum TTSTypeEnum {

View File

@@ -1,5 +1,9 @@
import { GET, POST, DELETE } from '@/web/common/api/request';
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
import type {
OutlinkAppType,
OutLinkEditType,
OutLinkSchema
} from '@fastgpt/global/support/outLink/type.d';
// create a shareChat
export function createShareChat<T>(
@@ -15,7 +19,10 @@ export const putShareChat = (data: OutLinkEditType) =>
POST<string>(`/support/outLink/update`, data);
// get shareChat
export function getShareChatList<T>(data: { appId: string; type: OutLinkSchema<T>['type'] }) {
export function getShareChatList<T extends OutlinkAppType>(data: {
appId: string;
type: OutLinkSchema<T>['type'];
}) {
return GET<OutLinkSchema<T>[]>(`/support/outLink/list`, data);
}
@@ -25,7 +32,7 @@ export function delShareChatById(id: string) {
}
// update a shareChat
export function updateShareChat<T>(data: OutLinkEditType<T>) {
export function updateShareChat<T extends OutlinkAppType>(data: OutLinkEditType<T>) {
return POST<string>(`/support/outLink/update`, data);
}