4.6.5- CoreferenceResolution Module (#631)

This commit is contained in:
Archer
2023-12-22 10:47:31 +08:00
committed by GitHub
parent 41115a96c0
commit cd682d4275
112 changed files with 4163 additions and 2700 deletions

View File

@@ -26,6 +26,43 @@ const QuoteModal = ({
isShare: boolean;
}) => {
const { t } = useTranslation();
return (
<>
<MyModal
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
isCentered
minW={['90vw', '600px']}
iconSrc="/imgs/modal/quote.svg"
title={
<Box>
{t('core.chat.Quote Amount', { amount: rawSearch.length })}
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{t('core.chat.quote.Quote Tip')}
</Box>
</Box>
}
>
<ModalBody whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
<QuoteList rawSearch={rawSearch} isShare={isShare} />
</ModalBody>
</MyModal>
</>
);
};
export default QuoteModal;
export const QuoteList = React.memo(function QuoteList({
rawSearch = [],
isShare
}: {
rawSearch: SearchDataResponseItemType[];
isShare: boolean;
}) {
const { t } = useTranslation();
const { isPc } = useSystemStore();
const theme = useTheme();
const { toast } = useToast();
@@ -60,124 +97,104 @@ const QuoteModal = ({
return (
<>
<MyModal
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
isCentered
minW={['90vw', '600px']}
iconSrc="/imgs/modal/quote.svg"
title={
<Box>
{t('core.chat.Quote Amount', { amount: rawSearch.length })}
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{t('core.chat.quote.Quote Tip')}
</Box>
</Box>
}
>
<ModalBody whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
{rawSearch.map((item, i) => (
<Box
key={i}
flex={'1 0 0'}
p={2}
borderRadius={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
overflow={'hidden'}
_hover={{ '& .hover-data': { display: 'flex' } }}
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
>
<Flex alignItems={'flex-end'} mb={3} fontSize={'sm'}>
<RawSourceText
fontWeight={'bold'}
color={'black'}
sourceName={item.sourceName}
sourceId={item.sourceId}
canView={!isShare}
/>
<Box flex={1} />
{!isShare && (
<Link
as={NextLink}
className="hover-data"
display={'none'}
alignItems={'center'}
color={'blue.500'}
href={`/dataset/detail?datasetId=${item.datasetId}&currentTab=dataCard&collectionId=${item.collectionId}`}
>
{t('core.dataset.Go Dataset')}
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
</Link>
)}
</Flex>
{rawSearch.map((item, i) => (
<Box
key={i}
flex={'1 0 0'}
p={2}
borderRadius={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
overflow={'hidden'}
_hover={{ '& .hover-data': { display: 'flex' } }}
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
>
<Flex alignItems={'flex-end'} mb={3} fontSize={'sm'}>
<RawSourceText
fontWeight={'bold'}
color={'black'}
sourceName={item.sourceName}
sourceId={item.sourceId}
canView={!isShare}
/>
<Box flex={1} />
{!isShare && (
<Link
as={NextLink}
className="hover-data"
display={'none'}
alignItems={'center'}
color={'blue.500'}
href={`/dataset/detail?datasetId=${item.datasetId}&currentTab=dataCard&collectionId=${item.collectionId}`}
>
{t('core.dataset.Go Dataset')}
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
</Link>
)}
</Flex>
<Box color={'black'}>{item.q}</Box>
<Box color={'myGray.600'}>{item.a}</Box>
{!isShare && (
<Flex alignItems={'center'} fontSize={'sm'} mt={3} gap={4} color={'myGray.500'}>
{isPc && (
<MyTooltip label={t('core.dataset.data.id')}>
<Flex border={theme.borders.base} py={'1px'} px={3} borderRadius={'3px'}>
# {item.id}
</Flex>
</MyTooltip>
)}
<MyTooltip label={t('core.dataset.Quote Length')}>
<Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
{item.q.length + (item.a?.length || 0)}
</Flex>
</MyTooltip>
{!isShare && item.score && (
<MyTooltip label={t('core.dataset.Similarity')}>
<Flex alignItems={'center'}>
<MyIcon name={'kbTest'} w={'12px'} />
<Progress
mx={2}
w={['60px', '90px']}
value={item.score * 100}
size="sm"
borderRadius={'20px'}
colorScheme="myGray"
border={theme.borders.base}
/>
<Box>{item.score.toFixed(4)}</Box>
</Flex>
</MyTooltip>
)}
<Box flex={1} />
{item.id && (
<MyTooltip label={t('core.dataset.data.Edit')}>
<Box
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={['16px', '18px']}
h={['16px', '18px']}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'blue.600'
}}
onClick={() => onclickEdit(item)}
/>
</Box>
</MyTooltip>
)}
</Flex>
<Box color={'black'}>{item.q}</Box>
<Box color={'myGray.600'}>{item.a}</Box>
{!isShare && (
<Flex alignItems={'center'} fontSize={'sm'} mt={3} gap={4} color={'myGray.500'}>
{isPc && (
<MyTooltip label={t('core.dataset.data.id')}>
<Flex border={theme.borders.base} py={'1px'} px={3} borderRadius={'3px'}>
# {item.id}
</Flex>
</MyTooltip>
)}
</Box>
))}
</ModalBody>
<Loading fixed={false} />
</MyModal>
<MyTooltip label={t('core.dataset.Quote Length')}>
<Flex alignItems={'center'}>
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
{item.q.length + (item.a?.length || 0)}
</Flex>
</MyTooltip>
{!isShare && item.score && (
<MyTooltip label={t('core.dataset.Similarity')}>
<Flex alignItems={'center'}>
<MyIcon name={'kbTest'} w={'12px'} />
<Progress
mx={2}
w={['60px', '90px']}
value={item.score * 100}
size="sm"
borderRadius={'20px'}
colorScheme="myGray"
border={theme.borders.base}
/>
<Box>{item.score.toFixed(4)}</Box>
</Flex>
</MyTooltip>
)}
<Box flex={1} />
{item.id && (
<MyTooltip label={t('core.dataset.data.Edit')}>
<Box
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={['16px', '18px']}
h={['16px', '18px']}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'blue.600'
}}
onClick={() => onclickEdit(item)}
/>
</Box>
</MyTooltip>
)}
</Flex>
)}
</Box>
))}
{editInputData && editInputData.id && (
<InputDataModal
onClose={() => setEditInputData(undefined)}
@@ -191,8 +208,7 @@ const QuoteModal = ({
collectionId={editInputData.collectionId}
/>
)}
<Loading fixed={false} />
</>
);
};
export default QuoteModal;
});

View File

@@ -222,7 +222,11 @@ const ResponseTags = ({
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
)}
{isOpenWholeModal && (
<WholeResponseModal response={responseData} onClose={onCloseWholeModal} />
<WholeResponseModal
response={responseData}
isShare={isShare}
onClose={onCloseWholeModal}
/>
)}
</Flex>
</>

View File

@@ -3,13 +3,14 @@ import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
import Tabs from '../Tabs';
import Tabs from '../Tabs';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import Markdown from '../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
function Row({
@@ -37,7 +38,9 @@ function Row({
fontSize={'sm'}
{...(isCodeBlock
? { transform: 'translateY(-3px)' }
: { px: 3, py: 1, border: theme.borders.base })}
: value
? { px: 3, py: 1, border: theme.borders.base }
: {})}
>
{value && <Markdown source={strValue} />}
{rawDom}
@@ -48,13 +51,51 @@ function Row({
const WholeResponseModal = ({
response,
isShare,
onClose
}: {
response: ChatHistoryItemResType[];
isShare: boolean;
onClose: () => void;
}) => {
const { t } = useTranslation();
return (
<MyModal
isCentered
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
minW={['90vw', '600px']}
iconSrc="/imgs/modal/wholeRecord.svg"
title={
<Flex alignItems={'center'}>
{t('chat.Complete Response')}
<MyTooltip label={'从左往右,为各个模块的响应顺序'}>
<QuestionOutlineIcon ml={2} />
</MyTooltip>
</Flex>
}
>
<Flex h={'100%'} flexDirection={'column'}>
<ResponseBox response={response} isShare={isShare} />
</Flex>
</MyModal>
);
};
export default WholeResponseModal;
const ResponseBox = React.memo(function ResponseBox({
response,
isShare
}: {
response: ChatHistoryItemResType[];
isShare: boolean;
}) {
const theme = useTheme();
const { t } = useTranslation();
const list = useMemo(
() =>
response.map((item, i) => ({
@@ -83,145 +124,129 @@ const WholeResponseModal = ({
const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]);
return (
<MyModal
isCentered
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
w={['90vw', '500px']}
iconSrc="/imgs/modal/wholeRecord.svg"
title={
<Flex alignItems={'center'}>
{t('chat.Complete Response')}
<MyTooltip label={'从左往右,为各个模块的响应顺序'}>
<QuestionOutlineIcon ml={2} />
</MyTooltip>
</Flex>
}
>
<Flex h={'100%'} flexDirection={'column'}>
<Box>
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
</Box>
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
{activeModule?.price !== undefined && (
<Row
label={t('core.chat.response.module price')}
value={`${formatPrice(activeModule?.price)}`}
/>
)}
<>
<Box>
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
</Box>
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
{activeModule?.price !== undefined && (
<Row
label={t('core.chat.response.module time')}
value={`${activeModule?.runningTime || 0}s`}
label={t('core.chat.response.module price')}
value={`${formatPrice(activeModule?.price)}`}
/>
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
)}
<Row
label={t('core.chat.response.module time')}
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
label={t('core.chat.response.context total length')}
value={activeModule?.contextTotalLen}
/>
{/* ai chat */}
<Row label={t('core.chat.response.module temperature')} value={activeModule?.temperature} />
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
<Row
label={t('core.chat.response.module historyPreview')}
rawDom={
activeModule.historyPreview ? (
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
{activeModule.historyPreview?.map((item, i) => (
<Box
key={i}
_notLast={{
borderBottom: '1px solid',
borderBottomColor: 'myWhite.700',
mb: 2
}}
pb={2}
>
<Box fontWeight={'bold'}>{item.obj}</Box>
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
</Box>
))}
</Box>
) : (
''
)
}
/>
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row
label={t('core.chat.response.context total length')}
value={activeModule?.contextTotalLen}
label={t('core.chat.response.module quoteList')}
rawDom={<QuoteList isShare={isShare} rawSearch={activeModule.quoteList} />}
/>
)}
{/* ai chat */}
{/* dataset search */}
{activeModule?.searchMode && (
<Row
label={t('core.chat.response.module temperature')}
value={activeModule?.temperature}
label={t('core.dataset.search.search mode')}
// @ts-ignore
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
/>
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
)}
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
{/* classify question */}
<Row
label={t('core.chat.response.module cq')}
value={(() => {
if (!activeModule?.cqList) return '';
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
})()}
/>
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
{/* extract */}
<Row
label={t('core.chat.response.module extract description')}
value={activeModule?.extractDescription}
/>
{activeModule?.extractResult && (
<Row
label={t('core.chat.response.module historyPreview')}
rawDom={
activeModule.historyPreview ? (
<>
{activeModule.historyPreview?.map((item, i) => (
<Box
key={i}
_notLast={{
borderBottom: '1px solid',
borderBottomColor: 'myWhite.700',
mb: 2
}}
pb={2}
>
<Box fontWeight={'bold'}>{item.obj}</Box>
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
</Box>
))}
</>
) : (
''
)
}
label={t('core.chat.response.module extract result')}
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
/>
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row
label={t('core.chat.response.module quoteList')}
value={`~~~json\n${JSON.stringify(activeModule.quoteList, null, 2)}`}
/>
)}
)}
{/* dataset search */}
{activeModule?.searchMode && (
<Row
label={t('core.dataset.search.search mode')}
// @ts-ignore
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
/>
)}
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
{/* classify question */}
{/* http */}
{activeModule?.body && (
<Row
label={t('core.chat.response.module cq')}
value={(() => {
if (!activeModule?.cqList) return '';
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
})()}
label={t('core.chat.response.module http body')}
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
/>
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
{/* extract */}
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module extract description')}
value={activeModule?.extractDescription}
label={t('core.chat.response.module http result')}
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
/>
{activeModule?.extractResult && (
<Row
label={t('core.chat.response.module extract result')}
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
/>
)}
)}
{/* http */}
{activeModule?.body && (
<Row
label={t('core.chat.response.module http body')}
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
/>
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module http result')}
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
/>
)}
{/* plugin */}
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin Resonse Detail')}
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
/>
)}
{activeModule?.pluginOutput && (
<Row
label={t('core.chat.response.plugin output')}
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
/>
)}
{/* plugin */}
{activeModule?.pluginOutput && (
<Row
label={t('core.chat.response.plugin output')}
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
/>
)}
{/* text editor */}
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
</Box>
</Flex>
</MyModal>
{/* text output */}
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
</Box>
</>
);
};
export default WholeResponseModal;
});

View File

@@ -349,7 +349,13 @@ const ChatBox = (
responseText,
isNewChat = false
} = await onStartChat({
chatList: newChatList,
chatList: newChatList.map((item) => ({
dataId: item.dataId,
obj: item.obj,
value: item.value,
status: item.status,
moduleName: item.moduleName
})),
messages,
controller: abortSignal,
generatingMessage,
@@ -386,7 +392,7 @@ const ChatBox = (
}, 100);
} catch (err: any) {
toast({
title: getErrText(err, '聊天出错了~'),
title: t(getErrText(err, 'core.chat.error.Chat error')),
status: 'error',
duration: 5000,
isClosable: true
@@ -419,7 +425,8 @@ const ChatBox = (
generatingMessage,
createQuestionGuide,
generatingScroll,
isPc
isPc,
t
]
);

View File

@@ -39,7 +39,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
const router = useRouter();
const { colorMode, setColorMode } = useColorMode();
const { Loading } = useLoading();
const { loading, setScreenWidth, isPc, loadGitStar } = useSystemStore();
const { loading, setScreenWidth, isPc } = useSystemStore();
const { userInfo } = useUserStore();
const isChatPage = useMemo(
@@ -61,12 +61,11 @@ const Layout = ({ children }: { children: JSX.Element }) => {
window.addEventListener('resize', resize);
resize();
loadGitStar();
return () => {
window.removeEventListener('resize', resize);
};
}, [loadGitStar, setScreenWidth]);
}, [setScreenWidth]);
const { data: unread = 0 } = useQuery(['getUnreadCount'], getUnreadCount, {
enabled: !!userInfo && !!feConfigs.isPlus,

View File

@@ -346,7 +346,7 @@
width: 100%;
* {
word-break: break-all;
word-break: break-word;
}
pre {

View File

@@ -19,49 +19,51 @@ type Props = TextareaProps & {
// variables: string[];
};
const PromptTextarea = (props: Props) => {
const ModalTextareaRef = useRef<HTMLTextAreaElement>(null);
const TextareaRef = useRef<HTMLTextAreaElement>(null);
const PromptTextarea = React.forwardRef<HTMLTextAreaElement, Props>(
function PromptTextarea(props, ref) {
const ModalTextareaRef = useRef<HTMLTextAreaElement>(null);
const TextareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const { title = t('core.app.edit.Prompt Editor'), value, ...childProps } = props;
const { t } = useTranslation();
const { title = t('core.app.edit.Prompt Editor'), ...childProps } = props;
const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} />
{isOpen && (
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}>
<ModalBody>
<Editor
textareaRef={ModalTextareaRef}
{...childProps}
minH={'300px'}
maxH={'auto'}
minW={['100%', '512px']}
/>
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
if (ModalTextareaRef.current && TextareaRef.current) {
TextareaRef.current.value = ModalTextareaRef.current.value;
}
return (
<>
<Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} />
{isOpen && (
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}>
<ModalBody>
<Editor
textareaRef={ModalTextareaRef}
{...childProps}
minH={'300px'}
maxH={'auto'}
minW={['100%', '512px']}
/>
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
if (ModalTextareaRef.current && TextareaRef.current) {
TextareaRef.current.value = ModalTextareaRef.current.value;
}
onClose();
}}
>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
)}
</>
);
};
onClose();
}}
>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
)}
</>
);
}
);
export default PromptTextarea;
export default React.memo(PromptTextarea);
const Editor = React.memo(function Editor({
onOpenModal,
@@ -75,7 +77,7 @@ const Editor = React.memo(function Editor({
return (
<Box h={'100%'} w={'100%'} position={'relative'}>
<Textarea ref={textareaRef} wordBreak={'break-all'} maxW={'100%'} {...props} />
<Textarea ref={textareaRef} textAlign={'justify'} maxW={'100%'} {...props} />
{onOpenModal && (
<Box
zIndex={1}

View File

@@ -1,5 +1,11 @@
import React from 'react';
import { SmoothStepEdge, EdgeLabelRenderer, EdgeProps, getSmoothStepPath } from 'reactflow';
import {
BezierEdge,
getBezierPath,
EdgeLabelRenderer,
EdgeProps,
getSmoothStepPath
} from 'reactflow';
import { Flex } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
@@ -21,7 +27,7 @@ const ButtonEdge = (
style = {}
} = props;
const [edgePath, labelX, labelY] = getSmoothStepPath({
const [edgePath, labelX, labelY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
@@ -42,7 +48,7 @@ const ButtonEdge = (
return (
<>
<SmoothStepEdge {...props} style={edgeStyle} />
<BezierEdge {...props} style={edgeStyle} />
<EdgeLabelRenderer>
<Flex
alignItems={'center'}

View File

@@ -14,7 +14,7 @@ import 'reactflow/dist/style.css';
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
const nodeTypes = {
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.userGuide]: dynamic(() => import('./components/nodes/NodeUserGuide')),
[FlowNodeTypeEnum.variable]: dynamic(() => import('./components/nodes/abandon/NodeVariable')),
[FlowNodeTypeEnum.questionInput]: dynamic(() => import('./components/nodes/NodeQuestionInput')),
@@ -28,7 +28,8 @@ const nodeTypes = {
[FlowNodeTypeEnum.runApp]: NodeSimple,
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
[FlowNodeTypeEnum.pluginModule]: NodeSimple
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
[FlowNodeTypeEnum.cfr]: NodeSimple
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge

View File

@@ -1,14 +1,16 @@
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
import { type Node, type Edge } from 'reactflow';
export function flowNode2Modules({
export const flowNode2Modules = ({
nodes,
edges
}: {
nodes: Node<FlowModuleItemType, string | undefined>[];
edges: Edge<any>[];
}) {
}) => {
const modules: ModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId,
name: item.data.name,
@@ -48,4 +50,19 @@ export function flowNode2Modules({
});
return modules;
}
};
export const filterExportModules = (modules: ModuleItemType[]) => {
modules.forEach((module) => {
// dataset - remove select dataset value
if (module.flowType === FlowNodeTypeEnum.datasetSearchNode) {
module.inputs.forEach((item) => {
if (item.key === ModuleInputKeyEnum.datasetSelectList) {
item.value = [];
}
});
}
});
return JSON.stringify(modules, null, 2);
};

View File

@@ -14,6 +14,9 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy
quoteTemplate: true,
quotePrompt: true
},
cfr: {
background: true
},
dataset: {
datasets: true,
similarity: true,

View File

@@ -39,6 +39,7 @@ function App({ Component, pageProps }: AppProps) {
const router = useRouter();
const { hiId } = router.query as { hiId?: string };
const { i18n } = useTranslation();
const { loadGitStar } = useSystemStore();
const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]);
const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI');
@@ -46,18 +47,23 @@ function App({ Component, pageProps }: AppProps) {
// get init data
(async () => {
const {
feConfigs: { scripts, isPlus, systemTitle }
feConfigs: { scripts, isPlus, show_git, systemTitle }
} = await clientInitData();
setTitle(systemTitle || 'FastGPT');
// log fastgpt
!isPlus &&
if (!isPlus) {
console.log(
'%cWelcome to FastGPT',
'font-family:Arial; color:#3370ff ; font-size:18px; font-weight:bold;',
`GitHubhttps://github.com/labring/FastGPT`
);
}
if (show_git) {
loadGitStar();
}
setScripts(scripts || []);
})();

View File

@@ -6,9 +6,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { getExtractModel } from '@/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -31,13 +31,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
}
function simpleChatTemplate({
formData,
maxToken
}: {
formData: AppSimpleEditFormType;
maxToken: number;
}): ModuleItemType[] {
type Props = { formData: AppSimpleEditFormType; maxToken: number };
function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
@@ -52,7 +48,10 @@ function simpleChatTemplate({
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
@@ -87,6 +86,8 @@ function simpleChatTemplate({
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
@@ -94,6 +95,9 @@ function simpleChatTemplate({
type: 'selectChatModel',
label: '对话模型',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
@@ -102,6 +106,7 @@ function simpleChatTemplate({
type: 'hidden',
label: '温度',
value: 1,
valueType: 'number',
min: 0,
max: 10,
step: 1,
@@ -115,6 +120,8 @@ function simpleChatTemplate({
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
@@ -122,6 +129,7 @@ function simpleChatTemplate({
type: 'hidden',
label: '回复上限',
value: maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
@@ -135,14 +143,18 @@ function simpleChatTemplate({
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
valueType: 'boolean',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
@@ -150,6 +162,9 @@ function simpleChatTemplate({
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
@@ -157,12 +172,18 @@ function simpleChatTemplate({
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
@@ -175,31 +196,42 @@ function simpleChatTemplate({
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 8,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'history',
type: 'target',
label: 'core.module.input.label.chat history',
valueType: 'chatHistory',
connected: false,
value: 8
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
@@ -232,29 +264,26 @@ function simpleChatTemplate({
}
];
}
function datasetTemplate({
formData,
maxToken
}: {
formData: AppSimpleEditFormType;
maxToken: number;
}): ModuleItemType[] {
return [
function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
connected: true
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
@@ -265,11 +294,11 @@ function datasetTemplate({
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
moduleId: 'vuc92c',
key: 'userChatInput'
},
{
moduleId: 'datasetSearch',
moduleId: 'chatModule',
key: 'userChatInput'
}
]
@@ -283,43 +312,65 @@ function datasetTemplate({
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 956.0838440206068,
y: 887.462827870246
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'datasets',
value: formData.dataset.datasets,
type: FlowNodeInputTypeEnum.custom,
type: 'selectDataset',
label: '关联的知识库',
value: formData.dataset.datasets,
valueType: 'selectDataset',
list: [],
required: true,
showTargetInApp: false,
showTargetInPlugin: true,
connected: false
},
{
key: 'similarity',
value: 0.1,
type: FlowNodeInputTypeEnum.slider,
label: '相关度',
type: 'hidden',
label: '最低相关性',
value: 0.15,
valueType: 'number',
min: 0,
max: 1,
step: 0.01,
markList: [
{
label: '0',
value: 0
},
{
label: '1',
value: 1
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'limit',
type: 'hidden',
label: '引用上限',
description: '单次搜索最大的 Tokens 数量中文约1字=1.7Tokens英文约1字=1Tokens',
value: 2000,
type: FlowNodeInputTypeEnum.slider,
label: '单次搜索上限',
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'switch',
type: FlowNodeInputTypeEnum.target,
label: '触发器',
connected: false
},
{
key: 'userChatInput',
type: FlowNodeInputTypeEnum.target,
label: '用户问题',
connected: true
},
{
key: 'searchMode',
type: 'hidden',
@@ -334,10 +385,20 @@ function datasetTemplate({
key: 'datasetParamsModal',
type: 'selectDatasetParamsModal',
label: '',
connected: false,
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false
showTargetInPlugin: false,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
@@ -386,8 +447,8 @@ function datasetTemplate({
flowType: 'chatNode',
showStatus: true,
position: {
x: 1551.71405495818,
y: 977.4911578918461
x: 2022.7264786978908,
y: 1006.3102431257475
},
inputs: [
{
@@ -395,6 +456,8 @@ function datasetTemplate({
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
@@ -402,6 +465,9 @@ function datasetTemplate({
type: 'selectChatModel',
label: '对话模型',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
@@ -410,6 +476,7 @@ function datasetTemplate({
type: 'hidden',
label: '温度',
value: 0,
valueType: 'number',
min: 0,
max: 10,
step: 1,
@@ -423,6 +490,8 @@ function datasetTemplate({
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
@@ -430,6 +499,7 @@ function datasetTemplate({
type: 'hidden',
label: '回复上限',
value: maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
@@ -443,14 +513,18 @@ function datasetTemplate({
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
valueType: 'boolean',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
@@ -458,6 +532,9 @@ function datasetTemplate({
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
@@ -465,12 +542,18 @@ function datasetTemplate({
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
@@ -483,31 +566,42 @@ function datasetTemplate({
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'history',
type: 'target',
label: 'core.module.input.label.chat history',
valueType: 'chatHistory',
connected: false,
value: 8
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
@@ -537,6 +631,91 @@ function datasetTemplate({
targets: []
}
]
},
{
moduleId: 'vuc92c',
name: 'core.module.template.cfr',
avatar: '/imgs/module/cfr.svg',
flowType: 'cfr',
showStatus: true,
position: {
x: 758.2985382279098,
y: 1124.6527309337314
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectExtractModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
value: getExtractModel().model,
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.module.input.label.cfr background',
max: 300,
value: formData.cfr.background,
valueType: 'string',
description: 'core.module.input.description.cfr background',
placeholder: 'core.module.input.placeholder.cfr background',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'system_text',
label: 'core.module.output.label.cfr result',
valueType: 'string',
type: 'source',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
}
];
return modules;
}

View File

@@ -6,15 +6,12 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
import { getExtractModel } from '@/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData, chatModelList } = req.body as FormatForm2ModulesProps;
const { formData } = req.body as FormatForm2ModulesProps;
const modules =
formData.dataset.datasets.length > 0
@@ -32,100 +29,34 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
}
function chatModelInput(formData: AppSimpleEditFormType): FlowNodeInputItemType[] {
return [
{
key: 'model',
value: formData.aiSettings.model,
type: 'custom',
label: '对话模型',
connected: false
},
{
key: 'temperature',
value: formData.aiSettings.temperature,
type: 'slider',
label: '温度',
connected: false
},
{
key: 'maxToken',
value: formData.aiSettings.maxToken,
type: 'custom',
label: '回复上限',
connected: false
},
{
key: 'systemPrompt',
value: formData.aiSettings.systemPrompt || '',
type: 'textarea',
label: '系统提示词',
connected: false
},
{
key: ModuleInputKeyEnum.aiChatIsResponseText,
value: true,
type: 'hidden',
label: '返回AI内容',
connected: false
},
{
key: 'quoteTemplate',
value: formData.aiSettings.quoteTemplate || '',
type: 'hidden',
label: '引用内容模板',
connected: false
},
{
key: 'quotePrompt',
value: formData.aiSettings.quotePrompt || '',
type: 'hidden',
label: '引用内容提示词',
connected: false
},
{
key: 'switch',
type: 'target',
label: '触发器',
connected: formData.dataset.datasets.length > 0 && !!formData.dataset.searchEmptyText
},
{
key: 'history',
type: 'target',
label: 'core.module.input.label.chat history',
connected: false,
value: 6
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
connected: formData.dataset.datasets.length > 0
},
{
key: 'userChatInput',
type: 'target',
label: '用户问题',
connected: true
}
];
}
function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
flowType: FlowNodeTypeEnum.questionInput,
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
connected: false,
type: 'systemInput',
valueType: 'string',
label: '用户问题',
type: 'systemInput'
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: '用户问题',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
@@ -133,115 +64,309 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
flowType: FlowNodeTypeEnum.chatNode,
inputs: chatModelInput(formData),
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '直接响应,无需配置',
type: 'hidden',
targets: []
},
{
key: 'finish',
label: '回复结束',
description: 'AI 回复完成后触发',
valueType: 'boolean',
type: 'source',
targets: []
}
],
position: {
x: 981.9682828103937,
y: 890.014595014464
},
moduleId: 'chatModule'
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: formData.aiSettings.temperature,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: formData.aiSettings.maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}
function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: '用户问题(对话入口)',
flowType: FlowNodeTypeEnum.questionInput,
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
key: 'userChatInput',
label: '用户问题',
type: 'systemInput',
valueType: 'string',
label: '用户问题',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: '用户问题',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
moduleId: 'vuc92c',
key: 'userChatInput'
},
{
moduleId: 'datasetSearch',
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
]
},
{
moduleId: 'datasetSearch',
name: '知识库搜索',
flowType: FlowNodeTypeEnum.datasetSearchNode,
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'datasets',
value: formData.dataset.datasets,
type: FlowNodeInputTypeEnum.custom,
type: 'selectDataset',
label: '关联的知识库',
value: formData.dataset.datasets,
valueType: 'selectDataset',
list: [],
required: true,
showTargetInApp: false,
showTargetInPlugin: true,
connected: false
},
{
key: 'similarity',
type: 'hidden',
label: '最低相关性',
value: formData.dataset.similarity,
type: FlowNodeInputTypeEnum.slider,
label: '相关度',
valueType: 'number',
min: 0,
max: 1,
step: 0.01,
markList: [
{
label: '0',
value: 0
},
{
label: '1',
value: 1
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'limit',
type: 'hidden',
label: '引用上限',
description: '单次搜索最大的 Tokens 数量中文约1字=1.7Tokens英文约1字=1Tokens',
value: formData.dataset.limit,
type: FlowNodeInputTypeEnum.slider,
label: '单次搜索上限',
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'switch',
type: FlowNodeInputTypeEnum.target,
label: '触发器',
connected: false
},
{
key: 'userChatInput',
type: FlowNodeInputTypeEnum.target,
label: '用户问题',
connected: true
},
{
key: 'searchMode',
type: 'hidden',
@@ -256,19 +381,32 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
key: 'datasetParamsModal',
type: 'selectDatasetParamsModal',
label: '',
connected: false,
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false
showTargetInPlugin: false,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'isEmpty',
label: '搜索结果为空',
type: 'source',
valueType: 'boolean',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: 'emptyText',
moduleId: '6dtsvu',
key: 'switch'
}
]
@@ -276,6 +414,9 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
},
{
key: 'unEmpty',
label: '搜索结果不为空',
type: 'source',
valueType: 'boolean',
targets: formData.dataset.searchEmptyText
? [
{
@@ -287,77 +428,352 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
},
{
key: 'quoteQA',
label: '引用内容',
description:
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
type: 'source',
valueType: 'datasetQuote',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
}
],
position: {
x: 956.0838440206068,
y: 887.462827870246
},
moduleId: 'datasetSearch'
},
...(formData.dataset.searchEmptyText
? [
{
name: '指定回复',
flowType: FlowNodeTypeEnum.answerNode,
inputs: [
{
key: ModuleInputKeyEnum.switch,
type: FlowNodeInputTypeEnum.target,
label: '触发器',
connected: true
},
{
key: ModuleInputKeyEnum.answerText,
value: formData.dataset.searchEmptyText,
type: FlowNodeInputTypeEnum.textarea,
valueType: ModuleIOValueTypeEnum.string,
label: '回复的内容',
connected: false
}
],
outputs: [],
position: {
x: 1553.5815811529146,
y: 637.8753731306779
},
moduleId: 'emptyText'
}
]
: []),
{
name: 'AI 对话',
flowType: FlowNodeTypeEnum.chatNode,
inputs: chatModelInput(formData),
showStatus: true,
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '直接响应,无需配置',
type: 'hidden',
targets: []
},
{
key: 'finish',
label: '回复结束',
description: 'AI 回复完成后触发',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
],
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 1551.71405495818,
y: 977.4911578918461
x: 2022.7264786978908,
y: 1006.3102431257475
},
moduleId: 'chatModule'
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: !!formData.dataset?.searchEmptyText
},
{
key: 'model',
type: 'selectChatModel',
label: '对话模型',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: formData.aiSettings.temperature,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: formData.aiSettings.maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: '系统提示词',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
},
{
moduleId: 'vuc92c',
name: 'core.module.template.cfr',
avatar: '/imgs/module/cfr.svg',
flowType: 'cfr',
showStatus: true,
position: {
x: 758.2985382279098,
y: 1124.6527309337314
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectExtractModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
value: getExtractModel().model,
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.module.input.label.cfr background',
max: 300,
value: formData.cfr.background,
valueType: 'string',
description: 'core.module.input.description.cfr background',
placeholder: 'core.module.input.placeholder.cfr background',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'system_text',
label: 'core.module.output.label.cfr result',
valueType: 'string',
type: 'source',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
}
];
if (formData.dataset?.searchEmptyText) {
modules.push({
moduleId: '6dtsvu',
name: '指定回复',
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 2018.2744321961648,
y: 616.1220817209096
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'text',
type: 'textarea',
value: formData.dataset.searchEmptyText,
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
placeholder:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
}
],
outputs: [
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
]
});
}
return modules;
}

View File

@@ -58,6 +58,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/* start process */
const { responseData } = await dispatchModules({
res,
mode: 'test',
teamId,
tmbId,
user,
@@ -101,6 +102,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
export const config = {
api: {
bodyParser: {
sizeLimit: '10mb'
},
responseLimit: '20mb'
}
};

View File

@@ -9,6 +9,7 @@ import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { searchDatasetData } from '@/service/core/dataset/data/pg';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -33,8 +34,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// auth balance
await authTeamBalance(teamId);
// query extension
// const { queries } = await searchQueryExtension({
// query: text,
// model: global.chatModels[0].model
// });
const { searchRes, tokenLen } = await searchDatasetData({
text,
rawQuery: text,
queries: [text],
model: dataset.vectorModel,
limit: Math.min(limit * 800, 30000),
datasetIds: [datasetId],

View File

@@ -19,6 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const result = (() => {
if (typeof input === 'string') {
const defaultReg: any[] = [
'',
undefined,
'undefined',
null,

View File

@@ -2,12 +2,14 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { request } from '@fastgpt/service/common/api/plusRequest';
import type { Method } from 'axios';
import { connectToDatabase } from '@/service/mongo';
import { setCookie } from '@fastgpt/service/support/permission/controller';
import { getInitConfig } from '../system/getInitData';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
if (!global.systemEnv?.pluginBaseUrl) {
await getInitConfig();
}
const method = (req.method || 'POST') as Method;
const { path = [], ...query } = req.query as any;

View File

@@ -1,4 +1,4 @@
import type { FeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types/index.d';
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { readFileSync, readdirSync } from 'fs';
@@ -6,23 +6,14 @@ import type { ConfigFileType, InitDateResponse } from '@/global/common/api/syste
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent';
import {
defaultChatModels,
defaultQAModels,
defaultCQModels,
defaultExtractModels,
defaultQGModels,
defaultVectorModels,
defaultAudioSpeechModels,
defaultWhisperModel,
defaultReRankModels
} from '@fastgpt/global/core/ai/model';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { getFastGPTFeConfig } from '@fastgpt/service/common/system/config/controller';
import { connectToDatabase } from '@/service/mongo';
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
import { readConfigData } from '@/service/common/system';
import { exit } from 'process';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await getInitConfig();
@@ -48,18 +39,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
const defaultSystemEnv: SystemEnvType = {
vectorMaxProcess: 15,
qaMaxProcess: 15,
pgHNSWEfSearch: 100
};
const defaultFeConfigs: FeConfigsType = {
show_emptyChat: true,
show_git: true,
show_register: false,
docUrl: 'https://doc.fastgpt.in',
openAPIDocUrl: 'https://doc.fastgpt.in/docs/development/openapi',
systemTitle: 'FastGPT',
concatMd:
'* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: ![](https://doc.fastgpt.in/wechat-fastgpt.webp)',
limit: {
exportLimitMinutes: 0
},
@@ -73,24 +60,45 @@ export async function getInitConfig() {
await connectToDatabase();
initGlobal();
const filename =
process.env.NODE_ENV === 'development' ? 'data/config.local.json' : '/app/data/config.json';
const res = JSON.parse(readFileSync(filename, 'utf-8')) as ConfigFileType;
// load config
const [dbConfig, fileConfig] = await Promise.all([
getFastGPTFeConfig(),
readConfigData('config.json')
]);
const fileRes = JSON.parse(fileConfig) as ConfigFileType;
// get config from database
const dbFeConfig = await getFastGPTFeConfig();
const concatConfig: ConfigFileType = {
...res,
const config: ConfigFileType = {
...fileRes,
FeConfig: {
...res.FeConfig,
...dbFeConfig
...defaultFeConfigs,
...fileRes.FeConfig,
...dbConfig
}
};
setDefaultData(concatConfig);
// set config
global.feConfigs = {
isPlus: !!config.SystemParams.pluginBaseUrl,
concatMd: config.FeConfig.show_git ? config.FeConfig.concatMd : '',
...config.FeConfig
};
global.systemEnv = config.SystemParams;
global.chatModels = config.ChatModels;
global.qaModels = config.QAModels;
global.cqModels = config.CQModels;
global.extractModels = config.ExtractModels;
global.qgModels = config.QGModels;
global.vectorModels = config.VectorModels;
global.reRankModels = config.ReRankModels;
global.audioSpeechModels = config.AudioSpeechModels;
global.whisperModel = config.WhisperModel;
global.priceMd = '';
} catch (error) {
setDefaultData();
console.log('get init config error, set default', error);
console.error('Load init config error', error);
exit(1);
}
await getSimpleModeTemplates();
@@ -117,45 +125,13 @@ export async function getInitConfig() {
}
export function initGlobal() {
// init tikToken
getTikTokenEnc();
initHttpAgent();
global.communityPlugins = [];
global.simpleModeTemplates = [];
global.qaQueueLen = global.qaQueueLen ?? 0;
global.vectorQueueLen = global.vectorQueueLen ?? 0;
}
export function setDefaultData(res?: ConfigFileType) {
global.systemEnv = res?.SystemParams
? { ...defaultSystemEnv, ...res.SystemParams }
: defaultSystemEnv;
global.feConfigs = res?.FeConfig
? {
concatMd: res?.FeConfig?.show_git
? '* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: ![](https://doc.fastgpt.in/wechat-fastgpt.webp)'
: '',
...defaultFeConfigs,
...res.FeConfig,
isPlus: !!res.SystemParams?.pluginBaseUrl
}
: defaultFeConfigs;
global.chatModels = res?.ChatModels || defaultChatModels;
global.qaModels = res?.QAModels || defaultQAModels;
global.cqModels = res?.CQModels || defaultCQModels;
global.extractModels = res?.ExtractModels || defaultExtractModels;
global.qgModels = res?.QGModels || defaultQGModels;
global.vectorModels = res?.VectorModels || defaultVectorModels;
global.reRankModels = res?.ReRankModels || defaultReRankModels;
global.audioSpeechModels = res?.AudioSpeechModels || defaultAudioSpeechModels;
global.whisperModel = res?.WhisperModel || defaultWhisperModel;
global.priceMd = '';
// init tikToken
getTikTokenEnc();
initHttpAgent();
}
export function getSystemVersion() {
@@ -209,9 +185,7 @@ async function getSimpleModeTemplates() {
try {
const basePath =
process.env.NODE_ENV === 'development'
? 'public/simpleTemplates'
: '/app/projects/app/public/simpleTemplates';
process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
// read data/simpleTemplates directory, get all json file
const files = readdirSync(basePath);
// filter json file
@@ -243,9 +217,7 @@ function getSystemPlugin() {
if (global.communityPlugins && global.communityPlugins.length > 0) return;
const basePath =
process.env.NODE_ENV === 'development'
? 'public/pluginTemplates'
: '/app/projects/app/public/pluginTemplates';
process.env.NODE_ENV === 'development' ? 'data/pluginTemplates' : '/app/data/pluginTemplates';
// read data/pluginTemplates directory, get all json file
const files = readdirSync(basePath);
// filter json file

View File

@@ -200,13 +200,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
/* start flow controller */
const { responseData, answerText } = await dispatchModules({
res,
mode: 'chat',
user,
teamId: String(user.team.teamId),
tmbId: String(user.team.tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
modules: app.modules,
user,
teamId: user.team.teamId,
tmbId: user.team.tmbId,
variables,
histories: concatHistories,
startParams: {

View File

@@ -13,7 +13,7 @@ import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest';
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { flowNode2Modules } from '@/components/core/module/utils';
import { flowNode2Modules, filterExportModules } from '@/components/core/module/utils';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useToast } from '@/web/common/hooks/useToast';
import { useConfirm } from '@/web/common/hooks/useConfirm';
@@ -136,12 +136,12 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
borderRadius={'lg'}
variant={'base'}
aria-label={'save'}
onClick={() =>
copyData(
JSON.stringify(flowNode2Modules({ nodes, edges }), null, 2),
t('app.Export Config Successful')
)
}
onClick={() => {
const modules = flow2ModulesAndCheck();
if (modules) {
copyData(filterExportModules(modules), t('app.Export Config Successful'));
}
}}
/>
</MyTooltip>

View File

@@ -24,7 +24,6 @@ import { chatNodeSystemPromptTip, welcomeTextTip } from '@fastgpt/global/core/mo
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { streamFetch } from '@/web/common/api/fetch';
import { useRouter } from 'next/router';
import { useToast } from '@/web/common/hooks/useToast';
@@ -51,6 +50,7 @@ import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constant
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import PromptTextarea from '@/components/common/Textarea/PromptTextarea/index';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
const InfoModal = dynamic(() => import('../InfoModal'));
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
@@ -132,6 +132,12 @@ function ConfigForm({
);
}, [getValues, refresh]);
const datasetSearchMode = useMemo(() => {
const mode = getValues('dataset.searchMode');
if (!mode) return '';
return t(DatasetSearchModeMap[mode]?.title);
}, [getValues, t, refresh]);
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
const modules = await postForm2Modules(data, data.templateId);
@@ -251,39 +257,6 @@ function ConfigForm({
/>
</Flex>
{/* welcome */}
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
<Box {...BoxStyles} mt={2}>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}>{t('core.app.Welcome Text')}</Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<Textarea
mt={2}
rows={5}
placeholder={welcomeTextTip}
borderColor={'myGray.100'}
{...register('userGuide.welcomeText')}
/>
</Box>
)}
{/* variable */}
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
<Box mt={2} {...BoxStyles}>
<VariableEdit
variables={getValues('userGuide.variables')}
onChange={(e) => {
setValue('userGuide.variables', e);
setRefresh(!refresh);
}}
/>
</Box>
)}
{/* ai */}
{selectSimpleTemplate?.systemForm?.aiSettings && (
<Box mt={5} {...BoxStyles}>
@@ -340,7 +313,6 @@ function ConfigForm({
defaultValue={getValues('aiSettings.systemPrompt')}
onBlur={(e) => {
setValue('aiSettings.systemPrompt', e.target.value || '');
setRefresh(!refresh);
}}
/>
</Flex>
@@ -372,16 +344,20 @@ function ConfigForm({
</Flex>
)}
</Flex>
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
{t('core.dataset.search.Min Similarity')}: {getValues('dataset.similarity')},{' '}
{t('core.dataset.search.Max Tokens')}: {getValues('dataset.limit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
{getValues('dataset.datasets').length > 0 && (
<Flex mt={1} color={'myGray.600'} fontSize={'sm'} mb={2}>
{t('core.dataset.search.search mode')}: {datasetSearchMode}
{', '}
{t('core.dataset.search.Min Similarity')}: {getValues('dataset.similarity')}
{', '}
{t('core.dataset.search.Max Tokens')}: {getValues('dataset.limit')}
{getValues('dataset.searchEmptyText') === ''
? ''
: t('core.dataset.Set Empty Result Tip')}
</Flex>
)}
<Grid
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
my={2}
gridGap={[2, 4]}
>
{selectDatasets.map((item) => (
@@ -412,6 +388,64 @@ function ConfigForm({
</MyTooltip>
))}
</Grid>
{selectSimpleTemplate?.systemForm?.cfr && getValues('dataset.datasets').length > 0 && (
<Box mt={10}>
<Box {...LabelStyles} w={'auto'}>
{t('core.app.edit.cfr background prompt')}
<MyTooltip label={t('core.app.edit.cfr background tip')} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<PromptTextarea
mt={1}
flex={1}
bg={'myWhite.400'}
rows={5}
placeholder={t('core.module.input.placeholder.cfr background')}
defaultValue={getValues('cfr.background')}
onBlur={(e) => {
setValue('cfr.background', e.target.value || '');
}}
/>
</Box>
)}
</Box>
)}
{/* variable */}
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
<Box mt={2} {...BoxStyles}>
<VariableEdit
variables={getValues('userGuide.variables')}
onChange={(e) => {
setValue('userGuide.variables', e);
setRefresh(!refresh);
}}
/>
</Box>
)}
{/* welcome */}
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
<Box {...BoxStyles} mt={2}>
<Flex alignItems={'center'}>
<Image alt={''} src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}>{t('core.app.Welcome Text')}</Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<PromptTextarea
mt={2}
bg={'myWhite.400'}
rows={5}
placeholder={welcomeTextTip}
defaultValue={getValues('userGuide.welcomeText')}
onBlur={(e) => {
setValue('userGuide.welcomeText', e.target.value || '');
}}
/>
</Box>
)}

View File

@@ -609,7 +609,6 @@ const CollectionCard = () => {
),
onClick: () =>
openSyncConfirm(() => {
console.log(collection._id);
onclickStartSync(collection._id);
})()
}

View File

@@ -8,7 +8,7 @@ import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { flowNode2Modules } from '@/components/core/module/utils';
import { filterExportModules, flowNode2Modules } from '@/components/core/module/utils';
import { putUpdatePlugin } from '@/web/core/plugin/api';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
@@ -62,7 +62,9 @@ const Header = ({ plugin, onClose }: Props) => {
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (!input.value || input.value === '' || input.value?.length === 0) return true;
if (input.value === undefined || input.value === '' || input.value?.length === 0) {
return true;
}
return false;
})
) {
@@ -155,7 +157,7 @@ const Header = ({ plugin, onClose }: Props) => {
onClick={() => {
const modules = flow2ModulesAndCheck();
if (modules) {
copyData(JSON.stringify(modules, null, 2), t('app.Export Config Successful'));
copyData(filterExportModules(modules), t('app.Export Config Successful'));
}
}}
/>

View File

@@ -0,0 +1,25 @@
import { existsSync, readFileSync } from 'fs';
export const readConfigData = (name: string) => {
const isDev = process.env.NODE_ENV === 'development';
const splitName = name.split('.');
const devName = `${splitName[0]}.local.${splitName[1]}`;
const filename = (() => {
if (isDev) {
// check local file exists
const hasLocalFile = existsSync(`data/${devName}`);
if (hasLocalFile) {
return `data/${devName}`;
}
return `data/${name}`;
}
// production path
return `/app/data/${name}`;
})();
const content = readFileSync(filename, 'utf-8');
return content;
};

View File

@@ -1,62 +1,26 @@
import {
defaultAudioSpeechModels,
defaultChatModels,
defaultCQModels,
defaultExtractModels,
defaultQAModels,
defaultQGModels,
defaultVectorModels
} from '@fastgpt/global/core/ai/model';
export const getChatModel = (model?: string) => {
return (
(global.chatModels || defaultChatModels).find((item) => item.model === model) ||
global.chatModels?.[0] ||
defaultChatModels[0]
);
return global.chatModels.find((item) => item.model === model) ?? global.chatModels[0];
};
export const getQAModel = (model?: string) => {
return (
(global.qaModels || defaultQAModels).find((item) => item.model === model) ||
global.qaModels?.[0] ||
defaultQAModels[0]
);
return global.qaModels.find((item) => item.model === model) || global.qaModels[0];
};
export const getCQModel = (model?: string) => {
return (
(global.cqModels || defaultCQModels).find((item) => item.model === model) ||
global.cqModels?.[0] ||
defaultCQModels[0]
);
return global.cqModels.find((item) => item.model === model) || global.cqModels[0];
};
export const getExtractModel = (model?: string) => {
return (
(global.extractModels || defaultExtractModels).find((item) => item.model === model) ||
global.extractModels?.[0] ||
defaultExtractModels[0]
);
return global.extractModels.find((item) => item.model === model) || global.extractModels[0];
};
export const getQGModel = (model?: string) => {
return (
(global.qgModels || defaultQGModels).find((item) => item.model === model) ||
global.qgModels?.[0] ||
defaultQGModels[0]
);
return global.qgModels.find((item) => item.model === model) || global.qgModels[0];
};
export const getVectorModel = (model?: string) => {
return (
global.vectorModels.find((item) => item.model === model) ||
global.vectorModels?.[0] ||
defaultVectorModels[0]
);
return global.vectorModels.find((item) => item.model === model) || global.vectorModels[0];
};
export function getAudioSpeechModel(model?: string) {
return (
global.audioSpeechModels.find((item) => item.model === model) ||
global.audioSpeechModels?.[0] ||
defaultAudioSpeechModels[0]
global.audioSpeechModels.find((item) => item.model === model) || global.audioSpeechModels[0]
);
}

View File

@@ -12,6 +12,7 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '../utils';
import { reRankRecall } from '../../ai/rerank';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { hashStr } from '@fastgpt/global/common/string/tools';
export async function insertData2Pg(props: {
mongoDataId: string;
@@ -98,34 +99,40 @@ export async function updatePgDataById({
// ------------------ search start ------------------
type SearchProps = {
text: string;
model: string;
similarity?: number; // min distance
limit: number; // max Token limit
datasetIds: string[];
searchMode?: `${DatasetSearchModeEnum}`;
};
export async function searchDatasetData(props: SearchProps) {
export async function searchDatasetData(
props: SearchProps & { rawQuery: string; queries: string[] }
) {
let {
text,
rawQuery,
queries,
model,
similarity = 0,
limit: maxTokens,
searchMode = DatasetSearchModeEnum.embedding
searchMode = DatasetSearchModeEnum.embedding,
datasetIds = []
} = props;
searchMode = global.systemEnv?.pluginBaseUrl ? searchMode : DatasetSearchModeEnum.embedding;
/* init params */
searchMode = global.systemEnv?.pluginBaseUrl ? searchMode : DatasetSearchModeEnum.embedding;
// Compatible with topk limit
if (maxTokens < 50) {
maxTokens = 1500;
}
const rerank =
global.reRankModels?.[0] &&
(searchMode === DatasetSearchModeEnum.embeddingReRank ||
searchMode === DatasetSearchModeEnum.embFullTextReRank);
let set = new Set<string>();
const oneChunkToken = 50;
const { embeddingLimit, fullTextLimit } = (() => {
/* function */
const countRecallLimit = () => {
const oneChunkToken = 50;
const estimatedLen = Math.max(20, Math.ceil(maxTokens / oneChunkToken));
// Increase search range, reduce hnsw loss. 20 ~ 100
@@ -148,34 +155,295 @@ export async function searchDatasetData(props: SearchProps) {
embeddingLimit: Math.min(80, Math.max(50, estimatedLen * 2)),
fullTextLimit: Math.min(40, Math.max(20, estimatedLen))
};
})();
};
const embeddingRecall = async ({ query, limit }: { query: string; limit: number }) => {
const { vectors, tokenLen } = await getVectorsByText({
model,
input: [query]
});
const [{ tokenLen, embeddingRecallResults }, { fullTextRecallResults }] = await Promise.all([
embeddingRecall({
...props,
rerank,
limit: embeddingLimit
}),
fullTextRecall({
...props,
limit: fullTextLimit
})
]);
const results: any = await PgClient.query(
`BEGIN;
SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100};
select id, collection_id, data_id, (vector <#> '[${vectors[0]}]') * -1 AS score
from ${PgDatasetTableName}
where dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')})
${rerank ? '' : `AND vector <#> '[${vectors[0]}]' < -${similarity}`}
order by score desc limit ${limit};
COMMIT;`
);
// concat embedding and fullText recall result
let set = new Set<string>(embeddingRecallResults.map((item) => item.id));
const concatRecallResults = embeddingRecallResults;
fullTextRecallResults.forEach((item) => {
if (!set.has(item.id) && item.score >= similarity) {
concatRecallResults.push(item);
set.add(item.id);
const rows = results?.[2]?.rows as PgSearchRawType[];
// concat same data_id
const filterRows: PgSearchRawType[] = [];
let set = new Set<string>();
for (const row of rows) {
if (!set.has(row.data_id)) {
filterRows.push(row);
set.add(row.data_id);
}
}
// get q and a
const [collections, dataList] = await Promise.all([
MongoDatasetCollection.find(
{
_id: { $in: filterRows.map((item) => item.collection_id) }
},
'name fileId rawLink'
).lean(),
MongoDatasetData.find(
{
_id: { $in: filterRows.map((item) => item.data_id?.trim()) }
},
'datasetId collectionId q a chunkIndex indexes'
).lean()
]);
const formatResult = filterRows
.map((item) => {
const collection = collections.find(
(collection) => String(collection._id) === item.collection_id
);
const data = dataList.find((data) => String(data._id) === item.data_id);
// if collection or data UnExist, the relational mongo data already deleted
if (!collection || !data) return null;
return {
id: String(data._id),
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
sourceName: collection.name || '',
sourceId: collection?.fileId || collection?.rawLink,
score: item.score
};
})
.filter((item) => item !== null) as SearchDataResponseItemType[];
return {
embeddingRecallResults: formatResult,
tokenLen
};
};
const fullTextRecall = async ({
query,
limit
}: {
query: string;
limit: number;
}): Promise<{
fullTextRecallResults: SearchDataResponseItemType[];
tokenLen: number;
}> => {
if (limit === 0) {
return {
fullTextRecallResults: [],
tokenLen: 0
};
}
let searchResults = (
await Promise.all(
datasetIds.map((id) =>
MongoDatasetData.find(
{
datasetId: id,
$text: { $search: jiebaSplit({ text: query }) }
},
{
score: { $meta: 'textScore' },
_id: 1,
datasetId: 1,
collectionId: 1,
q: 1,
a: 1,
indexes: 1,
chunkIndex: 1
}
)
.sort({ score: { $meta: 'textScore' } })
.limit(limit)
.lean()
)
)
).flat() as (DatasetDataSchemaType & { score: number })[];
// resort
searchResults.sort((a, b) => b.score - a.score);
searchResults.slice(0, limit);
const collections = await MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink'
);
return {
fullTextRecallResults: searchResults.map((item) => {
const collection = collections.find((col) => String(col._id) === String(item.collectionId));
return {
id: String(item._id),
datasetId: String(item.datasetId),
collectionId: String(item.collectionId),
sourceName: collection?.name || '',
sourceId: collection?.fileId || collection?.rawLink,
q: item.q,
a: item.a,
chunkIndex: item.chunkIndex,
indexes: item.indexes,
// @ts-ignore
score: item.score
};
}),
tokenLen: 0
};
};
const reRankSearchResult = async ({
data,
query
}: {
data: SearchDataResponseItemType[];
query: string;
}): Promise<SearchDataResponseItemType[]> => {
try {
const results = await reRankRecall({
query,
inputs: data.map((item) => ({
id: item.id,
text: `${item.q}\n${item.a}`
}))
});
if (!Array.isArray(results)) return data;
// add new score to data
const mergeResult = results
.map((item) => {
const target = data.find((dataItem) => dataItem.id === item.id);
if (!target) return null;
return {
...target,
score: item.score || target.score
};
})
.filter(Boolean) as SearchDataResponseItemType[];
return mergeResult;
} catch (error) {
return data;
}
};
const filterResultsByMaxTokens = (list: SearchDataResponseItemType[], maxTokens: number) => {
const results: SearchDataResponseItemType[] = [];
let totalTokens = 0;
for (let i = 0; i < list.length; i++) {
const item = list[i];
totalTokens += countPromptTokens(item.q + item.a);
if (totalTokens > maxTokens + 500) {
break;
}
results.push(item);
if (totalTokens > maxTokens) {
break;
}
}
return results.length === 0 ? list.slice(0, 1) : results;
};
const multiQueryRecall = async ({
embeddingLimit,
fullTextLimit
}: {
embeddingLimit: number;
fullTextLimit: number;
}) => {
// In a group n recall, as long as one of the data appears minAmount of times, it is retained
const getIntersection = (resultList: SearchDataResponseItemType[][], minAmount = 1) => {
minAmount = Math.min(resultList.length, minAmount);
const map: Record<
string,
{
amount: number;
data: SearchDataResponseItemType;
}
> = {};
for (const list of resultList) {
for (const item of list) {
map[item.id] = map[item.id]
? {
amount: map[item.id].amount + 1,
data: item
}
: {
amount: 1,
data: item
};
}
}
return Object.values(map)
.filter((item) => item.amount >= minAmount)
.map((item) => item.data);
};
// multi query recall
const embeddingRecallResList: SearchDataResponseItemType[][] = [];
const fullTextRecallResList: SearchDataResponseItemType[][] = [];
let embTokens = 0;
for await (const query of queries) {
const [{ tokenLen, embeddingRecallResults }, { fullTextRecallResults }] = await Promise.all([
embeddingRecall({
query,
limit: embeddingLimit
}),
fullTextRecall({
query,
limit: fullTextLimit
})
]);
embTokens += tokenLen;
embeddingRecallResList.push(embeddingRecallResults);
fullTextRecallResList.push(fullTextRecallResults);
}
return {
tokens: embTokens,
embeddingRecallResults: getIntersection(embeddingRecallResList, 2),
fullTextRecallResults: getIntersection(fullTextRecallResList, 2)
};
};
/* main step */
// count limit
const { embeddingLimit, fullTextLimit } = countRecallLimit();
// recall
const { embeddingRecallResults, fullTextRecallResults, tokens } = await multiQueryRecall({
embeddingLimit,
fullTextLimit
});
// concat recall results
set = new Set<string>(embeddingRecallResults.map((item) => item.id));
const concatRecallResults = embeddingRecallResults.concat(
fullTextRecallResults.filter((item) => !set.has(item.id))
);
// remove same q and a data
set = new Set<string>();
const filterSameDataResults = concatRecallResults.filter((item) => {
const str = `${item.q}${item.a}`.trim();
// 删除所有的标点符号与空格等,只对文本进行比较
const str = hashStr(`${item.q}${item.a}`.replace(/[^\p{L}\p{N}]/gu, ''));
if (set.has(str)) return false;
set.add(str);
return true;
@@ -187,14 +455,14 @@ export async function searchDatasetData(props: SearchProps) {
filterSameDataResults.filter((item) => item.score >= similarity),
maxTokens
),
tokenLen
tokenLen: tokens
};
}
// ReRank result
// ReRank results
const reRankResults = (
await reRankSearchResult({
query: text,
query: rawQuery,
data: filterSameDataResults
})
).filter((item) => item.score > similarity);
@@ -204,210 +472,7 @@ export async function searchDatasetData(props: SearchProps) {
reRankResults.filter((item) => item.score >= similarity),
maxTokens
),
tokenLen
tokenLen: tokens
};
}
export async function embeddingRecall({
text,
model,
similarity = 0,
limit,
datasetIds = [],
rerank = false
}: SearchProps & { rerank: boolean }) {
const { vectors, tokenLen } = await getVectorsByText({
model,
input: [text]
});
const results: any = await PgClient.query(
`BEGIN;
SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100};
select id, collection_id, data_id, (vector <#> '[${vectors[0]}]') * -1 AS score
from ${PgDatasetTableName}
where dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')})
${rerank ? '' : `AND vector <#> '[${vectors[0]}]' < -${similarity}`}
order by score desc limit ${limit};
COMMIT;`
);
const rows = results?.[2]?.rows as PgSearchRawType[];
// concat same data_id
const filterRows: PgSearchRawType[] = [];
let set = new Set<string>();
for (const row of rows) {
if (!set.has(row.data_id)) {
filterRows.push(row);
set.add(row.data_id);
}
}
// get q and a
const [collections, dataList] = await Promise.all([
MongoDatasetCollection.find(
{
_id: { $in: filterRows.map((item) => item.collection_id) }
},
'name fileId rawLink'
).lean(),
MongoDatasetData.find(
{
_id: { $in: filterRows.map((item) => item.data_id?.trim()) }
},
'datasetId collectionId q a chunkIndex indexes'
).lean()
]);
const formatResult = filterRows
.map((item) => {
const collection = collections.find(
(collection) => String(collection._id) === item.collection_id
);
const data = dataList.find((data) => String(data._id) === item.data_id);
// if collection or data UnExist, the relational mongo data already deleted
if (!collection || !data) return null;
return {
id: String(data._id),
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
sourceName: collection.name || '',
sourceId: collection?.fileId || collection?.rawLink,
score: item.score
};
})
.filter((item) => item !== null) as SearchDataResponseItemType[];
return {
embeddingRecallResults: formatResult,
tokenLen
};
}
export async function fullTextRecall({ text, limit, datasetIds = [] }: SearchProps): Promise<{
fullTextRecallResults: SearchDataResponseItemType[];
tokenLen: number;
}> {
if (limit === 0) {
return {
fullTextRecallResults: [],
tokenLen: 0
};
}
let searchResults = (
await Promise.all(
datasetIds.map((id) =>
MongoDatasetData.find(
{
datasetId: id,
$text: { $search: jiebaSplit({ text }) }
},
{
score: { $meta: 'textScore' },
_id: 1,
datasetId: 1,
collectionId: 1,
q: 1,
a: 1,
indexes: 1,
chunkIndex: 1
}
)
.sort({ score: { $meta: 'textScore' } })
.limit(limit)
.lean()
)
)
).flat() as (DatasetDataSchemaType & { score: number })[];
// resort
searchResults.sort((a, b) => b.score - a.score);
searchResults.slice(0, limit);
const collections = await MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink'
);
return {
fullTextRecallResults: searchResults.map((item) => {
const collection = collections.find((col) => String(col._id) === String(item.collectionId));
return {
id: String(item._id),
datasetId: String(item.datasetId),
collectionId: String(item.collectionId),
sourceName: collection?.name || '',
sourceId: collection?.fileId || collection?.rawLink,
q: item.q,
a: item.a,
chunkIndex: item.chunkIndex,
indexes: item.indexes,
// @ts-ignore
score: item.score
};
}),
tokenLen: 0
};
}
// plus reRank search result
export async function reRankSearchResult({
data,
query
}: {
data: SearchDataResponseItemType[];
query: string;
}): Promise<SearchDataResponseItemType[]> {
try {
const results = await reRankRecall({
query,
inputs: data.map((item) => ({
id: item.id,
text: `${item.q}\n${item.a}`
}))
});
if (!Array.isArray(results)) return data;
// add new score to data
const mergeResult = results
.map((item) => {
const target = data.find((dataItem) => dataItem.id === item.id);
if (!target) return null;
return {
...target,
score: item.score || target.score
};
})
.filter(Boolean) as SearchDataResponseItemType[];
return mergeResult;
} catch (error) {
return data;
}
}
export function filterResultsByMaxTokens(list: SearchDataResponseItemType[], maxTokens: number) {
const results: SearchDataResponseItemType[] = [];
let totalTokens = 0;
for (let i = 0; i < list.length; i++) {
const item = list[i];
totalTokens += countPromptTokens(item.q + item.a);
if (totalTokens > maxTokens + 200) {
break;
}
results.push(item);
if (totalTokens > maxTokens) {
break;
}
}
return results;
}
// ------------------ search end ------------------

View File

@@ -170,9 +170,11 @@ export async function generateVector(): Promise<any> {
err.response?.data?.error?.type === 'invalid_request_error' ||
err?.code === 500
) {
addLog.info('invalid message format', {
dataItem
});
addLog.info('Lock training data');
console.log(err?.code);
console.log(err.response?.data?.error?.type);
console.log(err?.message);
try {
await MongoDatasetTraining.findByIdAndUpdate(data._id, {
lockTime: new Date('2998/5/5')

View File

@@ -5,7 +5,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { Prompt_CQJson } from '@/global/core/prompt/agent';
import { FunctionModelItemType } from '@fastgpt/global/core/ai/model.d';
@@ -43,8 +43,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const chatHistories = getHistories(history, histories);
const { arg, tokens } = await (async () => {
if (cqModel.functionCall) {
return functionCall({
if (cqModel.toolChoice) {
return toolChoice({
...props,
histories: chatHistories,
cqModel
@@ -73,7 +73,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
};
};
async function functionCall({
async function toolChoice({
user,
cqModel,
histories,

View File

@@ -5,17 +5,19 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { Prompt_ExtractJson } from '@/global/core/prompt/agent';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { FunctionModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getHistories } from '../utils';
import { getExtractModel } from '@/service/core/ai/model';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.history]?: ChatItemType[];
[ModuleInputKeyEnum.contextExtractInput]: string;
[ModuleInputKeyEnum.extractKeys]: ContextExtractAgentItemType[];
[ModuleInputKeyEnum.description]: string;
[ModuleInputKeyEnum.aiModel]: string;
}>;
type Response = {
[ModuleOutputKeyEnum.success]?: boolean;
@@ -30,19 +32,19 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const {
user,
histories,
inputs: { content, history = 6, description, extractKeys }
inputs: { content, history = 6, model, description, extractKeys }
} = props;
if (!content) {
return Promise.reject('Input is empty');
}
const extractModel = global.extractModels[0];
const extractModel = getExtractModel(model);
const chatHistories = getHistories(history, histories);
const { arg, tokens, rawResponse } = await (async () => {
if (extractModel.functionCall) {
return functionCall({
const { arg, tokens } = await (async () => {
if (extractModel.toolChoice) {
return toolChoice({
...props,
histories: chatHistories,
extractModel
@@ -60,6 +62,9 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
if (!extractKeys.find((item) => item.key === key)) {
delete arg[key];
}
if (arg[key] === '') {
delete arg[key];
}
}
// auth fields
@@ -91,7 +96,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
};
}
async function functionCall({
async function toolChoice({
extractModel,
user,
histories,
@@ -101,17 +106,19 @@ async function functionCall({
...histories,
{
obj: ChatRoleEnum.Human,
value: `<任务描述>
value: `你的任务:
"""
${description || '根据用户要求获取适当的 JSON 字符串。'}
"""
要求:
"""
- 如果字段为空,你返回空字符串。
- 不要换行。
- 结合历史记录和文本进行获取。
</任务描述>
- 字符串不要换行。
- 结合上下文和当前问题进行获取。
"""
<文本>
${content}
</文本>`
当前问题: "${content}"`
}
];
const filterMessages = ChatContextFilter({

View File

@@ -16,7 +16,7 @@ import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { responseWrite, responseWriteController } from '@fastgpt/service/common/response';
import { getChatModel, ModelTypeEnum } from '@/service/core/ai/model';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';

View File

@@ -2,11 +2,12 @@ import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { countModelPrice } from '@/service/support/wallet/bill/utils';
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModelTypeEnum } from '@/service/core/ai/model';
import { searchDatasetData } from '@/service/core/dataset/data/pg';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
type DatasetSearchProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
@@ -26,24 +27,34 @@ export async function dispatchDatasetSearch(
props: DatasetSearchProps
): Promise<DatasetSearchResponse> {
const {
teamId,
tmbId,
inputs: { datasets = [], similarity = 0.4, limit = 5, searchMode, userChatInput }
} = props as DatasetSearchProps;
if (!Array.isArray(datasets)) {
return Promise.reject('Quote type error');
}
if (datasets.length === 0) {
return Promise.reject("You didn't choose the knowledge base");
return Promise.reject('core.chat.error.Select dataset empty');
}
if (!userChatInput) {
return Promise.reject('Your input is empty');
return Promise.reject('core.chat.error.User question empty');
}
// get vector
const vectorModel = datasets[0]?.vectorModel || global.vectorModels[0];
// const { queries: extensionQueries } = await searchQueryExtension({
// query: userChatInput,
// model: global.chatModels[0].model
// });
const concatQueries = [userChatInput];
// start search
const { searchRes, tokenLen } = await searchDatasetData({
text: userChatInput,
rawQuery: userChatInput,
queries: concatQueries,
model: vectorModel.model,
similarity,
limit,
@@ -61,7 +72,7 @@ export async function dispatchDatasetSearch(
tokens: tokenLen,
type: ModelTypeEnum.vector
}),
query: userChatInput,
query: concatQueries.join('\n'),
model: vectorModel.name,
tokens: tokenLen,
similarity,

View File

@@ -1,12 +1,11 @@
import { NextApiResponse } from 'next';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { RunningModuleItemType } from '@/types/app';
import { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ChatHistoryItemResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ChatDispatchProps, RunningModuleItemType } from '@fastgpt/global/core/module/type.d';
import { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { UserType } from '@fastgpt/global/support/user/type';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { responseWrite } from '@fastgpt/service/common/response';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
@@ -22,11 +21,12 @@ import { dispatchClassifyQuestion } from './agent/classifyQuestion';
import { dispatchContentExtract } from './agent/extract';
import { dispatchHttpRequest } from './tools/http';
import { dispatchAppRequest } from './tools/runApp';
import { dispatchCFR } from './tools/cfr';
import { dispatchRunPlugin } from './plugin/run';
import { dispatchPluginInput } from './plugin/runInput';
import { dispatchPluginOutput } from './plugin/runOutput';
const callbackMap: Record<string, Function> = {
const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
@@ -38,38 +38,28 @@ const callbackMap: Record<string, Function> = {
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput,
[FlowNodeTypeEnum.cfr]: dispatchCFR,
// none
[FlowNodeTypeEnum.userGuide]: () => Promise.resolve(),
[FlowNodeTypeEnum.variable]: () => Promise.resolve()
};
/* running */
export async function dispatchModules({
res,
teamId,
tmbId,
user,
appId,
modules,
chatId,
responseChatItemId,
histories = [],
startParams = {},
variables = {},
user,
stream = false,
detail = false
}: {
res: NextApiResponse;
teamId: string;
tmbId: string;
user: UserType;
appId: string;
detail = false,
...props
}: ChatDispatchProps & {
modules: ModuleItemType[];
chatId?: string;
responseChatItemId?: string;
histories: ChatItemType[];
startParams?: Record<string, any>;
variables?: Record<string, any>;
stream?: boolean;
detail?: boolean;
}) {
// set sse response headers
if (stream) {
@@ -196,25 +186,21 @@ export async function dispatchModules({
module.inputs.forEach((item: any) => {
params[item.key] = item.value;
});
const props: ModuleDispatchProps<Record<string, any>> = {
const dispatchData: ModuleDispatchProps<Record<string, any>> = {
...props,
res,
teamId,
tmbId,
user,
appId,
chatId,
responseChatItemId,
stream,
detail,
variables,
histories,
user,
stream,
detail,
outputs: module.outputs,
inputs: params
};
const dispatchRes: Record<string, any> = await (async () => {
if (callbackMap[module.flowType]) {
return callbackMap[module.flowType](props);
return callbackMap[module.flowType](dispatchData);
}
return {};
})();

View File

@@ -1,6 +1,6 @@
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { getHistories } from '../utils';
export type HistoryProps = ModuleDispatchProps<{
maxContext?: number;

View File

@@ -1,5 +1,5 @@
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
export type UserChatInputProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.userChatInput]: string;
}>;

View File

@@ -1,4 +1,4 @@
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { dispatchModules } from '../index';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import {
@@ -21,6 +21,7 @@ type RunPluginResponse = {
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
const {
mode,
teamId,
tmbId,
inputs: { pluginId, ...data }
@@ -71,6 +72,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
if (output) {
output.moduleLogo = plugin.avatar;
}
console.log(responseData.length);
return {
answerText,
@@ -79,7 +81,14 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
moduleLogo: plugin.avatar,
price: responseData.reduce((sum, item) => sum + (item.price || 0), 0),
runningTime: responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0),
pluginOutput: output?.pluginOutput
pluginOutput: output?.pluginOutput,
pluginDetail:
mode === 'test' && plugin.teamId === teamId
? responseData.filter((item) => {
const filterArr = [FlowNodeTypeEnum.pluginOutput];
return !filterArr.includes(item.moduleType as any);
})
: undefined
},
...(output ? output.pluginOutput : {})
};

View File

@@ -1,4 +1,4 @@
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
export type PluginInputProps = ModuleDispatchProps<{
[key: string]: any;

View File

@@ -1,5 +1,5 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
export type PluginOutputProps = ModuleDispatchProps<{

View File

@@ -1,7 +1,7 @@
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { responseWrite } from '@fastgpt/service/common/response';
import { textAdaptGptResponse } from '@/utils/adapt';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
export type AnswerProps = ModuleDispatchProps<{
text: string;

View File

@@ -0,0 +1,167 @@
import type { ChatItemType, moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getHistories } from '../utils';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { getExtractModel } from '@/service/core/ai/model';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.aiModel]: string;
[ModuleInputKeyEnum.aiSystemPrompt]?: string;
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
[ModuleInputKeyEnum.userChatInput]: string;
}>;
type Response = {
[ModuleOutputKeyEnum.text]: string;
[ModuleOutputKeyEnum.responseData]?: moduleDispatchResType;
};
export const dispatchCFR = async ({
histories,
inputs: { model, systemPrompt, history, userChatInput }
}: Props): Promise<Response> => {
if (!userChatInput) {
return Promise.reject('Question is empty');
}
if (histories.length === 0 && !systemPrompt) {
return {
[ModuleOutputKeyEnum.text]: userChatInput
};
}
const extractModel = getExtractModel(model);
const chatHistories = getHistories(history, histories);
const systemFewShot = systemPrompt
? `Q: 对话背景。
A: ${systemPrompt}
`
: '';
const historyFewShot = chatHistories
.map((item) => {
const role = item.obj === 'Human' ? 'Q' : 'A';
return `${role}: ${item.value}`;
})
.join('\n');
const concatFewShot = `${systemFewShot}${historyFewShot}`.trim();
const ai = getAIApi(undefined, 480000);
const result = await ai.chat.completions.create({
model: extractModel.model,
temperature: 0,
max_tokens: 150,
messages: [
{
role: 'user',
content: replaceVariable(defaultPrompt, {
query: userChatInput,
histories: concatFewShot
})
}
],
stream: false
});
let answer = result.choices?.[0]?.message?.content || '';
// console.log(
// replaceVariable(defaultPrompt, {
// query: userChatInput,
// histories: concatFewShot
// })
// );
// console.log(answer);
const tokens = result.usage?.total_tokens || 0;
return {
[ModuleOutputKeyEnum.responseData]: {
price: extractModel.price * tokens,
model: extractModel.name || '',
tokens,
query: userChatInput,
textOutput: answer
},
[ModuleOutputKeyEnum.text]: answer
};
};
const defaultPrompt = `请不要回答任何问题。
你的任务是结合上下文,为当前问题,实现代词替换,确保问题描述的对象清晰明确。例如:
历史记录:
"""
Q: 对话背景。
A: 关于 FatGPT 的介绍和使用等问题。
"""
当前问题: 怎么下载
输出: FastGPT 怎么下载?
----------------
历史记录:
"""
Q: 报错 "no connection"
A: FastGPT 报错"no connection"可能是因为……
"""
当前问题: 怎么解决
输出: FastGPT 报错"no connection"如何解决?
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
当前问题: 介绍下他
输出: 介绍下 FastGPT 的作者 labring。
----------------
历史记录:
"""
Q: 作者是谁?
A: FastGPT 的作者是 labring。
"""
当前问题: 我想购买商业版。
输出: FastGPT 商业版如何购买?
----------------
历史记录:
"""
Q: 对话背景。
A: 关于 FatGPT 的介绍和使用等问题。
"""
当前问题: nh
输出: nh
----------------
历史记录:
"""
Q: FastGPT 如何收费?
A: FastGPT 收费可以参考……
"""
当前问题: 你知道 laf 么?
输出: 你知道 laf 么?
----------------
历史记录:
"""
Q: FastGPT 的优势
A: 1. 开源
2. 简便
3. 扩展性强
"""
当前问题: 介绍下第2点。
输出: 介绍下 FastGPT 简便的优势。
----------------
历史记录:
"""
Q: 什么是 FastGPT
A: FastGPT 是一个 RAG 平台。
Q: 什么是 Sealos
A: Sealos 是一个云操作系统。
"""
当前问题: 它们有什么关系?
输出: FastGPT 和 Sealos 有什么关系?
----------------
历史记录:
"""
{{histories}}
"""
当前问题: {{query}}
输出: `;

View File

@@ -1,5 +1,5 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import axios from 'axios';
import { flatDynamicParams } from '../utils';

View File

@@ -1,5 +1,5 @@
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import type { ModuleDispatchProps } from '@/types/core/chat/type';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { SelectAppItemType } from '@fastgpt/global/core/module/type';
import { dispatchModules } from '../index';
import { MongoApp } from '@fastgpt/service/core/app/schema';
@@ -7,12 +7,12 @@ import { responseWrite } from '@fastgpt/service/common/response';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { textAdaptGptResponse } from '@/utils/adapt';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getHistories } from '../utils';
type Props = ModuleDispatchProps<{
userChatInput: string;
history?: ChatItemType[];
[ModuleInputKeyEnum.userChatInput]: string;
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
app: SelectAppItemType;
}>;
type Response = {
@@ -28,7 +28,7 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
stream,
detail,
histories,
inputs: { userChatInput, history = [], app }
inputs: { userChatInput, history, app }
} = props;
if (!userChatInput) {

View File

@@ -1,10 +1,9 @@
import { BillSourceEnum, PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { getAudioSpeechModel, getQAModel } from '@/service/core/ai/model';
import { getAudioSpeechModel, getQAModel, getVectorModel } from '@/service/core/ai/model';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { addLog } from '@fastgpt/service/common/system/log';
import type { ConcatBillProps, CreateBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
import { defaultQGModels } from '@fastgpt/global/core/ai/model';
import { POST } from '@fastgpt/service/common/api/plusRequest';
import { PostReRankProps } from '@fastgpt/global/core/ai/api';
@@ -113,8 +112,7 @@ export const pushGenerateVectorBill = ({
source?: `${BillSourceEnum}`;
}) => {
// 计算价格. 至少为1
const vectorModel =
global.vectorModels.find((item) => item.model === model) || global.vectorModels[0];
const vectorModel = getVectorModel(model);
const unitPrice = vectorModel.price || 0.2;
let total = unitPrice * tokenLen;
total = total > 1 ? total : 1;
@@ -158,7 +156,7 @@ export const pushQuestionGuideBill = ({
teamId: string;
tmbId: string;
}) => {
const qgModel = global.qgModels?.[0] || defaultQGModels[0];
const qgModel = global.qgModels[0];
const total = qgModel.price * tokens;
createBill({
teamId,

View File

@@ -30,28 +30,6 @@ export type AppItemType = {
modules: ModuleItemType[];
};
export type RunningModuleItemType = {
name: ModuleItemType['name'];
moduleId: ModuleItemType['moduleId'];
flowType: ModuleItemType['flowType'];
showStatus?: ModuleItemType['showStatus'];
} & {
inputs: {
key: string;
value?: any;
}[];
outputs: {
key: string;
answer?: boolean;
response?: boolean;
value?: any;
targets: {
moduleId: string;
key: string;
}[];
}[];
};
export type AppLogsListItemType = {
_id: string;
id: string;

View File

@@ -1,22 +0,0 @@
import type { NextApiResponse } from 'next';
import { RunningModuleItemType } from '@/types/app';
import type { UserType } from '@fastgpt/global/support/user/type';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
// module dispatch props type
export type ModuleDispatchProps<T> = {
res: NextApiResponse;
teamId: string;
tmbId: string;
user: UserType;
appId: string;
chatId?: string;
responseChatItemId?: string;
stream: boolean;
detail: boolean; // response detail
variables: Record<string, any>;
histories: ChatItemType[];
outputs: RunningModuleItemType['outputs'];
inputs: T;
};

View File

@@ -23,8 +23,8 @@ declare global {
var qaQueueLen: number;
var vectorQueueLen: number;
var vectorModels: VectorModelItemType[];
var chatModels: ChatModelItemType[];
var vectorModels: VectorModelItemType[];
var qaModels: LLMModelItemType[];
var cqModels: FunctionModelItemType[];
var extractModels: FunctionModelItemType[];

View File

@@ -3,10 +3,10 @@ import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import type { ModuleItemType, FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import type { Edge, Node } from 'reactflow';
import { customAlphabet } from 'nanoid';
import { EmptyModule } from '@fastgpt/global/core/module/template/system/empty';
import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
import { adaptRole_Message2Chat } from '@fastgpt/global/core/chat/adapt';
import { EDGE_TYPE } from '@fastgpt/global/core/module/node/constant';
import { UserInputModule } from '@fastgpt/global/core/module/template/system/userInput';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
export const gptMessage2ChatType = (messages: ChatMessageItemType[]): ChatItemType[] => {
@@ -45,7 +45,7 @@ export const appModule2FlowNode = ({
}): Node<FlowModuleItemType> => {
// init some static data
const template =
moduleTemplatesFlat.find((template) => template.flowType === item.flowType) || EmptyModule;
moduleTemplatesFlat.find((template) => template.flowType === item.flowType) || UserInputModule;
const concatInputs = template.inputs.concat(
item.inputs.filter(

View File

@@ -2,6 +2,7 @@ import mammoth from 'mammoth';
import Papa from 'papaparse';
import { compressBase64ImgAndUpload } from './controller';
import { simpleMarkdownText } from '@fastgpt/global/common/string/markdown';
import { htmlStr2Md } from '@fastgpt/web/common/string/markdown';
/**
* 读取 txt 文件内容
@@ -115,12 +116,13 @@ export const readDocContent = (file: File, metadata: Record<string, any>) =>
reader.onload = async ({ target }) => {
if (!target?.result) return reject('读取 doc 文件失败');
try {
// @ts-ignore
const res = await mammoth.convertToMarkdown({
arrayBuffer: target.result as ArrayBuffer
const buffer = target.result as ArrayBuffer;
const { value: html } = await mammoth.convertToHtml({
arrayBuffer: buffer
});
const md = htmlStr2Md(html);
const rawText = await formatMarkdown(res?.value, metadata);
const rawText = await formatMarkdown(md, metadata);
resolve(rawText);
} catch (error) {
@@ -198,9 +200,9 @@ export const formatMarkdown = async (rawText: string = '', metadata: Record<stri
);
// Remove white space on both sides of the picture
const trimReg = /\s*(!\[.*\]\(.*\))\s*/g;
const trimReg = /(!\[.*\]\(.*\))\s*/g;
if (trimReg.test(rawText)) {
rawText = rawText.replace(/\s*(!\[.*\]\(.*\))\s*/g, '$1');
rawText = rawText.replace(trimReg, '$1');
}
return simpleMarkdownText(rawText);

View File

@@ -2,53 +2,49 @@ import type { InitDateResponse } from '@/global/common/api/systemRes';
import { getSystemInitData } from '@/web/common/system/api';
import { delay } from '@fastgpt/global/common/system/utils';
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import {
defaultChatModels,
defaultQAModels,
defaultCQModels,
defaultExtractModels,
defaultQGModels,
defaultVectorModels,
defaultAudioSpeechModels,
defaultReRankModels
} from '@fastgpt/global/core/ai/model';
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type';
import type {
ChatModelItemType,
FunctionModelItemType,
LLMModelItemType,
ReRankModelItemType,
VectorModelItemType,
AudioSpeechModelType
} from '@fastgpt/global/core/ai/model.d';
export let feConfigs: FeConfigsType = {};
export let priceMd = '';
export let systemVersion = '0.0.0';
export let vectorModelList = defaultVectorModels;
export let chatModelList = defaultChatModels;
export let qaModelList = defaultQAModels;
export let cqModelList = defaultCQModels;
export let extractModelList = defaultExtractModels;
export let qgModelList = defaultQGModels;
export let audioSpeechModels = defaultAudioSpeechModels;
export let chatModelList: ChatModelItemType[] = [];
export let vectorModelList: VectorModelItemType[] = [];
export let qaModelList: LLMModelItemType[] = [];
export let cqModelList: FunctionModelItemType[] = [];
export let extractModelList: FunctionModelItemType[] = [];
export let audioSpeechModels: AudioSpeechModelType[] = [];
export let reRankModelList: ReRankModelItemType[] = [];
export let simpleModeTemplates: AppSimpleEditConfigTemplateType[] = [];
export let reRankModelList = defaultReRankModels;
let retryTimes = 3;
export const clientInitData = async (): Promise<InitDateResponse> => {
try {
const res = await getSystemInitData();
feConfigs = res.feConfigs;
chatModelList = res.chatModels ?? chatModelList;
vectorModelList = res.vectorModels ?? vectorModelList;
qaModelList = res.qaModels ?? qaModelList;
cqModelList = res.cqModels ?? cqModelList;
extractModelList = res.extractModels ?? extractModelList;
vectorModelList = res.vectorModels ?? vectorModelList;
audioSpeechModels = res.audioSpeechModels ?? audioSpeechModels;
reRankModelList = res.reRankModels ?? reRankModelList;
audioSpeechModels = res.audioSpeechModels ?? audioSpeechModels;
feConfigs = res.feConfigs;
priceMd = res.priceMd;
systemVersion = res.systemVersion;
simpleModeTemplates = res.simpleModeTemplates;
return res;

View File

@@ -303,7 +303,7 @@ export const appTemplates: (AppItemType & {
avatar: '/imgs/module/userGuide.png',
flowType: 'userGuide',
position: {
x: 454.98510354678695,
x: 447.98520778293346,
y: 721.4016845336229
},
inputs: [
@@ -314,7 +314,7 @@ export const appTemplates: (AppItemType & {
label: '开场白',
showTargetInApp: false,
showTargetInPlugin: false,
value: '你好,我是知识库助手,请不要忘记选择知识库噢~',
value: '你好,我是知识库助手,请不要忘记选择知识库噢~\n[你是谁]\n[如何使用]',
connected: false
},
{
@@ -334,6 +334,7 @@ export const appTemplates: (AppItemType & {
label: '问题引导',
showTargetInApp: false,
showTargetInPlugin: false,
value: false,
connected: false
},
{
@@ -343,6 +344,9 @@ export const appTemplates: (AppItemType & {
label: '语音播报',
showTargetInApp: false,
showTargetInPlugin: false,
value: {
type: 'web'
},
connected: false
}
],
@@ -354,8 +358,8 @@ export const appTemplates: (AppItemType & {
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
@@ -376,11 +380,11 @@ export const appTemplates: (AppItemType & {
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
moduleId: 'datasetSearch',
key: 'userChatInput'
},
{
moduleId: 'datasetSearch',
moduleId: 'chatModule',
key: 'userChatInput'
}
]
@@ -394,8 +398,8 @@ export const appTemplates: (AppItemType & {
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 956.0838440206068,
y: 887.462827870246
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
@@ -489,24 +493,14 @@ export const appTemplates: (AppItemType & {
label: '搜索结果为空',
type: 'source',
valueType: 'boolean',
targets: [
{
moduleId: '2752oj',
key: 'switch'
}
]
targets: []
},
{
key: 'unEmpty',
label: '搜索结果不为空',
type: 'source',
valueType: 'boolean',
targets: [
{
moduleId: 'chatModule',
key: 'switch'
}
]
targets: []
},
{
key: 'quoteQA',
@@ -539,8 +533,8 @@ export const appTemplates: (AppItemType & {
flowType: 'chatNode',
showStatus: true,
position: {
x: 1546.0823206390796,
y: 1008.9827344021824
x: 2022.7264786978908,
y: 1006.3102431257475
},
inputs: [
{
@@ -550,7 +544,7 @@ export const appTemplates: (AppItemType & {
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
connected: false
},
{
key: 'model',
@@ -626,6 +620,7 @@ export const appTemplates: (AppItemType & {
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
@@ -635,6 +630,7 @@ export const appTemplates: (AppItemType & {
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
@@ -658,6 +654,7 @@ export const appTemplates: (AppItemType & {
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: '',
connected: false
},
{
@@ -720,49 +717,6 @@ export const appTemplates: (AppItemType & {
targets: []
}
]
},
{
moduleId: '2752oj',
name: '指定回复',
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 1542.9271243684725,
y: 702.7819618017722
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'text',
type: 'textarea',
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
value: '搜索结果为空',
connected: false
}
],
outputs: [
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
]
}
]
},
@@ -1095,8 +1049,8 @@ export const appTemplates: (AppItemType & {
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 198.56612928723575,
y: 1622.7034463081607
x: -269.50851681351924,
y: 1657.6123698022448
},
inputs: [
{
@@ -1117,11 +1071,7 @@ export const appTemplates: (AppItemType & {
valueType: 'string',
targets: [
{
moduleId: 'remuj3',
key: 'userChatInput'
},
{
moduleId: 'fljhzy',
moduleId: '79iwqi',
key: 'userChatInput'
}
]
@@ -1135,8 +1085,8 @@ export const appTemplates: (AppItemType & {
flowType: 'classifyQuestion',
showStatus: true,
position: {
x: 672.9092284362648,
y: 1077.557793775116
x: 730.6899384278805,
y: 1079.2201234653105
},
inputs: [
{
@@ -1203,17 +1153,13 @@ export const appTemplates: (AppItemType & {
label: '',
value: [
{
value: '打招呼、问候等问题',
value: '关于电影《星际穿越》的问题',
key: 'wqre'
},
{
value: '关于 xxx 的问题',
value: '打招呼、问候等问题',
key: 'sdfa'
},
{
value: '商务问题',
key: 'agex'
},
{
value: '其他问题',
key: 'oy1c'
@@ -1231,7 +1177,7 @@ export const appTemplates: (AppItemType & {
type: 'hidden',
targets: [
{
moduleId: 'a99p6z',
moduleId: 'fljhzy',
key: 'switch'
}
]
@@ -1242,18 +1188,7 @@ export const appTemplates: (AppItemType & {
type: 'hidden',
targets: [
{
moduleId: 'fljhzy',
key: 'switch'
}
]
},
{
key: 'agex',
label: '',
type: 'hidden',
targets: [
{
moduleId: '5v78ap',
moduleId: 'a99p6z',
key: 'switch'
}
]
@@ -1268,6 +1203,12 @@ export const appTemplates: (AppItemType & {
key: 'switch'
}
]
},
{
key: 'agex',
label: '',
type: 'hidden',
targets: []
}
]
},
@@ -1277,8 +1218,8 @@ export const appTemplates: (AppItemType & {
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 1304.2886011902247,
y: 776.1589509539264
x: 1294.314623049058,
y: 1623.9470929531146
},
inputs: [
{
@@ -1296,7 +1237,9 @@ export const appTemplates: (AppItemType & {
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n\n如传入非字符串类型数据将会自动转成字符串',
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
placeholder:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
value: '你好,有什么可以帮助你的?',
@@ -1320,8 +1263,8 @@ export const appTemplates: (AppItemType & {
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 1294.2531189034548,
y: 2127.1297123368286
x: 1290.9284595230658,
y: 1992.4810074310749
},
inputs: [
{
@@ -1339,10 +1282,12 @@ export const appTemplates: (AppItemType & {
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n\n如传入非字符串类型数据将会自动转成字符串',
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
placeholder:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
value: '你好,我仅能回答 laf 相关问题,请问你有什么问题么?',
value: '你好,我仅能回答电影《星际穿越》相关问题,请问你有什么问题么?',
connected: false
}
],
@@ -1483,7 +1428,7 @@ export const appTemplates: (AppItemType & {
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: '知识库是关于 laf 的内容。',
value: '',
connected: false
},
{
@@ -1554,8 +1499,8 @@ export const appTemplates: (AppItemType & {
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 1305.5374262228029,
y: 1120.0404921820218
x: 1307.1997559129973,
y: 908.9246215273222
},
inputs: [
{
@@ -1698,8 +1643,8 @@ export const appTemplates: (AppItemType & {
avatar: '/imgs/module/userGuide.png',
flowType: 'userGuide',
position: {
x: 191.4857498376603,
y: 856.6847387508401
x: -272.66416216517086,
y: 842.9928682053646
},
inputs: [
{
@@ -1710,7 +1655,7 @@ export const appTemplates: (AppItemType & {
showTargetInApp: false,
showTargetInPlugin: false,
value:
'你好,我是 laf 助手,有什么可以帮助你的?\n[laf 是什么?有什么用?]\n[laf 在线体验地址]\n[官网地址是多少]',
'你好,我是电影《星际穿越》 AI 助手,有什么可以帮助你的?\n[导演是谁]\n[剧情介绍]\n[票房分析]',
connected: false
},
{
@@ -1769,7 +1714,9 @@ export const appTemplates: (AppItemType & {
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n\n如传入非字符串类型数据将会自动转成字符串',
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
placeholder:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
value: '对不起,我找不到你的问题,请更加详细的描述你的问题。',
@@ -1787,57 +1734,14 @@ export const appTemplates: (AppItemType & {
}
]
},
{
moduleId: '5v78ap',
name: '指定回复',
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 1294.814522053934,
y: 1822.7626988141562
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'text',
type: 'textarea',
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
value: '这是一个商务问题',
connected: false
}
],
outputs: [
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
]
},
{
moduleId: '9act94',
name: '用户问题(对话入口)',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 1827.2213090948171,
y: 2132.138812501788
x: 1902.0261451535691,
y: 1826.2701495060023
},
inputs: [
{
@@ -1864,6 +1768,93 @@ export const appTemplates: (AppItemType & {
]
}
]
},
{
moduleId: '79iwqi',
name: 'core.module.template.cfr',
avatar: '/imgs/module/cfr.svg',
flowType: 'cfr',
showStatus: true,
position: {
x: 149.7113934317785,
y: 1312.2668782737812
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectExtractModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: 'gpt-3.5-turbo',
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.module.input.label.cfr background',
max: 300,
valueType: 'string',
description: 'core.module.input.description.cfr background',
placeholder: 'core.module.input.placeholder.cfr background',
showTargetInApp: true,
showTargetInPlugin: true,
value: '关于电影《星际穿越》的讨论。',
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'system_text',
label: 'core.module.output.label.cfr result',
valueType: 'string',
type: 'source',
targets: [
{
moduleId: 'remuj3',
key: 'userChatInput'
},
{
moduleId: 'fljhzy',
key: 'userChatInput'
}
]
}
]
}
]
}

View File

@@ -19,25 +19,25 @@ export async function postForm2Modules(
{
key: ModuleInputKeyEnum.welcomeText,
type: FlowNodeInputTypeEnum.hidden,
label: '开场白',
label: 'core.app.Welcome Text',
value: formData.userGuide.welcomeText
},
{
key: ModuleInputKeyEnum.variables,
type: FlowNodeInputTypeEnum.hidden,
label: '对话框变量',
label: 'core.app.Chat Variable',
value: formData.userGuide.variables
},
{
key: ModuleInputKeyEnum.questionGuide,
type: FlowNodeInputTypeEnum.hidden,
label: '问题引导',
label: 'core.app.Question Guide',
value: formData.userGuide.questionGuide
},
{
key: ModuleInputKeyEnum.tts,
type: FlowNodeInputTypeEnum.hidden,
label: '语音播报',
label: 'core.app.TTS',
value: formData.userGuide.tts
}
],

View File

@@ -7,11 +7,11 @@ import { AssignedAnswerModule } from '@fastgpt/global/core/module/template/syste
import { ClassifyQuestionModule } from '@fastgpt/global/core/module/template/system/classifyQuestion';
import { ContextExtractModule } from '@fastgpt/global/core/module/template/system/contextExtract';
import { HttpModule } from '@fastgpt/global/core/module/template/system/http';
import { EmptyModule } from '@fastgpt/global/core/module/template/system/empty';
import { RunAppModule } from '@fastgpt/global/core/module/template/system/runApp';
import { PluginInputModule } from '@fastgpt/global/core/module/template/system/pluginInput';
import { PluginOutputModule } from '@fastgpt/global/core/module/template/system/pluginOutput';
import { RunPluginModule } from '@fastgpt/global/core/module/template/system/runPlugin';
import { AiCFR } from '@fastgpt/global/core/module/template/system/coreferenceResolution';
import type {
FlowModuleTemplateType,
@@ -28,7 +28,8 @@ export const appSystemModuleTemplates: FlowModuleTemplateType[] = [
RunAppModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule
HttpModule,
AiCFR
];
export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
PluginInputModule,
@@ -39,7 +40,8 @@ export const pluginSystemModuleTemplates: FlowModuleTemplateType[] = [
RunAppModule,
ClassifyQuestionModule,
ContextExtractModule,
HttpModule
HttpModule,
AiCFR
];
export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
UserGuideModule,
@@ -51,11 +53,11 @@ export const moduleTemplatesFlat: FlowModuleTemplateType[] = [
ClassifyQuestionModule,
ContextExtractModule,
HttpModule,
EmptyModule,
RunAppModule,
PluginInputModule,
PluginOutputModule,
RunPluginModule
RunPluginModule,
AiCFR
];
export const moduleTemplatesList: moduleTemplateListType = [