4.6.5- CoreferenceResolution Module (#631)
This commit is contained in:
@@ -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}¤tTab=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}¤tTab=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;
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -346,7 +346,7 @@
|
||||
width: 100%;
|
||||
|
||||
* {
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,9 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy
|
||||
quoteTemplate: true,
|
||||
quotePrompt: true
|
||||
},
|
||||
cfr: {
|
||||
background: true
|
||||
},
|
||||
dataset: {
|
||||
datasets: true,
|
||||
similarity: true,
|
||||
|
||||
@@ -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;',
|
||||
`GitHub:https://github.com/labring/FastGPT`
|
||||
);
|
||||
}
|
||||
if (show_git) {
|
||||
loadGitStar();
|
||||
}
|
||||
|
||||
setScripts(scripts || []);
|
||||
})();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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* 交流群: ',
|
||||
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* 交流群: '
|
||||
: '',
|
||||
...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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -609,7 +609,6 @@ const CollectionCard = () => {
|
||||
),
|
||||
onClick: () =>
|
||||
openSyncConfirm(() => {
|
||||
console.log(collection._id);
|
||||
onclickStartSync(collection._id);
|
||||
})()
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
25
projects/app/src/service/common/system/index.ts
Normal file
25
projects/app/src/service/common/system/index.ts
Normal 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;
|
||||
};
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ------------------
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {};
|
||||
})();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}>;
|
||||
|
||||
@@ -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 : {})
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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;
|
||||
|
||||
167
projects/app/src/service/moduleDispatch/tools/cfr.ts
Normal file
167
projects/app/src/service/moduleDispatch/tools/cfr.ts
Normal 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}}
|
||||
输出: `;
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
22
projects/app/src/types/app.d.ts
vendored
22
projects/app/src/types/app.d.ts
vendored
@@ -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;
|
||||
|
||||
22
projects/app/src/types/core/chat/type.d.ts
vendored
22
projects/app/src/types/core/chat/type.d.ts
vendored
@@ -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;
|
||||
};
|
||||
2
projects/app/src/types/index.d.ts
vendored
2
projects/app/src/types/index.d.ts
vendored
@@ -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[];
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user