v4.5.2 (#439)
This commit is contained in:
@@ -77,13 +77,7 @@ const QuoteModal = ({
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ModalBody
|
||||
pt={0}
|
||||
whiteSpace={'pre-wrap'}
|
||||
textAlign={'justify'}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
|
||||
{rawSearch.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -95,11 +89,18 @@ const QuoteModal = ({
|
||||
position={'relative'}
|
||||
overflow={'hidden'}
|
||||
_hover={{ '& .hover-data': { display: 'flex' } }}
|
||||
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
|
||||
>
|
||||
{!isShare && (
|
||||
<Flex alignItems={'flex-end'} mb={3} color={'myGray.500'}>
|
||||
<RawSourceText sourceName={item.sourceName} sourceId={item.sourceId} />
|
||||
<Box flex={1} />
|
||||
<Flex alignItems={'flex-end'} mb={3} fontSize={'sm'}>
|
||||
<RawSourceText
|
||||
fontWeight={'bold'}
|
||||
color={'black'}
|
||||
sourceName={item.sourceName}
|
||||
sourceId={item.sourceId}
|
||||
addr={!isShare}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
{!isShare && (
|
||||
<Link
|
||||
as={NextLink}
|
||||
className="hover-data"
|
||||
@@ -111,13 +112,13 @@ const QuoteModal = ({
|
||||
{t('core.dataset.Go Dataset')}
|
||||
<MyIcon name={'rightArrowLight'} w={'10px'} />
|
||||
</Link>
|
||||
</Flex>
|
||||
)}
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Box color={'black'}>{item.q}</Box>
|
||||
<Box color={'black'}>{item.a}</Box>
|
||||
<Box color={'myGray.600'}>{item.a}</Box>
|
||||
{!isShare && (
|
||||
<Flex alignItems={'center'} mt={3} gap={4} color={'myGray.500'}>
|
||||
<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} px={3} borderRadius={'md'}>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
|
||||
import { Flex, BoxProps, useDisclosure } from '@chakra-ui/react';
|
||||
import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Tag from '../Tag';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
|
||||
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
|
||||
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr: false });
|
||||
|
||||
const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemResType[] }) => {
|
||||
const theme = useTheme();
|
||||
const { isPc } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const [quoteModalData, setQuoteModalData] = useState<SearchDataResponseItemType[]>();
|
||||
@@ -27,18 +30,36 @@ const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemRes
|
||||
const {
|
||||
chatAccount,
|
||||
quoteList = [],
|
||||
sourceList = [],
|
||||
historyPreview = [],
|
||||
runningTime = 0
|
||||
} = useMemo(() => {
|
||||
const chatData = responseData.find((item) => item.moduleType === FlowModuleTypeEnum.chatNode);
|
||||
const chatData = responseData.find((item) => item.moduleType === FlowNodeTypeEnum.chatNode);
|
||||
const quoteList = responseData
|
||||
.filter((item) => item.moduleType === FlowNodeTypeEnum.chatNode)
|
||||
.map((item) => item.quoteList)
|
||||
.flat()
|
||||
.filter((item) => item) as SearchDataResponseItemType[];
|
||||
const sourceList = quoteList.reduce(
|
||||
(acc: Record<string, SearchDataResponseItemType[]>, cur) => {
|
||||
if (!acc[cur.sourceName]) {
|
||||
acc[cur.sourceName] = [cur];
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return {
|
||||
chatAccount: responseData.filter((item) => item.moduleType === FlowModuleTypeEnum.chatNode)
|
||||
chatAccount: responseData.filter((item) => item.moduleType === FlowNodeTypeEnum.chatNode)
|
||||
.length,
|
||||
quoteList: responseData
|
||||
.filter((item) => item.moduleType === FlowModuleTypeEnum.chatNode)
|
||||
.map((item) => item.quoteList)
|
||||
quoteList,
|
||||
sourceList: Object.values(sourceList)
|
||||
.flat()
|
||||
.filter((item) => item) as SearchDataResponseItemType[],
|
||||
.map((item) => ({
|
||||
sourceName: item.sourceName,
|
||||
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName })
|
||||
})),
|
||||
historyPreview: chatData?.historyPreview,
|
||||
runningTime: +responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
|
||||
};
|
||||
@@ -50,64 +71,93 @@ const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemRes
|
||||
};
|
||||
|
||||
return responseData.length === 0 ? null : (
|
||||
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
|
||||
{quoteList.length > 0 && (
|
||||
<MyTooltip label="查看引用">
|
||||
<Tag
|
||||
colorSchema="blue"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setQuoteModalData(quoteList)}
|
||||
>
|
||||
{quoteList.length}条引用
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{chatAccount === 1 && (
|
||||
<>
|
||||
{sourceList.length > 0 && (
|
||||
<>
|
||||
{historyPreview.length > 0 && (
|
||||
<MyTooltip label={'点击查看完整对话记录'}>
|
||||
<Tag
|
||||
colorSchema="green"
|
||||
<ChatBoxDivider icon="core/chat/quoteFill" text={t('chat.Quote')} />
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2}>
|
||||
{sourceList.map((item) => (
|
||||
<Flex
|
||||
key={item.sourceName}
|
||||
alignItems={'center'}
|
||||
flexWrap={'wrap'}
|
||||
fontSize={'sm'}
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setContextModalData(historyPreview)}
|
||||
border={theme.borders.sm}
|
||||
py={1}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
_hover={{
|
||||
bg: 'myBlue.100'
|
||||
}}
|
||||
onClick={() => setQuoteModalData(quoteList)}
|
||||
>
|
||||
{historyPreview.length}条上下文
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Image src={item.icon} alt={''} mr={1} w={'12px'} />
|
||||
{item.sourceName}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{chatAccount > 1 && (
|
||||
<Tag colorSchema="blue" {...TagStyles}>
|
||||
多组 AI 对话
|
||||
</Tag>
|
||||
)}
|
||||
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
|
||||
{quoteList.length > 0 && (
|
||||
<MyTooltip label="查看引用">
|
||||
<Tag
|
||||
colorSchema="blue"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setQuoteModalData(quoteList)}
|
||||
>
|
||||
{quoteList.length}条引用
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{chatAccount === 1 && (
|
||||
<>
|
||||
{historyPreview.length > 0 && (
|
||||
<MyTooltip label={'点击查看完整对话记录'}>
|
||||
<Tag
|
||||
colorSchema="green"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setContextModalData(historyPreview)}
|
||||
>
|
||||
{historyPreview.length}条上下文
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{chatAccount > 1 && (
|
||||
<Tag colorSchema="blue" {...TagStyles}>
|
||||
多组 AI 对话
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{isPc && runningTime > 0 && (
|
||||
<MyTooltip label={'模块运行时间和'}>
|
||||
<Tag colorSchema="purple" cursor={'default'} {...TagStyles}>
|
||||
{runningTime}s
|
||||
{isPc && runningTime > 0 && (
|
||||
<MyTooltip label={'模块运行时间和'}>
|
||||
<Tag colorSchema="purple" cursor={'default'} {...TagStyles}>
|
||||
{runningTime}s
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={'点击查看完整响应'}>
|
||||
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
|
||||
{t('chat.Complete Response')}
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={'点击查看完整响应'}>
|
||||
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
|
||||
{t('chat.Complete Response')}
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal rawSearch={quoteModalData} onClose={() => setQuoteModalData(undefined)} />
|
||||
)}
|
||||
{!!contextModalData && (
|
||||
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
|
||||
)}
|
||||
{isOpenWholeModal && (
|
||||
<WholeResponseModal response={responseData} onClose={onCloseWholeModal} />
|
||||
)}
|
||||
</Flex>
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal rawSearch={quoteModalData} onClose={() => setQuoteModalData(undefined)} />
|
||||
)}
|
||||
{!!contextModalData && (
|
||||
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
|
||||
)}
|
||||
{isOpenWholeModal && (
|
||||
<WholeResponseModal response={responseData} onClose={onCloseWholeModal} />
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function Row({ label, value }: { label: string; value?: string | number | React.
|
||||
) : null;
|
||||
}
|
||||
|
||||
const ResponseModal = ({
|
||||
const WholeResponseModal = ({
|
||||
response,
|
||||
onClose
|
||||
}: {
|
||||
@@ -50,6 +50,7 @@ const ResponseModal = ({
|
||||
<Image
|
||||
mr={2}
|
||||
src={
|
||||
item.moduleLogo ||
|
||||
ModuleTemplatesFlat.find((template) => item.moduleType === template.flowType)?.logo
|
||||
}
|
||||
alt={''}
|
||||
@@ -192,10 +193,22 @@ const ResponseModal = ({
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
|
||||
{/* plugin */}
|
||||
<Row
|
||||
label={t('chat.response.plugin output')}
|
||||
value={(() => {
|
||||
try {
|
||||
return JSON.stringify(activeModule?.pluginOutput, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResponseModal;
|
||||
export default WholeResponseModal;
|
||||
|
||||
@@ -35,7 +35,7 @@ import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { eventBus } from '@/web/common/utils/eventbus';
|
||||
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
|
||||
import { useMarkdown } from '@/web/common/hooks/useMarkdown';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { VariableInputEnum } from '@/constants/app';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { MessageItemType } from '@/types/core/chat/type';
|
||||
@@ -54,6 +54,7 @@ import Avatar from '@/components/Avatar';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MySelect from '@/components/Select';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
import dynamic from 'next/dynamic';
|
||||
const ResponseTags = dynamic(() => import('./ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
|
||||
@@ -99,7 +100,7 @@ type Props = {
|
||||
showEmptyIntro?: boolean;
|
||||
appAvatar?: string;
|
||||
userAvatar?: string;
|
||||
userGuideModule?: AppModuleItemType;
|
||||
userGuideModule?: ModuleItemType;
|
||||
active?: boolean;
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<{
|
||||
@@ -488,7 +489,7 @@ const ChatBox = (
|
||||
|
||||
return {
|
||||
bg: colorMap[chatContent.status] || colorMap.loading,
|
||||
name: t(chatContent.moduleName || 'Running')
|
||||
name: t(chatContent.moduleName || 'common.Loading')
|
||||
};
|
||||
}, [chatHistory, isChatting, t]);
|
||||
/* style end */
|
||||
@@ -496,6 +497,7 @@ const ChatBox = (
|
||||
// page change and abort request
|
||||
useEffect(() => {
|
||||
isNewChatReplace.current = false;
|
||||
setQuestionGuide([]);
|
||||
return () => {
|
||||
chatController.current?.abort('leave');
|
||||
if (!isNewChatReplace.current) {
|
||||
@@ -750,38 +752,28 @@ const ChatBox = (
|
||||
{index === chatHistory.length - 1 &&
|
||||
!isChatting &&
|
||||
questionGuides.length > 0 && (
|
||||
<Flex
|
||||
mt={2}
|
||||
borderTop={theme.borders.sm}
|
||||
alignItems={'center'}
|
||||
flexWrap={'wrap'}
|
||||
>
|
||||
<Box
|
||||
color={'myGray.500'}
|
||||
mt={2}
|
||||
mr={2}
|
||||
fontSize={'sm'}
|
||||
fontStyle={'italic'}
|
||||
>
|
||||
{t('chat.Question Guide Tips')}
|
||||
</Box>
|
||||
{questionGuides.map((item) => (
|
||||
<Button
|
||||
mt={2}
|
||||
key={item}
|
||||
mr="2"
|
||||
borderRadius={'md'}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
size={'xs'}
|
||||
onClick={() => {
|
||||
resetInputVal(item);
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
<Box mt={2}>
|
||||
<ChatBoxDivider
|
||||
icon="core/chat/QGFill"
|
||||
text={t('chat.Question Guide Tips')}
|
||||
/>
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2}>
|
||||
{questionGuides.map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
borderRadius={'md'}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
size={'xs'}
|
||||
onClick={() => {
|
||||
resetInputVal(item);
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698493025597" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4931" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M845.4 481.9c24.1 0 47.1 4.6 68.9 13.9 21.8 9.3 40.9 22.1 57.2 38.3 16.3 16.3 29.2 35.2 38.5 56.9 9.3 21.7 14 44.5 14 68.5 0 24.8-4.7 47.8-14 69.1-9.3 21.3-22.2 40.1-38.5 56.3-16.3 16.3-35.4 29-57.2 38.3-21.8 9.3-44.8 13.9-68.9 13.9-22.6 0-43.8-4.1-63.6-12.2-19.9-8.1-37.6-18.8-53.1-31.9v145.2c0 20.9-7.4 38.7-22.2 53.4-14.8 14.7-32.7 22.1-53.7 22.1H77.1c-21 0-39.1-7.4-54.3-22.1C7.6 977 0 959.2 0 938.3V809.4c3.9-16.3 10.9-28.3 21-36 10.1-7.7 24.9-5.8 44.4 5.8 9.3 5.4 16.3 8.5 21 9.3 20.2 10.1 40.9 15.1 61.9 15.1s40.7-3.9 59-11.6c18.3-7.7 34.2-18.4 47.9-31.9 13.6-13.5 24.3-29.2 32.1-47 7.8-17.8 11.7-37.2 11.7-58.1s-3.9-40.4-11.7-58.6-18.5-34.1-32.1-47.6c-13.6-13.5-29.6-24.2-47.9-31.9-18.3-7.7-37.9-11.6-59-11.6-20.2 0-40.1 4.3-59.5 12.8-4.7 1.5-8.6 3.1-11.7 4.6-9.3 4.6-18.5 8.9-27.4 12.8s-16.9 5.4-23.9 4.6c-7-0.8-12.8-4.6-17.5-11.6-4.7-7-7.4-18.6-8.2-34.8V371.6c0-20.9 7.6-38.9 22.8-54C37.9 302.5 56 295 77.1 295h182.1c-12.5-15.5-22.4-32.7-29.8-51.7-7.4-19-11.1-39.3-11.1-61 0-25.5 4.9-49.4 14.6-71.4 9.7-22.1 22.8-41.2 39.1-57.5 16.3-16.3 35.6-29.2 57.8-38.9C352 4.8 375.6 0 400.5 0c24.9 0 48.5 4.8 70.6 14.5 22.2 9.7 41.5 22.6 57.8 38.9s29.4 35.4 39.1 57.5c9.7 22.1 14.6 45.9 14.6 71.4 0 43.4-13.6 80.9-40.9 112.6h110.9c21 0 38.9 7.5 53.7 22.6 14.8 15.1 22.2 33.1 22.2 54V526c15.6-13.2 33.3-23.8 53.1-31.9 20-8.1 41.2-12.2 63.8-12.2z" p-id="4932"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698397550956" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2700" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M970.43915282 590.98409465a55.23363335 55.23363335 0 0 0-69.04204072 35.90186227A394.36813906 394.36813906 0 0 1 521.94205392 898.63543088 392.15879331 392.15879331 0 0 1 125.36456912 512a392.15879331 392.15879331 0 0 1 396.5774848-386.63543088 400.99617502 400.99617502 0 0 1 256.8363931 92.24016784l-119.85698388-19.88410786a55.23363335 55.23363335 0 0 0-63.518677 45.8439149 55.23363335 55.23363335 0 0 0 45.84391489 63.51867829l234.19060403 38.66354218h9.38971716a55.23363335 55.23363335 0 0 0 18.77943563-3.31401797 18.22709886 18.22709886 0 0 0 5.52336243-3.31401798 43.08223369 43.08223369 0 0 0 11.04672744-6.07569919l4.97102697-6.07569919c0-2.76168122 4.97102697-4.97102697 7.18037141-8.28504493s0-5.52336372 2.76168252-7.73270948a74.01306768 74.01306768 0 0 0 3.86635343-9.94205393l41.42522469-220.93453081a55.23363335 55.23363335 0 0 0-110.46726541-20.98878138l-14.91308088 80.08876817A508.7017592 508.7017592 0 0 0 521.94205392 14.8973037 502.62606002 502.62606002 0 0 0 14.8973037 512a502.62606002 502.62606002 0 0 0 507.04475022 497.1026963A503.73073225 503.73073225 0 0 0 1009.1026963 660.02613665a55.23363335 55.23363335 0 0 0-38.66354348-69.042042z" p-id="2701"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698504394130" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4081" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M928 448h-64a19.2 19.2 0 0 1 0-38.4h64a19.2 19.2 0 0 1 0 38.4zM797.1072 738.4064l-45.2608-45.2608a19.2 19.2 0 0 1 27.1488-27.1488l45.3248 45.2608a19.2 19.2 0 0 1-27.2128 27.1488zM779.008 204.4032a19.2 19.2 0 0 1-27.1488-27.1488l45.2608-45.2608a19.2 19.2 0 0 1 27.2 27.1488z m-121.216 472.0128a282.368 282.368 0 0 0-17.2032 77.5808v20.8A37.7856 37.7856 0 0 1 614.4 810.6752V819.2H409.6v-8.5248a37.7856 37.7856 0 0 1-26.1888-35.84v-23.9488a290.0352 290.0352 0 0 0-16.9344-74.24A279.04 279.04 0 0 1 243.2 443.5968C243.2 290.56 363.52 166.4 512 166.4s268.8 124.16 268.8 277.1968a279.04 279.04 0 0 1-123.008 232.8192zM505.6 691.2a19.2 19.2 0 1 0-19.2-19.2 19.2 19.2 0 0 0 19.2 19.2z m6.4-358.4a115.2 115.2 0 0 0-114.4448 102.4h6.5024a17.728 17.728 0 1 0 20.9024 0h8.6656A79.8848 79.8848 0 0 1 512 368.64a77.7216 77.7216 0 0 1 76.8 79.36c0.8064 45.6064-64 76.8-64 76.8a97.4976 97.4976 0 0 0-34.432 46.4768 18.7392 18.7392 0 0 0-3.968 11.1232v7.68a56.6784 56.6784 0 0 0 0 11.52v6.4a19.2 19.2 0 0 0 38.4 0v-14.4768a18.6496 18.6496 0 0 1 0.384-4.6336C533.3632 557.9904 588.8 524.8 588.8 524.8c36.2368-23.296 38.4-76.8 38.4-76.8a115.2 115.2 0 0 0-115.2-115.2z m6.4-230.4A19.2 19.2 0 0 1 499.2 83.2v-64a19.2 19.2 0 1 1 38.4 0v64A19.2 19.2 0 0 1 518.4 102.4z m-264.3456 92.9536l-45.2608-45.2608A19.2 19.2 0 1 1 235.9424 122.88l45.2608 45.248a19.2 19.2 0 0 1-27.1488 27.2256zM160 448h-64a19.2 19.2 0 0 1 0-38.4h64a19.2 19.2 0 0 1 0 38.4z m94.0544 227.0464a19.2 19.2 0 0 1 27.1488 27.1488L235.9424 747.52a19.2 19.2 0 0 1-27.1488-27.1488zM652.8 846.9376a8.384 8.384 0 0 1 0.384 1.9072V870.4a8.4096 8.4096 0 0 1-0.384 1.9072V883.2H371.2v-51.2h281.6v14.9376zM627.2 947.2H396.8v-51.2h230.4v51.2z m-183.6672 23.9104H579.84a8.5248 8.5248 0 0 1 4.8896 1.6896H601.6v38.4H422.4v-38.4h16.2432a8.5248 8.5248 0 0 1 4.8896-1.6896z" fill="#1296db" p-id="4082"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698497259520" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10081" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M156.09136 606.57001a457.596822 457.596822 0 0 1 221.680239-392.516385 50.844091 50.844091 0 1 1 50.844091 86.943396 355.90864 355.90864 0 0 0-138.804369 152.532274h16.77855a152.532274 152.532274 0 1 1-152.532274 152.532274z m406.752731 0a457.596822 457.596822 0 0 1 221.680239-392.007944 50.844091 50.844091 0 1 1 50.844091 86.943396 355.90864 355.90864 0 0 0-138.804369 152.532274h16.77855a152.532274 152.532274 0 1 1-152.532274 152.532274z" fill="#E67E22" p-id="10082"></path></svg>
|
||||
|
After Width: | Height: | Size: 819 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698491522536" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4046" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 642.9c81.6 0 157.6-42.3 229.5-130.9-72-88.6-147.9-130.9-229.5-130.9S354.4 423.4 282.5 512c71.9 88.6 147.9 130.9 229.5 130.9z m0 62.3c-105.3 0-201.4-53.6-288.5-160.7-15.4-18.9-15.4-46 0-64.9C310.6 372.4 406.7 318.8 512 318.8s201.4 53.6 288.5 160.7c15.4 18.9 15.4 46 0 64.9C713.4 651.6 617.3 705.2 512 705.2z m0 0" fill="#333333" p-id="4047"></path><path d="M512 540c15.5 0 28-12.5 28-28 0-15.4-12.5-28-28-28s-28 12.5-28 28c0 15.4 12.5 28 28 28z m0 71.9c-35.7 0-68.7-19-86.6-49.9-17.9-30.9-17.9-69 0-99.9 17.9-30.9 50.9-49.9 86.6-49.9 55.2 0 100 44.7 100 99.9 0 55.1-44.8 99.8-100 99.8z m0 0" fill="#333333" p-id="4048"></path><path d="M136 888V745c0-19.9-16.1-36-36-36s-36 16.1-36 36v155c0 33.1 26.9 60 60 60h155c19.9 0 36-16.1 36-36s-16.1-36-36-36H136zM136 136h143c19.9 0 36-16.1 36-36s-16.1-36-36-36H124c-33.1 0-60 26.9-60 60v155c0 19.9 16.1 36 36 36s36-16.1 36-36V136zM888 136v143c0 19.9 16.1 36 36 36s36-16.1 36-36V124c0-33.1-26.9-60-60-60H745c-19.9 0-36 16.1-36 36s16.1 36 36 36h143zM888 888H745c-19.9 0-36 16.1-36 36s16.1 36 36 36h155c33.1 0 60-26.9 60-60V745c0-19.9-16.1-36-36-36s-36 16.1-36 36v143z" fill="#333333" p-id="4049"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -92,7 +92,13 @@ const iconPaths = {
|
||||
pause: () => import('./icons/common/pause.svg'),
|
||||
'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'),
|
||||
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
|
||||
'common/text/t': () => import('./icons/common/text/t.svg')
|
||||
'common/text/t': () => import('./icons/common/text/t.svg'),
|
||||
'common/navbar/pluginLight': () => import('./icons/common/navbar/pluginLight.svg'),
|
||||
'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'),
|
||||
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
|
||||
'core/module/previewLight': () => import('./icons/core/module/previewLight.svg'),
|
||||
'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'),
|
||||
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg')
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof iconPaths;
|
||||
|
||||
@@ -40,6 +40,13 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
link: `/app/list`,
|
||||
activeLink: ['/app/list', '/app/detail']
|
||||
},
|
||||
{
|
||||
label: t('navbar.Plugin'),
|
||||
icon: 'common/navbar/pluginLight',
|
||||
activeIcon: 'common/navbar/pluginFill',
|
||||
link: `/plugin/list`,
|
||||
activeLink: ['/plugin/list', '/plugin/edit']
|
||||
},
|
||||
{
|
||||
label: t('navbar.Datasets'),
|
||||
icon: 'dbLight',
|
||||
|
||||
@@ -43,7 +43,6 @@ const MyModal = ({
|
||||
{...props}
|
||||
>
|
||||
{!!title && <ModalHeader>{title}</ModalHeader>}
|
||||
{onClose && <ModalCloseButton />}
|
||||
<Box
|
||||
overflow={props.overflow || 'overlay'}
|
||||
h={'100%'}
|
||||
@@ -52,6 +51,7 @@ const MyModal = ({
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
{onClose && <ModalCloseButton />}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Box, useTheme, type BoxProps } from '@chakra-ui/react';
|
||||
import MyBox from '../common/MyBox';
|
||||
|
||||
const PageContainer = ({ children, ...props }: BoxProps) => {
|
||||
const PageContainer = ({ children, ...props }: BoxProps & { isLoading?: boolean }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box bg={'myGray.100'} h={'100%'} p={[0, 5]} px={[0, 6]} {...props}>
|
||||
<MyBox bg={'myGray.100'} h={'100%'} p={[0, 5]} px={[0, 6]} {...props}>
|
||||
<Box
|
||||
h={'100%'}
|
||||
bg={'white'}
|
||||
@@ -14,7 +15,7 @@ const PageContainer = ({ children, ...props }: BoxProps) => {
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import MyModal from '../MyModal';
|
||||
import { Box, Button, Grid, useTheme } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid, useTheme } from '@chakra-ui/react';
|
||||
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
|
||||
import { ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
|
||||
@@ -13,14 +13,14 @@ const PromptTemplate = ({
|
||||
title: string;
|
||||
templates: PromptTemplateItem[];
|
||||
onClose: () => void;
|
||||
onSuccess: (e: string) => void;
|
||||
onSuccess: (e: PromptTemplateItem) => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [selectTemplateTitle, setSelectTemplateTitle] = useState<PromptTemplateItem>();
|
||||
|
||||
return (
|
||||
<MyModal isOpen title={title} onClose={onClose}>
|
||||
<ModalBody w={'600px'}>
|
||||
<MyModal isOpen title={title} onClose={onClose} isCentered>
|
||||
<ModalBody h="100%" w={'600px'} maxW={'90vw'} overflowY={'auto'}>
|
||||
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={4}>
|
||||
{templates.map((item) => (
|
||||
<Box
|
||||
@@ -38,8 +38,9 @@ const PromptTemplate = ({
|
||||
onClick={() => setSelectTemplateTitle(item)}
|
||||
>
|
||||
<Box>{item.title}</Box>
|
||||
|
||||
<Box color={'myGray.600'} fontSize={'sm'} whiteSpace={'pre-wrap'}>
|
||||
{item.value}
|
||||
{item.desc}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
@@ -50,7 +51,7 @@ const PromptTemplate = ({
|
||||
disabled={!selectTemplateTitle}
|
||||
onClick={() => {
|
||||
if (!selectTemplateTitle) return;
|
||||
onSuccess(selectTemplateTitle.value);
|
||||
onSuccess(selectTemplateTitle);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
|
||||
19
projects/app/src/components/common/MyBox/index.tsx
Normal file
19
projects/app/src/components/common/MyBox/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import Loading from '@/components/Loading';
|
||||
|
||||
type Props = BoxProps & {
|
||||
isLoading?: boolean;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
const MyBox = ({ text, isLoading, children, ...props }: Props) => {
|
||||
return (
|
||||
<Box position={'relative'} {...props}>
|
||||
{children}
|
||||
{isLoading && <Loading fixed={false} text={text} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyBox;
|
||||
@@ -30,9 +30,9 @@ const ParentPaths = (props: {
|
||||
{concatPaths.map((item, i) => (
|
||||
<Flex key={item.parentId} alignItems={'center'}>
|
||||
<Box
|
||||
fontSize={['md', 'lg']}
|
||||
fontSize={['sm', 'lg']}
|
||||
py={1}
|
||||
px={[0, 2]}
|
||||
px={[1, 2]}
|
||||
borderRadius={'md'}
|
||||
{...(i === concatPaths.length - 1
|
||||
? {
|
||||
@@ -51,7 +51,7 @@ const ParentPaths = (props: {
|
||||
{item.parentName}
|
||||
</Box>
|
||||
{i !== concatPaths.length - 1 && (
|
||||
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
|
||||
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['14px', '24px']} />
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
19
projects/app/src/components/core/chat/Divider/index.tsx
Normal file
19
projects/app/src/components/core/chat/Divider/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import MyIcon, { type IconName } from '@/components/Icon';
|
||||
|
||||
const ChatBoxDivider = ({ icon, text }: { icon: IconName; text: string }) => {
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'} py={2} gap={2}>
|
||||
<MyIcon name={icon} w={'14px'} color={'myGray.900'} />
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{text}
|
||||
</Box>
|
||||
<Box h={'1px'} mt={1} bg={'myGray.200'} flex={'1'} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBoxDivider;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EditFormType } from '@/web/core/app/basicSettings';
|
||||
@@ -45,7 +45,6 @@ const AIChatSettingsModal = ({
|
||||
|
||||
const [selectTemplateData, setSelectTemplateData] = useState<{
|
||||
title: string;
|
||||
key: 'quoteTemplate' | 'quotePrompt';
|
||||
templates: PromptTemplateItem[];
|
||||
}>();
|
||||
|
||||
@@ -163,8 +162,7 @@ const AIChatSettingsModal = ({
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: '选择引用内容模板',
|
||||
key: 'quoteTemplate',
|
||||
title: '选择知识库提示词模板',
|
||||
templates: Prompt_QuoteTemplateList
|
||||
})
|
||||
}
|
||||
@@ -190,19 +188,6 @@ const AIChatSettingsModal = ({
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Box
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: '选择引用提示词模板',
|
||||
key: 'quotePrompt',
|
||||
templates: Prompt_QuotePromptList
|
||||
})
|
||||
}
|
||||
>
|
||||
选择模板
|
||||
</Box>
|
||||
</Flex>
|
||||
<Textarea
|
||||
rows={11}
|
||||
@@ -227,7 +212,12 @@ const AIChatSettingsModal = ({
|
||||
title={selectTemplateData.title}
|
||||
templates={selectTemplateData.templates}
|
||||
onClose={() => setSelectTemplateData(undefined)}
|
||||
onSuccess={(e) => setValue(selectTemplateData.key, e)}
|
||||
onSuccess={(e) => {
|
||||
const quoteVal = e.value;
|
||||
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
|
||||
setValue('quoteTemplate', quoteVal);
|
||||
setValue('quotePrompt', promptVal);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { AppSchema } from '@/types/mongoSchema';
|
||||
import React, {
|
||||
useMemo,
|
||||
@@ -10,7 +10,7 @@ import React, {
|
||||
} from 'react';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
@@ -28,7 +28,7 @@ const ChatTest = (
|
||||
onClose
|
||||
}: {
|
||||
app: AppSchema;
|
||||
modules?: AppModuleItemType[];
|
||||
modules?: ModuleItemType[];
|
||||
onClose: () => void;
|
||||
},
|
||||
ref: ForwardedRef<ChatTestComponentRef>
|
||||
@@ -41,7 +41,7 @@ const ChatTest = (
|
||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const historyMaxLen =
|
||||
modules
|
||||
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
|
||||
?.find((item) => item.flowType === FlowNodeTypeEnum.historyNode)
|
||||
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0;
|
||||
const history = chatList.slice(-historyMaxLen - 2, -2);
|
||||
|
||||
@@ -10,9 +10,12 @@ import {
|
||||
} from 'reactflow';
|
||||
import type {
|
||||
FlowModuleItemType,
|
||||
FlowOutputTargetItemType,
|
||||
FlowModuleItemChangeProps
|
||||
} from '@/types/core/app/flow';
|
||||
FlowModuleTemplateType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type {
|
||||
FlowNodeOutputTargetItemType,
|
||||
FlowNodeChangeProps
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
@@ -25,17 +28,21 @@ import React, {
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { FlowModuleTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
export type useFlowStoreType = {
|
||||
appId: string;
|
||||
export type useFlowProviderStoreType = {
|
||||
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
|
||||
filterAppIds: string[];
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
setNodes: Dispatch<SetStateAction<Node<FlowModuleItemType, string | undefined>[]>>;
|
||||
onNodesChange: OnChange<NodeChange>;
|
||||
@@ -44,8 +51,9 @@ export type useFlowStoreType = {
|
||||
onEdgesChange: OnChange<EdgeChange>;
|
||||
onFixView: () => void;
|
||||
onDelNode: (nodeId: string) => void;
|
||||
onChangeNode: (e: FlowModuleItemChangeProps) => void;
|
||||
onChangeNode: (e: FlowNodeChangeProps) => void;
|
||||
onCopyNode: (nodeId: string) => void;
|
||||
onResetNode: (id: string, module: FlowModuleTemplateType) => void;
|
||||
onDelEdge: (e: {
|
||||
moduleId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
@@ -53,12 +61,12 @@ export type useFlowStoreType = {
|
||||
}) => void;
|
||||
onDelConnect: (id: string) => void;
|
||||
onConnect: ({ connect }: { connect: Connection }) => any;
|
||||
initData: (modules: AppModuleItemType[]) => void;
|
||||
initData: (modules: ModuleItemType[]) => void;
|
||||
};
|
||||
|
||||
const StateContext = createContext<useFlowStoreType>({
|
||||
appId: '',
|
||||
const StateContext = createContext<useFlowProviderStoreType>({
|
||||
reactFlowWrapper: null,
|
||||
filterAppIds: [],
|
||||
nodes: [],
|
||||
setNodes: function (
|
||||
value: React.SetStateAction<Node<FlowModuleItemType, string | undefined>[]>
|
||||
@@ -78,11 +86,10 @@ const StateContext = createContext<useFlowStoreType>({
|
||||
onFixView: function (): void {
|
||||
return;
|
||||
},
|
||||
|
||||
onDelNode: function (nodeId: string): void {
|
||||
return;
|
||||
},
|
||||
onChangeNode: function (e: FlowModuleItemChangeProps): void {
|
||||
onChangeNode: function (e: FlowNodeChangeProps): void {
|
||||
return;
|
||||
},
|
||||
onCopyNode: function (nodeId: string): void {
|
||||
@@ -101,13 +108,22 @@ const StateContext = createContext<useFlowStoreType>({
|
||||
onConnect: function ({ connect }: { connect: Connection }) {
|
||||
return;
|
||||
},
|
||||
initData: function (modules: AppModuleItemType[]): void {
|
||||
initData: function (modules: ModuleItemType[]): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onResetNode: function (id: string, module: FlowModuleTemplateType): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
export const useFlowStore = () => useContext(StateContext);
|
||||
export const useFlowProviderStore = () => useContext(StateContext);
|
||||
|
||||
export const FlowProvider = ({ appId, children }: { appId: string; children: React.ReactNode }) => {
|
||||
export const FlowProvider = ({
|
||||
filterAppIds = [],
|
||||
children
|
||||
}: {
|
||||
filterAppIds?: string[];
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@@ -156,8 +172,11 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
({ connect }: { connect: Connection }) => {
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
const sourceType = (() => {
|
||||
if (source?.flowType === FlowModuleTypeEnum.classifyQuestion) {
|
||||
return FlowValueTypeEnum.boolean;
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion) {
|
||||
return FlowNodeValTypeEnum.boolean;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
}
|
||||
return source?.outputs.find((output) => output.key === connect.sourceHandle)?.valueType;
|
||||
})();
|
||||
@@ -173,8 +192,8 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
});
|
||||
}
|
||||
if (
|
||||
sourceType !== FlowValueTypeEnum.any &&
|
||||
targetType !== FlowValueTypeEnum.any &&
|
||||
sourceType !== FlowNodeValTypeEnum.any &&
|
||||
targetType !== FlowNodeValTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
) {
|
||||
return toast({
|
||||
@@ -209,15 +228,31 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
);
|
||||
|
||||
const onChangeNode = useCallback(
|
||||
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
|
||||
({ moduleId, type, key, value, index }: FlowNodeChangeProps) => {
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
if (node.id !== moduleId) return node;
|
||||
|
||||
const updateObj: Record<string, any> = {};
|
||||
|
||||
if (type === 'inputs') {
|
||||
if (type === 'attr') {
|
||||
if (key) {
|
||||
updateObj[key] = value;
|
||||
}
|
||||
} else if (type === 'updateInput') {
|
||||
updateObj.inputs = node.data.inputs.map((item) => (item.key === key ? value : item));
|
||||
} else if (type === 'replaceInput') {
|
||||
onDelEdge({ moduleId, targetHandle: key });
|
||||
const oldInputIndex = node.data.inputs.findIndex((item) => item.key === key);
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
index: oldInputIndex,
|
||||
value
|
||||
});
|
||||
});
|
||||
} else if (type === 'addInput') {
|
||||
const input = node.data.inputs.find((input) => input.key === value.key);
|
||||
if (input) {
|
||||
@@ -227,22 +262,51 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
});
|
||||
updateObj.inputs = node.data.inputs;
|
||||
} else {
|
||||
updateObj.inputs = node.data.inputs.concat(value);
|
||||
if (index !== undefined) {
|
||||
const inputs = [...node.data.inputs];
|
||||
inputs.splice(index, 0, value);
|
||||
updateObj.inputs = inputs;
|
||||
} else {
|
||||
updateObj.inputs = node.data.inputs.concat(value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delInput') {
|
||||
onDelEdge({ moduleId, targetHandle: key });
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
|
||||
} else if (type === 'attr') {
|
||||
updateObj[key] = value;
|
||||
} else if (type === 'outputs') {
|
||||
// del output connect
|
||||
const delOutputs = node.data.outputs.filter(
|
||||
(item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key)
|
||||
);
|
||||
delOutputs.forEach((output) => {
|
||||
onDelEdge({ moduleId, sourceHandle: output.key });
|
||||
} else if (type === 'updateOutput') {
|
||||
updateObj.outputs = node.data.outputs.map((item) => (item.key === key ? value : item));
|
||||
} else if (type === 'replaceOutput') {
|
||||
onDelEdge({ moduleId, sourceHandle: key });
|
||||
const oldOutputIndex = node.data.outputs.findIndex((item) => item.key === key);
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
index: oldOutputIndex,
|
||||
value
|
||||
});
|
||||
});
|
||||
updateObj.outputs = value;
|
||||
} else if (type === 'addOutput') {
|
||||
const output = node.data.outputs.find((output) => output.key === value.key);
|
||||
if (output) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
updateObj.outputs = node.data.outputs;
|
||||
} else {
|
||||
if (index !== undefined) {
|
||||
const outputs = [...node.data.outputs];
|
||||
outputs.splice(index, 0, value);
|
||||
updateObj.outputs = outputs;
|
||||
} else {
|
||||
updateObj.outputs = node.data.outputs.concat(value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delOutput') {
|
||||
onDelEdge({ moduleId, sourceHandle: key });
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== key);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -287,8 +351,36 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
// reset a node data. delete edge and replace it
|
||||
const onResetNode = useCallback(
|
||||
(id: string, module: FlowModuleTemplateType) => {
|
||||
setNodes((state) =>
|
||||
state.map((node) => {
|
||||
if (node.id === id) {
|
||||
// delete edge
|
||||
node.data.inputs.forEach((item) => {
|
||||
onDelEdge({ moduleId: id, targetHandle: item.key });
|
||||
});
|
||||
node.data.outputs.forEach((item) => {
|
||||
onDelEdge({ moduleId: id, sourceHandle: item.key });
|
||||
});
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
...module
|
||||
}
|
||||
};
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
},
|
||||
[onDelEdge, setNodes]
|
||||
);
|
||||
|
||||
const initData = useCallback(
|
||||
(modules: AppModuleItemType[]) => {
|
||||
(modules: ModuleItemType[]) => {
|
||||
const edges = appModule2FlowEdge({
|
||||
modules,
|
||||
onDelete: onDelConnect
|
||||
@@ -304,7 +396,7 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
|
||||
// use eventbus to avoid refresh ReactComponents
|
||||
useEffect(() => {
|
||||
const update = (e: FlowModuleItemChangeProps) => {
|
||||
const update = (e: FlowNodeChangeProps) => {
|
||||
onChangeNode(e);
|
||||
};
|
||||
eventBus.on(EventNameEnum.updaterNode, update);
|
||||
@@ -314,8 +406,8 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
}, [onChangeNode]);
|
||||
|
||||
const value = {
|
||||
appId,
|
||||
reactFlowWrapper,
|
||||
filterAppIds,
|
||||
nodes,
|
||||
setNodes,
|
||||
onNodesChange,
|
||||
@@ -325,6 +417,7 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
onFixView,
|
||||
onDelNode,
|
||||
onChangeNode,
|
||||
onResetNode,
|
||||
onCopyNode,
|
||||
onDelEdge,
|
||||
onDelConnect,
|
||||
@@ -337,6 +430,53 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
|
||||
export default React.memo(FlowProvider);
|
||||
|
||||
export const onChangeNode = (e: FlowModuleItemChangeProps) => {
|
||||
export const onChangeNode = (e: FlowNodeChangeProps) => {
|
||||
eventBus.emit(EventNameEnum.updaterNode, e);
|
||||
};
|
||||
|
||||
export function 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,
|
||||
logo: item.data.logo,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((item) => ({
|
||||
...item,
|
||||
connected: item.connected ?? item.type !== FlowNodeInputTypeEnum.target
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected =
|
||||
input.connected ||
|
||||
!!edges.find((edge) => edge.target === module.moduleId && edge.targetHandle === input.key);
|
||||
});
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
@@ -3,13 +3,13 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowStore } from './Provider';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
|
||||
const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [value, setValue] = useState('');
|
||||
const { setNodes, setEdges, initData } = useFlowStore();
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<MyModal isOpen w={'600px'} onClose={onClose} title={t('app.Import Config')}>
|
||||
@@ -1,22 +1,22 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ModalBody, Flex, Box, useTheme, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { getMyModels } from '@/web/core/app/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { SelectAppItemType } from '@/types/core/app/flow';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
const SelectAppModal = ({
|
||||
defaultApps = [],
|
||||
filterApps = [],
|
||||
filterAppIds = [],
|
||||
max = 1,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultApps: string[];
|
||||
filterApps?: string[];
|
||||
filterAppIds?: string[];
|
||||
max?: number;
|
||||
onClose: () => void;
|
||||
onSuccess: (e: SelectAppItemType[]) => void;
|
||||
@@ -26,11 +26,12 @@ const SelectAppModal = ({
|
||||
const theme = useTheme();
|
||||
const [selectedApps, setSelectedApps] = React.useState<string[]>(defaultApps);
|
||||
/* 加载模型 */
|
||||
const { data = [], isLoading } = useQuery(['loadMyApos'], () => getMyModels());
|
||||
const { myApps, loadMyApps } = useUserStore();
|
||||
const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps());
|
||||
|
||||
const apps = useMemo(
|
||||
() => data.filter((app) => !filterApps.includes(app._id)),
|
||||
[data, filterApps]
|
||||
() => myApps.filter((app) => !filterAppIds.includes(app._id)),
|
||||
[myApps, filterAppIds]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -38,13 +39,13 @@ const SelectAppModal = ({
|
||||
isOpen
|
||||
title={`选择应用${max > 1 ? `(${selectedApps.length}/${max})` : ''}`}
|
||||
onClose={onClose}
|
||||
w={'700px'}
|
||||
minW={'700px'}
|
||||
position={'relative'}
|
||||
>
|
||||
<ModalBody
|
||||
minH={'300px'}
|
||||
display={'grid'}
|
||||
gridTemplateColumns={['1fr', 'repeat(3,1fr)']}
|
||||
gridTemplateColumns={['1fr', 'repeat(3, minmax(0, 1fr))']}
|
||||
gridGap={4}
|
||||
>
|
||||
{apps.map((app) => (
|
||||
@@ -53,8 +54,7 @@ const SelectAppModal = ({
|
||||
alignItems={'center'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
px={1}
|
||||
py={2}
|
||||
p={2}
|
||||
cursor={'pointer'}
|
||||
{...(selectedApps.includes(app._id)
|
||||
? {
|
||||
243
projects/app/src/components/core/module/Flow/TemplateList.tsx
Normal file
243
projects/app/src/components/core/module/Flow/TemplateList.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import type {
|
||||
FlowModuleTemplateType,
|
||||
SystemModuleTemplateType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getPluginModuleDetail } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
system = 'system',
|
||||
combine = 'combine'
|
||||
}
|
||||
|
||||
export type ModuleTemplateProps = {
|
||||
systemTemplates: SystemModuleTemplateType;
|
||||
pluginTemplates: SystemModuleTemplateType;
|
||||
show2Plugin?: boolean;
|
||||
};
|
||||
|
||||
const ModuleTemplateList = ({
|
||||
systemTemplates,
|
||||
pluginTemplates,
|
||||
show2Plugin = false,
|
||||
isOpen,
|
||||
onClose
|
||||
}: ModuleTemplateProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [templateType, setTemplateType] = React.useState(TemplateTypeEnum.system);
|
||||
|
||||
const typeList = useMemo(
|
||||
() => [
|
||||
{
|
||||
type: TemplateTypeEnum.system,
|
||||
label: t('app.module.System Module'),
|
||||
child: <RenderList templates={systemTemplates} onClose={onClose} />
|
||||
},
|
||||
{
|
||||
type: TemplateTypeEnum.combine,
|
||||
label: t('plugin.Plugin Module'),
|
||||
child: <RenderList templates={pluginTemplates} onClose={onClose} />
|
||||
}
|
||||
],
|
||||
[pluginTemplates, onClose, systemTemplates, t]
|
||||
);
|
||||
const TemplateItem = useMemo(
|
||||
() => typeList.find((item) => item.type === templateType)?.child,
|
||||
[templateType, typeList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
zIndex={2}
|
||||
display={isOpen ? 'block' : 'none'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
w={'360px'}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Flex
|
||||
zIndex={3}
|
||||
flexDirection={'column'}
|
||||
position={'absolute'}
|
||||
top={'65px'}
|
||||
left={0}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 100px)' : '0'}
|
||||
w={isOpen ? ['100%', '360px'] : '0'}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'20px'}
|
||||
overflow={'hidden'}
|
||||
transition={'.2s ease'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<Flex pt={4} pb={1} px={5} gap={4} alignItems={'center'} fontSize={['md', 'xl']}>
|
||||
{typeList.map((item) => (
|
||||
<Box
|
||||
key={item.label}
|
||||
borderBottom={'2px solid transparent'}
|
||||
{...(item.type === templateType
|
||||
? {
|
||||
color: 'myBlue.700',
|
||||
borderBottomColor: 'myBlue.700',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {
|
||||
cursor: 'pointer',
|
||||
onClick: () => setTemplateType(item.type)
|
||||
})}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
))}
|
||||
<Box flex={1} />
|
||||
{show2Plugin && templateType === TemplateTypeEnum.combine && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() => router.push('/plugin/list')}
|
||||
>
|
||||
<Box fontSize={'sm'} transform={'translateY(-1px)'}>
|
||||
{t('plugin.To Edit Plugin')}
|
||||
</Box>
|
||||
<MyIcon name={'rightArrowLight'} w={'12px'} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{TemplateItem}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ModuleTemplateList);
|
||||
|
||||
var RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
onClose
|
||||
}: {
|
||||
templates: {
|
||||
label: string;
|
||||
list: FlowModuleTemplateType[];
|
||||
}[];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystemStore();
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const onAddNode = useCallback(
|
||||
async ({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
|
||||
if (!reactFlowWrapper?.current) return;
|
||||
|
||||
let templateModule = { ...template };
|
||||
|
||||
// get plugin module
|
||||
try {
|
||||
if (templateModule.flowType === FlowNodeTypeEnum.pluginModule) {
|
||||
setLoading(true);
|
||||
const pluginModule = await getPluginModuleDetail(templateModule.id);
|
||||
templateModule = {
|
||||
...templateModule,
|
||||
...pluginModule
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('plugin.Get Plugin Module Detail Failed'))
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
|
||||
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
|
||||
|
||||
setNodes((state) =>
|
||||
state.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...templateModule,
|
||||
moduleId: nanoid(),
|
||||
position: { x: mouseX, y: mouseY - 20 }
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
[reactFlowWrapper, setLoading, setNodes, t, toast, x, y, zoom]
|
||||
);
|
||||
|
||||
const list = useMemo(() => templates.map((item) => item.list).flat(), [templates]);
|
||||
|
||||
return list.length === 0 ? (
|
||||
<EmptyTip text={t('app.module.No Modules')} />
|
||||
) : (
|
||||
<Box flex={'1 0 0'} overflow={'overlay'}>
|
||||
<Box w={['100%', '330px']} mx={'auto'}>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
alignItems={'center'}
|
||||
p={5}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'md'}
|
||||
draggable
|
||||
onDragEnd={(e) => {
|
||||
if (e.clientX < 360) return;
|
||||
onAddNode({
|
||||
template: item,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (isPc) return;
|
||||
onClose();
|
||||
onAddNode({
|
||||
template: item,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.logo} w={'34px'} objectFit={'contain'} borderRadius={'0'} />
|
||||
<Box ml={5} flex={'1 0 0'}>
|
||||
<Box color={'black'}>{item.name}</Box>
|
||||
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
|
||||
{item.intro}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
|
||||
const typeSelectList = [
|
||||
{
|
||||
label: '字符串',
|
||||
value: FlowNodeValTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: FlowNodeValTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: '布尔',
|
||||
value: FlowNodeValTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: '历史记录',
|
||||
value: FlowNodeValTypeEnum.chatHistory
|
||||
},
|
||||
{
|
||||
label: '引用内容',
|
||||
value: FlowNodeValTypeEnum.datasetQuote
|
||||
},
|
||||
{
|
||||
label: '任意',
|
||||
value: FlowNodeValTypeEnum.any
|
||||
}
|
||||
];
|
||||
|
||||
export type EditFieldModeType = 'input' | 'output' | 'pluginInput';
|
||||
export type EditFieldType = {
|
||||
key: string;
|
||||
label?: string;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
mode,
|
||||
defaultField = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
required: false
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
mode: EditFieldModeType;
|
||||
defaultField?: EditFieldType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: EditFieldType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<EditFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const title = ['input', 'pluginInput'].includes(mode)
|
||||
? t('app.Input Field Settings')
|
||||
: t('app.Output Field Settings');
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
title={
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{title}
|
||||
</Flex>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody minH={'260px'} overflow={'visible'}>
|
||||
{mode === 'input' && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段类型</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={typeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeValTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === FlowNodeValTypeEnum.chatHistory ||
|
||||
type === FlowNodeValTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = typeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>字段描述</Box>
|
||||
<Textarea placeholder="可选" rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
@@ -0,0 +1,196 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
import {
|
||||
FlowNodeSpecialInputKeyEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getPluginModuleDetail } from '@/web/core/plugin/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
|
||||
type Props = FlowModuleItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
minW?: string | number;
|
||||
isPreview?: boolean;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const {
|
||||
children,
|
||||
logo = '/icon/logo.svg',
|
||||
name = '未知模块',
|
||||
description,
|
||||
minW = '300px',
|
||||
moduleId,
|
||||
flowType,
|
||||
inputs,
|
||||
isPreview
|
||||
} = props;
|
||||
const { onCopyNode, onResetNode, onDelNode } = useFlowProviderStore();
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { setLoading } = useSystemStore();
|
||||
|
||||
// custom title edit
|
||||
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||
title: t('common.Custom Title'),
|
||||
placeholder: t('app.module.Custom Title Tip') || ''
|
||||
});
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: t('module.Confirm Sync Plugin')
|
||||
});
|
||||
|
||||
const menuList = useMemo(
|
||||
() => [
|
||||
...(flowType === FlowNodeTypeEnum.pluginModule
|
||||
? [
|
||||
{
|
||||
icon: 'common/refreshLight',
|
||||
label: t('plugin.Synchronous version'),
|
||||
onClick: () => {
|
||||
const pluginId = inputs.find(
|
||||
(item) => item.key === FlowNodeSpecialInputKeyEnum.pluginId
|
||||
)?.value;
|
||||
if (!pluginId) return;
|
||||
openConfirm(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const pluginModule = await getPluginModuleDetail(pluginId);
|
||||
onResetNode(moduleId, pluginModule);
|
||||
} catch (e) {
|
||||
return toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('plugin.Get Plugin Module Detail Failed'))
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
})();
|
||||
}
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('common.Rename'),
|
||||
onClick: () =>
|
||||
onOpenModal({
|
||||
defaultVal: name,
|
||||
onSuccess: (e) => {
|
||||
if (!e) {
|
||||
return toast({
|
||||
title: t('app.modules.Title is required'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'attr',
|
||||
key: 'name',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
]),
|
||||
{
|
||||
icon: 'copy',
|
||||
label: t('common.Copy'),
|
||||
onClick: () => onCopyNode(moduleId)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
onClick: () => onDelNode(moduleId)
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'back',
|
||||
label: t('common.Back'),
|
||||
onClick: () => {}
|
||||
}
|
||||
],
|
||||
[
|
||||
flowType,
|
||||
inputs,
|
||||
moduleId,
|
||||
name,
|
||||
onCopyNode,
|
||||
onDelNode,
|
||||
onOpenModal,
|
||||
onResetNode,
|
||||
openConfirm,
|
||||
setLoading,
|
||||
t,
|
||||
toast
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={minW}
|
||||
maxW={'500px'}
|
||||
bg={'white'}
|
||||
border={theme.borders.md}
|
||||
borderRadius={'md'}
|
||||
boxShadow={'sm'}
|
||||
className={isPreview ? 'nodrag' : ''}
|
||||
>
|
||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||
<Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||
{name}
|
||||
</Box>
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon
|
||||
display={['none', 'inline']}
|
||||
transform={'translateY(1px)'}
|
||||
mb={'1px'}
|
||||
ml={1}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
{!isPreview && (
|
||||
<Menu autoSelect={false} isLazy>
|
||||
<MenuButton
|
||||
className={'nodrag'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||
</MenuButton>
|
||||
<MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
|
||||
{menuList.map((item) => (
|
||||
<MenuItem key={item.label} onClick={item.onClick} py={[2, 3]}>
|
||||
<MyIcon name={item.icon as any} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
{children}
|
||||
<EditTitleModal />
|
||||
<ConfirmModal />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeCard);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Input, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
@@ -10,11 +10,15 @@ import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum,
|
||||
FlowNodeSpecialInputKeyEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -28,7 +32,7 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[SpecialInputKeyEnum.agents]: ({
|
||||
[FlowNodeSpecialInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = [],
|
||||
...props
|
||||
@@ -50,26 +54,20 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
const newInputValue = agents.filter((input) => input.key !== item.key);
|
||||
const newOutputVal = outputs.filter(
|
||||
(output) => output.key !== item.key
|
||||
);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
value: agents.filter((input) => input.key !== item.key)
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputVal
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -92,7 +90,7 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
@@ -102,36 +100,35 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SourceHandle handleKey={item.key} valueType={FlowValueTypeEnum.boolean} />
|
||||
<SourceHandle handleKey={item.key} valueType={FlowNodeValTypeEnum.boolean} />
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
const newInputValue = agents.concat({ value: '', key });
|
||||
const newOutputValue = outputs.concat({
|
||||
key,
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
targets: []
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
value: agents.concat({ value: '', key })
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
type: 'updateOutput',
|
||||
key: agentKey,
|
||||
value: newOutputValue
|
||||
value: outputs.concat({
|
||||
key,
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
})
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
return <NodeCard {...data}></NodeCard>;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import Container from '../modules/Container';
|
||||
@@ -13,14 +13,17 @@ import RenderOutput from '../render/RenderOutput';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||
import { ContextExtractEnum } from '@/constants/flow/flowField';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import { useFlowStore, onChangeNode } from '../Provider';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
const { t } = useTranslation();
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
const { onDelEdge } = useFlowStore();
|
||||
const { onDelEdge } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -88,29 +91,21 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const newInputValue = extractKeys.filter(
|
||||
(extract) => item.key !== extract.key
|
||||
);
|
||||
const newOutputVal = outputs.filter(
|
||||
(output) => output.key !== item.key
|
||||
);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: {
|
||||
...props,
|
||||
value: newInputValue
|
||||
value: extractKeys.filter((extract) => item.key !== extract.key)
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputVal
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
onDelEdge({ moduleId, sourceHandle: item.key });
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
@@ -145,7 +140,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: {
|
||||
...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
|
||||
@@ -153,62 +148,42 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.concat({
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
targets: []
|
||||
})
|
||||
});
|
||||
} else {
|
||||
const newOutput = {
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
|
||||
if (exists) {
|
||||
if (editExtractFiled.key === data.key) {
|
||||
const output = outputs.find((output) => output.key === data.key);
|
||||
// update
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.map((output) =>
|
||||
output.key === data.key
|
||||
? {
|
||||
...output,
|
||||
label: `提取结果-${data.desc}`
|
||||
}
|
||||
: output
|
||||
)
|
||||
type: 'updateOutput',
|
||||
key: data.key,
|
||||
value: {
|
||||
...output,
|
||||
label: `提取结果-${data.desc}`
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// del and push
|
||||
const newOutputs = outputs.filter((output) => output.key !== editExtractFiled.key);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputs
|
||||
type: 'replaceOutput',
|
||||
key: editExtractFiled.key,
|
||||
value: newOutput
|
||||
});
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputs.concat({
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
targets: []
|
||||
})
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
setEditExtractField(undefined);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
@@ -9,10 +9,14 @@ import { Box, Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
@@ -33,8 +37,8 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
key,
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length - 1}`,
|
||||
edit: true
|
||||
}
|
||||
@@ -52,21 +56,17 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key,
|
||||
value: [
|
||||
{
|
||||
key,
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
].concat(outputs as any)
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: nanoid(),
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -0,0 +1,191 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import { EditFieldType } from '../modules/FieldEditModal';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Container mt={1} borderTop={'2px solid'} borderTopColor={'myGray.300'}>
|
||||
{inputs.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'right'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={4}
|
||||
>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'right'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
edit: true,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={'pluginInput'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
const input = {
|
||||
...memInput,
|
||||
...e
|
||||
};
|
||||
const output = {
|
||||
...memOutput,
|
||||
...e
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeInput);
|
||||
@@ -0,0 +1,191 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { EditFieldType } from '../modules/FieldEditModal';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Container mt={1} borderTop={'2px solid'} borderTopColor={'myGray.300'}>
|
||||
{inputs.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'left'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={4}
|
||||
>
|
||||
<TargetHandle handleKey={item.key} valueType={item.valueType} />
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key,
|
||||
value: ''
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'left'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
edit: true,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={'output'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
if (!memInput || !memOutput) return;
|
||||
const input = {
|
||||
...memInput,
|
||||
...e
|
||||
};
|
||||
const output = {
|
||||
...memOutput,
|
||||
...e
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeOutput);
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeDatasetSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodeSimple = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
<NodeCard minW={'300px'} isPreview {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
@@ -23,4 +23,4 @@ const NodeDatasetSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeDatasetSearch);
|
||||
export default React.memo(NodeSimple);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeAPP = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodeRunAPP = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
@@ -22,4 +22,4 @@ const NodeAPP = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeAPP);
|
||||
export default React.memo(NodeRunAPP);
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodeSimple = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
@@ -23,4 +23,4 @@ const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeChat);
|
||||
export default React.memo(NodeSimple);
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate';
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
|
||||
import MyIcon from '@/components/Icon';
|
||||
@@ -76,7 +76,7 @@ export function WelcomeText({ data }: { data: FlowModuleItemType }) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...welcomeText,
|
||||
value: e.target.value
|
||||
@@ -106,14 +106,14 @@ function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, onChangeNode, moduleId]
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
@@ -230,7 +230,7 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.questionGuide,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
|
||||
value
|
||||
@@ -4,7 +4,7 @@ import { NodeProps } from 'reactflow';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
|
||||
import type { VariableItemType } from '@/types/app';
|
||||
@@ -12,7 +12,7 @@ import MyIcon from '@/components/Icon';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
id: nanoid(),
|
||||
@@ -41,14 +41,14 @@ const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, onChangeNode, moduleId]
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { FlowInputItemType, SelectAppItemType } from '@/types/core/app/flow';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import {
|
||||
Box,
|
||||
Textarea,
|
||||
@@ -15,10 +16,10 @@ import {
|
||||
useTheme,
|
||||
Grid
|
||||
} from '@chakra-ui/react';
|
||||
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode, useFlowStore } from '../Provider';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MySelect from '@/components/Select';
|
||||
import MySlider from '@/components/Slider';
|
||||
@@ -33,22 +34,25 @@ import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@/types/core/dataset';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { LLMModelItemType } from '@/types/model';
|
||||
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
|
||||
|
||||
const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../../SelectAppModal'));
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../SelectAppModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('../../../DatasetSelectModal'));
|
||||
|
||||
export const Label = React.memo(function Label({
|
||||
moduleId,
|
||||
inputKey,
|
||||
editFiledType = 'input',
|
||||
...item
|
||||
}: FlowInputItemType & {
|
||||
}: FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) {
|
||||
const { required = false, description, edit, label, type, valueType } = item;
|
||||
const [editField, setEditField] = useState<FlowInputItemType>();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
@@ -72,7 +76,7 @@ export const Label = React.memo(function Label({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{(type === FlowInputItemTypeEnum.target || valueType) && (
|
||||
{(type === FlowNodeInputTypeEnum.target || valueType) && (
|
||||
<TargetHandle handleKey={inputKey} valueType={valueType} />
|
||||
)}
|
||||
|
||||
@@ -86,8 +90,11 @@ export const Label = React.memo(function Label({
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...item,
|
||||
key: inputKey
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
required: item.required,
|
||||
key: inputKey,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -110,34 +117,31 @@ export const Label = React.memo(function Label({
|
||||
</>
|
||||
)}
|
||||
{!!editField && (
|
||||
<SetInputFieldModal
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
...item,
|
||||
...e
|
||||
};
|
||||
// same key
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: inputKey,
|
||||
type: 'updateInput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
// diff key. del and add
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: data.key,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: editField.key,
|
||||
value: ''
|
||||
});
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
@@ -150,55 +154,64 @@ export const Label = React.memo(function Label({
|
||||
const RenderInput = ({
|
||||
flowInputList,
|
||||
moduleId,
|
||||
CustomComponent = {}
|
||||
CustomComponent = {},
|
||||
editFiledType
|
||||
}: {
|
||||
flowInputList: FlowInputItemType[];
|
||||
flowInputList: FlowNodeInputItemType[];
|
||||
moduleId: string;
|
||||
CustomComponent?: Record<string, (e: FlowInputItemType) => React.ReactNode>;
|
||||
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortInputs = useMemo(
|
||||
() => flowInputList.sort((a, b) => (a.key === FlowInputItemTypeEnum.switch ? -1 : 1)),
|
||||
() => flowInputList.sort((a, b) => (a.key === FlowNodeInputTypeEnum.switch ? -1 : 1)),
|
||||
[flowInputList]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{sortInputs.map(
|
||||
(item) =>
|
||||
item.type !== FlowInputItemTypeEnum.hidden && (
|
||||
item.type !== FlowNodeInputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!item.label && <Label moduleId={moduleId} inputKey={item.key} {...item} />}
|
||||
{!!item.label && (
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
inputKey={item.key}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{item.type === FlowInputItemTypeEnum.numberInput && (
|
||||
{item.type === FlowNodeInputTypeEnum.numberInput && (
|
||||
<NumberInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.input && (
|
||||
{item.type === FlowNodeInputTypeEnum.input && (
|
||||
<TextInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.textarea && (
|
||||
{item.type === FlowNodeInputTypeEnum.textarea && (
|
||||
<TextareaRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.select && (
|
||||
{item.type === FlowNodeInputTypeEnum.select && (
|
||||
<SelectRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.slider && (
|
||||
{item.type === FlowNodeInputTypeEnum.slider && (
|
||||
<SliderRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.selectApp && (
|
||||
{item.type === FlowNodeInputTypeEnum.selectApp && (
|
||||
<SelectAppRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.aiSettings && (
|
||||
{item.type === FlowNodeInputTypeEnum.aiSettings && (
|
||||
<AISetting inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.maxToken && (
|
||||
{item.type === FlowNodeInputTypeEnum.maxToken && (
|
||||
<MaxTokenRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.selectChatModel && (
|
||||
{item.type === FlowNodeInputTypeEnum.selectChatModel && (
|
||||
<SelectChatModelRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.selectDataset && (
|
||||
{item.type === FlowNodeInputTypeEnum.selectDataset && (
|
||||
<SelectDatasetRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.custom && CustomComponent[item.key] && (
|
||||
{item.type === FlowNodeInputTypeEnum.custom && CustomComponent[item.key] && (
|
||||
<>{CustomComponent[item.key]({ ...item })}</>
|
||||
)}
|
||||
</Box>
|
||||
@@ -212,8 +225,8 @@ const RenderInput = ({
|
||||
export default React.memo(RenderInput);
|
||||
|
||||
type RenderProps = {
|
||||
inputs?: FlowInputItemType[];
|
||||
item: FlowInputItemType;
|
||||
inputs?: FlowNodeInputItemType[];
|
||||
item: FlowNodeInputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
@@ -226,7 +239,7 @@ var NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -252,7 +265,7 @@ var TextInputRender = React.memo(function TextInputRender({ item, moduleId }: Re
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -274,7 +287,7 @@ var TextareaRender = React.memo(function TextareaRender({ item, moduleId }: Rend
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -295,7 +308,7 @@ var SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderPr
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -320,7 +333,7 @@ var SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderPr
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -368,7 +381,7 @@ var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderP
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -411,7 +424,7 @@ var MaxTokenRender = React.memo(function MaxTokenRender({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -435,7 +448,7 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
{
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -449,7 +462,7 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: 'maxToken',
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
@@ -477,7 +490,15 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
|
||||
return <MySelect width={'100%'} value={item.value} list={list} onchange={onChangeModel} />;
|
||||
return (
|
||||
<MySelect
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={list}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, moduleId }: RenderProps) {
|
||||
@@ -498,7 +519,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} w={'100%'}>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
@@ -532,7 +553,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
@@ -546,7 +567,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
});
|
||||
|
||||
var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
|
||||
const { appId } = useFlowStore();
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
@@ -577,12 +598,12 @@ var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: Re
|
||||
{isOpenSelectApp && (
|
||||
<SelectAppModal
|
||||
defaultApps={item.value?.id ? [item.value.id] : []}
|
||||
filterApps={[appId]}
|
||||
filterAppIds={filterAppIds}
|
||||
onClose={onCloseSelectApp}
|
||||
onSuccess={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: 'app',
|
||||
value: {
|
||||
...item,
|
||||
@@ -1,28 +1,32 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { FlowOutputItemType } from '@/types/core/app/flow';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from './SourceHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import { SystemOutputEnum } from '@/constants/app';
|
||||
|
||||
const Label = ({
|
||||
import type { EditFieldType, EditFieldModeType } from '../modules/FieldEditModal';
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
|
||||
export const Label = ({
|
||||
moduleId,
|
||||
outputKey,
|
||||
outputs,
|
||||
editFiledType = 'output',
|
||||
...item
|
||||
}: FlowOutputItemType & {
|
||||
}: FlowNodeOutputItemType & {
|
||||
outputKey: string;
|
||||
moduleId: string;
|
||||
outputs: FlowOutputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const { label, description, edit } = item;
|
||||
const [editField, setEditField] = useState<FlowOutputItemType>();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -42,8 +46,10 @@ const Label = ({
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...item,
|
||||
key: outputKey
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
key: outputKey,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -57,9 +63,8 @@ const Label = ({
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.filter((output) => output.key !== outputKey)
|
||||
type: 'delOutput',
|
||||
key: outputKey
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -73,43 +78,29 @@ const Label = ({
|
||||
<Box>{label}</Box>
|
||||
|
||||
{!!editField && (
|
||||
<SetOutputFieldModal
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
...item,
|
||||
...e
|
||||
};
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.map((output) => (output.key === outputKey ? data : output))
|
||||
type: 'updateOutput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
let index = 0;
|
||||
const storeOutputs = outputs.filter((output, i) => {
|
||||
if (output.key !== editField.key) {
|
||||
return true;
|
||||
}
|
||||
index = i;
|
||||
return false;
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: storeOutputs
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
setTimeout(() => {
|
||||
storeOutputs.splice(index, 0, data);
|
||||
console.log(index, storeOutputs);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: [...storeOutputs]
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
@@ -122,10 +113,12 @@ const Label = ({
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList
|
||||
flowOutputList,
|
||||
editFiledType
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowOutputItemType[];
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortOutput = useMemo(
|
||||
() =>
|
||||
@@ -141,11 +134,17 @@ const RenderOutput = ({
|
||||
<>
|
||||
{sortOutput.map(
|
||||
(item) =>
|
||||
item.type !== FlowOutputItemTypeEnum.hidden && (
|
||||
item.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
<Label moduleId={moduleId} outputKey={item.key} outputs={sortOutput} {...item} />
|
||||
<Box mt={FlowOutputItemTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowOutputItemTypeEnum.source && (
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
outputKey={item.key}
|
||||
outputs={sortOutput}
|
||||
{...item}
|
||||
/>
|
||||
<Box mt={FlowNodeOutputTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
)}
|
||||
</Box>
|
||||
@@ -1,20 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeTip } from '@/constants/flow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${FlowValueTypeEnum}`;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
}
|
||||
|
||||
const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? FlowNodeValTypeEnum.any;
|
||||
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
|
||||
: (FlowValueTypeStyle[FlowNodeValTypeEnum.any] as any),
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -26,7 +32,12 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
transform={'translate(50%,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
<MyTooltip label={`${valueType}类型`}>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeTip[valType].label),
|
||||
example: FlowValueTypeTip[valType].example
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '12px',
|
||||
@@ -1,21 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, OnConnect, Position } from 'reactflow';
|
||||
import { FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeTip } from '@/constants/flow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${FlowValueTypeEnum}`;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
|
||||
const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? FlowNodeValTypeEnum.any;
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
|
||||
: (FlowValueTypeStyle[FlowNodeValTypeEnum.any] as any),
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -28,7 +33,12 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
transform={'translate(50%,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
<MyTooltip label={`${valueType}类型`}>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeTip[valType].label),
|
||||
example: FlowValueTypeTip[valType].example
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '12px',
|
||||
141
projects/app/src/components/core/module/Flow/index.tsx
Normal file
141
projects/app/src/components/core/module/Flow/index.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { edgeOptions, connectionLineStyle } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import ButtonEdge from './components/modules/ButtonEdge';
|
||||
import TemplateList, { type ModuleTemplateProps } from './TemplateList';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
|
||||
const nodeTypes = {
|
||||
[FlowNodeTypeEnum.userGuide]: dynamic(() => import('./components/nodes/NodeUserGuide')),
|
||||
[FlowNodeTypeEnum.variable]: dynamic(() => import('./components/nodes/NodeVariable')),
|
||||
[FlowNodeTypeEnum.questionInput]: dynamic(() => import('./components/nodes/NodeQuestionInput')),
|
||||
[FlowNodeTypeEnum.historyNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.chatNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodeInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodeOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
buttonedge: ButtonEdge
|
||||
};
|
||||
type Props = {
|
||||
modules: ModuleItemType[];
|
||||
Header: React.ReactNode;
|
||||
} & ModuleTemplateProps;
|
||||
|
||||
const Container = React.memo(function Container(props: Props) {
|
||||
const { modules = [], Header, systemTemplates, pluginTemplates, show2Plugin } = props;
|
||||
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
onOpen: onOpenTemplate,
|
||||
onClose: onCloseTemplate
|
||||
} = useDisclosure();
|
||||
|
||||
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect, initData } =
|
||||
useFlowProviderStore();
|
||||
|
||||
useEffect(() => {
|
||||
initData(JSON.parse(JSON.stringify(modules)));
|
||||
}, [modules.length]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* header */}
|
||||
{Header}
|
||||
<Box
|
||||
minH={'400px'}
|
||||
flex={'1 0 0'}
|
||||
w={'100%'}
|
||||
h={0}
|
||||
position={'relative'}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{/* open module template */}
|
||||
<IconButton
|
||||
position={'absolute'}
|
||||
top={5}
|
||||
left={5}
|
||||
w={'38px'}
|
||||
h={'38px'}
|
||||
borderRadius={'50%'}
|
||||
icon={<SmallCloseIcon fontSize={'26px'} />}
|
||||
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
|
||||
transition={'0.2s ease'}
|
||||
aria-label={''}
|
||||
zIndex={1}
|
||||
boxShadow={'2px 2px 6px #85b1ff'}
|
||||
onClick={() => {
|
||||
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
||||
}}
|
||||
/>
|
||||
|
||||
<ReactFlow
|
||||
ref={reactFlowWrapper}
|
||||
fitView
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={edgeOptions}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={(connect) => {
|
||||
connect.sourceHandle &&
|
||||
connect.targetHandle &&
|
||||
onConnect({
|
||||
connect
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Background />
|
||||
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
|
||||
</ReactFlow>
|
||||
|
||||
<TemplateList
|
||||
systemTemplates={systemTemplates}
|
||||
pluginTemplates={pluginTemplates}
|
||||
show2Plugin={show2Plugin}
|
||||
isOpen={isOpenTemplate}
|
||||
onClose={onCloseTemplate}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const Flow = (data: Props) => {
|
||||
return (
|
||||
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
|
||||
<ReactFlowProvider>
|
||||
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
|
||||
<Container {...data} />
|
||||
</Flex>
|
||||
</ReactFlowProvider>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Flow);
|
||||
@@ -1,14 +1,14 @@
|
||||
import { AppTypeEnum, SystemInputEnum } from '../app';
|
||||
import { TaskResponseKeyEnum } from '../chat';
|
||||
import {
|
||||
FlowModuleTypeEnum,
|
||||
FlowInputItemTypeEnum,
|
||||
FlowOutputItemTypeEnum,
|
||||
SpecialInputKeyEnum,
|
||||
FlowValueTypeEnum
|
||||
} from './index';
|
||||
FlowNodeTypeEnum,
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeSpecialInputKeyEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import type { AppItemType } from '@/types/app';
|
||||
import type { FlowModuleTemplateType } from '@/types/core/app/flow';
|
||||
import type { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
|
||||
import { chatModelList, cqModelList } from '@/web/common/system/staticData';
|
||||
import {
|
||||
Input_Template_History,
|
||||
@@ -30,7 +30,8 @@ export const variableTip =
|
||||
export const questionGuideTip = `对话结束后,会为生成 3 个引导性问题。`;
|
||||
|
||||
export const VariableModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.variable,
|
||||
id: FlowNodeTypeEnum.variable,
|
||||
flowType: FlowNodeTypeEnum.variable,
|
||||
logo: '/imgs/module/variable.png',
|
||||
name: '全局变量',
|
||||
intro: variableTip,
|
||||
@@ -39,7 +40,7 @@ export const VariableModule: FlowModuleTemplateType = {
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '变量输入',
|
||||
value: []
|
||||
}
|
||||
@@ -47,39 +48,41 @@ export const VariableModule: FlowModuleTemplateType = {
|
||||
outputs: []
|
||||
};
|
||||
export const UserGuideModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.userGuide,
|
||||
id: FlowNodeTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
logo: '/imgs/module/userGuide.png',
|
||||
name: '用户引导',
|
||||
intro: userGuideTip,
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '开场白'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '对话框变量',
|
||||
value: []
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.questionGuide,
|
||||
type: FlowInputItemTypeEnum.switch,
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '问题引导'
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
};
|
||||
export const UserInputModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.questionInput,
|
||||
id: FlowNodeTypeEnum.questionInput,
|
||||
flowType: FlowNodeTypeEnum.questionInput,
|
||||
logo: '/imgs/module/userChatInput.png',
|
||||
name: '用户问题(对话入口)',
|
||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '用户问题'
|
||||
}
|
||||
],
|
||||
@@ -87,21 +90,22 @@ export const UserInputModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
label: '用户问题',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const HistoryModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.historyNode,
|
||||
id: FlowNodeTypeEnum.historyNode,
|
||||
flowType: FlowNodeTypeEnum.historyNode,
|
||||
logo: '/imgs/module/history.png',
|
||||
name: '聊天记录',
|
||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
type: FlowInputItemTypeEnum.numberInput,
|
||||
type: FlowNodeInputTypeEnum.numberInput,
|
||||
label: '最长记录数',
|
||||
value: 6,
|
||||
min: 0,
|
||||
@@ -109,7 +113,7 @@ export const HistoryModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '聊天记录'
|
||||
}
|
||||
],
|
||||
@@ -117,15 +121,15 @@ export const HistoryModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
label: '聊天记录',
|
||||
valueType: FlowValueTypeEnum.chatHistory,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const ChatModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.chatNode,
|
||||
id: FlowNodeTypeEnum.chatNode,
|
||||
flowType: FlowNodeTypeEnum.chatNode,
|
||||
logo: '/imgs/module/AI.png',
|
||||
name: 'AI 对话',
|
||||
intro: 'AI 大模型对话',
|
||||
@@ -134,7 +138,7 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'model',
|
||||
type: FlowInputItemTypeEnum.selectChatModel,
|
||||
type: FlowNodeInputTypeEnum.selectChatModel,
|
||||
label: '对话模型',
|
||||
value: chatModelList?.[0]?.model,
|
||||
customData: () => chatModelList,
|
||||
@@ -143,7 +147,7 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '温度',
|
||||
value: 0,
|
||||
min: 0,
|
||||
@@ -156,7 +160,7 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '回复上限',
|
||||
value: chatModelList?.[0] ? chatModelList[0].maxToken / 2 : 2000,
|
||||
min: 100,
|
||||
@@ -172,47 +176,47 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'aiSettings',
|
||||
type: FlowInputItemTypeEnum.aiSettings,
|
||||
type: FlowNodeInputTypeEnum.aiSettings,
|
||||
label: '',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
label: '系统提示词',
|
||||
max: 300,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
description: ChatModelSystemTip,
|
||||
placeholder: ChatModelSystemTip,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.isResponseAnswerText,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '返回AI内容',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '引用内容模板',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '引用内容提示词',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: FlowValueTypeEnum.kbQuote,
|
||||
valueType: FlowNodeValTypeEnum.datasetQuote,
|
||||
connected: false
|
||||
},
|
||||
Input_Template_History,
|
||||
@@ -223,24 +227,24 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
key: TaskResponseKeyEnum.history,
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: FlowValueTypeEnum.chatHistory,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: TaskResponseKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
description: '将在 stream 回复完毕后触发',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
|
||||
export const KBSearchModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.datasetSearchNode,
|
||||
export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.datasetSearchNode,
|
||||
flowType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
logo: '/imgs/module/db.png',
|
||||
name: '知识库搜索',
|
||||
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
|
||||
@@ -249,7 +253,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'datasets',
|
||||
type: FlowInputItemTypeEnum.selectDataset,
|
||||
type: FlowNodeInputTypeEnum.selectDataset,
|
||||
label: '关联的知识库',
|
||||
value: [],
|
||||
list: [],
|
||||
@@ -258,7 +262,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
type: FlowInputItemTypeEnum.slider,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '相似度',
|
||||
value: 0.4,
|
||||
min: 0,
|
||||
@@ -271,7 +275,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
type: FlowInputItemTypeEnum.slider,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '单次搜索上限',
|
||||
description: '最多取 n 条记录作为本次问题引用',
|
||||
value: 5,
|
||||
@@ -289,15 +293,15 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: 'isEmpty',
|
||||
label: '搜索结果为空',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'unEmpty',
|
||||
label: '搜索结果不为空',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
@@ -305,16 +309,16 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
label: '引用内容',
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.kbQuote,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.datasetQuote,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
|
||||
export const AnswerModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.answerNode,
|
||||
id: FlowNodeTypeEnum.answerNode,
|
||||
flowType: FlowNodeTypeEnum.answerNode,
|
||||
logo: '/imgs/module/reply.png',
|
||||
name: '指定回复',
|
||||
intro: '该模块可以直接回复一段指定的内容。常用于引导、提示',
|
||||
@@ -322,9 +326,9 @@ export const AnswerModule: FlowModuleTemplateType = {
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: SpecialInputKeyEnum.answerText,
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.any,
|
||||
key: FlowNodeSpecialInputKeyEnum.answerText,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: FlowNodeValTypeEnum.any,
|
||||
value: '',
|
||||
label: '回复的内容',
|
||||
description:
|
||||
@@ -334,7 +338,8 @@ export const AnswerModule: FlowModuleTemplateType = {
|
||||
outputs: [Output_Template_Finish]
|
||||
};
|
||||
export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.classifyQuestion,
|
||||
id: FlowNodeTypeEnum.classifyQuestion,
|
||||
flowType: FlowNodeTypeEnum.classifyQuestion,
|
||||
logo: '/imgs/module/cq.png',
|
||||
name: '问题分类',
|
||||
intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。',
|
||||
@@ -345,7 +350,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'model',
|
||||
type: FlowInputItemTypeEnum.selectChatModel,
|
||||
type: FlowNodeInputTypeEnum.selectChatModel,
|
||||
label: '分类模型',
|
||||
value: cqModelList?.[0]?.model,
|
||||
customData: () => cqModelList,
|
||||
@@ -354,8 +359,8 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: '',
|
||||
label: '背景知识',
|
||||
description:
|
||||
@@ -365,8 +370,8 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput,
|
||||
{
|
||||
key: SpecialInputKeyEnum.agents,
|
||||
type: FlowInputItemTypeEnum.custom,
|
||||
key: FlowNodeSpecialInputKeyEnum.agents,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '',
|
||||
value: [
|
||||
{
|
||||
@@ -388,25 +393,26 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: 'fasw',
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'fqsw',
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'fesw',
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.contentExtract,
|
||||
id: FlowNodeTypeEnum.contentExtract,
|
||||
flowType: FlowNodeTypeEnum.contentExtract,
|
||||
logo: '/imgs/module/extract.png',
|
||||
name: '文本内容提取',
|
||||
intro: '从文本中提取出指定格式的数据',
|
||||
@@ -416,8 +422,8 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ContextExtractEnum.description,
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: '',
|
||||
label: '提取要求描述',
|
||||
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
|
||||
@@ -427,14 +433,14 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
Input_Template_History,
|
||||
{
|
||||
key: ContextExtractEnum.content,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '需要提取的文本',
|
||||
required: true,
|
||||
valueType: FlowValueTypeEnum.string
|
||||
valueType: FlowNodeValTypeEnum.string
|
||||
},
|
||||
{
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
type: FlowInputItemTypeEnum.custom,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '目标字段',
|
||||
description: "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
|
||||
value: []
|
||||
@@ -444,29 +450,30 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: ContextExtractEnum.success,
|
||||
label: '字段完全提取',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ContextExtractEnum.failed,
|
||||
label: '提取字段缺失',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ContextExtractEnum.fields,
|
||||
label: '完整提取结果',
|
||||
description: '一个 JSON 字符串,例如:{"name:":"YY","Time":"2023/7/2 18:00"}',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const HttpModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.httpRequest,
|
||||
id: FlowNodeTypeEnum.httpRequest,
|
||||
flowType: FlowNodeTypeEnum.httpRequest,
|
||||
logo: '/imgs/module/http.png',
|
||||
name: 'HTTP模块',
|
||||
intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
@@ -477,7 +484,7 @@ export const HttpModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: HttpPropsEnum.url,
|
||||
value: '',
|
||||
type: FlowInputItemTypeEnum.input,
|
||||
type: FlowNodeInputTypeEnum.input,
|
||||
label: '请求地址',
|
||||
description: '请求目标地址',
|
||||
placeholder: 'https://api.fastgpt.run/getInventory',
|
||||
@@ -488,7 +495,8 @@ export const HttpModule: FlowModuleTemplateType = {
|
||||
outputs: [Output_Template_Finish]
|
||||
};
|
||||
export const EmptyModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.empty,
|
||||
id: FlowNodeTypeEnum.empty,
|
||||
flowType: FlowNodeTypeEnum.empty,
|
||||
logo: '/imgs/module/cq.png',
|
||||
name: '该模块已被移除',
|
||||
intro: '',
|
||||
@@ -496,10 +504,11 @@ export const EmptyModule: FlowModuleTemplateType = {
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
export const AppModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.app,
|
||||
export const RunAppModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.runApp,
|
||||
flowType: FlowNodeTypeEnum.runApp,
|
||||
logo: '/imgs/module/app.png',
|
||||
name: '应用调用(测试版)',
|
||||
name: '应用调用',
|
||||
intro: '可以选择一个其他应用进行调用',
|
||||
description: '可以选择一个其他应用进行调用',
|
||||
showStatus: true,
|
||||
@@ -507,7 +516,7 @@ export const AppModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'app',
|
||||
type: FlowInputItemTypeEnum.selectApp,
|
||||
type: FlowNodeInputTypeEnum.selectApp,
|
||||
label: '选择一个应用',
|
||||
description: '选择一个其他应用进行调用',
|
||||
required: true
|
||||
@@ -520,38 +529,87 @@ export const AppModule: FlowModuleTemplateType = {
|
||||
key: TaskResponseKeyEnum.history,
|
||||
label: '新的上下文',
|
||||
description: '将该应用回复内容拼接到历史记录中,作为新的上下文返回',
|
||||
valueType: FlowValueTypeEnum.chatHistory,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: TaskResponseKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
description: '将在应用完全结束后触发',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
export const PluginInputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginInput,
|
||||
flowType: FlowNodeTypeEnum.pluginInput,
|
||||
logo: '/imgs/module/input.png',
|
||||
name: '定义插件输入',
|
||||
intro: '自定义配置外部输入,使用插件时,仅暴露自定义配置的输入',
|
||||
description: '自定义配置外部输入,使用插件时,仅暴露自定义配置的输入',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
export const PluginOutputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginOutput,
|
||||
flowType: FlowNodeTypeEnum.pluginOutput,
|
||||
logo: '/imgs/module/output.png',
|
||||
name: '定义插件输出',
|
||||
intro: '自定义配置外部输出,使用插件时,仅暴露自定义配置的输出',
|
||||
description: '自定义配置外部输出,使用插件时,仅暴露自定义配置的输出',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
export const PluginModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginModule,
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
logo: '/imgs/module/custom.png',
|
||||
name: '自定义模块',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
export const ModuleTemplates = [
|
||||
{
|
||||
label: '输入模块',
|
||||
list: [UserInputModule, HistoryModule]
|
||||
},
|
||||
export const SystemModuleTemplates = [
|
||||
{
|
||||
label: '引导模块',
|
||||
list: [UserGuideModule]
|
||||
},
|
||||
{
|
||||
label: '输入模块',
|
||||
list: [UserInputModule, HistoryModule]
|
||||
},
|
||||
{
|
||||
label: '内容生成',
|
||||
list: [ChatModule, AnswerModule]
|
||||
},
|
||||
{
|
||||
label: '核心调用',
|
||||
list: [KBSearchModule, AppModule]
|
||||
list: [DatasetSearchModule, RunAppModule]
|
||||
},
|
||||
{
|
||||
label: '函数模块',
|
||||
list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
|
||||
}
|
||||
];
|
||||
export const PluginModuleTemplates = [
|
||||
{
|
||||
label: '输入输出',
|
||||
list: [PluginInputModule, PluginOutputModule, HistoryModule]
|
||||
},
|
||||
{
|
||||
label: '内容生成',
|
||||
list: [ChatModule, AnswerModule]
|
||||
},
|
||||
{
|
||||
label: '核心调用',
|
||||
list: [DatasetSearchModule, RunAppModule]
|
||||
},
|
||||
{
|
||||
label: '函数模块',
|
||||
@@ -564,13 +622,16 @@ export const ModuleTemplatesFlat = [
|
||||
UserInputModule,
|
||||
HistoryModule,
|
||||
ChatModule,
|
||||
KBSearchModule,
|
||||
DatasetSearchModule,
|
||||
AnswerModule,
|
||||
ClassifyQuestionModule,
|
||||
ContextExtractModule,
|
||||
HttpModule,
|
||||
EmptyModule,
|
||||
AppModule
|
||||
RunAppModule,
|
||||
PluginInputModule,
|
||||
PluginOutputModule,
|
||||
PluginModule
|
||||
];
|
||||
|
||||
// template
|
||||
@@ -665,7 +726,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -757,14 +818,14 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -886,7 +947,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -1003,7 +1064,7 @@ export const appTemplates: (AppItemType & {
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: 'source',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
@@ -1094,14 +1155,14 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1292,7 +1353,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -1401,14 +1462,14 @@ export const appTemplates: (AppItemType & {
|
||||
type: 'custom',
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1441,7 +1502,7 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'history',
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
@@ -1520,7 +1581,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -1564,7 +1625,7 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1863,14 +1924,14 @@ export const appTemplates: (AppItemType & {
|
||||
type: 'custom',
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1903,7 +1964,7 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'history',
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
@@ -1938,7 +1999,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -2055,7 +2116,7 @@ export const appTemplates: (AppItemType & {
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: 'source',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'nlfwkc',
|
||||
|
||||
@@ -1,86 +1,68 @@
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import { FlowNodeTypeEnum, FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export enum FlowInputItemTypeEnum {
|
||||
systemInput = 'systemInput', // history, userChatInput, variableInput
|
||||
input = 'input', // one line input
|
||||
textarea = 'textarea',
|
||||
numberInput = 'numberInput',
|
||||
select = 'select',
|
||||
slider = 'slider',
|
||||
custom = 'custom',
|
||||
target = 'target', // data input
|
||||
switch = 'switch',
|
||||
chatInput = 'chatInput',
|
||||
selectApp = 'selectApp',
|
||||
// chat special input
|
||||
aiSettings = 'aiSettings',
|
||||
maxToken = 'maxToken',
|
||||
selectChatModel = 'selectChatModel',
|
||||
// dataset special input
|
||||
selectDataset = 'selectDataset',
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
export enum FlowOutputItemTypeEnum {
|
||||
answer = 'answer',
|
||||
source = 'source',
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
export enum FlowModuleTypeEnum {
|
||||
empty = 'empty',
|
||||
variable = 'variable',
|
||||
userGuide = 'userGuide',
|
||||
questionInput = 'questionInput',
|
||||
historyNode = 'historyNode',
|
||||
chatNode = 'chatNode',
|
||||
datasetSearchNode = 'datasetSearchNode',
|
||||
tfSwitchNode = 'tfSwitchNode',
|
||||
answerNode = 'answerNode',
|
||||
classifyQuestion = 'classifyQuestion',
|
||||
contentExtract = 'contentExtract',
|
||||
httpRequest = 'httpRequest',
|
||||
app = 'app'
|
||||
}
|
||||
|
||||
export enum SpecialInputKeyEnum {
|
||||
'answerText' = 'text',
|
||||
'agents' = 'agents' // cq agent key
|
||||
}
|
||||
|
||||
export enum FlowValueTypeEnum {
|
||||
'string' = 'string',
|
||||
'number' = 'number',
|
||||
'boolean' = 'boolean',
|
||||
'chatHistory' = 'chat_history',
|
||||
'kbQuote' = 'kb_quote',
|
||||
'any' = 'any'
|
||||
}
|
||||
|
||||
export const FlowValueTypeStyle: Record<`${FlowValueTypeEnum}`, BoxProps> = {
|
||||
[FlowValueTypeEnum.string]: {
|
||||
export const FlowValueTypeStyle: Record<`${FlowNodeValTypeEnum}`, BoxProps> = {
|
||||
[FlowNodeValTypeEnum.string]: {
|
||||
background: '#36ADEF'
|
||||
},
|
||||
[FlowValueTypeEnum.number]: {
|
||||
[FlowNodeValTypeEnum.number]: {
|
||||
background: '#FB7C3C'
|
||||
},
|
||||
[FlowValueTypeEnum.boolean]: {
|
||||
[FlowNodeValTypeEnum.boolean]: {
|
||||
background: '#E7D118'
|
||||
},
|
||||
[FlowValueTypeEnum.chatHistory]: {
|
||||
[FlowNodeValTypeEnum.chatHistory]: {
|
||||
background: '#00A9A6'
|
||||
},
|
||||
[FlowValueTypeEnum.kbQuote]: {
|
||||
[FlowNodeValTypeEnum.datasetQuote]: {
|
||||
background: '#A558C9'
|
||||
},
|
||||
[FlowValueTypeEnum.any]: {
|
||||
[FlowNodeValTypeEnum.any]: {
|
||||
background: '#9CA2A8'
|
||||
}
|
||||
};
|
||||
export const FlowValueTypeTip = {
|
||||
[FlowNodeValTypeEnum.string]: {
|
||||
label: 'app.module.valueType.string',
|
||||
example: ''
|
||||
},
|
||||
[FlowNodeValTypeEnum.number]: {
|
||||
label: 'app.module.valueType.number',
|
||||
example: ''
|
||||
},
|
||||
[FlowNodeValTypeEnum.boolean]: {
|
||||
label: 'app.module.valueType.boolean',
|
||||
example: ''
|
||||
},
|
||||
[FlowNodeValTypeEnum.chatHistory]: {
|
||||
label: 'app.module.valueType.chatHistory',
|
||||
example: `{
|
||||
obj: System | Human | AI;
|
||||
value: string;
|
||||
}`
|
||||
},
|
||||
[FlowNodeValTypeEnum.datasetQuote]: {
|
||||
label: 'app.module.valueType.datasetQuote',
|
||||
example: `{
|
||||
id: string;
|
||||
datasetId: string;
|
||||
collectionId: string;
|
||||
sourceName: string;
|
||||
sourceId?: string;
|
||||
q: string;
|
||||
a: string
|
||||
}`
|
||||
},
|
||||
[FlowNodeValTypeEnum.any]: {
|
||||
label: 'app.module.valueType.any',
|
||||
example: ''
|
||||
}
|
||||
};
|
||||
|
||||
export const initModuleType: Record<string, boolean> = {
|
||||
[FlowModuleTypeEnum.historyNode]: true,
|
||||
[FlowModuleTypeEnum.questionInput]: true
|
||||
[FlowNodeTypeEnum.historyNode]: true,
|
||||
[FlowNodeTypeEnum.questionInput]: true,
|
||||
[FlowNodeTypeEnum.pluginInput]: true
|
||||
};
|
||||
|
||||
export const edgeOptions = {
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import type { FlowInputItemType } from '@/types/core/app/flow';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { SystemInputEnum } from '../app';
|
||||
import { FlowInputItemTypeEnum, FlowValueTypeEnum } from './index';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const Input_Template_TFSwitch: FlowInputItemType = {
|
||||
export const Input_Template_TFSwitch: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.switch,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '触发器',
|
||||
valueType: FlowValueTypeEnum.any
|
||||
valueType: FlowNodeValTypeEnum.any
|
||||
};
|
||||
|
||||
export const Input_Template_History: FlowInputItemType = {
|
||||
export const Input_Template_History: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.history,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '聊天记录',
|
||||
valueType: FlowValueTypeEnum.chatHistory
|
||||
valueType: FlowNodeValTypeEnum.chatHistory
|
||||
};
|
||||
|
||||
export const Input_Template_UserChatInput: FlowInputItemType = {
|
||||
export const Input_Template_UserChatInput: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.userChatInput,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '用户问题',
|
||||
required: true,
|
||||
valueType: FlowValueTypeEnum.string
|
||||
valueType: FlowNodeValTypeEnum.string
|
||||
};
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import type { FlowOutputItemType } from '@/types/core/app/flow';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { SystemOutputEnum } from '../app';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from './index';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const Output_Template_Finish: FlowOutputItemType = {
|
||||
export const Output_Template_Finish: FlowNodeOutputItemType = {
|
||||
key: SystemOutputEnum.finish,
|
||||
label: '模块调用结束',
|
||||
description: '模块调用结束时触发',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
|
||||
@@ -25,18 +25,3 @@ export enum PromotionEnum {
|
||||
register = 'register',
|
||||
pay = 'pay'
|
||||
}
|
||||
|
||||
export enum InformTypeEnum {
|
||||
system = 'system'
|
||||
}
|
||||
|
||||
export const InformTypeMap = {
|
||||
[InformTypeEnum.system]: {
|
||||
label: '系统通知'
|
||||
}
|
||||
};
|
||||
|
||||
export enum MyModelsTypeEnum {
|
||||
my = 'my',
|
||||
collection = 'collection'
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { countPromptTokens } from '@/global/common/tiktoken';
|
||||
export function replaceVariable(text: string, obj: Record<string, string | number>) {
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
if (typeof val !== 'string') continue;
|
||||
if (!['string', 'number'].includes(typeof val)) continue;
|
||||
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), val);
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), String(val));
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { AppSchema } from '@/types/mongoSchema';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
export type InitChatResponse = {
|
||||
chatId: string;
|
||||
appId: string;
|
||||
app: {
|
||||
userGuideModule?: AppModuleItemType;
|
||||
userGuideModule?: ModuleItemType;
|
||||
chatModels?: string[];
|
||||
name: string;
|
||||
avatar: string;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
export const getGuideModule = (modules: AppModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowModuleTypeEnum.userGuide);
|
||||
export const getGuideModule = (modules: ModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowNodeTypeEnum.userGuide);
|
||||
|
||||
export const splitGuideModule = (guideModules?: AppModuleItemType) => {
|
||||
export const splitGuideModule = (guideModules?: ModuleItemType) => {
|
||||
const welcomeText: string =
|
||||
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
|
||||
|
||||
|
||||
@@ -3,41 +3,81 @@ import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
|
||||
export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模板',
|
||||
desc: '包含 q 和 a 两个变量的标准模板',
|
||||
desc: '标准提示词,用于结构不固定的知识库。',
|
||||
value: `{{q}}\n{{a}}`
|
||||
},
|
||||
{
|
||||
title: '问答模板',
|
||||
desc: '适合 QA 问答结构的知识库,或大部分核心介绍位于 a 的知识库。',
|
||||
value: `{instruction:"{{q}}",output:"{{a}}"}`
|
||||
},
|
||||
{
|
||||
title: '全部变量',
|
||||
desc: '包含 q 和 a 两个变量的标准模板',
|
||||
value: `{instruction:"{{q}}",output:"{{a}}",source:"{{source}}",sourceId:"{{sourceId}}",index:"{{index}}"}`
|
||||
title: '标准严格模板',
|
||||
desc: '在标准模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `{{q}}\n{{a}}`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
desc: '在问答模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `{question:"{{q}}",answer:"{{a}}"}`
|
||||
}
|
||||
];
|
||||
|
||||
export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模式',
|
||||
title: '标准模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 使用对话的风格回答我的问题,答案要和背景知识表述一致。
|
||||
1. 背景知识是最新的实时的信息,使用背景知识回答问题。
|
||||
2. 优先使用背景知识的内容回答我的问题,答案应与背景知识严格一致。
|
||||
3. 背景知识无法回答我的问题时,可以忽略背景知识,根据你的知识来自由回答。
|
||||
4. 使用对话的风格,自然的回答问题。
|
||||
我的问题是:"{{question}}"`
|
||||
},
|
||||
{
|
||||
title: '严格模式',
|
||||
title: '问答模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 背景知识无法满足问题时,你需要回答:我不清楚关于xxx的内容。
|
||||
1. 背景知识是最新的实时的信息,使用背景知识回答问题,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 优先使用背景知识的内容回答我的问题,答案应与背景知识严格一致。
|
||||
3. 背景知识无法回答我的问题时,可以忽略背景知识,根据你的知识来自由回答。
|
||||
4. 使用对话的风格,自然的回答问题。
|
||||
我的问题是:"{{question}}"`
|
||||
},
|
||||
{
|
||||
title: '标准严格模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的实时的信息,是你的唯一信息来源,使用背景知识回答问题。
|
||||
2. 优先使用背景知识回答我的问题,答案与背景知识完全一致,无需做其他回答。
|
||||
3. 背景知识与问题无关,或背景知识无法回答本次问题时,则拒绝回答本次问题:“我不太清除xxx”。
|
||||
4. 使用对话的风格,自然的回答问题。
|
||||
我的问题是:"{{question}}"`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的实时的信息,是你的唯一信息来源,使用背景知识回答问题。
|
||||
2. 在背景知识的 JSON 中,question 是相关问题,answer 是已知答案。
|
||||
3. 选择 answer 中的内容作为答案,要求答案与 answer 完全一致,无需做其他回答。
|
||||
4. answer 中的答案无法满足问题,直接回复:“我不太清除xxx”。
|
||||
我的问题是:"{{question}}"`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UserType } from '@/types/user';
|
||||
import type { PromotionRecordSchema } from '@/types/mongoSchema';
|
||||
import type { PromotionRecordSchema } from '@fastgpt/global/support/activity/type.d';
|
||||
export interface ResLogin {
|
||||
user: UserType;
|
||||
token: string;
|
||||
|
||||
@@ -33,6 +33,7 @@ const BillTable = () => {
|
||||
to: new Date()
|
||||
});
|
||||
const { isPc } = useSystemStore();
|
||||
const [billDetail, setBillDetail] = useState<UserBillType>();
|
||||
|
||||
const {
|
||||
data: bills,
|
||||
@@ -48,8 +49,6 @@ const BillTable = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const [billDetail, setBillDetail] = useState<UserBillType>();
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
|
||||
@@ -106,4 +105,4 @@ const BillTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default BillTable;
|
||||
export default React.memo(BillTable);
|
||||
|
||||
@@ -271,7 +271,7 @@ const UserInfo = () => {
|
||||
>
|
||||
<Avatar src={'/imgs/openai.png'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI 账号
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, Flex, useTheme } from '@chakra-ui/react';
|
||||
import { getInforms, readInform } from '@/web/support/user/api';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import type { informSchema } from '@/types/mongoSchema';
|
||||
import type { UserInformSchema } from '@fastgpt/global/support/user/type';
|
||||
import { formatTimeToChatTime } from '@/utils/tools';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@/components/Icon';
|
||||
@@ -20,7 +20,7 @@ const BillTable = () => {
|
||||
Pagination,
|
||||
getData,
|
||||
pageNum
|
||||
} = usePagination<informSchema>({
|
||||
} = usePagination<UserInformSchema>({
|
||||
api: getInforms,
|
||||
pageSize: isPc ? 20 : 10
|
||||
});
|
||||
|
||||
@@ -32,8 +32,8 @@ const OpenAIAccountModal = ({
|
||||
<MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
可以填写 OpenAI 的 key,也可以是 OneAPI 的可以。如果你填写了该内容,在线上平台使用 OpenAI
|
||||
Chat 模型不会计费(不包含知识库训练、索引生成)。请注意你的 Key 是否有访问对应模型的权限。
|
||||
可以填写 OpenAI/OneAPI 的相关秘钥。如果你填写了该内容,在线上平台使用 OpenAI Chat
|
||||
模型不会计费(不包含知识库训练、索引生成)。请注意你的 Key 是否有访问对应模型的权限。
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 65px'}>API Key:</Box>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { getPayOrders, checkPayResult } from '@/web/common/bill/api';
|
||||
import { PaySchema } from '@/types/mongoSchema';
|
||||
import type { PaySchema } from '@fastgpt/global/support/wallet/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { formatPrice } from '@fastgpt/global/common/bill/tools';
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { connectToDatabase, App } from '@/service/mongo';
|
||||
import { FlowInputItemTypeEnum, FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
|
||||
const limit = 300;
|
||||
@@ -46,19 +46,19 @@ async function initVariable(): Promise<any> {
|
||||
const modules = jsonAPP.modules;
|
||||
|
||||
// 找到 variable
|
||||
const variable = modules.find((item) => item.flowType === FlowModuleTypeEnum.variable);
|
||||
const variable = modules.find((item) => item.flowType === FlowNodeTypeEnum.variable);
|
||||
if (!variable) return await app.save();
|
||||
|
||||
// 找到 guide 模块
|
||||
const userGuideModule = modules.find(
|
||||
(item) => item.flowType === FlowModuleTypeEnum.userGuide
|
||||
(item) => item.flowType === FlowNodeTypeEnum.userGuide
|
||||
);
|
||||
if (userGuideModule) {
|
||||
userGuideModule.inputs = [
|
||||
userGuideModule.inputs[0],
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '对话框变量',
|
||||
value: variable.inputs[0]?.value
|
||||
}
|
||||
@@ -66,7 +66,7 @@ async function initVariable(): Promise<any> {
|
||||
} else {
|
||||
modules.unshift({
|
||||
moduleId: 'userGuide',
|
||||
flowType: FlowModuleTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
name: '用户引导',
|
||||
position: {
|
||||
x: 447.98520778293346,
|
||||
@@ -75,12 +75,12 @@ async function initVariable(): Promise<any> {
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: FlowInputItemTypeEnum.input,
|
||||
type: FlowNodeInputTypeEnum.input,
|
||||
label: '开场白'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '对话框变量',
|
||||
value: variable.inputs[0]?.value
|
||||
}
|
||||
@@ -90,7 +90,7 @@ async function initVariable(): Promise<any> {
|
||||
}
|
||||
|
||||
jsonAPP.modules = jsonAPP.modules.filter(
|
||||
(item) => item.flowType !== FlowModuleTypeEnum.variable
|
||||
(item) => item.flowType !== FlowNodeTypeEnum.variable
|
||||
);
|
||||
|
||||
app.modules = JSON.parse(JSON.stringify(jsonAPP.modules));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { App, connectToDatabase } from '@/service/mongo';
|
||||
import { PgClient } from '@/service/pg';
|
||||
import { connectionMongo } from '@fastgpt/service/common/mongo';
|
||||
import { PgDatasetTableName } from '@/constants/plugin';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { delay } from '@/utils/tools';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
@@ -98,7 +98,7 @@ async function initMongo(limit: number) {
|
||||
let success = 0;
|
||||
|
||||
async function initApp(limit = 100): Promise<any> {
|
||||
// 遍历所有 app,更新 app modules 里的 FlowModuleTypeEnum.kbSearchNode
|
||||
// 遍历所有 app,更新 app modules 里的 FlowNodeTypeEnum.kbSearchNode
|
||||
const apps = await App.find({ inited: false }).limit(limit);
|
||||
|
||||
if (apps.length === 0) return;
|
||||
@@ -113,7 +113,7 @@ async function initMongo(limit: number) {
|
||||
modules.forEach((module) => {
|
||||
// @ts-ignore
|
||||
if (module.flowType === 'kbSearchNode') {
|
||||
module.flowType = FlowModuleTypeEnum.datasetSearchNode;
|
||||
module.flowType = FlowNodeTypeEnum.datasetSearchNode;
|
||||
module.inputs.forEach((input) => {
|
||||
if (input.key === 'kbList') {
|
||||
input.key = 'datasets';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Chat, App, connectToDatabase, Collection } from '@/service/mongo';
|
||||
import { Chat, App, connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { authApp } from '@/service/utils/auth';
|
||||
@@ -29,11 +29,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
appId
|
||||
});
|
||||
|
||||
// 删除收藏列表
|
||||
await Collection.deleteMany({
|
||||
modelId: appId
|
||||
});
|
||||
|
||||
// 删除分享链接
|
||||
await MongoOutLink.deleteMany({
|
||||
appId
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Collection, App } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
|
||||
/* 模型收藏切换 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
// 凭证校验
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const collectionRecord = await Collection.findOne({
|
||||
userId,
|
||||
modelId: appId
|
||||
});
|
||||
|
||||
if (collectionRecord) {
|
||||
await Collection.findByIdAndRemove(collectionRecord._id);
|
||||
} else {
|
||||
await Collection.create({
|
||||
userId,
|
||||
modelId: appId
|
||||
});
|
||||
}
|
||||
|
||||
await App.findByIdAndUpdate(appId, {
|
||||
'share.collection': await Collection.countDocuments({ modelId: appId })
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, App } from '@/service/mongo';
|
||||
import type { PagingData } from '@/types';
|
||||
import type { ShareAppItem } from '@/types/app';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
|
||||
/* 获取模型列表 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const {
|
||||
searchText = '',
|
||||
pageNum = 1,
|
||||
pageSize = 20
|
||||
} = req.body as { searchText: string; pageNum: number; pageSize: number };
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const regex = new RegExp(searchText, 'i');
|
||||
|
||||
const where = {
|
||||
$and: [
|
||||
{ 'share.isShare': true },
|
||||
{
|
||||
$or: [{ name: { $regex: regex } }, { intro: { $regex: regex } }]
|
||||
}
|
||||
]
|
||||
};
|
||||
const pipeline = [
|
||||
{
|
||||
$match: where
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: 'collections',
|
||||
let: { modelId: '$_id' },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{ $eq: ['$modelId', '$$modelId'] },
|
||||
{
|
||||
$eq: ['$userId', userId ? new Types.ObjectId(userId) : new Types.ObjectId()]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
as: 'collections'
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
avatar: { $ifNull: ['$avatar', '/icon/logo.svg'] },
|
||||
name: 1,
|
||||
userId: 1,
|
||||
intro: 1,
|
||||
share: 1,
|
||||
isCollection: {
|
||||
$cond: {
|
||||
if: { $gt: [{ $size: '$collections' }, 0] },
|
||||
then: true,
|
||||
else: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { 'share.topNum': -1, 'share.collection': -1 }
|
||||
},
|
||||
{
|
||||
$skip: (pageNum - 1) * pageSize
|
||||
},
|
||||
{
|
||||
$limit: pageSize
|
||||
}
|
||||
];
|
||||
|
||||
// 获取被分享的模型
|
||||
const [models, total] = await Promise.all([
|
||||
// @ts-ignore
|
||||
App.aggregate(pipeline),
|
||||
App.countDocuments(where)
|
||||
]);
|
||||
|
||||
jsonRes<PagingData<ShareAppItem>>(res, {
|
||||
data: {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data: models,
|
||||
total
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { sseErrRes } from '@/service/response';
|
||||
import { sseResponseEventEnum } from '@/constants/chat';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { dispatchModules } from '@/pages/api/v1/chat/completions';
|
||||
import { pushChatBill } from '@/service/common/bill/push';
|
||||
import { BillSourceEnum } from '@/constants/user';
|
||||
@@ -13,7 +13,7 @@ import { ChatItemType } from '@/types/chat';
|
||||
export type Props = {
|
||||
history: ChatItemType[];
|
||||
prompt: string;
|
||||
modules: AppModuleItemType[];
|
||||
modules: ModuleItemType[];
|
||||
variables: Record<string, any>;
|
||||
appId: string;
|
||||
appName: string;
|
||||
|
||||
24
projects/app/src/pages/api/core/plugin/create.ts
Normal file
24
projects/app/src/pages/api/core/plugin/create.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { createOnePlugin } from '@fastgpt/service/core/plugin/controller';
|
||||
import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
import { defaultModules } from '@fastgpt/global/core/plugin/constants';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
const body = req.body as CreateOnePluginParams;
|
||||
|
||||
jsonRes(res, {
|
||||
data: await createOnePlugin({ userId, modules: defaultModules, ...body })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/app/src/pages/api/core/plugin/delete.ts
Normal file
22
projects/app/src/pages/api/core/plugin/delete.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { deleteOnePlugin } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await deleteOnePlugin({ id, userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/app/src/pages/api/core/plugin/detail.ts
Normal file
22
projects/app/src/pages/api/core/plugin/detail.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getOnePluginDetail } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getOnePluginDetail({ id, userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
21
projects/app/src/pages/api/core/plugin/list.ts
Normal file
21
projects/app/src/pages/api/core/plugin/list.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getUserPlugins } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getUserPlugins({ userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/app/src/pages/api/core/plugin/moduleDetail.ts
Normal file
22
projects/app/src/pages/api/core/plugin/moduleDetail.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getPluginModuleDetail } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getPluginModuleDetail({ id, userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
21
projects/app/src/pages/api/core/plugin/templateList.ts
Normal file
21
projects/app/src/pages/api/core/plugin/templateList.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getUserPlugins2Templates } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getUserPlugins2Templates({ userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
23
projects/app/src/pages/api/core/plugin/update.ts
Normal file
23
projects/app/src/pages/api/core/plugin/update.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { updateOnePlugin } from '@fastgpt/service/core/plugin/controller';
|
||||
import type { UpdatePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
const body = req.body as UpdatePluginParams;
|
||||
|
||||
jsonRes(res, {
|
||||
data: await updateOnePlugin({ userId, ...body })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ type FileType = {
|
||||
/**
|
||||
* Creates the multer uploader
|
||||
*/
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
const maxSize = 500 * 1024 * 1024;
|
||||
class UploadModel {
|
||||
uploader = multer({
|
||||
limits: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Image } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
|
||||
|
||||
type Props = { base64Img: string };
|
||||
|
||||
@@ -11,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
const { base64Img } = req.body as Props;
|
||||
|
||||
const data = await uploadImg({
|
||||
const data = await uploadMongoImg({
|
||||
userId,
|
||||
base64Img
|
||||
});
|
||||
@@ -24,14 +25,3 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadImg({ base64Img, userId }: Props & { userId: string }) {
|
||||
const base64Data = base64Img.split(',')[1];
|
||||
|
||||
const { _id } = await Image.create({
|
||||
userId,
|
||||
binary: Buffer.from(base64Data, 'base64')
|
||||
});
|
||||
|
||||
return `/api/system/img/${_id}`;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Image } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { readMongoImg } from '@fastgpt/service/common/file/image/controller';
|
||||
|
||||
// get the models available to the system
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { id } = req.query;
|
||||
const { id } = req.query as { id: string };
|
||||
|
||||
const data = await Image.findById(id);
|
||||
|
||||
if (!data) {
|
||||
throw new Error('no image');
|
||||
}
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
|
||||
res.send(data.binary);
|
||||
res.send(await readMongoImg({ id }));
|
||||
} catch (error) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { Pay, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoPay } from '@fastgpt/service/support/wallet/pay/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const records = await Pay.find({
|
||||
const records = await MongoPay.find({
|
||||
userId,
|
||||
status: { $ne: 'CLOSED' }
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -14,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const data = await Inform.countDocuments({
|
||||
const data = await MongoUserInform.countDocuments({
|
||||
userId,
|
||||
read: false
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -15,11 +16,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
};
|
||||
|
||||
const [informs, total] = await Promise.all([
|
||||
Inform.find({ userId })
|
||||
MongoUserInform.find({ userId })
|
||||
.sort({ time: -1 }) // 按照创建时间倒序排列
|
||||
.skip((pageNum - 1) * pageSize)
|
||||
.limit(pageSize),
|
||||
Inform.countDocuments({ userId })
|
||||
MongoUserInform.countDocuments({ userId })
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -11,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const { id } = req.query as { id: string };
|
||||
|
||||
await Inform.findOneAndUpdate(
|
||||
await MongoUserInform.findOneAndUpdate(
|
||||
{
|
||||
_id: id,
|
||||
userId
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { InformTypeEnum } from '@/constants/user';
|
||||
import { startSendInform } from '@/service/events/sendInform';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
import { InformTypeEnum } from '@fastgpt/global/support/user/constant';
|
||||
import {
|
||||
sendInform2AllUser,
|
||||
sendInform2OneUser
|
||||
} from '@fastgpt/service/support/user/inform/controller';
|
||||
|
||||
export type Props = {
|
||||
type: `${InformTypeEnum}`;
|
||||
@@ -38,39 +42,13 @@ export async function sendInform({ type, title, content, userId }: Props) {
|
||||
|
||||
try {
|
||||
if (userId) {
|
||||
global.sendInformQueue.push(async () => {
|
||||
// skip it if have same inform within 5 minutes
|
||||
const inform = await Inform.findOne({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
userId,
|
||||
time: { $gte: new Date(Date.now() - 5 * 60 * 1000) }
|
||||
});
|
||||
|
||||
if (inform) return;
|
||||
|
||||
await Inform.create({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
userId
|
||||
});
|
||||
});
|
||||
global.sendInformQueue.push(async () => sendInform2OneUser({ type, title, content, userId }));
|
||||
startSendInform();
|
||||
return;
|
||||
}
|
||||
|
||||
// send to all user
|
||||
const users = await MongoUser.find({}, '_id');
|
||||
await Inform.insertMany(
|
||||
users.map(({ _id }) => ({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
userId: _id
|
||||
}))
|
||||
);
|
||||
sendInform2AllUser({ type, title, content });
|
||||
} catch (error) {
|
||||
console.log('send inform error', error);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, promotionRecord } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoPromotionRecord } from '@fastgpt/service/support/activity/promotion/schema';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import mongoose from '@fastgpt/service/common/mongo';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
@@ -16,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
// 计算累计合
|
||||
const countHistory: { totalAmount: number }[] = await promotionRecord.aggregate([
|
||||
const countHistory: { totalAmount: number }[] = await MongoPromotionRecord.aggregate([
|
||||
{
|
||||
$match: {
|
||||
userId: new mongoose.Types.ObjectId(userId),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, promotionRecord } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { MongoPromotionRecord } from '@fastgpt/service/support/activity/promotion/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -13,13 +14,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const data = await promotionRecord
|
||||
.find(
|
||||
{
|
||||
userId
|
||||
},
|
||||
'_id createTime type amount'
|
||||
)
|
||||
const data = await MongoPromotionRecord.find(
|
||||
{
|
||||
userId
|
||||
},
|
||||
'_id createTime type amount'
|
||||
)
|
||||
.sort({ _id: -1 })
|
||||
.skip((pageNum - 1) * pageSize)
|
||||
.limit(pageSize);
|
||||
@@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
pageNum,
|
||||
pageSize,
|
||||
data,
|
||||
total: await promotionRecord.countDocuments({
|
||||
total: await MongoPromotionRecord.countDocuments({
|
||||
userId
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ import {
|
||||
dispatchHistory,
|
||||
dispatchChatInput,
|
||||
dispatchChatCompletion,
|
||||
dispatchKBSearch,
|
||||
dispatchDatasetSearch,
|
||||
dispatchAnswer,
|
||||
dispatchClassifyQuestion,
|
||||
dispatchContentExtract,
|
||||
dispatchHttpRequest,
|
||||
dispatchAppRequest
|
||||
dispatchAppRequest,
|
||||
dispatchRunPlugin,
|
||||
dispatchPluginInput,
|
||||
dispatchPluginOutput
|
||||
} from '@/service/moduleDispatch';
|
||||
import type { CreateChatCompletionRequest } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { MessageItemType } from '@/types/core/chat/type';
|
||||
@@ -23,8 +26,10 @@ import { getChatHistory } from './getHistory';
|
||||
import { saveChat } from '@/service/utils/chat/saveChat';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { FlowModuleTypeEnum, initModuleType } from '@/constants/flow';
|
||||
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
|
||||
import { initModuleType } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { RunningModuleItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { pushChatBill } from '@/service/common/bill/push';
|
||||
import { BillSourceEnum } from '@/constants/user';
|
||||
import { ChatHistoryItemResType } from '@/types/chat';
|
||||
@@ -305,7 +310,7 @@ export async function dispatchModules({
|
||||
detail = false
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
modules: AppModuleItemType[];
|
||||
modules: ModuleItemType[];
|
||||
user: UserModelSchema;
|
||||
params?: Record<string, any>;
|
||||
variables?: Record<string, any>;
|
||||
@@ -425,7 +430,6 @@ export async function dispatchModules({
|
||||
stream,
|
||||
detail,
|
||||
variables,
|
||||
moduleName: module.name,
|
||||
outputs: module.outputs,
|
||||
user,
|
||||
inputs: params
|
||||
@@ -433,15 +437,18 @@ export async function dispatchModules({
|
||||
|
||||
const dispatchRes: Record<string, any> = await (async () => {
|
||||
const callbackMap: Record<string, Function> = {
|
||||
[FlowModuleTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowModuleTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowModuleTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowModuleTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowModuleTypeEnum.datasetSearchNode]: dispatchKBSearch,
|
||||
[FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowModuleTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowModuleTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowModuleTypeEnum.app]: dispatchAppRequest
|
||||
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
|
||||
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
|
||||
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
|
||||
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
|
||||
};
|
||||
if (callbackMap[module.flowType]) {
|
||||
return callbackMap[module.flowType](props);
|
||||
@@ -449,9 +456,21 @@ export async function dispatchModules({
|
||||
return {};
|
||||
})();
|
||||
|
||||
const formatResponseData = (() => {
|
||||
if (!dispatchRes[TaskResponseKeyEnum.responseData]) return undefined;
|
||||
if (Array.isArray(dispatchRes[TaskResponseKeyEnum.responseData]))
|
||||
return dispatchRes[TaskResponseKeyEnum.responseData];
|
||||
return {
|
||||
...dispatchRes[TaskResponseKeyEnum.responseData],
|
||||
moduleName: module.name,
|
||||
moduleType: module.flowType
|
||||
};
|
||||
})();
|
||||
|
||||
return moduleOutput(module, {
|
||||
[SystemOutputEnum.finish]: true,
|
||||
...dispatchRes
|
||||
...dispatchRes,
|
||||
[TaskResponseKeyEnum.responseData]: formatResponseData
|
||||
});
|
||||
}
|
||||
|
||||
@@ -468,7 +487,7 @@ export async function dispatchModules({
|
||||
|
||||
/* init store modules to running modules */
|
||||
function loadModules(
|
||||
modules: AppModuleItemType[],
|
||||
modules: ModuleItemType[],
|
||||
variables: Record<string, any>
|
||||
): RunningModuleItemType[] {
|
||||
return modules.map((module) => {
|
||||
@@ -495,12 +514,19 @@ function loadModules(
|
||||
value: replacedVal
|
||||
};
|
||||
}),
|
||||
outputs: module.outputs.map((item) => ({
|
||||
key: item.key,
|
||||
answer: item.key === TaskResponseKeyEnum.answerText,
|
||||
value: undefined,
|
||||
targets: item.targets
|
||||
}))
|
||||
outputs: module.outputs
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
answer: item.key === TaskResponseKeyEnum.answerText,
|
||||
value: undefined,
|
||||
targets: item.targets
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
// finish output always at last
|
||||
if (a.key === SystemOutputEnum.finish) return 1;
|
||||
if (b.key === SystemOutputEnum.finish) return -1;
|
||||
return 0;
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
||||
import { FlowOutputTargetItemType } from '@/types/core/app/flow';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import type { AppSchema } from '@/types/mongoSchema';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { AppTypeEnum, SystemOutputEnum } from '@/constants/app';
|
||||
import { AppTypeEnum } from '@/constants/app';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import ChatTest, { type ChatTestComponentRef } from './ChatTest';
|
||||
import { useFlowStore } from './Provider';
|
||||
import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest';
|
||||
import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
|
||||
const ImportSettings = dynamic(() => import('./ImportSettings'));
|
||||
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
|
||||
|
||||
type Props = { app: AppSchema; onCloseSettings: () => void };
|
||||
type Props = { app: AppSchema; onClose: () => void };
|
||||
|
||||
const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
app,
|
||||
ChatTestRef,
|
||||
testModules,
|
||||
setTestModules,
|
||||
onCloseSettings
|
||||
onClose
|
||||
}: Props & {
|
||||
ChatTestRef: React.RefObject<ChatTestComponentRef>;
|
||||
testModules?: AppModuleItemType[];
|
||||
setTestModules: React.Dispatch<AppModuleItemType[] | undefined>;
|
||||
testModules?: ModuleItemType[];
|
||||
setTestModules: React.Dispatch<ModuleItemType[] | undefined>;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -38,54 +38,11 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
const { updateAppDetail } = useUserStore();
|
||||
|
||||
const { nodes, edges, onFixView } = useFlowStore();
|
||||
|
||||
const flow2AppModules = useCallback(() => {
|
||||
const modules: AppModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((item) => ({
|
||||
...item,
|
||||
connected: item.connected ?? item.type !== FlowInputItemTypeEnum.target
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected =
|
||||
input.connected ||
|
||||
!!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId &&
|
||||
edge.sourceHandle === output.key &&
|
||||
edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
return modules;
|
||||
}, [edges, nodes]);
|
||||
const { nodes, edges, onFixView } = useFlowProviderStore();
|
||||
|
||||
const { mutate: onclickSave, isLoading } = useRequest({
|
||||
mutationFn: () => {
|
||||
const modules = flow2AppModules();
|
||||
const modules = flowNode2Modules({ nodes, edges });
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
@@ -127,7 +84,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
variant={'base'}
|
||||
aria-label={''}
|
||||
onClick={() => {
|
||||
onCloseSettings();
|
||||
onClose();
|
||||
onFixView();
|
||||
}}
|
||||
/>
|
||||
@@ -155,7 +112,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
aria-label={'save'}
|
||||
onClick={() =>
|
||||
copyData(
|
||||
JSON.stringify(flow2AppModules(), null, 2),
|
||||
JSON.stringify(flowNode2Modules({ nodes, edges }), null, 2),
|
||||
t('app.Export Config Successful')
|
||||
)
|
||||
}
|
||||
@@ -181,7 +138,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
aria-label={'save'}
|
||||
variant={'base'}
|
||||
onClick={() => {
|
||||
setTestModules(flow2AppModules());
|
||||
setTestModules(flowNode2Modules({ nodes, edges }));
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
@@ -206,7 +163,7 @@ const Header = (props: Props) => {
|
||||
const { app } = props;
|
||||
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
||||
|
||||
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
|
||||
const [testModules, setTestModules] = useState<ModuleItemType[]>();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeHistory = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeHistory);
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Handle, Position, NodeProps } from 'reactflow';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import Label from '../modules/Label';
|
||||
|
||||
const NodeTFSwitch = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
return (
|
||||
<NodeCard minW={'220px'} {...data}>
|
||||
<Divider text="输入输出" />
|
||||
<Container h={'100px'} py={0} px={0} display={'flex'} alignItems={'center'}>
|
||||
<Box flex={1} pl={'12px'}>
|
||||
<Label
|
||||
required
|
||||
description="接收到 false、0、null、undefined或空字符串时,执行 False,反之执行 True"
|
||||
>
|
||||
输入
|
||||
</Label>
|
||||
<Handle
|
||||
style={{
|
||||
top: '50%',
|
||||
left: '0',
|
||||
transform: 'translate(-50%,-50%)',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
background: '#9CA2A8'
|
||||
}}
|
||||
id={SystemInputEnum.switch}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
onConnect={(params) => console.log('input onConnect', params)}
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={1} pr={'12px'}>
|
||||
<Flex alignItems={'center'} justifyContent={'flex-end'} mb={'26px'} position={'relative'}>
|
||||
<Label>True</Label>
|
||||
<Handle
|
||||
style={{
|
||||
top: '0',
|
||||
right: '-12px',
|
||||
transform: 'translate(50%,5px)',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
background: '#9CA2A8'
|
||||
}}
|
||||
id={'true'}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
onConnect={(params) => console.log('handle onConnect', params)}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} justifyContent={'flex-end'} position={'relative'}>
|
||||
<Label>False</Label>
|
||||
<Handle
|
||||
style={{
|
||||
bottom: '0',
|
||||
right: '-12px',
|
||||
transform: 'translate(50%,-5px)',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
background: '#9CA2A8'
|
||||
}}
|
||||
id={'false'}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
onConnect={(params) => console.log('handle onConnect', params)}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeTFSwitch);
|
||||
@@ -1,144 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate';
|
||||
import { FlowModuleTemplateType } from '@/types/core/app/flow';
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { useFlowStore } from './Provider';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowNode } from '@/utils/adapt';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const ModuleTemplateList = ({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) => {
|
||||
const { nodes, setNodes, reactFlowWrapper } = useFlowStore();
|
||||
const { isPc } = useSystemStore();
|
||||
const { x, y, zoom } = useViewport();
|
||||
|
||||
const filterTemplates = useMemo(() => {
|
||||
const guideModulesIndex = ModuleTemplates.findIndex((item) => item.label === '引导模块');
|
||||
const guideModule: {
|
||||
label: string;
|
||||
list: FlowModuleTemplateType[];
|
||||
} = JSON.parse(JSON.stringify(ModuleTemplates[guideModulesIndex]));
|
||||
|
||||
if (nodes?.find((item) => item.type === FlowModuleTypeEnum.userGuide)) {
|
||||
const index = guideModule.list.findIndex(
|
||||
(item) => item.flowType === FlowModuleTypeEnum.userGuide
|
||||
);
|
||||
guideModule.list.splice(index, 1);
|
||||
}
|
||||
if (nodes?.find((item) => item.type === FlowModuleTypeEnum.variable)) {
|
||||
const index = guideModule.list.findIndex(
|
||||
(item) => item.flowType === FlowModuleTypeEnum.variable
|
||||
);
|
||||
guideModule.list.splice(index, 1);
|
||||
}
|
||||
|
||||
return [
|
||||
...ModuleTemplates.slice(0, guideModulesIndex),
|
||||
guideModule,
|
||||
...ModuleTemplates.slice(guideModulesIndex + 1)
|
||||
];
|
||||
}, [nodes]);
|
||||
|
||||
const onAddNode = useCallback(
|
||||
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
|
||||
if (!reactFlowWrapper?.current) return;
|
||||
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
|
||||
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
|
||||
setNodes((state) =>
|
||||
state.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...template,
|
||||
moduleId: nanoid(),
|
||||
position: { x: mouseX, y: mouseY }
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
[reactFlowWrapper, setNodes, x, y, zoom]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
zIndex={2}
|
||||
display={isOpen ? 'block' : 'none'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
w={'360px'}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Flex
|
||||
zIndex={3}
|
||||
flexDirection={'column'}
|
||||
position={'absolute'}
|
||||
top={'65px'}
|
||||
left={0}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 100px)' : '0'}
|
||||
w={isOpen ? ['100%', '360px'] : '0'}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'20px'}
|
||||
overflow={'hidden'}
|
||||
transition={'.2s ease'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<Box w={['100%', '330px']} py={4} px={5} fontSize={'xl'} fontWeight={'bold'}>
|
||||
系统模块
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} overflow={'overlay'}>
|
||||
<Box w={['100%', '330px']} mx={'auto'}>
|
||||
{filterTemplates.map((item) =>
|
||||
item.list.map((item) => (
|
||||
<Flex
|
||||
key={item.name}
|
||||
alignItems={'center'}
|
||||
p={5}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'md'}
|
||||
draggable
|
||||
onDragEnd={(e) => {
|
||||
if (e.clientX < 360) return;
|
||||
onAddNode({
|
||||
template: item,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (isPc) return;
|
||||
onClose();
|
||||
onAddNode({
|
||||
template: item,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.logo} w={'34px'} objectFit={'contain'} borderRadius={'0'} />
|
||||
<Box ml={5} flex={'1 0 0'}>
|
||||
<Box color={'black'}>{item.name}</Box>
|
||||
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
|
||||
{item.intro}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ModuleTemplateList);
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
|
||||
const Label = ({
|
||||
required = false,
|
||||
children,
|
||||
description
|
||||
}: {
|
||||
required?: boolean;
|
||||
children: React.ReactNode | string;
|
||||
description?: string;
|
||||
}) => (
|
||||
<Box as={'label'} display={'inline-block'} position={'relative'}>
|
||||
{children}
|
||||
{required && (
|
||||
<Box position={'absolute'} top={'-2px'} right={'-10px'} color={'red.500'} fontWeight={'bold'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} fontSize={'12px'} mb={1} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default React.memo(Label);
|
||||
@@ -1,135 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowStore, onChangeNode } from '../Provider';
|
||||
|
||||
type Props = FlowModuleItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
minW?: string | number;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const {
|
||||
children,
|
||||
logo = '/icon/logo.svg',
|
||||
name = '未知模块',
|
||||
description,
|
||||
minW = '300px',
|
||||
moduleId
|
||||
} = props;
|
||||
const { onCopyNode, onDelNode } = useFlowStore();
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
|
||||
// custom title edit
|
||||
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||
title: t('common.Custom Title'),
|
||||
placeholder: t('app.module.Custom Title Tip') || ''
|
||||
});
|
||||
|
||||
const menuList = useMemo(
|
||||
() => [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('common.Rename'),
|
||||
onClick: () =>
|
||||
onOpenModal({
|
||||
defaultVal: name,
|
||||
onSuccess: (e) => {
|
||||
if (!e) {
|
||||
return toast({
|
||||
title: t('app.modules.Title is required'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'attr',
|
||||
key: 'name',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
icon: 'copy',
|
||||
label: t('common.Copy'),
|
||||
onClick: () => onCopyNode(moduleId)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
onClick: () => onDelNode(moduleId)
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'back',
|
||||
label: t('common.Close'),
|
||||
onClick: () => {}
|
||||
}
|
||||
],
|
||||
[moduleId, name, onChangeNode, onCopyNode, onDelNode, onOpenModal, t, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={minW}
|
||||
maxW={'500px'}
|
||||
bg={'white'}
|
||||
border={theme.borders.md}
|
||||
borderRadius={'md'}
|
||||
boxShadow={'sm'}
|
||||
>
|
||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||
<Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||
{name}
|
||||
</Box>
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon
|
||||
display={['none', 'inline']}
|
||||
transform={'translateY(1px)'}
|
||||
mb={'1px'}
|
||||
ml={1}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
<Menu autoSelect={false} isLazy>
|
||||
<MenuButton
|
||||
className={'nodrag'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||
</MenuButton>
|
||||
<MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
|
||||
{menuList.map((item) => (
|
||||
<MenuItem key={item.label} onClick={item.onClick} py={[2, 3]}>
|
||||
<MyIcon name={item.icon as any} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
{children}
|
||||
<EditTitleModal />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeCard);
|
||||
@@ -1,115 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
FormControl
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { FlowInputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import type { FlowInputItemType } from '@/types/core/app/flow';
|
||||
|
||||
const typeSelectList = [
|
||||
{
|
||||
label: '字符串',
|
||||
value: FlowValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: FlowValueTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: '布尔',
|
||||
value: FlowValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: '任意',
|
||||
value: FlowValueTypeEnum.any
|
||||
}
|
||||
];
|
||||
|
||||
const SetInputFieldModal = ({
|
||||
defaultField = {
|
||||
label: '',
|
||||
key: '',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
description: '',
|
||||
required: false
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField?: FlowInputItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: FlowInputItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<FlowInputItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{t('app.Input Field Settings')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段类型</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={typeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SetInputFieldModal);
|
||||
@@ -1,94 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Box, Button, ModalHeader, ModalFooter, ModalBody, Flex, Input } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowOutputItemType } from '@/types/core/app/flow';
|
||||
|
||||
const typeSelectList = [
|
||||
{
|
||||
label: '字符串',
|
||||
value: FlowValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: FlowValueTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: '布尔',
|
||||
value: FlowValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: '任意',
|
||||
value: FlowValueTypeEnum.any
|
||||
}
|
||||
];
|
||||
|
||||
const SetInputFieldModal = ({
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField: FlowOutputItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: FlowOutputItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<FlowOutputItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{t('app.Output Field Settings')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段类型</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={typeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SetInputFieldModal);
|
||||
@@ -1,145 +1,60 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { edgeOptions, connectionLineStyle, FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import type { AppSchema } from '@/types/mongoSchema';
|
||||
import React, { useMemo } from 'react';
|
||||
import { AppSchema } from '@/types/mongoSchema';
|
||||
import Header from './Header';
|
||||
import Flow from '@/components/core/module/Flow';
|
||||
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
import { SystemModuleTemplateType } from '@fastgpt/global/core/module/type.d';
|
||||
import { SystemModuleTemplates } from '@/constants/flow/ModuleTemplate';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { usePluginStore } from '@/web/core/plugin/store/plugin';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
type Props = { app: AppSchema; onClose: () => void };
|
||||
|
||||
import ButtonEdge from './components/modules/ButtonEdge';
|
||||
import TemplateList from './components/TemplateList';
|
||||
import FlowProvider, { useFlowStore } from './components/Provider';
|
||||
import Header from './components/Header';
|
||||
const Render = ({ app, onClose }: Props) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
const { pluginModuleTemplates, loadPluginModuleTemplates } = usePluginStore();
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
const filterTemplates = useMemo(() => {
|
||||
const copyTemplates: SystemModuleTemplateType = JSON.parse(
|
||||
JSON.stringify(SystemModuleTemplates)
|
||||
);
|
||||
const filterType: Record<string, 1> = {
|
||||
[FlowNodeTypeEnum.userGuide]: 1
|
||||
};
|
||||
// filter some template
|
||||
nodes.forEach((node) => {
|
||||
if (node.type && filterType[node.type]) {
|
||||
copyTemplates.forEach((item) => {
|
||||
item.list.forEach((module, index) => {
|
||||
if (module.flowType === node.type) {
|
||||
item.list.splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const NodeChat = dynamic(() => import('./components/Nodes/NodeChat'));
|
||||
const NodeDatasetSearch = dynamic(() => import('./components/Nodes/NodeDatasetSearch'));
|
||||
const NodeHistory = dynamic(() => import('./components/Nodes/NodeHistory'));
|
||||
const NodeTFSwitch = dynamic(() => import('./components/Nodes/NodeTFSwitch'));
|
||||
const NodeAnswer = dynamic(() => import('./components/Nodes/NodeAnswer'));
|
||||
const NodeQuestionInput = dynamic(() => import('./components/Nodes/NodeQuestionInput'));
|
||||
const NodeCQNode = dynamic(() => import('./components/Nodes/NodeCQNode'));
|
||||
const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'));
|
||||
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'));
|
||||
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'));
|
||||
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'));
|
||||
const NodeAPP = dynamic(() => import('./components/Nodes/NodeAPP'));
|
||||
return copyTemplates;
|
||||
}, [nodes]);
|
||||
|
||||
const nodeTypes = {
|
||||
[FlowModuleTypeEnum.userGuide]: NodeUserGuide,
|
||||
[FlowModuleTypeEnum.variable]: NodeVariable,
|
||||
[FlowModuleTypeEnum.questionInput]: NodeQuestionInput,
|
||||
[FlowModuleTypeEnum.historyNode]: NodeHistory,
|
||||
[FlowModuleTypeEnum.chatNode]: NodeChat,
|
||||
[FlowModuleTypeEnum.datasetSearchNode]: NodeDatasetSearch,
|
||||
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
||||
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
|
||||
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
|
||||
[FlowModuleTypeEnum.contentExtract]: NodeExtract,
|
||||
[FlowModuleTypeEnum.httpRequest]: NodeHttp,
|
||||
[FlowModuleTypeEnum.app]: NodeAPP
|
||||
// [FlowModuleTypeEnum.empty]: EmptyModule
|
||||
};
|
||||
const edgeTypes = {
|
||||
buttonedge: ButtonEdge
|
||||
};
|
||||
type Props = { app: AppSchema; onCloseSettings: () => void };
|
||||
|
||||
const AppEdit = React.memo(function AppEdit(props: Props) {
|
||||
const { app } = props;
|
||||
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
onOpen: onOpenTemplate,
|
||||
onClose: onCloseTemplate
|
||||
} = useDisclosure();
|
||||
|
||||
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect, initData } =
|
||||
useFlowStore();
|
||||
|
||||
useEffect(() => {
|
||||
initData(JSON.parse(JSON.stringify(app.modules)));
|
||||
}, [app.modules]);
|
||||
useQuery(['getUserPlugs2ModuleTemplates'], () => loadPluginModuleTemplates());
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* header */}
|
||||
<Header app={app} onCloseSettings={props.onCloseSettings} />
|
||||
<Box
|
||||
minH={'400px'}
|
||||
flex={'1 0 0'}
|
||||
w={'100%'}
|
||||
h={0}
|
||||
position={'relative'}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{/* open module template */}
|
||||
<IconButton
|
||||
position={'absolute'}
|
||||
top={5}
|
||||
left={5}
|
||||
w={'38px'}
|
||||
h={'38px'}
|
||||
borderRadius={'50%'}
|
||||
icon={<SmallCloseIcon fontSize={'26px'} />}
|
||||
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
|
||||
transition={'0.2s ease'}
|
||||
aria-label={''}
|
||||
zIndex={1}
|
||||
boxShadow={'2px 2px 6px #85b1ff'}
|
||||
onClick={() => {
|
||||
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
||||
}}
|
||||
/>
|
||||
<Flow
|
||||
systemTemplates={filterTemplates}
|
||||
pluginTemplates={[{ label: '', list: pluginModuleTemplates }]}
|
||||
show2Plugin
|
||||
modules={app.modules}
|
||||
Header={<Header app={app} onClose={onClose} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
<ReactFlow
|
||||
ref={reactFlowWrapper}
|
||||
fitView
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={edgeOptions}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={(connect) => {
|
||||
connect.sourceHandle &&
|
||||
connect.targetHandle &&
|
||||
onConnect({
|
||||
connect
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Background />
|
||||
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
|
||||
</ReactFlow>
|
||||
|
||||
<TemplateList isOpen={isOpenTemplate} onClose={onCloseTemplate} />
|
||||
</Box>
|
||||
</>
|
||||
export default React.memo(function AdEdit(props: Props) {
|
||||
return (
|
||||
<FlowProvider filterAppIds={[props.app._id]}>
|
||||
<Render {...props} />
|
||||
</FlowProvider>
|
||||
);
|
||||
});
|
||||
|
||||
const Flow = (data: Props) => {
|
||||
return (
|
||||
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
|
||||
<ReactFlowProvider>
|
||||
<FlowProvider appId={data?.app?._id}>
|
||||
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
|
||||
{!!data.app._id && <AppEdit {...data} />}
|
||||
</Flex>
|
||||
</FlowProvider>
|
||||
</ReactFlowProvider>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Flow);
|
||||
|
||||
@@ -37,10 +37,11 @@ import {
|
||||
welcomeTextTip,
|
||||
questionGuideTip
|
||||
} from '@/constants/flow/ModuleTemplate';
|
||||
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
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';
|
||||
@@ -56,15 +57,15 @@ import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||
|
||||
import { addVariable } from '../VariableEditModal';
|
||||
import { KbParamsModal } from '../DatasetSelectModal';
|
||||
import { addVariable } from '@/components/core/module/VariableEditModal';
|
||||
import { KbParamsModal } from '@/components/core/module/DatasetSelectModal';
|
||||
import { AppTypeEnum } from '@/constants/app';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
|
||||
const VariableEditModal = dynamic(() => import('../VariableEditModal'));
|
||||
const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal'));
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('../DatasetSelectModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../AIChatSettingsModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
|
||||
|
||||
const Settings = ({ appId }: { appId: string }) => {
|
||||
const theme = useTheme();
|
||||
@@ -596,13 +597,13 @@ const ChatTest = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail, userInfo } = useUserStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const [modules, setModules] = useState<AppModuleItemType[]>([]);
|
||||
const [modules, setModules] = useState<ModuleItemType[]>([]);
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const historyMaxLen =
|
||||
modules
|
||||
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
|
||||
?.find((item) => item.flowType === FlowNodeTypeEnum.historyNode)
|
||||
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0;
|
||||
const history = chatList.slice(-historyMaxLen - 2, -2);
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ const Logs = ({ appId }: { appId: string }) => {
|
||||
data: logs,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData,
|
||||
pageNum
|
||||
getData
|
||||
} = usePagination<AppLogsListItemType>({
|
||||
api: getAppChatLogs,
|
||||
pageSize: 20,
|
||||
@@ -178,7 +177,7 @@ const Logs = ({ appId }: { appId: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Logs;
|
||||
export default React.memo(Logs);
|
||||
|
||||
function DetailLogsModal({
|
||||
appId,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user