v4.6.2-alpah (#511)
This commit is contained in:
@@ -3,7 +3,7 @@ import { Image } from '@chakra-ui/react';
|
||||
import type { ImageProps } from '@chakra-ui/react';
|
||||
import { LOGO_ICON } from '@fastgpt/global/core/chat/constants';
|
||||
|
||||
const Avatar = ({ w = '30px', ...props }: ImageProps) => {
|
||||
const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
|
||||
return (
|
||||
<Image
|
||||
fallbackSrc={LOGO_ICON}
|
||||
@@ -14,6 +14,7 @@ const Avatar = ({ w = '30px', ...props }: ImageProps) => {
|
||||
w={w}
|
||||
h={w}
|
||||
p={'1px'}
|
||||
src={src || LOGO_ICON}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
|
||||
import { ModalBody, Box, useTheme, Flex, Image } from '@chakra-ui/react';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import MyModal from '../MyModal';
|
||||
|
||||
@@ -16,13 +16,13 @@ const ContextModal = ({
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/chatHistory.svg"
|
||||
title={`完整对话记录(${context.length}条)`}
|
||||
h={['90vh', '80vh']}
|
||||
minW={['90vw', '600px']}
|
||||
isCentered
|
||||
>
|
||||
<ModalBody
|
||||
pt={0}
|
||||
whiteSpace={'pre-wrap'}
|
||||
textAlign={'justify'}
|
||||
wordBreak={'break-all'}
|
||||
|
||||
@@ -33,7 +33,12 @@ const FeedbackModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('chat.Feedback Modal')}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/badAnswer.svg"
|
||||
title={t('chat.Feedback Modal')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Textarea
|
||||
ref={ref}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { ModalBody, Box, useTheme, Flex, Progress, Link } from '@chakra-ui/react';
|
||||
import { ModalBody, Box, useTheme, Flex, Progress, Link, Image } from '@chakra-ui/react';
|
||||
import { getDatasetDataItemById } from '@/web/core/dataset/api';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
@@ -68,6 +68,7 @@ const QuoteModal = ({
|
||||
h={['90vh', '80vh']}
|
||||
isCentered
|
||||
minW={['90vw', '600px']}
|
||||
iconSrc="/imgs/modal/quote.svg"
|
||||
title={
|
||||
<Box>
|
||||
知识库引用({rawSearch.length}条)
|
||||
@@ -77,7 +78,7 @@ const QuoteModal = ({
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
|
||||
<ModalBody whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
|
||||
{rawSearch.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
|
||||
@@ -36,7 +36,12 @@ const ReadFeedbackModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('chat.Feedback Modal')}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/readFeedback.svg"
|
||||
title={t('chat.Feedback Modal')}
|
||||
>
|
||||
<ModalBody>{content}</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={2} isLoading={isLoading} variant={'base'} onClick={mutate}>
|
||||
|
||||
@@ -35,7 +35,7 @@ const SelectMarkCollection = ({
|
||||
const theme = useTheme();
|
||||
const [selectedDatasetId, setSelectedDatasetId] = useState<string>();
|
||||
const [selectedDatasetCollectionIds, setSelectedDatasetCollectionIds] = useState<string[]>([]);
|
||||
const { paths, parentId, setParentId, datasets, isLoading } = useDatasetSelect();
|
||||
const { paths, parentId, setParentId, datasets, isFetching } = useDatasetSelect();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -107,7 +107,7 @@ const SelectMarkCollection = ({
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
isLoading={isFetching}
|
||||
isDisabled={!selectedDatasetId}
|
||||
onClick={() => {
|
||||
setAdminMarkData({ ...adminMarkData, datasetId: selectedDatasetId });
|
||||
|
||||
@@ -2,31 +2,33 @@ import React, { useMemo, useState } from 'react';
|
||||
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate';
|
||||
import { moduleTemplatesFlat } from '@/web/core/modules/template/system';
|
||||
import Tabs from '../Tabs';
|
||||
|
||||
import MyModal from '../MyModal';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import Markdown from '../Markdown';
|
||||
|
||||
function Row({ label, value }: { label: string; value?: string | number | React.ReactNode }) {
|
||||
function Row({ label, value }: { label: string; value?: string | number }) {
|
||||
const theme = useTheme();
|
||||
const strValue = `${value}`;
|
||||
const isCodeBlock = strValue.startsWith('~~~json');
|
||||
|
||||
return value !== undefined && value !== '' && value !== 'undefined' ? (
|
||||
<Box mb={2}>
|
||||
<Box fontSize={['sm', 'md']} mb={1} flex={'0 0 90px'}>
|
||||
<Box mb={3}>
|
||||
<Box fontSize={['sm', 'md']} mb={isCodeBlock ? 0 : 1} flex={'0 0 90px'}>
|
||||
{label}:
|
||||
</Box>
|
||||
<Box
|
||||
borderRadius={'lg'}
|
||||
border={theme.borders.base}
|
||||
px={3}
|
||||
py={1}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
borderRadius={'md'}
|
||||
fontSize={'sm'}
|
||||
{...(isCodeBlock
|
||||
? { transform: 'translateY(-3px)' }
|
||||
: { px: 3, py: 1, border: theme.borders.base })}
|
||||
>
|
||||
{value}
|
||||
<Markdown source={strValue} />
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
@@ -39,7 +41,6 @@ const WholeResponseModal = ({
|
||||
response: ChatHistoryItemResType[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const list = useMemo(
|
||||
@@ -51,7 +52,8 @@ const WholeResponseModal = ({
|
||||
mr={2}
|
||||
src={
|
||||
item.moduleLogo ||
|
||||
ModuleTemplatesFlat.find((template) => item.moduleType === template.flowType)?.logo
|
||||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowType)
|
||||
?.avatar
|
||||
}
|
||||
alt={''}
|
||||
w={['14px', '16px']}
|
||||
@@ -75,6 +77,7 @@ const WholeResponseModal = ({
|
||||
onClose={onClose}
|
||||
h={['90vh', '80vh']}
|
||||
w={['90vw', '500px']}
|
||||
iconSrc="/imgs/modal/wholeRecord.svg"
|
||||
title={
|
||||
<Flex alignItems={'center'}>
|
||||
{t('chat.Complete Response')}
|
||||
@@ -102,37 +105,26 @@ const WholeResponseModal = ({
|
||||
/>
|
||||
<Row label={t('chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row label={t('chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('chat.response.module query')} value={activeModule?.query} />
|
||||
|
||||
{/* ai chat */}
|
||||
<Row label={t('chat.response.module question')} value={activeModule?.question} />
|
||||
<Row label={t('chat.response.module temperature')} value={activeModule?.temperature} />
|
||||
<Row label={t('chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('chat.response.module quoteList')}
|
||||
value={(() => {
|
||||
try {
|
||||
JSON.stringify(activeModule.quoteList, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
<Row
|
||||
label={t('chat.response.module historyPreview')}
|
||||
value={(() => {
|
||||
if (!activeModule?.historyPreview) return '';
|
||||
return (
|
||||
<>
|
||||
{activeModule.historyPreview.map((item, i) => (
|
||||
<Box key={i} _notLast={{ mb: 3, borderBottom: theme.borders.base }} pb={3}>
|
||||
<Box fontWeight={'bold'}>{item.obj}</Box>
|
||||
<Box>{item.value}</Box>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
return activeModule.historyPreview
|
||||
.map((item, i) => `**${item.obj}**\n${item.value}`)
|
||||
.join('\n---\n');
|
||||
})()}
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('chat.response.module quoteList')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule.quoteList, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* dataset search */}
|
||||
<Row label={t('chat.response.module similarity')} value={activeModule?.similarity} />
|
||||
@@ -143,15 +135,7 @@ const WholeResponseModal = ({
|
||||
label={t('chat.response.module cq')}
|
||||
value={(() => {
|
||||
if (!activeModule?.cqList) return '';
|
||||
return (
|
||||
<Box as={'ol'} px={3}>
|
||||
{activeModule.cqList.map((item) => (
|
||||
<Box key={item.key} as={'li'}>
|
||||
{item.value}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||
})()}
|
||||
/>
|
||||
<Row label={t('chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
@@ -161,50 +145,34 @@ const WholeResponseModal = ({
|
||||
label={t('chat.response.module extract description')}
|
||||
value={activeModule?.extractDescription}
|
||||
/>
|
||||
<Row
|
||||
label={t('chat.response.module extract result')}
|
||||
value={(() => {
|
||||
try {
|
||||
return JSON.stringify(activeModule?.extractResult, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
{activeModule?.extractResult && (
|
||||
<Row
|
||||
label={t('chat.response.module extract result')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* http */}
|
||||
<Row
|
||||
label={t('chat.response.module http body')}
|
||||
value={(() => {
|
||||
try {
|
||||
return JSON.stringify(activeModule?.body, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
<Row
|
||||
label={t('chat.response.module http result')}
|
||||
value={(() => {
|
||||
try {
|
||||
return JSON.stringify(activeModule?.httpResult, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
{activeModule?.body && (
|
||||
<Row
|
||||
label={t('chat.response.module http body')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
{activeModule?.httpResult && (
|
||||
<Row
|
||||
label={t('chat.response.module http result')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* plugin */}
|
||||
<Row
|
||||
label={t('chat.response.plugin output')}
|
||||
value={(() => {
|
||||
try {
|
||||
return JSON.stringify(activeModule?.pluginOutput, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
{activeModule?.pluginOutput && (
|
||||
<Row
|
||||
label={t('chat.response.plugin output')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
|
||||
@@ -33,14 +33,13 @@ import { eventBus } from '@/web/common/utils/eventbus';
|
||||
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { useMarkdown } from '@/web/common/hooks/useMarkdown';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { VariableInputEnum } from '@/constants/app';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import { htmlTemplate } from '@/constants/common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { adminUpdateChatFeedback, userUpdateChatFeedback } from '@/web/core/chat/api';
|
||||
@@ -60,9 +59,10 @@ const SelectMarkCollection = dynamic(() => import('./SelectMarkCollection'));
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { postQuestionGuide } from '@/web/core/ai/api';
|
||||
import { splitGuideModule } from '@/global/core/app/modules/utils';
|
||||
import { AppTTSConfigType } from '@/types/app';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
|
||||
import MessageInput from './MessageInput';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||
|
||||
@@ -105,7 +105,7 @@ type Props = {
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<{
|
||||
responseText: string;
|
||||
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType[];
|
||||
[ModuleOutputKeyEnum.responseData]: ChatHistoryItemResType[];
|
||||
isNewChat?: boolean;
|
||||
}>;
|
||||
onDelMessage?: (e: { contentId?: string; index: number }) => void;
|
||||
@@ -760,6 +760,9 @@ const ChatBox = (
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
size={'xs'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
h={'auto'}
|
||||
py={1}
|
||||
onClick={() => {
|
||||
resetInputVal(item);
|
||||
}}
|
||||
|
||||
@@ -13,7 +13,12 @@ const md = `
|
||||
const CommunityModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('home.Community')}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/concat.svg"
|
||||
title={t('home.Community')}
|
||||
>
|
||||
<ModalBody textAlign={'center'}>
|
||||
<Markdown source={md} />
|
||||
</ModalBody>
|
||||
|
||||
@@ -8,6 +8,7 @@ const unAuthPage: { [key: string]: boolean } = {
|
||||
'/': true,
|
||||
'/login': true,
|
||||
'/login/provider': true,
|
||||
'/login/fastlogin': true,
|
||||
'/appStore': true,
|
||||
'/chat/share': true
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
|
||||
'/': true,
|
||||
'/login': true,
|
||||
'/login/provider': true,
|
||||
'/login/fastlogin': true,
|
||||
'/chat/share': true,
|
||||
'/app/edit': true,
|
||||
'/chat': true
|
||||
@@ -30,6 +31,7 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
|
||||
'/': true,
|
||||
'/login': true,
|
||||
'/login/provider': true,
|
||||
'/login/fastlogin': true,
|
||||
'/chat/share': true
|
||||
};
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalContentProps,
|
||||
Box
|
||||
Box,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
interface Props extends ModalContentProps {
|
||||
iconSrc?: string;
|
||||
title?: any;
|
||||
isCentered?: boolean;
|
||||
isOpen: boolean;
|
||||
@@ -19,6 +21,7 @@ interface Props extends ModalContentProps {
|
||||
const MyModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
iconSrc,
|
||||
title,
|
||||
children,
|
||||
isCentered,
|
||||
@@ -51,8 +54,9 @@ const MyModal = ({
|
||||
background={'#FBFBFC'}
|
||||
borderBottom={'1px solid #F4F6F8'}
|
||||
roundedTop={'lg'}
|
||||
py={3}
|
||||
py={'10px'}
|
||||
>
|
||||
{iconSrc && <Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />}
|
||||
{title}
|
||||
<Box flex={1} />
|
||||
{onClose && <ModalCloseButton position={'relative'} top={0} right={0} />}
|
||||
|
||||
@@ -19,7 +19,7 @@ const PromptTemplate = ({
|
||||
const [selectTemplateTitle, setSelectTemplateTitle] = useState<PromptTemplateItem>();
|
||||
|
||||
return (
|
||||
<MyModal isOpen title={title} onClose={onClose} isCentered>
|
||||
<MyModal isOpen title={title} onClose={onClose} iconSrc="/imgs/modal/prompt.svg">
|
||||
<ModalBody h="100%" w={'600px'} maxW={'90vw'} overflowY={'auto'}>
|
||||
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={4}>
|
||||
{templates.map((item) => (
|
||||
|
||||
@@ -4,8 +4,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import React, { Dispatch, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex, ModalHeader } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
|
||||
type PathItemType = {
|
||||
@@ -29,12 +28,12 @@ const DatasetSelectContainer = ({
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
return (
|
||||
<MyModal isOpen={isOpen} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered>
|
||||
<Flex flexDirection={'column'} h={'90vh'}>
|
||||
<ModalHeader fontWeight={'normal'}>
|
||||
<MyModal
|
||||
iconSrc="/imgs/module/db.png"
|
||||
title={
|
||||
<Box fontWeight={'normal'}>
|
||||
<ParentPaths
|
||||
paths={paths.map((path, i) => ({
|
||||
parentId: path.parentId,
|
||||
@@ -50,7 +49,15 @@ const DatasetSelectContainer = ({
|
||||
{tips}
|
||||
</Box>
|
||||
)}
|
||||
</ModalHeader>
|
||||
</Box>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '900px']}
|
||||
isCentered
|
||||
>
|
||||
<Flex flexDirection={'column'} h={'90vh'}>
|
||||
<Box flex={'1 0 0'}>{children}</Box>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
@@ -58,10 +65,9 @@ const DatasetSelectContainer = ({
|
||||
};
|
||||
|
||||
export function useDatasetSelect() {
|
||||
const { t } = useTranslation();
|
||||
const [parentId, setParentId] = useState<string>();
|
||||
const [parentId, setParentId] = useState<string>('');
|
||||
|
||||
const { data, isLoading } = useQuery(['loadDatasetData', parentId], () =>
|
||||
const { data, isFetching } = useQuery(['loadDatasetData', parentId], () =>
|
||||
Promise.all([getDatasets({ parentId }), getDatasetPaths(parentId)])
|
||||
);
|
||||
|
||||
@@ -72,7 +78,7 @@ export function useDatasetSelect() {
|
||||
setParentId,
|
||||
datasets: data?.[0] || [],
|
||||
paths,
|
||||
isLoading
|
||||
isFetching
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EditFormType } from '@/web/core/app/basicSettings';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Button,
|
||||
Flex,
|
||||
Image,
|
||||
Link,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
@@ -19,9 +19,12 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
|
||||
import { chatModelList, feConfigs } from '@/web/common/system/staticData';
|
||||
import MySlider from '@/components/Slider';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
|
||||
import type { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
|
||||
const PromptTemplate = dynamic(() => import('@/components/PromptTemplate'));
|
||||
|
||||
@@ -29,12 +32,14 @@ const AIChatSettingsModal = ({
|
||||
isAdEdit,
|
||||
onClose,
|
||||
onSuccess,
|
||||
defaultData
|
||||
defaultData,
|
||||
simpleModeTemplate = SimpleModeTemplate_FastGPT_Universal
|
||||
}: {
|
||||
isAdEdit?: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: (e: EditFormType['chatModel']) => void;
|
||||
defaultData: EditFormType['chatModel'];
|
||||
onSuccess: (e: AIChatModuleProps) => void;
|
||||
defaultData: AIChatModuleProps;
|
||||
simpleModeTemplate?: AppSimpleEditConfigTemplateType;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
@@ -49,7 +54,10 @@ const AIChatSettingsModal = ({
|
||||
}>();
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
return chatModelList.find((item) => item.model === getValues('model'))?.maxResponse || 4000;
|
||||
return (
|
||||
chatModelList.find((item) => item.model === getValues(ModuleInputKeyEnum.aiModel))
|
||||
?.maxResponse || 4000
|
||||
);
|
||||
}, [getValues, refresh]);
|
||||
|
||||
const LabelStyles: BoxProps = {
|
||||
@@ -63,8 +71,9 @@ const AIChatSettingsModal = ({
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="/imgs/module/AI.png"
|
||||
title={
|
||||
<Flex alignItems={'flex-end'}>
|
||||
<>
|
||||
{t('app.AI Advanced Settings')}
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
@@ -78,13 +87,13 @@ const AIChatSettingsModal = ({
|
||||
查看说明
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
}
|
||||
isCentered
|
||||
w={'700px'}
|
||||
h={['90vh', 'auto']}
|
||||
>
|
||||
<ModalBody flex={['1 0 0', 'auto']} overflowY={'auto'}>
|
||||
<ModalBody flex={['1 0 0', 'auto']} overflowY={'auto'} minH={'40vh'}>
|
||||
{isAdEdit && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box {...LabelStyles} w={'80px'}>
|
||||
@@ -92,112 +101,123 @@ const AIChatSettingsModal = ({
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
<Switch
|
||||
isChecked={getValues(SystemInputEnum.isResponseAnswerText)}
|
||||
isChecked={getValues(ModuleInputKeyEnum.aiChatIsResponseText)}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue(SystemInputEnum.isResponseAnswerText, value);
|
||||
setValue(ModuleInputKeyEnum.aiChatIsResponseText, value);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} mb={10} mt={isAdEdit ? 8 : 5}>
|
||||
<Box {...LabelStyles} mr={2} w={'80px'}>
|
||||
温度
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '严谨', value: 0 },
|
||||
{ label: '发散', value: 10 }
|
||||
]}
|
||||
width={'95%'}
|
||||
min={0}
|
||||
max={10}
|
||||
value={getValues('temperature')}
|
||||
onChange={(e) => {
|
||||
setValue('temperature', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={12} mb={10}>
|
||||
<Box {...LabelStyles} mr={2} w={'80px'}>
|
||||
回复上限
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${tokenLimit}`, value: tokenLimit }
|
||||
]}
|
||||
width={'95%'}
|
||||
min={100}
|
||||
max={tokenLimit}
|
||||
step={50}
|
||||
value={getValues('maxToken')}
|
||||
onChange={(val) => {
|
||||
setValue('maxToken', val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
引用内容模板
|
||||
<MyTooltip
|
||||
label={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
})}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Box
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: '选择知识库提示词模板',
|
||||
templates: Prompt_QuoteTemplateList
|
||||
})
|
||||
}
|
||||
>
|
||||
选择模板
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.temperature && (
|
||||
<Flex alignItems={'center'} mb={10} mt={isAdEdit ? 8 : 5}>
|
||||
<Box {...LabelStyles} mr={2} w={'80px'}>
|
||||
温度
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '严谨', value: 0 },
|
||||
{ label: '发散', value: 10 }
|
||||
]}
|
||||
width={'95%'}
|
||||
min={0}
|
||||
max={10}
|
||||
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Textarea
|
||||
rows={6}
|
||||
placeholder={
|
||||
t('template.Quote Content Tip', { default: Prompt_QuoteTemplateList[0].value }) || ''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('quoteTemplate')}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
引用内容提示词
|
||||
<MyTooltip
|
||||
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.maxToken && (
|
||||
<Flex alignItems={'center'} mt={12} mb={10}>
|
||||
<Box {...LabelStyles} mr={2} w={'80px'}>
|
||||
回复上限
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${tokenLimit}`, value: tokenLimit }
|
||||
]}
|
||||
width={'95%'}
|
||||
min={100}
|
||||
max={tokenLimit}
|
||||
step={50}
|
||||
value={getValues(ModuleInputKeyEnum.aiChatMaxToken)}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatMaxToken, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Textarea
|
||||
rows={11}
|
||||
placeholder={
|
||||
t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value }) || ''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('quotePrompt')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.quoteTemplate && (
|
||||
<Box>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
引用内容模板
|
||||
<MyTooltip
|
||||
label={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
})}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Box
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: '选择知识库提示词模板',
|
||||
templates: Prompt_QuoteTemplateList
|
||||
})
|
||||
}
|
||||
>
|
||||
选择模板
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Textarea
|
||||
rows={6}
|
||||
placeholder={
|
||||
t('template.Quote Content Tip', { default: Prompt_QuoteTemplateList[0].value }) ||
|
||||
''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register(ModuleInputKeyEnum.aiChatQuoteTemplate)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{simpleModeTemplate?.systemForm?.aiSettings?.quotePrompt && (
|
||||
<Box mt={4}>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
引用内容提示词
|
||||
<MyTooltip
|
||||
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Textarea
|
||||
rows={11}
|
||||
placeholder={
|
||||
t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value }) || ''
|
||||
}
|
||||
borderColor={'myGray.100'}
|
||||
{...register(ModuleInputKeyEnum.aiChatQuotePrompt)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} onClick={onClose}>
|
||||
@@ -215,8 +235,8 @@ const AIChatSettingsModal = ({
|
||||
onSuccess={(e) => {
|
||||
const quoteVal = e.value;
|
||||
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
|
||||
setValue('quoteTemplate', quoteVal);
|
||||
setValue('quotePrompt', promptVal);
|
||||
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, quoteVal);
|
||||
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, promptVal);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
Textarea,
|
||||
Grid,
|
||||
Divider,
|
||||
Switch
|
||||
Switch,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -28,47 +29,44 @@ import { feConfigs } from '@/web/common/system/staticData';
|
||||
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
export type KbParamsType = {
|
||||
searchSimilarity: number;
|
||||
searchLimit: number;
|
||||
searchEmptyText: string;
|
||||
rerank: boolean;
|
||||
};
|
||||
type DatasetParamsProps = AppSimpleEditFormType['dataset'];
|
||||
|
||||
export const DatasetSelectModal = ({
|
||||
isOpen,
|
||||
activeDatasets = [],
|
||||
defaultSelectedDatasets = [],
|
||||
onChange,
|
||||
onClose
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
activeDatasets: SelectedDatasetType;
|
||||
defaultSelectedDatasets: SelectedDatasetType;
|
||||
onChange: (e: SelectedDatasetType) => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { allDatasets } = useDatasetStore();
|
||||
const [selectedKbList, setSelectedKbList] = useState<SelectedDatasetType>(
|
||||
activeDatasets.filter((dataset) => {
|
||||
const [selectedDatasets, setSelectedDatasets] = useState<SelectedDatasetType>(
|
||||
defaultSelectedDatasets.filter((dataset) => {
|
||||
return allDatasets.find((item) => item._id === dataset.datasetId);
|
||||
})
|
||||
);
|
||||
const { toast } = useToast();
|
||||
const { paths, setParentId, datasets, isLoading } = useDatasetSelect();
|
||||
const { paths, setParentId, datasets, isFetching } = useDatasetSelect();
|
||||
const { Loading } = useLoading();
|
||||
|
||||
const filterKbList = useMemo(() => {
|
||||
const filterDatasets = useMemo(() => {
|
||||
return {
|
||||
selected: allDatasets.filter((item) =>
|
||||
selectedKbList.find((dataset) => dataset.datasetId === item._id)
|
||||
selectedDatasets.find((dataset) => dataset.datasetId === item._id)
|
||||
),
|
||||
unSelected: datasets.filter(
|
||||
(item) => !selectedKbList.find((dataset) => dataset.datasetId === item._id)
|
||||
(item) => !selectedDatasets.find((dataset) => dataset.datasetId === item._id)
|
||||
)
|
||||
};
|
||||
}, [datasets, allDatasets, selectedKbList]);
|
||||
}, [datasets, allDatasets, selectedDatasets]);
|
||||
|
||||
return (
|
||||
<DatasetSelectContainer
|
||||
@@ -88,7 +86,7 @@ export const DatasetSelectModal = ({
|
||||
]}
|
||||
gridGap={3}
|
||||
>
|
||||
{filterKbList.selected.map((item) =>
|
||||
{filterDatasets.selected.map((item) =>
|
||||
(() => {
|
||||
return (
|
||||
<Card
|
||||
@@ -109,7 +107,7 @@ export const DatasetSelectModal = ({
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
setSelectedKbList((state) =>
|
||||
setSelectedDatasets((state) =>
|
||||
state.filter((kb) => kb.datasetId !== item._id)
|
||||
);
|
||||
}}
|
||||
@@ -121,7 +119,7 @@ export const DatasetSelectModal = ({
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{filterKbList.selected.length > 0 && <Divider my={3} />}
|
||||
{filterDatasets.selected.length > 0 && <Divider my={3} />}
|
||||
|
||||
<Grid
|
||||
gridTemplateColumns={[
|
||||
@@ -131,7 +129,7 @@ export const DatasetSelectModal = ({
|
||||
]}
|
||||
gridGap={3}
|
||||
>
|
||||
{filterKbList.unSelected.map((item) =>
|
||||
{filterDatasets.unSelected.map((item) =>
|
||||
(() => {
|
||||
return (
|
||||
<MyTooltip
|
||||
@@ -155,7 +153,7 @@ export const DatasetSelectModal = ({
|
||||
if (item.type === DatasetTypeEnum.folder) {
|
||||
setParentId(item._id);
|
||||
} else if (item.type === DatasetTypeEnum.dataset) {
|
||||
const vectorModel = selectedKbList[0]?.vectorModel?.model;
|
||||
const vectorModel = selectedDatasets[0]?.vectorModel?.model;
|
||||
|
||||
if (vectorModel && vectorModel !== item.vectorModel.model) {
|
||||
return toast({
|
||||
@@ -163,7 +161,7 @@ export const DatasetSelectModal = ({
|
||||
title: t('dataset.Select Dataset Tips')
|
||||
});
|
||||
}
|
||||
setSelectedKbList((state) => [
|
||||
setSelectedDatasets((state) => [
|
||||
...state,
|
||||
{ datasetId: item._id, vectorModel: item.vectorModel }
|
||||
]);
|
||||
@@ -199,26 +197,26 @@ export const DatasetSelectModal = ({
|
||||
})()
|
||||
)}
|
||||
</Grid>
|
||||
{filterKbList.unSelected.length === 0 && <EmptyTip text={t('common.folder.empty')} />}
|
||||
{filterDatasets.unSelected.length === 0 && <EmptyTip text={t('common.folder.empty')} />}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// filter out the dataset that is not in the kList
|
||||
const filterKbList = selectedKbList.filter((dataset) => {
|
||||
const filterDatasets = selectedDatasets.filter((dataset) => {
|
||||
return allDatasets.find((item) => item._id === dataset.datasetId);
|
||||
});
|
||||
|
||||
onClose();
|
||||
onChange(filterKbList);
|
||||
onChange(filterDatasets);
|
||||
}}
|
||||
>
|
||||
{t('common.Done')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
<Loading fixed={false} loading={isLoading} />
|
||||
<Loading fixed={false} loading={isFetching} />
|
||||
</Flex>
|
||||
</DatasetSelectContainer>
|
||||
);
|
||||
@@ -226,24 +224,30 @@ export const DatasetSelectModal = ({
|
||||
|
||||
export const DatasetParamsModal = ({
|
||||
searchEmptyText,
|
||||
searchLimit,
|
||||
searchSimilarity,
|
||||
limit,
|
||||
similarity,
|
||||
rerank,
|
||||
onClose,
|
||||
onChange
|
||||
}: KbParamsType & { onClose: () => void; onChange: (e: KbParamsType) => void }) => {
|
||||
}: DatasetParamsProps & { onClose: () => void; onChange: (e: DatasetParamsProps) => void }) => {
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<KbParamsType>({
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
|
||||
defaultValues: {
|
||||
searchEmptyText,
|
||||
searchLimit,
|
||||
searchSimilarity,
|
||||
limit,
|
||||
similarity,
|
||||
rerank
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={'搜索参数调整'} minW={['90vw', '600px']}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/params.svg"
|
||||
title={'搜索参数调整'}
|
||||
minW={['90vw', '600px']}
|
||||
>
|
||||
<Flex flexDirection={'column'}>
|
||||
<ModalBody>
|
||||
{feConfigs?.isPlus && (
|
||||
@@ -256,9 +260,9 @@ export const DatasetParamsModal = ({
|
||||
</Box>
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={getValues('rerank')}
|
||||
isChecked={getValues(ModuleInputKeyEnum.datasetStartReRank)}
|
||||
onChange={(e) => {
|
||||
setValue('rerank', e.target.checked);
|
||||
setValue(ModuleInputKeyEnum.datasetStartReRank, e.target.checked);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
@@ -282,9 +286,9 @@ export const DatasetParamsModal = ({
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={getValues('searchSimilarity')}
|
||||
value={getValues(ModuleInputKeyEnum.datasetSimilarity)}
|
||||
onChange={(val) => {
|
||||
setValue('searchSimilarity', val);
|
||||
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
@@ -301,9 +305,9 @@ export const DatasetParamsModal = ({
|
||||
]}
|
||||
min={1}
|
||||
max={20}
|
||||
value={getValues('searchLimit')}
|
||||
value={getValues(ModuleInputKeyEnum.datasetLimit)}
|
||||
onChange={(val) => {
|
||||
setValue('searchLimit', val);
|
||||
setValue(ModuleInputKeyEnum.datasetLimit, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { streamFetch } from '@/web/common/api/fetch';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||
import { getGuideModule } from '@/global/core/app/modules/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
|
||||
export type ChatTestComponentRef = {
|
||||
|
||||
@@ -28,11 +28,8 @@ import React, {
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
@@ -42,6 +39,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
export type useFlowProviderStoreType = {
|
||||
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
|
||||
mode: 'app' | 'plugin';
|
||||
filterAppIds: string[];
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
setNodes: Dispatch<SetStateAction<Node<FlowModuleItemType, string | undefined>[]>>;
|
||||
@@ -66,6 +64,7 @@ export type useFlowProviderStoreType = {
|
||||
|
||||
const StateContext = createContext<useFlowProviderStoreType>({
|
||||
reactFlowWrapper: null,
|
||||
mode: 'app',
|
||||
filterAppIds: [],
|
||||
nodes: [],
|
||||
setNodes: function (
|
||||
@@ -118,9 +117,11 @@ const StateContext = createContext<useFlowProviderStoreType>({
|
||||
export const useFlowProviderStore = () => useContext(StateContext);
|
||||
|
||||
export const FlowProvider = ({
|
||||
mode,
|
||||
filterAppIds = [],
|
||||
children
|
||||
}: {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
filterAppIds?: string[];
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
@@ -173,7 +174,7 @@ export const FlowProvider = ({
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
const sourceType = (() => {
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion) {
|
||||
return FlowNodeValTypeEnum.boolean;
|
||||
return ModuleDataTypeEnum.boolean;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
@@ -192,8 +193,8 @@ export const FlowProvider = ({
|
||||
});
|
||||
}
|
||||
if (
|
||||
sourceType !== FlowNodeValTypeEnum.any &&
|
||||
targetType !== FlowNodeValTypeEnum.any &&
|
||||
sourceType !== ModuleDataTypeEnum.any &&
|
||||
targetType !== ModuleDataTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
) {
|
||||
return toast({
|
||||
@@ -328,10 +329,9 @@ export const FlowProvider = ({
|
||||
const node = nodes.find((node) => node.id === nodeId);
|
||||
if (!node) return nodes;
|
||||
const template = {
|
||||
logo: node.data.logo,
|
||||
avatar: node.data.avatar,
|
||||
name: node.data.name,
|
||||
intro: node.data.intro,
|
||||
description: node.data.description,
|
||||
flowType: node.data.flowType,
|
||||
inputs: node.data.inputs,
|
||||
outputs: node.data.outputs,
|
||||
@@ -407,6 +407,7 @@ export const FlowProvider = ({
|
||||
|
||||
const value = {
|
||||
reactFlowWrapper,
|
||||
mode,
|
||||
filterAppIds,
|
||||
nodes,
|
||||
setNodes,
|
||||
@@ -444,13 +445,13 @@ export function flowNode2Modules({
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
logo: item.data.logo,
|
||||
avatar: item.data.avatar,
|
||||
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
|
||||
connected: Boolean(item.value ?? item.connected ?? item.type !== FlowNodeInputTypeEnum.target)
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
|
||||
@@ -12,7 +12,13 @@ const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<MyModal isOpen w={'600px'} onClose={onClose} title={t('app.Import Config')}>
|
||||
<MyModal
|
||||
isOpen
|
||||
w={'600px'}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/params.svg"
|
||||
title={t('app.Import Configs')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Textarea
|
||||
placeholder={t('app.Paste Config') || 'app.Paste Config'}
|
||||
@@ -38,7 +44,7 @@ const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
onClose();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: t('app.Import Config Failed')
|
||||
title: t('app.Import Configs Failed')
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import type {
|
||||
FlowModuleTemplateType,
|
||||
SystemModuleTemplateType
|
||||
moduleTemplateListType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -16,32 +25,31 @@ 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 { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { moduleTemplatesList } from '@/web/core/modules/template/system';
|
||||
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
system = 'system',
|
||||
combine = 'combine'
|
||||
plugin = 'plugin'
|
||||
}
|
||||
|
||||
export type ModuleTemplateProps = {
|
||||
systemTemplates: SystemModuleTemplateType;
|
||||
pluginTemplates: SystemModuleTemplateType;
|
||||
show2Plugin?: boolean;
|
||||
systemTemplates: FlowModuleTemplateType[];
|
||||
pluginTemplates: FlowModuleTemplateType[];
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@@ -53,9 +61,9 @@ const ModuleTemplateList = ({
|
||||
child: <RenderList templates={systemTemplates} onClose={onClose} />
|
||||
},
|
||||
{
|
||||
type: TemplateTypeEnum.combine,
|
||||
type: TemplateTypeEnum.plugin,
|
||||
label: t('plugin.Plugin Module'),
|
||||
child: <RenderList templates={pluginTemplates} onClose={onClose} />
|
||||
child: <RenderList templates={pluginTemplates} onClose={onClose} isPlugin />
|
||||
}
|
||||
],
|
||||
[pluginTemplates, onClose, systemTemplates, t]
|
||||
@@ -112,20 +120,6 @@ const ModuleTemplateList = ({
|
||||
{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={'common/rightArrowLight'} w={'12px'} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{TemplateItem}
|
||||
</Flex>
|
||||
@@ -135,47 +129,56 @@ const ModuleTemplateList = ({
|
||||
|
||||
export default React.memo(ModuleTemplateList);
|
||||
|
||||
var RenderList = React.memo(function RenderList({
|
||||
const RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
isPlugin = false,
|
||||
onClose
|
||||
}: {
|
||||
templates: {
|
||||
label: string;
|
||||
list: FlowModuleTemplateType[];
|
||||
}[];
|
||||
templates: FlowModuleTemplateType[];
|
||||
isPlugin?: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystemStore();
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const formatTemplates = useMemo<moduleTemplateListType>(() => {
|
||||
const copy: moduleTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList));
|
||||
templates.forEach((item) => {
|
||||
const index = copy.findIndex((template) => template.type === item.templateType);
|
||||
if (index === -1) return;
|
||||
copy[index].list.push(item);
|
||||
});
|
||||
return copy.filter((item) => item.list.length > 0);
|
||||
}, [templates]);
|
||||
|
||||
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
|
||||
};
|
||||
const templateModule = await (async () => {
|
||||
try {
|
||||
// get plugin preview module
|
||||
if (template.flowType === FlowNodeTypeEnum.pluginModule) {
|
||||
setLoading(true);
|
||||
const res = await getPreviewPluginModule(template.id);
|
||||
setLoading(false);
|
||||
return res;
|
||||
}
|
||||
return { ...template };
|
||||
} catch (e) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('plugin.Get Plugin Module Detail Failed'))
|
||||
});
|
||||
setLoading(false);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
} 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;
|
||||
@@ -196,46 +199,73 @@ var RenderList = React.memo(function RenderList({
|
||||
[reactFlowWrapper, setLoading, setNodes, t, toast, x, y, zoom]
|
||||
);
|
||||
|
||||
const list = useMemo(() => templates.map((item) => item.list).flat(), [templates]);
|
||||
|
||||
return list.length === 0 ? (
|
||||
return templates.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}
|
||||
{formatTemplates.map((item, i) => (
|
||||
<Box key={item.type}>
|
||||
<Flex>
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{item.label}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
{isPlugin && item.type === ModuleTemplateTypeEnum.personalPlugin && (
|
||||
<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={'common/rightArrowLight'} w={'12px'} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<>
|
||||
{item.list.map((template) => (
|
||||
<Flex
|
||||
key={template.id}
|
||||
alignItems={'center'}
|
||||
p={5}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'sm'}
|
||||
draggable
|
||||
onDragEnd={(e) => {
|
||||
if (e.clientX < 360) return;
|
||||
onAddNode({
|
||||
template: template,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (isPc) return;
|
||||
onClose();
|
||||
onAddNode({
|
||||
template: template,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={template.avatar}
|
||||
w={'34px'}
|
||||
objectFit={'contain'}
|
||||
borderRadius={'0'}
|
||||
/>
|
||||
<Box ml={5} flex={'1 0 0'}>
|
||||
<Box color={'black'}>{template.name}</Box>
|
||||
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
|
||||
{template.intro}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -39,6 +39,7 @@ const SelectAppModal = ({
|
||||
<MyModal
|
||||
isOpen
|
||||
title={`选择应用${max > 1 ? `(${selectedApps.length}/${max})` : ''}`}
|
||||
iconSrc="/imgs/module/ai.svg"
|
||||
onClose={onClose}
|
||||
minW={'700px'}
|
||||
position={'relative'}
|
||||
|
||||
@@ -35,11 +35,12 @@ const ExtractFieldModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
提取字段配置
|
||||
</ModalHeader>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={'提取字段配置'}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -11,65 +11,69 @@ import {
|
||||
} 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 { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-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
|
||||
}
|
||||
];
|
||||
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export type EditFieldModeType = 'input' | 'output' | 'pluginInput';
|
||||
export type EditFieldType = {
|
||||
type?: `${FlowNodeInputTypeEnum}`; // input type
|
||||
key: string;
|
||||
label?: string;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
createSign?: boolean;
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
mode,
|
||||
defaultField = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
required: false
|
||||
},
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
mode: EditFieldModeType;
|
||||
defaultField?: EditFieldType;
|
||||
defaultField: EditFieldType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: EditFieldType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const inputTypeList = [
|
||||
{
|
||||
label: t('core.module.inputType.target'),
|
||||
value: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
valueType: ModuleDataTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
valueType: ModuleDataTypeEnum.selectDataset
|
||||
}
|
||||
];
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -2)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<EditFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
@@ -79,75 +83,113 @@ const FieldEditModal = ({
|
||||
? t('app.Input Field Settings')
|
||||
: t('app.Output Field Settings');
|
||||
|
||||
const showValueTypeSelect = useMemo(() => {
|
||||
return getValues('type') === FlowNodeInputTypeEnum.target || mode === 'output';
|
||||
}, [getValues, mode, refresh]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
title={
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{title}
|
||||
</Flex>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<MyModal isOpen={true} iconSrc="/imgs/module/extract.png" title={title} onClose={onClose}>
|
||||
<ModalBody minH={'260px'} overflow={'visible'}>
|
||||
{mode === 'input' && (
|
||||
{/* input type select: target, input, textarea.... */}
|
||||
{mode === 'pluginInput' && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Input Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={inputTypeList}
|
||||
value={getValues('type')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeInputTypeEnum}`;
|
||||
const selectedItem = inputTypeList.find((item) => item.value === type);
|
||||
setValue('type', type);
|
||||
setValue('valueType', selectedItem?.valueType);
|
||||
|
||||
if (type === FlowNodeInputTypeEnum.selectDataset) {
|
||||
setValue('label', selectedItem?.label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{['input', 'pluginInput'].includes(mode) && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</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);
|
||||
{showValueTypeSelect && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Data Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={dataTypeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${ModuleDataTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === FlowNodeValTypeEnum.chatHistory ||
|
||||
type === FlowNodeValTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = typeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
if (
|
||||
type === ModuleDataTypeEnum.chatHistory ||
|
||||
type === ModuleDataTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = dataTypeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>字段描述</Box>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Description')}</Box>
|
||||
<Textarea placeholder="可选" rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
|
||||
export const defaultInputField: EditFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
required: true,
|
||||
createSign: true
|
||||
};
|
||||
export const defaultOutputField: EditFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
required: true,
|
||||
createSign: true
|
||||
};
|
||||
|
||||
@@ -9,14 +9,13 @@ import { useTranslation } from 'next-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 { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getPluginModuleDetail } from '@/web/core/plugin/api';
|
||||
import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { LOGO_ICON } from '@fastgpt/global/core/chat/constants';
|
||||
|
||||
type Props = FlowModuleItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -27,9 +26,9 @@ type Props = FlowModuleItemType & {
|
||||
const NodeCard = (props: Props) => {
|
||||
const {
|
||||
children,
|
||||
logo = '/icon/logo.svg',
|
||||
avatar = LOGO_ICON,
|
||||
name = '未知模块',
|
||||
description,
|
||||
intro,
|
||||
minW = '300px',
|
||||
moduleId,
|
||||
flowType,
|
||||
@@ -59,14 +58,13 @@ const NodeCard = (props: Props) => {
|
||||
icon: 'common/refreshLight',
|
||||
label: t('plugin.Synchronous version'),
|
||||
onClick: () => {
|
||||
const pluginId = inputs.find(
|
||||
(item) => item.key === FlowNodeSpecialInputKeyEnum.pluginId
|
||||
)?.value;
|
||||
const pluginId = inputs.find((item) => item.key === ModuleInputKeyEnum.pluginId)
|
||||
?.value;
|
||||
if (!pluginId) return;
|
||||
openConfirm(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const pluginModule = await getPluginModuleDetail(pluginId);
|
||||
const pluginModule = await getPreviewPluginModule(pluginId);
|
||||
onResetNode(moduleId, pluginModule);
|
||||
} catch (e) {
|
||||
return toast({
|
||||
@@ -147,18 +145,13 @@ const NodeCard = (props: Props) => {
|
||||
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'} />
|
||||
<Avatar src={avatar} 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}
|
||||
/>
|
||||
{intro && (
|
||||
<MyTooltip label={intro} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mb={'1px'} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { TTSTypeEnum } from '@/constants/app';
|
||||
import { AppTTSConfigType } from '@/types/app';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { audioSpeechModels } from '@/web/common/system/staticData';
|
||||
import MyModal from '@/components/MyModal';
|
||||
@@ -0,0 +1,342 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Grid,
|
||||
FormControl,
|
||||
useTheme,
|
||||
Image,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
BoxProps,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useFieldArray } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { variableTip } from '@fastgpt/global/core/module/template/tip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const VariableEdit = ({
|
||||
variables,
|
||||
onChange
|
||||
}: {
|
||||
variables: VariableItemType[];
|
||||
onChange: (data: VariableItemType[]) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const theme = useTheme();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const VariableTypeList = [
|
||||
{
|
||||
label: t('core.module.variable.text type'),
|
||||
icon: 'settingLight',
|
||||
key: VariableInputEnum.input
|
||||
},
|
||||
{
|
||||
label: t('core.module.variable.select type'),
|
||||
icon: 'settingLight',
|
||||
key: VariableInputEnum.select
|
||||
}
|
||||
];
|
||||
|
||||
const { isOpen: isOpenEdit, onOpen: onOpenEdit, onClose: onCloseEdit } = useDisclosure();
|
||||
const {
|
||||
reset: resetEdit,
|
||||
register: registerEdit,
|
||||
getValues: getValuesEdit,
|
||||
setValue: setValuesEdit,
|
||||
control: editVariableController,
|
||||
handleSubmit: handleSubmitEdit
|
||||
} = useForm<{ variable: VariableItemType }>();
|
||||
|
||||
const {
|
||||
fields: selectEnums,
|
||||
append: appendEnums,
|
||||
remove: removeEnums
|
||||
} = useFieldArray({
|
||||
control: editVariableController,
|
||||
name: 'variable.enums'
|
||||
});
|
||||
|
||||
const BoxBtnStyles: BoxProps = {
|
||||
cursor: 'pointer',
|
||||
px: 3,
|
||||
py: '2px',
|
||||
borderRadius: 'md',
|
||||
_hover: {
|
||||
bg: 'myGray.200'
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Image alt={''} src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('core.module.Variable')}
|
||||
<MyTooltip label={variableTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Flex
|
||||
{...BoxBtnStyles}
|
||||
onClick={() => {
|
||||
resetEdit({ variable: addVariable() });
|
||||
onOpenEdit();
|
||||
}}
|
||||
>
|
||||
+ {t('common.Add New')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{variables.length > 0 && (
|
||||
<Box mt={2} borderRadius={'lg'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('core.module.variable.variable name')}</Th>
|
||||
<Th>{t('core.module.variable.key')}</Th>
|
||||
<Th>{t('common.Require Input')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
resetEdit({ variable: item });
|
||||
onOpenEdit();
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
onChange(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
<MyModal
|
||||
iconSrc="/imgs/module/variable.png"
|
||||
title={t('core.module.Variable Setting')}
|
||||
isOpen={isOpenEdit}
|
||||
onClose={onCloseEdit}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box w={'70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch {...registerEdit('variable.required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box w={'80px'}>{t('core.module.variable.variable name')}</Box>
|
||||
<Input
|
||||
{...registerEdit('variable.label', {
|
||||
required: t('core.module.variable.variable name is required')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box w={'80px'}>{t('core.module.variable.key')}</Box>
|
||||
<Input
|
||||
{...registerEdit('variable.key', {
|
||||
required: t('core.module.variable.key is required')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Box mt={5} mb={2}>
|
||||
{t('core.module.Field Type')}
|
||||
</Box>
|
||||
<Grid gridTemplateColumns={'repeat(2,130px)'} gridGap={4}>
|
||||
{VariableTypeList.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
px={4}
|
||||
py={1}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
{...(item.key === getValuesEdit('variable.type')
|
||||
? {
|
||||
bg: 'myWhite.600'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
boxShadow: 'md'
|
||||
},
|
||||
onClick: () => {
|
||||
setValuesEdit('variable.type', item.key);
|
||||
setRefresh(!refresh);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<MyIcon name={item.icon as any} w={'16px'} />
|
||||
<Box ml={3}>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{getValuesEdit('variable.type') === VariableInputEnum.input && (
|
||||
<>
|
||||
<Box mt={5} mb={2}>
|
||||
{t('core.module.variable.text max length')}
|
||||
</Box>
|
||||
<Box>
|
||||
<NumberInput max={100} min={1} step={1} position={'relative'}>
|
||||
<NumberInputField
|
||||
{...registerEdit('variable.maxLen', {
|
||||
min: 1,
|
||||
max: 100,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
max={100}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{getValuesEdit('variable.type') === VariableInputEnum.select && (
|
||||
<>
|
||||
<Box mt={5} mb={2}>
|
||||
{t('core.module.variable.variable options')}
|
||||
</Box>
|
||||
<Box>
|
||||
{selectEnums.map((item, i) => (
|
||||
<Flex key={item.id} mb={2} alignItems={'center'}>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...registerEdit(`variable.enums.${i}.value`, {
|
||||
required: t('core.module.variable.variable option is value is required')
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
<MyIcon
|
||||
ml={3}
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'lg'}
|
||||
_hover={{ bg: 'red.100' }}
|
||||
onClick={() => removeEnums(i)}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
<Button
|
||||
variant={'solid'}
|
||||
w={'100%'}
|
||||
textAlign={'left'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
bg={'myGray.100 !important'}
|
||||
onClick={() => appendEnums({ value: '' })}
|
||||
>
|
||||
{t('core.module.variable add option')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onCloseEdit}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmitEdit(({ variable }) => {
|
||||
// check select
|
||||
if (variable.type === VariableInputEnum.select) {
|
||||
const enums = variable.enums.filter((item) => item.value);
|
||||
if (enums.length === 0) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.variable.variable option is required')
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const onChangeVariable = [...variables];
|
||||
// update
|
||||
if (variable.id) {
|
||||
const index = variables.findIndex((item) => item.id === variable.id);
|
||||
onChangeVariable[index] = variable;
|
||||
} else {
|
||||
onChangeVariable.push({
|
||||
...variable,
|
||||
id: nanoid()
|
||||
});
|
||||
}
|
||||
onChange(onChangeVariable);
|
||||
onCloseEdit();
|
||||
})}
|
||||
>
|
||||
{getValuesEdit('variable.id') ? t('common.Confirm Update') : t('common.Add New')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(VariableEdit);
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
id: nanoid(),
|
||||
key: 'key',
|
||||
label: 'label',
|
||||
type: VariableInputEnum.input,
|
||||
required: true,
|
||||
maxLen: 50,
|
||||
enums: [{ value: '' }]
|
||||
};
|
||||
export const addVariable = () => {
|
||||
const newVariable = { ...defaultVariable, key: nanoid(), id: '' };
|
||||
return newVariable;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Input, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
@@ -10,11 +10,8 @@ import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum,
|
||||
FlowNodeSpecialInputKeyEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
@@ -32,7 +29,7 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[FlowNodeSpecialInputKeyEnum.agents]: ({
|
||||
[ModuleInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = [],
|
||||
...props
|
||||
@@ -100,7 +97,7 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SourceHandle handleKey={item.key} valueType={FlowNodeValTypeEnum.boolean} />
|
||||
<SourceHandle handleKey={item.key} valueType={ModuleDataTypeEnum.boolean} />
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
@@ -12,11 +12,9 @@ import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/ty
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||
import { ContextExtractEnum } from '@/constants/flow/flowField';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
@@ -33,7 +31,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[ContextExtractEnum.extractKeys]: ({
|
||||
[ModuleInputKeyEnum.extractKeys]: ({
|
||||
value: extractKeys = [],
|
||||
...props
|
||||
}: {
|
||||
@@ -94,7 +92,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
key: ModuleInputKeyEnum.extractKeys,
|
||||
value: {
|
||||
...props,
|
||||
value: extractKeys.filter((extract) => item.key !== extract.key)
|
||||
@@ -130,7 +128,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
onClose={() => setEditExtractField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
const extracts: ContextExtractAgentItemType[] =
|
||||
inputs.find((item) => item.key === ContextExtractEnum.extractKeys)?.value || [];
|
||||
inputs.find((item) => item.key === ModuleInputKeyEnum.extractKeys)?.value || [];
|
||||
|
||||
const exists = extracts.find((item) => item.key === editExtractFiled.key);
|
||||
|
||||
@@ -141,9 +139,9 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
key: ModuleInputKeyEnum.extractKeys,
|
||||
value: {
|
||||
...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
|
||||
...inputs.find((input) => input.key === ModuleInputKeyEnum.extractKeys),
|
||||
value: newInputs
|
||||
}
|
||||
});
|
||||
@@ -152,7 +150,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
|
||||
@@ -11,9 +11,9 @@ import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
@@ -37,7 +37,7 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
key,
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length - 1}`,
|
||||
edit: true
|
||||
@@ -62,7 +62,7 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
value: {
|
||||
key: nanoid(),
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
|
||||
@@ -3,27 +3,22 @@ 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 { FlowNodeOutputTypeEnum } 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';
|
||||
import { defaultInputField, type EditFieldType } from '../modules/FieldEditModal';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toast } = useToast();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
@@ -37,7 +32,7 @@ const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
justifyContent={'right'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={4}
|
||||
mb={7}
|
||||
>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
@@ -47,6 +42,7 @@ const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
type: item.type,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
@@ -103,31 +99,7 @@ const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
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: []
|
||||
}
|
||||
});
|
||||
setEditField(defaultInputField);
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
@@ -140,6 +112,44 @@ const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
// create field
|
||||
if (e.createSign) {
|
||||
// check key repeat
|
||||
const memInput = inputs.find((item) => item.key === e.key);
|
||||
if (memInput) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '字段key已存在'
|
||||
});
|
||||
}
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
type: e.type,
|
||||
label: e.label,
|
||||
required: e.required,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
label: e.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
return setEditField(undefined);
|
||||
}
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
@@ -188,4 +198,4 @@ const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeInput);
|
||||
export default React.memo(NodePluginInput);
|
||||
@@ -3,27 +3,22 @@ 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 { FlowNodeOutputTypeEnum } 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 { EditFieldType, defaultOutputField } from '../modules/FieldEditModal';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toast } = useToast();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
@@ -37,22 +32,20 @@ const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
justifyContent={'left'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={4}
|
||||
mb={7}
|
||||
>
|
||||
<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
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<MyIcon
|
||||
@@ -104,34 +97,10 @@ const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
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: []
|
||||
}
|
||||
});
|
||||
setEditField(defaultOutputField);
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
添加出参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
@@ -141,6 +110,42 @@ const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
if (e.createSign) {
|
||||
// check key repeat
|
||||
const memInput = inputs.find((item) => item.key === e.key);
|
||||
if (memInput) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '字段key已存在'
|
||||
});
|
||||
}
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
type: e.type,
|
||||
label: e.label,
|
||||
required: e.required,
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: e.key,
|
||||
valueType: e.valueType,
|
||||
label: e.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
return setEditField(undefined);
|
||||
}
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
if (!memInput || !memOutput) return;
|
||||
@@ -188,4 +193,4 @@ const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeOutput);
|
||||
export default React.memo(NodePluginOutput);
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
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 NodeSimple = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} isPreview {...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(NodeSimple);
|
||||
@@ -11,15 +11,23 @@ const NodeSimple = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = 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 minW={'350px'} {...data}>
|
||||
{inputs.length > 0 && (
|
||||
<>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
{outputs.length > 0 && (
|
||||
<>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,19 +16,19 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { welcomeTextTip, variableTip } from '@/constants/flow/ModuleTemplate';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { welcomeTextTip, variableTip } from '@fastgpt/global/core/module/template/tip';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
|
||||
import VariableEdit from '../modules/VariableEdit';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import QGSwitch from '@/pages/app/detail/components/QGSwitch';
|
||||
import TTSSelect from '@/pages/app/detail/components/TTSSelect';
|
||||
import { splitGuideModule } from '@/global/core/app/modules/utils';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
|
||||
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
|
||||
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const theme = useTheme();
|
||||
@@ -57,7 +57,7 @@ export function WelcomeText({ data }: { data: FlowModuleItemType }) {
|
||||
const { inputs, moduleId } = data;
|
||||
|
||||
const welcomeText = useMemo(
|
||||
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
|
||||
() => inputs.find((item) => item.key === ModuleInputKeyEnum.welcomeText),
|
||||
[inputs]
|
||||
);
|
||||
|
||||
@@ -81,7 +81,7 @@ export function WelcomeText({ data }: { data: FlowModuleItemType }) {
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.welcomeText,
|
||||
key: ModuleInputKeyEnum.welcomeText,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...welcomeText,
|
||||
@@ -100,21 +100,19 @@ function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
|
||||
|
||||
const variables = useMemo(
|
||||
() =>
|
||||
(inputs.find((item) => item.key === SystemInputEnum.variables)
|
||||
(inputs.find((item) => item.key === ModuleInputKeyEnum.variables)
|
||||
?.value as VariableItemType[]) || [],
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const [editVariable, setEditVariable] = useState<VariableItemType>();
|
||||
|
||||
const updateVariables = useCallback(
|
||||
(value: VariableItemType[]) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
key: ModuleInputKeyEnum.variables,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
@@ -122,92 +120,7 @@ function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
({ variable }: { variable: VariableItemType }) => {
|
||||
updateVariables(variables.map((item) => (item.id === variable.id ? variable : item)));
|
||||
setEditVariable(undefined);
|
||||
},
|
||||
[updateVariables, variables]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex mb={1} alignItems={'center'}>
|
||||
<MyIcon name={'variable'} mr={2} w={'16px'} color={'#fb7c3d'} />
|
||||
<Box>对话框变量</Box>
|
||||
<MyTooltip label={variableTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Flex
|
||||
ml={2}
|
||||
textAlign={'right'}
|
||||
cursor={'pointer'}
|
||||
px={3}
|
||||
py={'2px'}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() => {
|
||||
const newVariable = addVariable();
|
||||
updateVariables(variables.concat(newVariable));
|
||||
setEditVariable(newVariable);
|
||||
}}
|
||||
>
|
||||
+ 新增
|
||||
</Flex>
|
||||
</Flex>
|
||||
{variables.length > 0 && (
|
||||
<TableContainer borderWidth={'1px'} borderBottom="none" borderRadius={'lg'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setEditVariable(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
updateVariables(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{!!editVariable && (
|
||||
<VariableEditModal
|
||||
defaultVariable={editVariable}
|
||||
onClose={() => setEditVariable(undefined)}
|
||||
onSubmit={onclickSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
|
||||
}
|
||||
|
||||
function QuestionGuide({ data }: { data: FlowModuleItemType }) {
|
||||
@@ -215,7 +128,7 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) {
|
||||
|
||||
const questionGuide = useMemo(
|
||||
() =>
|
||||
(inputs.find((item) => item.key === SystemInputEnum.questionGuide)?.value as boolean) ||
|
||||
(inputs.find((item) => item.key === ModuleInputKeyEnum.questionGuide)?.value as boolean) ||
|
||||
false,
|
||||
[inputs]
|
||||
);
|
||||
@@ -228,10 +141,10 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) {
|
||||
const value = e.target.checked;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.questionGuide,
|
||||
key: ModuleInputKeyEnum.questionGuide,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.questionGuide),
|
||||
value
|
||||
}
|
||||
});
|
||||
@@ -250,10 +163,10 @@ function TTSGuide({ data }: { data: FlowModuleItemType }) {
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.tts,
|
||||
key: ModuleInputKeyEnum.tts,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.tts),
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.tts),
|
||||
value: e
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,12 +6,12 @@ import { AddIcon } from '@chakra-ui/icons';
|
||||
import NodeCard from '../../modules/NodeCard';
|
||||
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';
|
||||
import { VariableInputEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import VariableEditModal, { addVariable } from '../../../../VariableEditModal';
|
||||
import VariableEditModal, { addVariable } from '../../modules/VariableEdit';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
@@ -29,102 +29,31 @@ const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
const variables = useMemo(
|
||||
() =>
|
||||
(inputs.find((item) => item.key === SystemInputEnum.variables)
|
||||
(inputs.find((item) => item.key === ModuleInputKeyEnum.variables)
|
||||
?.value as VariableItemType[]) || [],
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const [editVariable, setEditVariable] = useState<VariableItemType>();
|
||||
|
||||
const updateVariables = useCallback(
|
||||
(value: VariableItemType[]) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
({ variable }: { variable: VariableItemType }) => {
|
||||
updateVariables(variables.map((item) => (item.id === variable.id ? variable : item)));
|
||||
setEditVariable(undefined);
|
||||
},
|
||||
[updateVariables, variables]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setEditVariable(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
updateVariables(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Box mt={2} textAlign={'right'}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() => {
|
||||
const newVariable = addVariable();
|
||||
updateVariables(variables.concat(newVariable));
|
||||
setEditVariable(newVariable);
|
||||
}}
|
||||
>
|
||||
新增
|
||||
</Button>
|
||||
</Box>
|
||||
<VariableEditModal
|
||||
variables={variables}
|
||||
onChange={(e) =>
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.variables,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.variables),
|
||||
value: e
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
{!!editVariable && (
|
||||
<VariableEditModal
|
||||
defaultVariable={editVariable}
|
||||
onClose={() => setEditVariable(undefined)}
|
||||
onSubmit={onclickSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,13 +28,12 @@ import MyTooltip from '@/components/MyTooltip';
|
||||
import TargetHandle from './TargetHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { AIChatProps } from '@/types/core/aiChat';
|
||||
import type { AIChatModuleProps } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { chatModelList } from '@/web/common/system/staticData';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
|
||||
@@ -53,13 +52,31 @@ export const Label = React.memo(function Label({
|
||||
inputKey: string;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) {
|
||||
const { required = false, description, edit, label, type, valueType } = item;
|
||||
const { t } = useTranslation();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const {
|
||||
required = false,
|
||||
description,
|
||||
edit,
|
||||
label,
|
||||
type,
|
||||
valueType,
|
||||
showTargetInApp,
|
||||
showTargetInPlugin
|
||||
} = item;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
const targetHandle = useMemo(() => {
|
||||
if (type === FlowNodeInputTypeEnum.target) return true;
|
||||
if (mode === 'app' && showTargetInApp) return true;
|
||||
if (mode === 'plugin' && showTargetInPlugin) return true;
|
||||
return false;
|
||||
}, [mode, showTargetInApp, showTargetInPlugin, type]);
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{label}
|
||||
{t(label)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
@@ -78,9 +95,7 @@ export const Label = React.memo(function Label({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{(type === FlowNodeInputTypeEnum.target || valueType) && (
|
||||
<TargetHandle handleKey={inputKey} valueType={valueType} />
|
||||
)}
|
||||
{targetHandle && <TargetHandle handleKey={inputKey} valueType={valueType} />}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
@@ -93,6 +108,7 @@ export const Label = React.memo(function Label({
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
label: item.label,
|
||||
type: item.type,
|
||||
valueType: item.valueType,
|
||||
required: item.required,
|
||||
key: inputKey,
|
||||
@@ -210,9 +226,6 @@ const RenderInput = ({
|
||||
{item.type === FlowNodeInputTypeEnum.aiSettings && (
|
||||
<AISetting inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.maxToken && (
|
||||
<MaxTokenRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowNodeInputTypeEnum.selectChatModel && (
|
||||
<SelectChatModelRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
@@ -381,7 +394,7 @@ var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderP
|
||||
inputs.forEach((item) => {
|
||||
obj[item.key] = item.value;
|
||||
});
|
||||
return obj as AIChatProps;
|
||||
return obj as AIChatModuleProps;
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
@@ -427,50 +440,12 @@ var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderP
|
||||
);
|
||||
});
|
||||
|
||||
var MaxTokenRender = React.memo(function MaxTokenRender({
|
||||
inputs = [],
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const model = inputs.find((item) => item.key === 'model')?.value;
|
||||
const modelData = chatModelList.find((item) => item.model === model);
|
||||
const maxToken = modelData ? modelData.maxResponse : 4000;
|
||||
const markList = [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${maxToken}`, value: maxToken }
|
||||
];
|
||||
|
||||
return (
|
||||
<Box pt={5} pb={4} px={2}>
|
||||
<MySlider
|
||||
markList={markList}
|
||||
width={'100%'}
|
||||
min={item.min || 100}
|
||||
max={maxToken}
|
||||
step={item.step || 1}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
inputs = [],
|
||||
item,
|
||||
moduleId
|
||||
}: RenderProps) {
|
||||
const modelList = (item.customData?.() as LLMModelItemType[]) || chatModelList || [];
|
||||
const modelList = chatModelList || [];
|
||||
|
||||
function onChangeModel(e: string) {
|
||||
{
|
||||
@@ -531,6 +506,7 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
|
||||
var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, moduleId }: RenderProps) {
|
||||
const theme = useTheme();
|
||||
const { mode } = useFlowProviderStore();
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
@@ -538,9 +514,9 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const showKbList = useMemo(() => {
|
||||
const selectedDatasets = useMemo(() => {
|
||||
const value = item.value as SelectedDatasetType;
|
||||
return allDatasets.filter((dataset) => value.find((kb) => kb.datasetId === dataset._id));
|
||||
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
|
||||
}, [allDatasets, item.value]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
@@ -551,7 +527,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
{showKbList.map((item) => (
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
@@ -574,22 +550,24 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
activeDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseKbSelect}
|
||||
/>
|
||||
{isOpenKbSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
defaultSelectedDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseKbSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@ import SourceHandle from './SourceHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import { SystemOutputEnum } from '@/constants/app';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import type { EditFieldType, EditFieldModeType } from '../modules/FieldEditModal';
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
@@ -25,7 +26,8 @@ export const Label = ({
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const { label, description, edit } = item;
|
||||
const { t } = useTranslation();
|
||||
const { label = '', description, edit } = item;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
@@ -71,11 +73,11 @@ export const Label = ({
|
||||
</>
|
||||
)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<MyTooltip label={t(description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box>{label}</Box>
|
||||
<Box>{t(label)}</Box>
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
@@ -123,8 +125,8 @@ const RenderOutput = ({
|
||||
const sortOutput = useMemo(
|
||||
() =>
|
||||
[...flowOutputList].sort((a, b) => {
|
||||
if (a.key === SystemOutputEnum.finish) return -1;
|
||||
if (b.key === SystemOutputEnum.finish) return 1;
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
return 0;
|
||||
}),
|
||||
[flowOutputList]
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeTip } from '@/constants/flow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
}
|
||||
|
||||
const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? FlowNodeValTypeEnum.any;
|
||||
const valType = valueType ?? ModuleDataTypeEnum.any;
|
||||
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowNodeValTypeEnum.any] as any),
|
||||
: (FlowValueTypeStyle[ModuleDataTypeEnum.any] as any),
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -34,8 +34,8 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeTip[valType].label),
|
||||
example: FlowValueTypeTip[valType].example
|
||||
type: t(FlowValueTypeMap[valType].label),
|
||||
example: FlowValueTypeMap[valType].example
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, OnConnect, Position } from 'reactflow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeTip } from '@/constants/flow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
valueType?: `${ModuleDataTypeEnum}`;
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
|
||||
const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? FlowNodeValTypeEnum.any;
|
||||
const valType = valueType ?? ModuleDataTypeEnum.any;
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowNodeValTypeEnum.any] as any),
|
||||
: (FlowValueTypeStyle[ModuleDataTypeEnum.any] as any),
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -35,8 +35,8 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeTip[valType].label),
|
||||
example: FlowValueTypeTip[valType].example
|
||||
type: t(FlowValueTypeMap[valType].label),
|
||||
example: FlowValueTypeMap[valType].example
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
|
||||
@@ -2,13 +2,13 @@ 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 { edgeOptions, connectionLineStyle } from '@/web/core/modules/constants/flowUi';
|
||||
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 ModuleTemplateList, { type ModuleTemplateProps } from './ModuleTemplateList';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
@@ -27,8 +27,8 @@ const nodeTypes = {
|
||||
[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.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
@@ -40,7 +40,7 @@ type Props = {
|
||||
} & ModuleTemplateProps;
|
||||
|
||||
const Container = React.memo(function Container(props: Props) {
|
||||
const { modules = [], Header, systemTemplates, pluginTemplates, show2Plugin } = props;
|
||||
const { modules = [], Header, systemTemplates, pluginTemplates } = props;
|
||||
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
@@ -114,10 +114,9 @@ const Container = React.memo(function Container(props: Props) {
|
||||
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
|
||||
</ReactFlow>
|
||||
|
||||
<TemplateList
|
||||
<ModuleTemplateList
|
||||
systemTemplates={systemTemplates}
|
||||
pluginTemplates={pluginTemplates}
|
||||
show2Plugin={show2Plugin}
|
||||
isOpen={isOpenTemplate}
|
||||
onClose={onCloseTemplate}
|
||||
/>
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Grid,
|
||||
FormControl,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { VariableInputEnum } from '@/constants/app';
|
||||
import type { VariableItemType } from '@/types/app';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useFieldArray } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
|
||||
const VariableTypeList = [
|
||||
{ label: '文本', icon: 'settingLight', key: VariableInputEnum.input },
|
||||
{ label: '下拉单选', icon: 'settingLight', key: VariableInputEnum.select }
|
||||
];
|
||||
|
||||
export type VariableFormType = {
|
||||
variable: VariableItemType;
|
||||
};
|
||||
|
||||
const VariableEditModal = ({
|
||||
defaultVariable,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultVariable: VariableItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: VariableFormType) => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const { reset, getValues, setValue, register, control, handleSubmit } = useForm<VariableFormType>(
|
||||
{
|
||||
defaultValues: {
|
||||
variable: defaultVariable
|
||||
}
|
||||
}
|
||||
);
|
||||
const {
|
||||
fields: selectEnums,
|
||||
append: appendEnums,
|
||||
remove: removeEnums
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: 'variable.enums'
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'}>
|
||||
<MyIcon name={'variable'} mr={2} w={'24px'} color={'#FF8A4C'} />
|
||||
变量设置
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box w={'70px'}>必填</Box>
|
||||
<Switch {...register('variable.required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box w={'80px'}>变量名</Box>
|
||||
<Input {...register('variable.label', { required: '变量名不能为空' })} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box w={'80px'}>变量 key</Box>
|
||||
<Input {...register('variable.key', { required: '变量 key 不能为空' })} />
|
||||
</Flex>
|
||||
|
||||
<Box mt={5} mb={2}>
|
||||
字段类型
|
||||
</Box>
|
||||
<Grid gridTemplateColumns={'repeat(2,130px)'} gridGap={4}>
|
||||
{VariableTypeList.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
px={4}
|
||||
py={1}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
{...(item.key === getValues('variable.type')
|
||||
? {
|
||||
bg: 'myWhite.600'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
boxShadow: 'md'
|
||||
},
|
||||
onClick: () => {
|
||||
setValue('variable.type', item.key);
|
||||
setRefresh(!refresh);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<MyIcon name={item.icon as any} w={'16px'} />
|
||||
<Box ml={3}>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{getValues('variable.type') === VariableInputEnum.input && (
|
||||
<>
|
||||
<Box mt={5} mb={2}>
|
||||
最大长度
|
||||
</Box>
|
||||
<Box>
|
||||
<NumberInput max={100} min={1} step={1} position={'relative'}>
|
||||
<NumberInputField
|
||||
{...register('variable.maxLen', {
|
||||
min: 1,
|
||||
max: 100,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
max={100}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{getValues('variable.type') === VariableInputEnum.select && (
|
||||
<>
|
||||
<Box mt={5} mb={2}>
|
||||
选项
|
||||
</Box>
|
||||
<Box>
|
||||
{selectEnums.map((item, i) => (
|
||||
<Flex key={item.id} mb={2} alignItems={'center'}>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...register(`variable.enums.${i}.value`, {
|
||||
required: '选项内容不能为空'
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
<MyIcon
|
||||
ml={3}
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
p={2}
|
||||
borderRadius={'lg'}
|
||||
_hover={{ bg: 'red.100' }}
|
||||
onClick={() => removeEnums(i)}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
<Button
|
||||
variant={'solid'}
|
||||
w={'100%'}
|
||||
textAlign={'left'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
bg={'myGray.100 !important'}
|
||||
onClick={() => appendEnums({ value: '' })}
|
||||
>
|
||||
添加选项
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(VariableEditModal);
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
id: nanoid(),
|
||||
key: 'key',
|
||||
label: 'label',
|
||||
type: VariableInputEnum.input,
|
||||
required: true,
|
||||
maxLen: 50,
|
||||
enums: [{ value: '' }]
|
||||
};
|
||||
export const addVariable = () => {
|
||||
const newVariable = { ...defaultVariable, key: nanoid(), id: nanoid() };
|
||||
return newVariable;
|
||||
};
|
||||
@@ -219,16 +219,23 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<MyModal isOpen={!!apiKey} w={['400px', '600px']} onClose={() => setApiKey('')}>
|
||||
<Box py={3} px={5}>
|
||||
<Box fontWeight={'bold'} fontSize={'2xl'}>
|
||||
新的 API 秘钥
|
||||
<MyModal
|
||||
isOpen={!!apiKey}
|
||||
w={['400px', '600px']}
|
||||
iconSrc="/imgs/modal/key.svg"
|
||||
title={
|
||||
<Box>
|
||||
<Box fontWeight={'bold'} fontSize={'xl'}>
|
||||
新的 API 秘钥
|
||||
</Box>
|
||||
<Box fontSize={'sm'} color={'myGray.600'}>
|
||||
请保管好你的秘钥,秘钥不会再次展示~
|
||||
</Box>
|
||||
</Box>
|
||||
<Box fontSize={'sm'} color={'myGray.600'}>
|
||||
请保管好你的秘钥,秘钥不会再次展示~
|
||||
</Box>
|
||||
</Box>
|
||||
<ModalBody>
|
||||
}
|
||||
onClose={() => setApiKey('')}
|
||||
>
|
||||
<ModalBody pt={5}>
|
||||
<Flex
|
||||
bg={'myGray.100'}
|
||||
px={3}
|
||||
@@ -236,6 +243,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={() => copyData(apiKey)}
|
||||
>
|
||||
<Box flex={1}>{apiKey}</Box>
|
||||
@@ -292,7 +300,11 @@ function EditKeyModal({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} title={isEdit ? t('outlink.Edit API Key') : t('outlink.Create API Key')}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/modal/key.svg"
|
||||
title={isEdit ? t('outlink.Edit API Key') : t('outlink.Create API Key')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 90px'}>{t('Name')}:</Box>
|
||||
|
||||
@@ -98,6 +98,7 @@ function EditModal({
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/team.svg"
|
||||
title={defaultData.id ? t('user.team.Update Team') : t('user.team.Create Team')}
|
||||
>
|
||||
<ModalBody>
|
||||
|
||||
@@ -68,13 +68,14 @@ const InviteModal = ({
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="/imgs/modal/team.svg"
|
||||
title={
|
||||
<>
|
||||
<Box>
|
||||
<Box>{t('user.team.Invite Member')}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
|
||||
{t('user.team.Invite Member Tips')}
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
}
|
||||
maxW={['90vw', '400px']}
|
||||
overflow={'unset'}
|
||||
|
||||
@@ -64,7 +64,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
isLoading: isLoadingTeams,
|
||||
isFetching: isLoadingTeams,
|
||||
refetch: refetchTeam
|
||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||
const defaultTeam = useMemo(
|
||||
|
||||
@@ -53,13 +53,14 @@ const UpdateInviteModal = () => {
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={inviteList && inviteList.length > 0}
|
||||
iconSrc="/imgs/modal/team.svg"
|
||||
title={
|
||||
<>
|
||||
<Box>
|
||||
<Box>{t('user.team.Processing invitations')}</Box>
|
||||
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('user.team.Processing invitations Tips', { amount: inviteList?.length })}
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
}
|
||||
maxW={['90vw', '500px']}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,8 @@ export const defaultApp: AppDetailType = {
|
||||
_id: '',
|
||||
userId: 'userId',
|
||||
name: '模型加载中',
|
||||
type: 'basic',
|
||||
type: 'simple',
|
||||
simpleTemplateId: 'fastgpt-universal',
|
||||
avatar: '/icon/logo.svg',
|
||||
intro: '',
|
||||
updateTime: Date.now(),
|
||||
@@ -26,26 +27,6 @@ export const defaultOutLinkForm: OutLinkEditType = {
|
||||
}
|
||||
};
|
||||
|
||||
/* module special */
|
||||
export enum SystemInputEnum {
|
||||
'welcomeText' = 'welcomeText',
|
||||
'variables' = 'variables',
|
||||
'switch' = 'switch', // a trigger switch
|
||||
'history' = 'history',
|
||||
'userChatInput' = 'userChatInput',
|
||||
'questionGuide' = 'questionGuide',
|
||||
'tts' = 'tts',
|
||||
isResponseAnswerText = 'isResponseAnswerText'
|
||||
}
|
||||
export enum SystemOutputEnum {
|
||||
finish = 'finish'
|
||||
}
|
||||
|
||||
export enum VariableInputEnum {
|
||||
input = 'input',
|
||||
select = 'select'
|
||||
}
|
||||
|
||||
export enum TTSTypeEnum {
|
||||
none = 'none',
|
||||
web = 'web',
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export enum ContextExtractEnum {
|
||||
extractKeys = 'extractKeys',
|
||||
content = 'content',
|
||||
description = 'description',
|
||||
success = 'success',
|
||||
failed = 'failed',
|
||||
fields = 'fields'
|
||||
}
|
||||
|
||||
export enum HttpPropsEnum {
|
||||
url = 'url',
|
||||
failed = 'failed'
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import { FlowNodeTypeEnum, FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const FlowValueTypeStyle: Record<`${FlowNodeValTypeEnum}`, BoxProps> = {
|
||||
[FlowNodeValTypeEnum.string]: {
|
||||
background: '#36ADEF'
|
||||
},
|
||||
[FlowNodeValTypeEnum.number]: {
|
||||
background: '#FB7C3C'
|
||||
},
|
||||
[FlowNodeValTypeEnum.boolean]: {
|
||||
background: '#E7D118'
|
||||
},
|
||||
[FlowNodeValTypeEnum.chatHistory]: {
|
||||
background: '#00A9A6'
|
||||
},
|
||||
[FlowNodeValTypeEnum.datasetQuote]: {
|
||||
background: '#A558C9'
|
||||
},
|
||||
[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> = {
|
||||
[FlowNodeTypeEnum.historyNode]: true,
|
||||
[FlowNodeTypeEnum.questionInput]: true,
|
||||
[FlowNodeTypeEnum.pluginInput]: true
|
||||
};
|
||||
|
||||
export const edgeOptions = {
|
||||
style: {
|
||||
strokeWidth: 1.5,
|
||||
stroke: '#5A646Es'
|
||||
}
|
||||
};
|
||||
export const connectionLineStyle = { strokeWidth: 1.5, stroke: '#5A646Es' };
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { SystemInputEnum } from '../app';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const Input_Template_TFSwitch: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.switch,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '触发器',
|
||||
valueType: FlowNodeValTypeEnum.any
|
||||
};
|
||||
|
||||
export const Input_Template_History: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.history,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '聊天记录',
|
||||
valueType: FlowNodeValTypeEnum.chatHistory
|
||||
};
|
||||
|
||||
export const Input_Template_UserChatInput: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.userChatInput,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '用户问题',
|
||||
required: true,
|
||||
valueType: FlowNodeValTypeEnum.string
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { SystemOutputEnum } from '../app';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const Output_Template_Finish: FlowNodeOutputItemType = {
|
||||
key: SystemOutputEnum.finish,
|
||||
label: '模块调用结束',
|
||||
description: '模块调用结束时触发',
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
} from '@fastgpt/global/core/ai/model.d';
|
||||
|
||||
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
|
||||
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
export type ConfigFileType = {
|
||||
FeConfig: FeConfigsType;
|
||||
@@ -31,4 +32,5 @@ export type InitDateResponse = {
|
||||
feConfigs: FeConfigsType;
|
||||
priceMd: string;
|
||||
systemVersion: string;
|
||||
simpleModeTemplates: AppSimpleEditConfigTemplateType[];
|
||||
};
|
||||
|
||||
30
projects/app/src/global/core/app/constants.ts
Normal file
30
projects/app/src/global/core/app/constants.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
|
||||
|
||||
export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateType = {
|
||||
id: 'fastgpt-universal',
|
||||
name: '通用模板',
|
||||
desc: '通用模板\n可完全自行配置AI属性和知识库',
|
||||
systemForm: {
|
||||
aiSettings: {
|
||||
model: true,
|
||||
systemPrompt: true,
|
||||
temperature: true,
|
||||
maxToken: true,
|
||||
quoteTemplate: true,
|
||||
quotePrompt: true
|
||||
},
|
||||
dataset: {
|
||||
datasets: true,
|
||||
similarity: true,
|
||||
limit: true,
|
||||
rerank: true,
|
||||
searchEmptyText: true
|
||||
},
|
||||
userGuide: {
|
||||
welcomeText: true,
|
||||
variables: true,
|
||||
questionGuide: true,
|
||||
tts: true
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { AppTTSConfigType, VariableItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
export const getGuideModule = (modules: ModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowNodeTypeEnum.userGuide);
|
||||
|
||||
export const splitGuideModule = (guideModules?: ModuleItemType) => {
|
||||
const welcomeText: string =
|
||||
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
|
||||
|
||||
const variableModules: VariableItemType[] =
|
||||
guideModules?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
|
||||
|
||||
const questionGuide: boolean =
|
||||
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.questionGuide)?.value ||
|
||||
false;
|
||||
|
||||
const ttsConfig: AppTTSConfigType = guideModules?.inputs?.find(
|
||||
(item) => item.key === SystemInputEnum.tts
|
||||
)?.value || { type: 'web' };
|
||||
|
||||
return {
|
||||
welcomeText,
|
||||
variableModules,
|
||||
questionGuide,
|
||||
ttsConfig
|
||||
};
|
||||
};
|
||||
2
projects/app/src/global/core/chat/api.d.ts
vendored
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import type { AppTTSConfigType } from '@/types/app';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
export type GetChatSpeechProps = {
|
||||
ttsConfig: AppTTSConfigType;
|
||||
|
||||
@@ -27,6 +27,7 @@ Router.events.on('routeChangeError', () => NProgress.done());
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
keepPreviousData: true,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
cacheTime: 10
|
||||
|
||||
@@ -26,7 +26,12 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('user.Bill Detail')}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('user.Bill Detail')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>用户:</Box>
|
||||
@@ -63,15 +68,15 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
|
||||
<Th>模块名</Th>
|
||||
<Th>AI模型</Th>
|
||||
<Th>Token长度</Th>
|
||||
<Th>费用</Th>
|
||||
<Th>费用(¥)</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{filterBillList.map((item, i) => (
|
||||
<Tr key={i}>
|
||||
<Td>{t(item.moduleName)}</Td>
|
||||
<Td>{item.model}</Td>
|
||||
<Td>{item.tokenLen}</Td>
|
||||
<Td>{item.model || '-'}</Td>
|
||||
<Td>{item.tokenLen || '-'}</Td>
|
||||
<Td>{formatPrice(item.amount)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
|
||||
@@ -29,7 +29,12 @@ const OpenAIAccountModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/openai.svg"
|
||||
title={t('user.OpenAI Account Setting')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
可以填写 OpenAI/OneAPI 的相关秘钥。如果你填写了该内容,在线上平台使用 OpenAI Chat
|
||||
|
||||
@@ -67,9 +67,10 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
|
||||
isOpen={true}
|
||||
onClose={payId ? undefined : onClose}
|
||||
title={t('user.Pay')}
|
||||
iconSrc="/imgs/modal/pay.svg"
|
||||
isCentered={!payId}
|
||||
>
|
||||
<ModalBody p={0} minH={payId ? 'auto' : '70vh'} display={'flex'} flexDirection={'column'}>
|
||||
<ModalBody px={0} minH={payId ? 'auto' : '70vh'} display={'flex'} flexDirection={'column'}>
|
||||
{!payId && (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(4,1fr)'} gridGap={5} mb={4} px={6}>
|
||||
|
||||
@@ -37,7 +37,12 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} title={t('user.Update Password')}>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/password.svg"
|
||||
title={t('user.Update Password')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>旧密码:</Box>
|
||||
|
||||
@@ -106,7 +106,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
(tab: string) => {
|
||||
if (tab === TabEnum.loginout) {
|
||||
openConfirm(() => {
|
||||
clearToken();
|
||||
setUserInfo(null);
|
||||
router.replace('/login');
|
||||
})();
|
||||
|
||||
@@ -41,7 +41,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
]);
|
||||
} catch (error) {}
|
||||
|
||||
await initPgData();
|
||||
try {
|
||||
await initPgData();
|
||||
} catch (error) {}
|
||||
|
||||
await MongoDataset.updateMany(
|
||||
{},
|
||||
|
||||
40
projects/app/src/pages/api/admin/initv462.ts
Normal file
40
projects/app/src/pages/api/admin/initv462.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { delay } from '@/utils/tools';
|
||||
import { PgClient } from '@fastgpt/service/common/pg';
|
||||
import {
|
||||
DatasetDataIndexTypeEnum,
|
||||
PgDatasetTableName
|
||||
} from '@fastgpt/global/core/dataset/constant';
|
||||
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
import { defaultQAModels } from '@fastgpt/global/core/ai/model';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
|
||||
let success = 0;
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { limit = 50 } = req.body as { limit: number };
|
||||
await authCert({ req, authRoot: true });
|
||||
await connectToDatabase();
|
||||
|
||||
await initFullTextToken(limit);
|
||||
|
||||
jsonRes(res, {
|
||||
message: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function initFullTextToken(limit = 50) {}
|
||||
@@ -0,0 +1,615 @@
|
||||
/*
|
||||
universal mode.
|
||||
@author: FastGpt Team
|
||||
*/
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { formData, chatModelMaxToken, chatModelList } = req.body as FormatForm2ModulesProps;
|
||||
|
||||
const modules = [
|
||||
...(formData.dataset.datasets.length > 0
|
||||
? datasetTemplate({ formData, maxToken: chatModelMaxToken })
|
||||
: simpleChatTemplate({ formData, maxToken: chatModelMaxToken }))
|
||||
];
|
||||
|
||||
jsonRes<ModuleItemType[]>(res, {
|
||||
data: modules
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function simpleChatTemplate({
|
||||
formData,
|
||||
maxToken
|
||||
}: {
|
||||
formData: AppSimpleEditFormType;
|
||||
maxToken: number;
|
||||
}): ModuleItemType[] {
|
||||
return [
|
||||
{
|
||||
moduleId: 'userChatInput',
|
||||
name: '用户问题(对话入口)',
|
||||
logo: '/imgs/module/userChatInput.png',
|
||||
flowType: 'questionInput',
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'systemInput',
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
label: '用户问题',
|
||||
type: 'source',
|
||||
valueType: 'string',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'history',
|
||||
name: '聊天记录',
|
||||
logo: '/imgs/module/history.png',
|
||||
flowType: 'historyNode',
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
type: 'numberInput',
|
||||
label: '最长记录数',
|
||||
value: 10,
|
||||
min: 0,
|
||||
max: 50,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'hidden',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
name: 'AI 对话',
|
||||
logo: '/imgs/module/AI.png',
|
||||
flowType: 'chatNode',
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 981.9682828103937,
|
||||
y: 890.014595014464
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.switch',
|
||||
valueType: 'any',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectChatModel',
|
||||
label: '对话模型',
|
||||
required: true,
|
||||
value: formData.aiSettings.model,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
type: 'hidden',
|
||||
label: '温度',
|
||||
value: 1,
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
markList: [
|
||||
{
|
||||
label: '严谨',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '发散',
|
||||
value: 10
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
type: 'hidden',
|
||||
label: '回复上限',
|
||||
value: maxToken,
|
||||
min: 100,
|
||||
max: 4000,
|
||||
step: 50,
|
||||
markList: [
|
||||
{
|
||||
label: '100',
|
||||
value: 100
|
||||
},
|
||||
{
|
||||
label: '4000',
|
||||
value: 4000
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'isResponseAnswerText',
|
||||
type: 'hidden',
|
||||
label: '返回AI内容',
|
||||
valueType: 'boolean',
|
||||
value: true,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
valueType: 'string',
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
valueType: 'string',
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'aiSettings',
|
||||
type: 'aiSettings',
|
||||
label: '',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
max: 300,
|
||||
valueType: 'string',
|
||||
description:
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
|
||||
placeholder:
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
|
||||
value: formData.aiSettings.systemPrompt,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: 'datasetQuote',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.chat history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.user question',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answerText',
|
||||
label: 'AI回复',
|
||||
description: '将在 stream 回复完毕后触发',
|
||||
valueType: 'string',
|
||||
type: 'source',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: 'core.module.output.label.running done',
|
||||
description: 'core.module.output.description.running done',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
function datasetTemplate({
|
||||
formData,
|
||||
maxToken
|
||||
}: {
|
||||
formData: AppSimpleEditFormType;
|
||||
maxToken: number;
|
||||
}): ModuleItemType[] {
|
||||
return [
|
||||
{
|
||||
moduleId: 'userChatInput',
|
||||
name: '用户问题(对话入口)',
|
||||
logo: '/imgs/module/userChatInput.png',
|
||||
flowType: 'questionInput',
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'systemInput',
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
label: '用户问题',
|
||||
type: 'source',
|
||||
valueType: 'string',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
},
|
||||
{
|
||||
moduleId: 'datasetSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'history',
|
||||
name: '聊天记录',
|
||||
logo: '/imgs/module/history.png',
|
||||
flowType: 'historyNode',
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
type: 'numberInput',
|
||||
label: '最长记录数',
|
||||
value: 6,
|
||||
min: 0,
|
||||
max: 50,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'hidden',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'datasetSearch',
|
||||
name: '知识库搜索',
|
||||
logo: '/imgs/module/db.png',
|
||||
flowType: 'datasetSearchNode',
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 956.0838440206068,
|
||||
y: 887.462827870246
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'datasets',
|
||||
value: formData.dataset.datasets,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '关联的知识库',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
value: 0.5,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '相似度',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
value: 8,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '单次搜索上限',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '触发器',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'rerank',
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '结果重排',
|
||||
description: '将召回的结果进行进一步重排,可增加召回率',
|
||||
plusField: true,
|
||||
connected: true,
|
||||
value: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'isEmpty',
|
||||
label: '搜索结果为空',
|
||||
type: 'source',
|
||||
valueType: 'boolean',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'unEmpty',
|
||||
label: '搜索结果不为空',
|
||||
type: 'source',
|
||||
valueType: 'boolean',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
label: '引用内容',
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: 'source',
|
||||
valueType: 'datasetQuote',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'quoteQA'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: 'core.module.output.label.running done',
|
||||
description: 'core.module.output.description.running done',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
name: 'AI 对话',
|
||||
logo: '/imgs/module/AI.png',
|
||||
flowType: 'chatNode',
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 1551.71405495818,
|
||||
y: 977.4911578918461
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.switch',
|
||||
valueType: 'any',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectChatModel',
|
||||
label: '对话模型',
|
||||
required: true,
|
||||
value: formData.aiSettings.model,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
type: 'hidden',
|
||||
label: '温度',
|
||||
value: 0,
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
markList: [
|
||||
{
|
||||
label: '严谨',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '发散',
|
||||
value: 10
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
type: 'hidden',
|
||||
label: '回复上限',
|
||||
value: maxToken,
|
||||
min: 100,
|
||||
max: 4000,
|
||||
step: 50,
|
||||
markList: [
|
||||
{
|
||||
label: '100',
|
||||
value: 100
|
||||
},
|
||||
{
|
||||
label: '4000',
|
||||
value: 4000
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'isResponseAnswerText',
|
||||
type: 'hidden',
|
||||
label: '返回AI内容',
|
||||
valueType: 'boolean',
|
||||
value: true,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
valueType: 'string',
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
valueType: 'string',
|
||||
value: '',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'aiSettings',
|
||||
type: 'aiSettings',
|
||||
label: '',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
max: 300,
|
||||
valueType: 'string',
|
||||
description:
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
|
||||
placeholder:
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
|
||||
value: formData.aiSettings.systemPrompt,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: 'datasetQuote',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.chat history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.user question',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answerText',
|
||||
label: 'AI回复',
|
||||
description: '将在 stream 回复完毕后触发',
|
||||
valueType: 'string',
|
||||
type: 'source',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: 'core.module.output.label.running done',
|
||||
description: 'core.module.output.description.running done',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
universal mode.
|
||||
@author: FastGpt Team
|
||||
*/
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleDataTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { formData, chatModelList } = req.body as FormatForm2ModulesProps;
|
||||
|
||||
const modules =
|
||||
formData.dataset.datasets.length > 0
|
||||
? datasetTemplate(formData)
|
||||
: simpleChatTemplate(formData);
|
||||
|
||||
jsonRes<ModuleItemType[]>(res, {
|
||||
data: modules
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function chatModelInput(formData: AppSimpleEditFormType): FlowNodeInputItemType[] {
|
||||
return [
|
||||
{
|
||||
key: 'model',
|
||||
value: formData.aiSettings.model,
|
||||
type: 'custom',
|
||||
label: '对话模型',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
value: formData.aiSettings.temperature,
|
||||
type: 'slider',
|
||||
label: '温度',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
value: formData.aiSettings.maxToken,
|
||||
type: 'custom',
|
||||
label: '回复上限',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
value: formData.aiSettings.systemPrompt || '',
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatIsResponseText,
|
||||
value: true,
|
||||
type: 'hidden',
|
||||
label: '返回AI内容',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
value: formData.aiSettings.quoteTemplate || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: formData.aiSettings.quotePrompt || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: 'target',
|
||||
label: '触发器',
|
||||
connected: formData.dataset.datasets.length > 0 && !!formData.dataset.searchEmptyText
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
connected: formData.dataset.datasets.length > 0
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: 'target',
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
}
|
||||
];
|
||||
}
|
||||
function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
return [
|
||||
{
|
||||
name: '用户问题(对话入口)',
|
||||
flowType: FlowNodeTypeEnum.questionInput,
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
connected: true,
|
||||
label: '用户问题',
|
||||
type: 'target'
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
moduleId: 'userChatInput'
|
||||
},
|
||||
{
|
||||
name: '聊天记录',
|
||||
flowType: FlowNodeTypeEnum.historyNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
value: 6,
|
||||
connected: true,
|
||||
type: 'numberInput',
|
||||
label: '最长记录数'
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'hidden',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
moduleId: 'history'
|
||||
},
|
||||
{
|
||||
name: 'AI 对话',
|
||||
flowType: FlowNodeTypeEnum.chatNode,
|
||||
inputs: chatModelInput(formData),
|
||||
showStatus: true,
|
||||
outputs: [
|
||||
{
|
||||
key: 'answerText',
|
||||
label: 'AI回复',
|
||||
description: '直接响应,无需配置',
|
||||
type: 'hidden',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: '回复结束',
|
||||
description: 'AI 回复完成后触发',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 981.9682828103937,
|
||||
y: 890.014595014464
|
||||
},
|
||||
moduleId: 'chatModule'
|
||||
}
|
||||
];
|
||||
}
|
||||
function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
return [
|
||||
{
|
||||
name: '用户问题(对话入口)',
|
||||
flowType: FlowNodeTypeEnum.questionInput,
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
label: '用户问题',
|
||||
type: 'target',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'userChatInput'
|
||||
},
|
||||
{
|
||||
moduleId: 'datasetSearch',
|
||||
key: 'userChatInput'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 464.32198615344566,
|
||||
y: 1602.2698463081606
|
||||
},
|
||||
moduleId: 'userChatInput'
|
||||
},
|
||||
{
|
||||
name: '聊天记录',
|
||||
flowType: FlowNodeTypeEnum.historyNode,
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
value: 6,
|
||||
connected: true,
|
||||
type: 'numberInput',
|
||||
label: '最长记录数'
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'hidden',
|
||||
label: '聊天记录',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'history'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 452.5466249541586,
|
||||
y: 1276.3930310334215
|
||||
},
|
||||
moduleId: 'history'
|
||||
},
|
||||
{
|
||||
name: '知识库搜索',
|
||||
flowType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
showStatus: true,
|
||||
inputs: [
|
||||
{
|
||||
key: 'datasets',
|
||||
value: formData.dataset.datasets,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '关联的知识库',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
value: formData.dataset.similarity,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '相似度',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
value: formData.dataset.limit,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '单次搜索上限',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '触发器',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'userChatInput',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'rerank',
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '结果重排',
|
||||
description: '将召回的结果进行进一步重排,可增加召回率',
|
||||
plusField: true,
|
||||
connected: true,
|
||||
value: formData.dataset.rerank
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'isEmpty',
|
||||
targets: formData.dataset.searchEmptyText
|
||||
? [
|
||||
{
|
||||
moduleId: 'emptyText',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
: []
|
||||
},
|
||||
{
|
||||
key: 'unEmpty',
|
||||
targets: formData.dataset.searchEmptyText
|
||||
? [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
: []
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
key: 'quoteQA'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 956.0838440206068,
|
||||
y: 887.462827870246
|
||||
},
|
||||
moduleId: 'datasetSearch'
|
||||
},
|
||||
...(formData.dataset.searchEmptyText
|
||||
? [
|
||||
{
|
||||
name: '指定回复',
|
||||
flowType: FlowNodeTypeEnum.answerNode,
|
||||
inputs: [
|
||||
{
|
||||
key: ModuleInputKeyEnum.switch,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '触发器',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.answerText,
|
||||
value: formData.dataset.searchEmptyText,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleDataTypeEnum.string,
|
||||
label: '回复的内容',
|
||||
connected: true
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
position: {
|
||||
x: 1553.5815811529146,
|
||||
y: 637.8753731306779
|
||||
},
|
||||
moduleId: 'emptyText'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: 'AI 对话',
|
||||
flowType: FlowNodeTypeEnum.chatNode,
|
||||
inputs: chatModelInput(formData),
|
||||
showStatus: true,
|
||||
outputs: [
|
||||
{
|
||||
key: 'answerText',
|
||||
label: 'AI回复',
|
||||
description: '直接响应,无需配置',
|
||||
type: 'hidden',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'finish',
|
||||
label: '回复结束',
|
||||
description: 'AI 回复完成后触发',
|
||||
valueType: 'boolean',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
],
|
||||
position: {
|
||||
x: 1551.71405495818,
|
||||
y: 977.4911578918461
|
||||
},
|
||||
moduleId: 'chatModule'
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -4,13 +4,14 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { SystemOutputEnum } from '@/constants/app';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { name, avatar, type, intro, modules, permission } = req.body as AppUpdateParams;
|
||||
const { name, avatar, type, simpleTemplateId, intro, modules, permission } =
|
||||
req.body as AppUpdateParams;
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
@@ -28,19 +29,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
{
|
||||
name,
|
||||
type,
|
||||
simpleTemplateId,
|
||||
avatar,
|
||||
intro,
|
||||
permission,
|
||||
...(modules && {
|
||||
modules: modules.map((modules) => ({
|
||||
...modules,
|
||||
outputs: modules.outputs.sort((a, b) => {
|
||||
// finish output always at last
|
||||
if (a.key === SystemOutputEnum.finish) return 1;
|
||||
if (b.key === SystemOutputEnum.finish) return -1;
|
||||
return 0;
|
||||
})
|
||||
}))
|
||||
modules
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
@@ -52,7 +52,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
/* start process */
|
||||
const { responseData } = await dispatchModules({
|
||||
res,
|
||||
modules: modules,
|
||||
appId,
|
||||
modules,
|
||||
variables,
|
||||
teamId,
|
||||
tmbId,
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import type { InitChatResponse } from '@fastgpt/global/core/chat/api.d';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import type { ChatSchema } from '@fastgpt/global/core/chat/type.d';
|
||||
import { getGuideModule } from '@/global/core/app/modules/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/module';
|
||||
import { TaskResponseKeyEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { authChat } from '@fastgpt/service/support/permission/auth/chat';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -55,7 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
{
|
||||
chatId
|
||||
},
|
||||
`dataId obj value adminFeedback userFeedback ${TaskResponseKeyEnum.responseData}`
|
||||
`dataId obj value adminFeedback userFeedback ${ModuleOutputKeyEnum.responseData}`
|
||||
)
|
||||
.sort({ _id: -1 })
|
||||
.limit(30);
|
||||
|
||||
@@ -9,8 +9,15 @@ import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/use
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { name, tags, avatar, vectorModel, agentModel, parentId, type } =
|
||||
req.body as CreateDatasetParams;
|
||||
const {
|
||||
name,
|
||||
tags,
|
||||
avatar,
|
||||
vectorModel = global.vectorModels[0].model,
|
||||
agentModel,
|
||||
parentId,
|
||||
type
|
||||
} = req.body as CreateDatasetParams;
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
|
||||
@@ -51,8 +51,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
|
||||
// token check
|
||||
const token = countPromptTokens(formatQ, 'system');
|
||||
const vectorModelData = getVectorModel(vectorModel);
|
||||
|
||||
if (token > getVectorModel(vectorModel).maxToken) {
|
||||
if (token > vectorModelData.maxToken) {
|
||||
return Promise.reject('Q Over Tokens');
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
collectionId,
|
||||
q: formatQ,
|
||||
a: formatA,
|
||||
model: vectorModel,
|
||||
model: vectorModelData.model,
|
||||
indexes
|
||||
});
|
||||
|
||||
@@ -78,7 +79,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
teamId,
|
||||
tmbId,
|
||||
tokenLen: tokenLen,
|
||||
model: vectorModel
|
||||
model: vectorModelData.model
|
||||
});
|
||||
|
||||
jsonRes<string>(res, {
|
||||
|
||||
28
projects/app/src/pages/api/core/plugin/getPreviewModule.ts
Normal file
28
projects/app/src/pages/api/core/plugin/getPreviewModule.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
get plugin preview modules
|
||||
*/
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getPluginPreviewModule } from '@fastgpt/service/core/plugin/controller';
|
||||
import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin';
|
||||
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
await authPluginCanUse({ id, teamId, tmbId });
|
||||
|
||||
jsonRes<FlowModuleTemplateType>(res, {
|
||||
data: await getPluginPreviewModule({ id })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getPluginModuleDetail } from '@fastgpt/service/core/plugin/controller';
|
||||
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
await authPluginCrud({ req, authToken: true, id, per: 'r' });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getPluginModuleDetail({ id })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserPlugins2Templates } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { teamId } = await authCert({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getUserPlugins2Templates({ teamId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
56
projects/app/src/pages/api/core/plugin/templates.ts
Normal file
56
projects/app/src/pages/api/core/plugin/templates.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
|
||||
import { ModuleTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { GET } from '@fastgpt/service/common/api/plusRequest';
|
||||
import type { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { teamId } = await authCert({ req, authToken: true });
|
||||
|
||||
const [userPlugins, plusPlugins] = await Promise.all([
|
||||
MongoPlugin.find({ teamId }).lean(),
|
||||
GET<PluginTemplateType[]>('/core/plugin/getTemplates')
|
||||
]);
|
||||
|
||||
const data: FlowModuleTemplateType[] = [
|
||||
...userPlugins.map((plugin) => ({
|
||||
id: String(plugin._id),
|
||||
templateType: ModuleTemplateTypeEnum.personalPlugin,
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
intro: plugin.intro,
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
})),
|
||||
...(global.communityPlugins?.map((plugin) => ({
|
||||
id: plugin.id,
|
||||
templateType: ModuleTemplateTypeEnum.communityPlugin,
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
intro: plugin.intro,
|
||||
showStatus: true,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
})) || [])
|
||||
];
|
||||
|
||||
jsonRes<FlowModuleTemplateType[]>(res, {
|
||||
data
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import type { InitShareChatResponse } from '@fastgpt/global/support/outLink/api.d';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/core/chat/constants';
|
||||
import { getGuideModule } from '@/global/core/app/modules/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { authShareChatInit } from '@/service/support/outLink/auth';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/module';
|
||||
import { authOutLinkValid } from '@fastgpt/service/support/permission/auth/outLink';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types/index.d';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { readFileSync } from 'fs';
|
||||
import { readFileSync, readdirSync } from 'fs';
|
||||
import type { ConfigFileType, InitDateResponse } from '@/global/common/api/systemRes';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
defaultAudioSpeechModels,
|
||||
defaultWhisperModel
|
||||
} from '@fastgpt/global/core/ai/model';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
|
||||
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
getInitConfig();
|
||||
getModelPrice();
|
||||
await getInitConfig();
|
||||
|
||||
jsonRes<InitDateResponse>(res, {
|
||||
data: {
|
||||
@@ -35,7 +37,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
key: undefined
|
||||
})),
|
||||
priceMd: global.priceMd,
|
||||
systemVersion: global.systemVersion || '0.0.0'
|
||||
systemVersion: global.systemVersion || '0.0.0',
|
||||
simpleModeTemplates: global.simpleModeTemplates
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -60,31 +63,35 @@ const defaultFeConfigs: FeConfigsType = {
|
||||
favicon: '/favicon.ico'
|
||||
};
|
||||
|
||||
export function initGlobal() {
|
||||
// init tikToken
|
||||
getTikTokenEnc();
|
||||
initHttpAgent();
|
||||
global.qaQueueLen = 0;
|
||||
global.vectorQueueLen = 0;
|
||||
}
|
||||
|
||||
export function getInitConfig() {
|
||||
export async function getInitConfig() {
|
||||
try {
|
||||
if (global.feConfigs) return;
|
||||
|
||||
getSystemVersion();
|
||||
initGlobal();
|
||||
|
||||
const filename =
|
||||
process.env.NODE_ENV === 'development' ? 'data/config.local.json' : '/app/data/config.json';
|
||||
const res = JSON.parse(readFileSync(filename, 'utf-8')) as ConfigFileType;
|
||||
|
||||
console.log(`System Version: ${global.systemVersion}`);
|
||||
|
||||
setDefaultData(res);
|
||||
} catch (error) {
|
||||
setDefaultData();
|
||||
console.log('get init config error, set default', error);
|
||||
}
|
||||
await getSimpleModeTemplates();
|
||||
|
||||
getSystemVersion();
|
||||
getModelPrice();
|
||||
getSystemPlugin();
|
||||
}
|
||||
|
||||
export function initGlobal() {
|
||||
// init tikToken
|
||||
getTikTokenEnc();
|
||||
initHttpAgent();
|
||||
global.communityPlugins = [];
|
||||
global.simpleModeTemplates = [];
|
||||
global.qaQueueLen = global.qaQueueLen ?? 0;
|
||||
global.vectorQueueLen = global.vectorQueueLen ?? 0;
|
||||
}
|
||||
|
||||
export function setDefaultData(res?: ConfigFileType) {
|
||||
@@ -109,18 +116,19 @@ export function setDefaultData(res?: ConfigFileType) {
|
||||
|
||||
global.priceMd = '';
|
||||
|
||||
console.log(global);
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
export function getSystemVersion() {
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
global.systemVersion = process.env.npm_package_version || '0.0.0';
|
||||
return;
|
||||
}
|
||||
const packageJson = JSON.parse(readFileSync('/app/package.json', 'utf-8'));
|
||||
} else {
|
||||
const packageJson = JSON.parse(readFileSync('/app/package.json', 'utf-8'));
|
||||
|
||||
global.systemVersion = packageJson?.version;
|
||||
global.systemVersion = packageJson?.version;
|
||||
}
|
||||
console.log(`System Version: ${global.systemVersion}`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -157,3 +165,67 @@ ${`| 语音输入-${global.whisperModel.name} | ${global.whisperModel.price}/分
|
||||
`;
|
||||
console.log(global.priceMd);
|
||||
}
|
||||
|
||||
async function getSimpleModeTemplates() {
|
||||
if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
|
||||
|
||||
try {
|
||||
const basePath =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? 'public/simpleTemplates'
|
||||
: '/app/projects/app/public/simpleTemplates';
|
||||
// read data/simpleTemplates directory, get all json file
|
||||
const files = readdirSync(basePath);
|
||||
// filter json file
|
||||
const filterFiles = files.filter((item) => item.endsWith('.json'));
|
||||
|
||||
// read json file
|
||||
const fileTemplates = filterFiles.map((item) => {
|
||||
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
|
||||
return {
|
||||
id: item.replace('.json', ''),
|
||||
...JSON.parse(content)
|
||||
};
|
||||
});
|
||||
|
||||
// fetch templates from plus
|
||||
const plusTemplates = await getSimpleTemplatesFromPlus();
|
||||
|
||||
global.simpleModeTemplates = [
|
||||
SimpleModeTemplate_FastGPT_Universal,
|
||||
...plusTemplates,
|
||||
...fileTemplates
|
||||
];
|
||||
} catch (error) {
|
||||
global.simpleModeTemplates = [SimpleModeTemplate_FastGPT_Universal];
|
||||
}
|
||||
console.log('simple mode templates: ');
|
||||
console.log(global.simpleModeTemplates);
|
||||
}
|
||||
|
||||
function getSystemPlugin() {
|
||||
if (global.communityPlugins && global.communityPlugins.length > 0) return;
|
||||
|
||||
const basePath =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? 'public/pluginTemplates'
|
||||
: '/app/projects/app/public/pluginTemplates';
|
||||
// read data/pluginTemplates directory, get all json file
|
||||
const files = readdirSync(basePath);
|
||||
// filter json file
|
||||
const filterFiles = files.filter((item) => item.endsWith('.json'));
|
||||
|
||||
// read json file
|
||||
const fileTemplates = filterFiles.map((item) => {
|
||||
const content = readFileSync(`${basePath}/${item}`, 'utf-8');
|
||||
return {
|
||||
id: `${PluginTypeEnum.community}-${item.replace('.json', '')}`,
|
||||
type: PluginTypeEnum.community,
|
||||
...JSON.parse(content)
|
||||
};
|
||||
});
|
||||
|
||||
global.communityPlugins = fileTemplates;
|
||||
console.log('community plugins: ');
|
||||
console.log(fileTemplates);
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
/* start flow controller */
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
res,
|
||||
appId: String(app._id),
|
||||
chatId,
|
||||
modules: app.modules,
|
||||
user,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
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 { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
@@ -47,11 +44,14 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
if (item.inputs.find((input) => input.required && !input.connected)) {
|
||||
return Promise.reject(`【${item.name}】存在未连接的必填输入`);
|
||||
}
|
||||
if (item.inputs.find((input) => input.valueCheck && !input.valueCheck(input.value))) {
|
||||
return Promise.reject(`【${item.name}】存在为填写的必填项`);
|
||||
if (
|
||||
item.inputs.find((input) => {
|
||||
if (!input.required || input.connected) return false;
|
||||
if (!input.value || input.value === '' || input.value?.length === 0) return true;
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return Promise.reject(`【${item.name}】存在未填或未连接参数`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
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 type { FlowModuleTemplateType } from '@fastgpt/global/core/module/type.d';
|
||||
import { appSystemModuleTemplates } from '@/web/core/modules/template/system';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { usePluginStore } from '@/web/core/plugin/store/plugin';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@@ -13,24 +13,24 @@ type Props = { app: AppSchema; onClose: () => void };
|
||||
|
||||
const Render = ({ app, onClose }: Props) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
const { pluginModuleTemplates, loadPluginModuleTemplates } = usePluginStore();
|
||||
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
|
||||
|
||||
const filterTemplates = useMemo(() => {
|
||||
const copyTemplates: SystemModuleTemplateType = JSON.parse(
|
||||
JSON.stringify(SystemModuleTemplates)
|
||||
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
|
||||
JSON.stringify(appSystemModuleTemplates)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
copyTemplates.forEach((module, index) => {
|
||||
if (module.flowType === node.type) {
|
||||
copyTemplates.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -38,13 +38,12 @@ const Render = ({ app, onClose }: Props) => {
|
||||
return copyTemplates;
|
||||
}, [nodes]);
|
||||
|
||||
useQuery(['getUserPlugs2ModuleTemplates'], () => loadPluginModuleTemplates());
|
||||
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
|
||||
|
||||
return (
|
||||
<Flow
|
||||
systemTemplates={filterTemplates}
|
||||
pluginTemplates={[{ label: '', list: pluginModuleTemplates }]}
|
||||
show2Plugin
|
||||
pluginTemplates={pluginModuleTemplates}
|
||||
modules={app.modules}
|
||||
Header={<Header app={app} onClose={onClose} />}
|
||||
/>
|
||||
@@ -53,7 +52,7 @@ const Render = ({ app, onClose }: Props) => {
|
||||
|
||||
export default React.memo(function AdEdit(props: Props) {
|
||||
return (
|
||||
<FlowProvider filterAppIds={[props.app._id]}>
|
||||
<FlowProvider mode={'app'} filterAppIds={[props.app._id]}>
|
||||
<Render {...props} />
|
||||
</FlowProvider>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
Input,
|
||||
Textarea,
|
||||
ModalFooter,
|
||||
ModalBody
|
||||
ModalBody,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
@@ -118,7 +119,12 @@ const InfoModal = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={'应用信息设置'}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/module/ai.svg"
|
||||
title={t('core.app.setting')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box>头像 & 名称</Box>
|
||||
<Flex mt={2} alignItems={'center'}>
|
||||
|
||||
@@ -277,7 +277,11 @@ function EditLinkModal({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/modal/shareLight.svg"
|
||||
title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 90px'}>{t('Name')}:</Box>
|
||||
|
||||
@@ -6,32 +6,22 @@ import {
|
||||
BoxProps,
|
||||
Textarea,
|
||||
useTheme,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
useDisclosure,
|
||||
Button,
|
||||
IconButton
|
||||
IconButton,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import {
|
||||
appModules2Form,
|
||||
getDefaultAppForm,
|
||||
appForm2Modules,
|
||||
type EditFormType
|
||||
} from '@/web/core/app/basicSettings';
|
||||
import { chatModelList } from '@/web/common/system/staticData';
|
||||
import { appModules2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { chatModelList, simpleModeTemplates } from '@/web/common/system/staticData';
|
||||
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { ChatModelSystemTip, welcomeTextTip } from '@/constants/flow/ModuleTemplate';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import { chatNodeSystemPromptTip, welcomeTextTip } from '@fastgpt/global/core/module/template/tip';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
@@ -42,7 +32,16 @@ import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { delModelById } from '@/web/core/app/api';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getGuideModule } from '@/global/core/app/modules/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { DatasetParamsModal } from '@/components/core/module/DatasetSelectModal';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import PermissionIconText from '@/components/support/permission/IconText';
|
||||
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { useSticky } from '@/web/common/hooks/useSticky';
|
||||
import { postForm2Modules } from '@/web/core/app/utils';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import MySelect from '@/components/Select';
|
||||
@@ -50,19 +49,11 @@ import MyTooltip from '@/components/MyTooltip';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import QGSwitch from '@/components/core/module/Flow/components/modules/QGSwitch';
|
||||
import TTSSelect from '@/components/core/module/Flow/components/modules/TTSSelect';
|
||||
import VariableEdit from '@/components/core/module/Flow/components/modules/VariableEdit';
|
||||
|
||||
import { addVariable } from '@/components/core/module/VariableEditModal';
|
||||
import { DatasetParamsModal } from '@/components/core/module/DatasetSelectModal';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import QGSwitch from '../QGSwitch';
|
||||
import TTSSelect from '../TTSSelect';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { useSticky } from '@/web/common/hooks/useSticky';
|
||||
|
||||
const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal'));
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
|
||||
@@ -84,22 +75,14 @@ function ConfigForm({
|
||||
const [editVariable, setEditVariable] = useState<VariableItemType>();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const { register, setValue, getValues, reset, handleSubmit, control } = useForm<EditFormType>({
|
||||
defaultValues: getDefaultAppForm()
|
||||
});
|
||||
const { register, setValue, getValues, reset, handleSubmit, control } =
|
||||
useForm<AppSimpleEditFormType>({
|
||||
defaultValues: getDefaultAppForm()
|
||||
});
|
||||
|
||||
const {
|
||||
fields: variables,
|
||||
append: appendVariable,
|
||||
remove: removeVariable,
|
||||
replace: replaceVariables
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: 'variables'
|
||||
});
|
||||
const { fields: datasets, replace: replaceKbList } = useFieldArray({
|
||||
control,
|
||||
name: 'dataset.list'
|
||||
name: 'dataset.datasets'
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -120,7 +103,7 @@ function ConfigForm({
|
||||
|
||||
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
|
||||
content: t('app.Confirm Save App Tip'),
|
||||
bg: appDetail.type === AppTypeEnum.basic ? '' : 'red.600'
|
||||
bg: appDetail.type === AppTypeEnum.simple ? '' : 'red.600'
|
||||
});
|
||||
|
||||
const chatModelSelectList = useMemo(() => {
|
||||
@@ -135,13 +118,21 @@ function ConfigForm({
|
||||
[allDatasets, datasets]
|
||||
);
|
||||
|
||||
const selectSimpleTemplate = useMemo(
|
||||
() =>
|
||||
simpleModeTemplates?.find((item) => item.id === getValues('templateId')) ||
|
||||
SimpleModeTemplate_FastGPT_Universal,
|
||||
[getValues, refresh]
|
||||
);
|
||||
|
||||
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
|
||||
mutationFn: async (data: EditFormType) => {
|
||||
const modules = appForm2Modules(data);
|
||||
mutationFn: async (data: AppSimpleEditFormType) => {
|
||||
const modules = await postForm2Modules(data, data.templateId);
|
||||
|
||||
await updateAppDetail(appDetail._id, {
|
||||
modules,
|
||||
type: AppTypeEnum.basic,
|
||||
type: AppTypeEnum.simple,
|
||||
simpleTemplateId: data.templateId,
|
||||
permission: undefined
|
||||
});
|
||||
},
|
||||
@@ -150,18 +141,20 @@ function ConfigForm({
|
||||
});
|
||||
|
||||
const appModule2Form = useCallback(() => {
|
||||
const formVal = appModules2Form(appDetail.modules);
|
||||
const formVal = appModules2Form({
|
||||
templateId: appDetail.simpleTemplateId,
|
||||
modules: appDetail.modules
|
||||
});
|
||||
reset(formVal);
|
||||
setTimeout(() => {
|
||||
setRefresh((state) => !state);
|
||||
}, 100);
|
||||
}, [appDetail.modules, reset]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
}, [appDetail.modules, appDetail.simpleTemplateId, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
appModule2Form();
|
||||
}, [appModule2Form]);
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
const BoxStyles: BoxProps = {
|
||||
bg: 'myWhite.200',
|
||||
@@ -213,9 +206,9 @@ function ConfigForm({
|
||||
isLoading={isSaving}
|
||||
fontSize={'sm'}
|
||||
size={['sm', 'md']}
|
||||
variant={appDetail.type === AppTypeEnum.basic ? 'primary' : 'base'}
|
||||
variant={appDetail.type === AppTypeEnum.simple ? 'primary' : 'base'}
|
||||
onClick={() => {
|
||||
if (appDetail.type !== AppTypeEnum.basic) {
|
||||
if (appDetail.type !== AppTypeEnum.simple) {
|
||||
openConfirmSave(handleSubmit((data) => onSubmitSave(data)))();
|
||||
} else {
|
||||
handleSubmit((data) => onSubmitSave(data))();
|
||||
@@ -227,255 +220,234 @@ function ConfigForm({
|
||||
</Flex>
|
||||
|
||||
<Box px={4}>
|
||||
{/* welcome */}
|
||||
<Box {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
|
||||
<Box mx={2}>对话开场白</Box>
|
||||
<MyTooltip label={welcomeTextTip} forceShow>
|
||||
<QuestionOutlineIcon />
|
||||
</MyTooltip>
|
||||
{/* simple mode select */}
|
||||
<Flex {...BoxStyles}>
|
||||
<Flex alignItems={'center'} flex={'1 0 0'}>
|
||||
<Image alt={''} src={'/imgs/module/templates.png'} w={'18px'} />
|
||||
<Box mx={2}>{t('core.app.simple.mode template select')}</Box>
|
||||
</Flex>
|
||||
<Textarea
|
||||
mt={2}
|
||||
rows={5}
|
||||
placeholder={welcomeTextTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('guide.welcome.text')}
|
||||
<MySelect
|
||||
w={['200px', '250px']}
|
||||
list={
|
||||
simpleModeTemplates?.map((item) => ({
|
||||
alias: item.name,
|
||||
label: item.desc,
|
||||
value: item.id
|
||||
})) || []
|
||||
}
|
||||
value={getValues('templateId')}
|
||||
onchange={(val) => {
|
||||
setValue('templateId', val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* welcome */}
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.welcomeText && (
|
||||
<Box {...BoxStyles} mt={2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Image alt={''} src={'/imgs/module/userGuide.png'} w={'18px'} />
|
||||
<Box mx={2}>对话开场白</Box>
|
||||
<MyTooltip label={welcomeTextTip} forceShow>
|
||||
<QuestionOutlineIcon />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Textarea
|
||||
mt={2}
|
||||
rows={5}
|
||||
placeholder={welcomeTextTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('userGuide.welcomeText')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* variable */}
|
||||
<Box mt={2} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
变量
|
||||
</Box>
|
||||
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
|
||||
+ 新增
|
||||
</Flex>
|
||||
</Flex>
|
||||
{variables.length > 0 && (
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'lg'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setEditVariable(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => removeVariable(index)}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.variables && (
|
||||
<Box mt={2} {...BoxStyles}>
|
||||
<VariableEdit
|
||||
variables={getValues('userGuide.variables')}
|
||||
onChange={(e) => {
|
||||
setValue('userGuide.variables', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* ai */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('app.AI Settings')}
|
||||
</Box>
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
|
||||
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
|
||||
{t('app.Open AI Advanced Settings')}
|
||||
{selectSimpleTemplate?.systemForm?.aiSettings && (
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Image alt={''} src={'/imgs/module/AI.png'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('app.AI Settings')}
|
||||
</Box>
|
||||
{(selectSimpleTemplate.systemForm.aiSettings.maxToken ||
|
||||
selectSimpleTemplate.systemForm.aiSettings.temperature ||
|
||||
selectSimpleTemplate.systemForm.aiSettings.quoteTemplate ||
|
||||
selectSimpleTemplate.systemForm.aiSettings.quotePrompt) && (
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
|
||||
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
|
||||
{t('app.Open AI Advanced Settings')}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{selectSimpleTemplate.systemForm.aiSettings?.model && (
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={getValues(`aiSettings.model`)}
|
||||
list={chatModelSelectList}
|
||||
onchange={(val: any) => {
|
||||
setValue('aiSettings.model', val);
|
||||
const maxToken =
|
||||
chatModelList.find((item) => item.model === getValues('aiSettings.model'))
|
||||
?.maxResponse || 4000;
|
||||
const token = maxToken / 2;
|
||||
setValue('aiSettings.maxToken', token);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={getValues('chatModel.model')}
|
||||
list={chatModelSelectList}
|
||||
onchange={(val: any) => {
|
||||
setValue('chatModel.model', val);
|
||||
const maxToken =
|
||||
chatModelList.find((item) => item.model === getValues('chatModel.model'))
|
||||
?.maxResponse || 4000;
|
||||
const token = maxToken / 2;
|
||||
setValue('chatModel.maxToken', token);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={10} alignItems={'flex-start'}>
|
||||
<Box {...LabelStyles}>
|
||||
{t('core.ai.Prompt')}
|
||||
<MyTooltip label={ChatModelSystemTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
rows={5}
|
||||
minH={'60px'}
|
||||
placeholder={ChatModelSystemTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('chatModel.systemPrompt')}
|
||||
></Textarea>
|
||||
</Flex>
|
||||
</Box>
|
||||
{selectSimpleTemplate.systemForm.aiSettings?.systemPrompt && (
|
||||
<Flex mt={10} alignItems={'flex-start'}>
|
||||
<Box {...LabelStyles}>
|
||||
{t('core.ai.Prompt')}
|
||||
<MyTooltip label={chatNodeSystemPromptTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
rows={5}
|
||||
minH={'60px'}
|
||||
placeholder={chatNodeSystemPromptTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('aiSettings.systemPrompt')}
|
||||
></Textarea>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* dataset */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
|
||||
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
|
||||
<SmallAddIcon />
|
||||
{t('common.Choose')}
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
|
||||
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||
{t('common.Params')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
|
||||
{t('core.dataset.Similarity')}: {getValues('dataset.searchSimilarity')},{' '}
|
||||
{t('core.dataset.Search Top K')}: {getValues('dataset.searchLimit')}
|
||||
{getValues('dataset.searchEmptyText') === ''
|
||||
? ''
|
||||
: t('core.dataset.Set Empty Result Tip')}
|
||||
</Flex>
|
||||
<Grid
|
||||
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
|
||||
my={2}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item._id
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'18px'} mr={1} />
|
||||
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
{selectSimpleTemplate?.systemForm?.dataset && (
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<Image alt={''} src={'/imgs/module/db.png'} w={'18px'} />
|
||||
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
|
||||
</Flex>
|
||||
{selectSimpleTemplate.systemForm.dataset.datasets && (
|
||||
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbSelect}>
|
||||
<SmallAddIcon />
|
||||
{t('common.Choose')}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
{(selectSimpleTemplate.systemForm.dataset.limit ||
|
||||
selectSimpleTemplate.systemForm.dataset.rerank ||
|
||||
selectSimpleTemplate.systemForm.dataset.searchEmptyText ||
|
||||
selectSimpleTemplate.systemForm.dataset.similarity) && (
|
||||
<Flex alignItems={'center'} ml={3} {...BoxBtnStyles} onClick={onOpenKbParams}>
|
||||
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||
{t('common.Params')}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
|
||||
{t('core.dataset.Similarity')}: {getValues('dataset.similarity')},{' '}
|
||||
{t('core.dataset.Search Top K')}: {getValues('dataset.limit')}
|
||||
{getValues('dataset.searchEmptyText') === ''
|
||||
? ''
|
||||
: t('core.dataset.Set Empty Result Tip')}
|
||||
</Flex>
|
||||
<Grid
|
||||
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
|
||||
my={2}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item._id
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Image alt={''} src={item.avatar} w={'18px'} mr={1} />
|
||||
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* tts */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<TTSSelect
|
||||
value={getValues('tts')}
|
||||
onChange={(e) => {
|
||||
setValue('tts', e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.tts && (
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<TTSSelect
|
||||
value={getValues('userGuide.tts')}
|
||||
onChange={(e) => {
|
||||
setValue('userGuide.tts', e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* whisper */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<QGSwitch
|
||||
isChecked={getValues('questionGuide')}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue('questionGuide', value);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* question guide */}
|
||||
{selectSimpleTemplate?.systemForm?.userGuide?.questionGuide && (
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<QGSwitch
|
||||
isChecked={getValues('userGuide.questionGuide')}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue('userGuide.questionGuide', value);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<ConfirmSaveModal />
|
||||
{editVariable && (
|
||||
<VariableEditModal
|
||||
defaultVariable={editVariable}
|
||||
onClose={() => setEditVariable(undefined)}
|
||||
onSubmit={({ variable }) => {
|
||||
const record = variables.find((item) => item.id === variable.id);
|
||||
if (record) {
|
||||
replaceVariables(
|
||||
variables.map((item) => (item.id === variable.id ? variable : item))
|
||||
);
|
||||
} else {
|
||||
// auth same key
|
||||
if (variables.find((item) => item.key === variable.key)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('app.Variable Key Repeat Tip')
|
||||
});
|
||||
}
|
||||
appendVariable(variable);
|
||||
}
|
||||
|
||||
setEditVariable(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenAIChatSetting && (
|
||||
<AIChatSettingsModal
|
||||
onClose={onCloseAIChatSetting}
|
||||
onSuccess={(e) => {
|
||||
setValue('chatModel', e);
|
||||
setValue('aiSettings', e);
|
||||
onCloseAIChatSetting();
|
||||
}}
|
||||
defaultData={getValues('chatModel')}
|
||||
defaultData={getValues('aiSettings')}
|
||||
simpleModeTemplate={selectSimpleTemplate}
|
||||
/>
|
||||
)}
|
||||
{isOpenDatasetSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenDatasetSelect}
|
||||
activeDatasets={selectDatasets.map((item) => ({
|
||||
defaultSelectedDatasets={selectDatasets.map((item) => ({
|
||||
datasetId: item._id,
|
||||
vectorModel: item.vectorModel
|
||||
}))}
|
||||
@@ -689,9 +661,8 @@ function ChatTest({ appId }: { appId: string }) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const formVal = appModules2Form(appDetail.modules);
|
||||
setModules(appForm2Modules(formVal));
|
||||
resetChatBox();
|
||||
setModules(appDetail.modules);
|
||||
}, [appDetail, resetChatBox]);
|
||||
|
||||
return (
|
||||
@@ -727,7 +698,7 @@ function ChatTest({ appId }: { appId: string }) {
|
||||
onDelMessage={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
{appDetail.type !== AppTypeEnum.basic && (
|
||||
{appDetail.type !== AppTypeEnum.simple && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
@@ -750,7 +721,7 @@ function ChatTest({ appId }: { appId: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
const BasicEdit = ({ appId }: { appId: string }) => {
|
||||
const SimpleEdit = ({ appId }: { appId: string }) => {
|
||||
const { isPc } = useSystemStore();
|
||||
return (
|
||||
<Grid gridTemplateColumns={['1fr', '550px 1fr']} h={'100%'}>
|
||||
@@ -760,4 +731,4 @@ const BasicEdit = ({ appId }: { appId: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicEdit;
|
||||
export default SimpleEdit;
|
||||
@@ -12,7 +12,7 @@ import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import Loading from '@/components/Loading';
|
||||
import BasicEdit from './components/BasicEdit';
|
||||
import SimpleEdit from './components/SimpleEdit';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
|
||||
@@ -23,7 +23,7 @@ const OutLink = dynamic(() => import('./components/OutLink'), {});
|
||||
const Logs = dynamic(() => import('./components/Logs'), {});
|
||||
|
||||
enum TabEnum {
|
||||
'basicEdit' = 'basicEdit',
|
||||
'simpleEdit' = 'simpleEdit',
|
||||
'adEdit' = 'adEdit',
|
||||
'outLink' = 'outLink',
|
||||
'logs' = 'logs',
|
||||
@@ -51,7 +51,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
|
||||
const tabList = useMemo(
|
||||
() => [
|
||||
{ label: '简易配置', id: TabEnum.basicEdit, icon: 'overviewLight' },
|
||||
{ label: '简易配置', id: TabEnum.simpleEdit, icon: 'overviewLight' },
|
||||
...(feConfigs?.hide_app_flow
|
||||
? []
|
||||
: [{ label: '高级编排', id: TabEnum.adEdit, icon: 'settingLight' }]),
|
||||
@@ -167,9 +167,9 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
|
||||
{currentTab === TabEnum.basicEdit && <BasicEdit appId={appId} />}
|
||||
{currentTab === TabEnum.simpleEdit && <SimpleEdit appId={appId} />}
|
||||
{currentTab === TabEnum.adEdit && appDetail && (
|
||||
<AdEdit app={appDetail} onClose={() => setCurrentTab(TabEnum.basicEdit)} />
|
||||
<AdEdit app={appDetail} onClose={() => setCurrentTab(TabEnum.simpleEdit)} />
|
||||
)}
|
||||
{currentTab === TabEnum.logs && <Logs appId={appId} />}
|
||||
{currentTab === TabEnum.outLink && <OutLink appId={appId} />}
|
||||
@@ -180,7 +180,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const currentTab = context?.query?.currentTab || TabEnum.basicEdit;
|
||||
const currentTab = context?.query?.currentTab || TabEnum.simpleEdit;
|
||||
|
||||
return {
|
||||
props: { currentTab, ...(await serviceSideProps(context)) }
|
||||
|
||||
@@ -18,13 +18,14 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { postCreateApp } from '@/web/core/app/api';
|
||||
import { useRouter } from 'next/router';
|
||||
import { appTemplates } from '@/constants/flow/ModuleTemplate';
|
||||
import { appTemplates } from '@/web/core/app/templates';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
type FormType = {
|
||||
avatar: string;
|
||||
@@ -33,6 +34,7 @@ type FormType = {
|
||||
};
|
||||
|
||||
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
@@ -96,8 +98,13 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} isCentered={!isPc}>
|
||||
<ModalHeader fontSize={'2xl'}>创建属于你的 AI 应用</ModalHeader>
|
||||
<MyModal
|
||||
iconSrc="/imgs/module/ai.svg"
|
||||
title={t('core.app.create app')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
isCentered={!isPc}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
取个响亮的名字
|
||||
|
||||
@@ -189,6 +189,14 @@ const MyApps = () => {
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
{myApps.length === 0 && (
|
||||
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
还没有应用,快去创建一个吧!
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<ConfirmModal />
|
||||
{isOpenCreateModal && (
|
||||
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps(true)} />
|
||||
|
||||
@@ -254,7 +254,7 @@ const ChatHistorySlider = ({
|
||||
}}
|
||||
>
|
||||
<MyIcon mr={2} name={'customTitle'} w={'16px'}></MyIcon>
|
||||
自定义标题
|
||||
{t('common.Custom Title')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
|
||||
@@ -42,7 +42,7 @@ const EditFolderModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} title={typeMap.title}>
|
||||
<MyModal isOpen onClose={onClose} iconSrc="/imgs/modal/folder.svg" title={typeMap.title}>
|
||||
<ModalBody>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
|
||||
@@ -28,7 +28,13 @@ const CreateFileModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal title={t('file.Create File')} isOpen w={'600px'} top={'15vh'}>
|
||||
<MyModal
|
||||
title={t('file.Create File')}
|
||||
iconSrc="/imgs/modal/txt.svg"
|
||||
isOpen
|
||||
w={'600px'}
|
||||
top={'15vh'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box mb={1} fontSize={'sm'}>
|
||||
文件名
|
||||
|
||||
@@ -71,6 +71,7 @@ const ImportData = ({
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc="/imgs/modal/import.svg"
|
||||
title={<Box {...TitleStyle}>{t('dataset.data.File import')}</Box>}
|
||||
isOpen
|
||||
isCentered
|
||||
@@ -79,7 +80,7 @@ const ImportData = ({
|
||||
h={'90vh'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<Flex flexDirection={'column'} flex={'1 0 0'}>
|
||||
<Flex mt={2} flexDirection={'column'} flex={'1 0 0'}>
|
||||
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
|
||||
<MyRadio
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(3,1fr)']}
|
||||
|
||||
@@ -30,13 +30,14 @@ const UrlFetchModal = ({
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc="/imgs/modal/network.svg"
|
||||
title={
|
||||
<>
|
||||
<Box>
|
||||
<Box>{t('file.Fetch Url')}</Box>
|
||||
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'} mt={1}>
|
||||
目前仅支持读取静态链接,请注意检查结果
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
}
|
||||
top={'15vh'}
|
||||
isOpen
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import React, { useCallback, useState, useRef } from 'react';
|
||||
import { Box, Flex, Button, ModalHeader, ModalFooter, ModalBody, Input } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Input,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { compressImgAndUpload } from '@/web/common/file/controller';
|
||||
@@ -79,8 +88,14 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen onClose={onClose} isCentered={!isPc} w={'450px'}>
|
||||
<ModalHeader fontSize={'2xl'}>创建一个知识库</ModalHeader>
|
||||
<MyModal
|
||||
iconSrc="/imgs/module/db.png"
|
||||
title={t('core.dataset.Create dataset')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
isCentered={!isPc}
|
||||
w={'450px'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
取个名字
|
||||
|
||||
@@ -59,9 +59,13 @@ const MoveModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} maxW={['90vw', '800px']} w={'800px'} onClose={onClose}>
|
||||
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
|
||||
<ModalHeader>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
maxW={['90vw', '800px']}
|
||||
w={'800px'}
|
||||
iconSrc="/imgs/modal/move.svg"
|
||||
title={
|
||||
<>
|
||||
{!!parentId ? (
|
||||
<Flex flex={1} userSelect={'none'} fontSize={['sm', 'lg']} fontWeight={'normal'}>
|
||||
{paths.map((item, i) => (
|
||||
@@ -93,8 +97,11 @@ const MoveModal = ({
|
||||
) : (
|
||||
<Box>我的知识库</Box>
|
||||
)}
|
||||
</ModalHeader>
|
||||
|
||||
</>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
|
||||
<ModalBody
|
||||
flex={['1 0 0', '0 0 auto']}
|
||||
maxH={'80vh'}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||
import type { ResLogin } from '@/global/support/api/userRes';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { postCreateApp } from '@/web/core/app/api';
|
||||
import { appTemplates } from '@/constants/flow/ModuleTemplate';
|
||||
import { appTemplates } from '@/web/core/app/templates';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
|
||||
interface Props {
|
||||
|
||||
95
projects/app/src/pages/login/fastlogin.tsx
Normal file
95
projects/app/src/pages/login/fastlogin.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { clearToken, setToken } from '@/web/support/user/auth';
|
||||
import { postFastLogin } from '@/web/support/user/api';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import Loading from '@/components/Loading';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
const FastLogin = ({
|
||||
code,
|
||||
token,
|
||||
callbackUrl
|
||||
}: {
|
||||
code: string;
|
||||
token: string;
|
||||
callbackUrl: string;
|
||||
}) => {
|
||||
const { setLastChatId, setLastChatAppId } = useChatStore();
|
||||
const { setUserInfo } = useUserStore();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
|
||||
const loginSuccess = useCallback(
|
||||
(res: ResLogin) => {
|
||||
setToken(res.token);
|
||||
setUserInfo(res.user);
|
||||
|
||||
// init store
|
||||
setLastChatId('');
|
||||
setLastChatAppId('');
|
||||
|
||||
setTimeout(() => {
|
||||
router.push(decodeURIComponent(callbackUrl));
|
||||
}, 100);
|
||||
},
|
||||
[setLastChatId, setLastChatAppId, setUserInfo, router, callbackUrl]
|
||||
);
|
||||
|
||||
const authCode = useCallback(
|
||||
async (code: string, token: string) => {
|
||||
try {
|
||||
const res = await postFastLogin({
|
||||
code,
|
||||
token
|
||||
});
|
||||
if (!res) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: '登录异常'
|
||||
});
|
||||
return setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1000);
|
||||
}
|
||||
loginSuccess(res);
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: getErrText(error, '登录异常')
|
||||
});
|
||||
setTimeout(() => {
|
||||
router.replace('/login');
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
[loginSuccess, router, toast]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
clearToken();
|
||||
router.prefetch(callbackUrl);
|
||||
authCode(code, token);
|
||||
}, []);
|
||||
|
||||
return <Loading />;
|
||||
};
|
||||
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
code: content?.query?.code || '',
|
||||
token: content?.query?.token || '',
|
||||
callbackUrl: content?.query?.callbackUrl || '/app/list',
|
||||
...(await serviceSideProps(content))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default FastLogin;
|
||||
@@ -9,10 +9,11 @@ import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import LoginForm from './components/LoginForm';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { setToken } from '@/web/support/user/auth';
|
||||
import { clearToken, setToken } from '@/web/support/user/auth';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import CommunityModal from '@/components/CommunityModal';
|
||||
import Script from 'next/script';
|
||||
import { loginOut } from '@/web/support/user/api';
|
||||
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
|
||||
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
|
||||
|
||||
@@ -53,6 +54,7 @@ const Login = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
clearToken();
|
||||
router.prefetch('/app/list');
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { setToken } from '@/web/support/user/auth';
|
||||
import { clearToken, setToken } from '@/web/support/user/auth';
|
||||
import { oauthLogin } from '@/web/support/user/api';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import Loading from '@/components/Loading';
|
||||
@@ -21,12 +21,13 @@ const provider = ({ code, state }: { code: string; state: string }) => {
|
||||
|
||||
const loginSuccess = useCallback(
|
||||
(res: ResLogin) => {
|
||||
setToken(res.token);
|
||||
setUserInfo(res.user);
|
||||
|
||||
// init store
|
||||
setLastChatId('');
|
||||
setLastChatAppId('');
|
||||
|
||||
setUserInfo(res.user);
|
||||
setToken(res.token);
|
||||
setTimeout(() => {
|
||||
router.push(
|
||||
loginStore?.lastRoute ? decodeURIComponent(loginStore?.lastRoute) : '/app/list'
|
||||
@@ -72,8 +73,11 @@ const provider = ({ code, state }: { code: string; state: string }) => {
|
||||
[loginStore, loginSuccess, router, toast]
|
||||
);
|
||||
|
||||
useQuery(['init', code], () => {
|
||||
useEffect(() => {
|
||||
clearToken();
|
||||
router.prefetch('/app/list');
|
||||
if (!code) return;
|
||||
|
||||
if (state !== loginStore?.state) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
@@ -85,11 +89,6 @@ const provider = ({ code, state }: { code: string; state: string }) => {
|
||||
return;
|
||||
}
|
||||
authCode(code);
|
||||
return null;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/app/list');
|
||||
}, []);
|
||||
|
||||
return <Loading />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
@@ -42,12 +42,21 @@ const Header = ({ plugin, onClose }: Props) => {
|
||||
return Promise.reject(t('module.Plugin input must connect'));
|
||||
}
|
||||
}
|
||||
|
||||
if (item.inputs.find((input) => input.required && !input.connected)) {
|
||||
return Promise.reject(`【${item.name}】存在未连接的必填输入`);
|
||||
if (
|
||||
item.flowType === FlowNodeTypeEnum.pluginOutput &&
|
||||
item.inputs.find((input) => !input.connected)
|
||||
) {
|
||||
return Promise.reject(t('core.module.Plugin output must connect'));
|
||||
}
|
||||
if (item.inputs.find((input) => input.valueCheck && !input.valueCheck(input.value))) {
|
||||
return Promise.reject(`【${item.name}】存在为填写的必填项`);
|
||||
|
||||
if (
|
||||
item.inputs.find((input) => {
|
||||
if (!input.required || input.connected) return false;
|
||||
if (!input.value || input.value === '' || input.value?.length === 0) return true;
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return Promise.reject(`【${item.name}】存在未填或未连接参数`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import ReactFlow, { Background, ReactFlowProvider, useNodesState } from 'reactfl
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { formatPluginIOModules } from '@fastgpt/global/core/module/utils';
|
||||
import { formatPluginToPreviewModule } from '@fastgpt/global/core/module/utils';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -12,7 +12,7 @@ import { appModule2FlowNode } from '@/utils/adapt';
|
||||
|
||||
const nodeTypes = {
|
||||
[FlowNodeTypeEnum.pluginModule]: dynamic(
|
||||
() => import('@/components/core/module/Flow/components/nodes/NodePreviewPlugin')
|
||||
() => import('@/components/core/module/Flow/components/nodes/NodeSimple')
|
||||
)
|
||||
};
|
||||
|
||||
@@ -36,16 +36,21 @@ const PreviewPlugin = ({
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
logo: plugin.avatar,
|
||||
name: plugin.name,
|
||||
description: plugin.intro,
|
||||
intro: plugin.intro,
|
||||
...formatPluginIOModules(plugin._id, modules)
|
||||
...formatPluginToPreviewModule(plugin._id, modules)
|
||||
}
|
||||
})
|
||||
]);
|
||||
}, [modules, plugin, setNodes]);
|
||||
|
||||
return (
|
||||
<MyModal isOpen title={t('module.Preview Plugin')} onClose={onClose} isCentered>
|
||||
<MyModal
|
||||
isOpen
|
||||
title={t('module.Preview Plugin')}
|
||||
iconSrc="/imgs/modal/preview.svg"
|
||||
onClose={onClose}
|
||||
isCentered
|
||||
>
|
||||
<Box h={'400px'} w={'400px'}>
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useRouter } from 'next/router';
|
||||
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 { PluginModuleTemplates } from '@/constants/flow/ModuleTemplate';
|
||||
import { FlowModuleTemplateType } from '@fastgpt/global/core/module/type.d';
|
||||
import { pluginSystemModuleTemplates } from '@/web/core/modules/template/system';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getOnePlugin, getUserPlugs2ModuleTemplates } from '@/web/core/plugin/api';
|
||||
import { getOnePlugin } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import Loading from '@/components/Loading';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
@@ -22,11 +22,11 @@ const Render = ({ pluginId }: Props) => {
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { nodes = [] } = useFlowProviderStore();
|
||||
const { pluginModuleTemplates, loadPluginModuleTemplates } = usePluginStore();
|
||||
const { pluginModuleTemplates, loadPluginTemplates } = usePluginStore();
|
||||
|
||||
const filterTemplates = useMemo(() => {
|
||||
const copyTemplates: SystemModuleTemplateType = JSON.parse(
|
||||
JSON.stringify(PluginModuleTemplates)
|
||||
const copyTemplates: FlowModuleTemplateType[] = JSON.parse(
|
||||
JSON.stringify(pluginSystemModuleTemplates)
|
||||
);
|
||||
const filterType: Record<string, 1> = {
|
||||
[FlowNodeTypeEnum.userGuide]: 1,
|
||||
@@ -37,12 +37,10 @@ const Render = ({ pluginId }: Props) => {
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
copyTemplates.forEach((module, index) => {
|
||||
if (module.flowType === node.type) {
|
||||
copyTemplates.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -60,16 +58,15 @@ const Render = ({ pluginId }: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
useQuery(['getUserPlugs2ModuleTemplates'], () => loadPluginModuleTemplates());
|
||||
const filterPlugins = useMemo(
|
||||
() => pluginModuleTemplates.filter((item) => item.id !== pluginId),
|
||||
[pluginId, pluginModuleTemplates]
|
||||
);
|
||||
useQuery(['getPlugTemplates'], () => loadPluginTemplates());
|
||||
const filterPlugins = useMemo(() => {
|
||||
return pluginModuleTemplates.filter((item) => item.id !== pluginId);
|
||||
}, [pluginId, pluginModuleTemplates]);
|
||||
|
||||
return data ? (
|
||||
<Flow
|
||||
systemTemplates={filterTemplates}
|
||||
pluginTemplates={[{ label: '', list: filterPlugins }]}
|
||||
pluginTemplates={filterPlugins}
|
||||
modules={data?.modules || []}
|
||||
Header={<Header plugin={data} onClose={() => router.back()} />}
|
||||
/>
|
||||
@@ -80,7 +77,7 @@ const Render = ({ pluginId }: Props) => {
|
||||
|
||||
export default function AdEdit(props: any) {
|
||||
return (
|
||||
<FlowProvider filterAppIds={[]}>
|
||||
<FlowProvider mode={'plugin'} filterAppIds={[]}>
|
||||
<Render {...props} />
|
||||
</FlowProvider>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user