4.7-alpha2 (#1027)

* feat: stop toolCall and rename some field. (#46)

* perf: node delete tip;pay tip

* fix: toolCall cannot save child answer

* feat: stop tool

* fix: team modal

* fix feckbackMoal  auth bug (#47)

* 简单的支持提示词运行tool。优化workflow模板 (#49)

* remove templates

* fix: request body undefined

* feat: prompt tool run

* feat: workflow tamplates modal

* perf: plugin start

* 4.7 (#50)

* fix docker-compose download url (#994)

original code is a bad url with '404 NOT FOUND' return.
fix docker-compose download url, add 'v' before docker-compose version

* Update ai_settings.md (#1000)

* Update configuration.md

* Update configuration.md

* Fix history in classifyQuestion and extract modules (#1012)

* Fix history in classifyQuestion and extract modules

* Add chatValue2RuntimePrompt import and update text formatting

* flow controller to packages

* fix: rerank select

* modal ui

* perf: modal code path

* point not sufficient

* feat: http url support variable

* fix http key

* perf: prompt

* perf: ai setting modal

* simple edit ui

---------

Co-authored-by: entorick <entorick11@qq.com>
Co-authored-by: liujianglc <liujianglc@163.com>
Co-authored-by: Fengrui Liu <liufengrui.work@bytedance.com>

* fix team share redirect to login (#51)

* feat: support openapi import plugins (#48)

* feat: support openapi import plugins

* feat: import from url

* fix: add body params parse

* fix build

* fix

* fix

* fix

* tool box ui (#52)

* fix: training queue

* feat: simple edit tool select

* perf: simple edit dataset prompt

* fix: chatbox tool ux

* feat: quote prompt module

* perf: plugin tools sign

* perf: model avatar

* tool selector ui

* feat: max histories

* perf: http plugin import (#53)

* perf: plugin http import

* chatBox ui

* perf: name

* fix: Node template card (#54)

* fix: ts

* setting modal

* package

* package

* feat: add plugins search (#57)

* feat: add plugins search

* perf: change http plugin header input

* Yjl (#56)

* perf: prompt tool call

* perf: chat box ux

* doc

* doc

* price tip

* perf: tool selector

* ui'

* fix: vector queue

* fix: empty tool and empty response

* fix: empty msg

* perf: pg index

* perf: ui tip

* doc

* tool tip

---------

Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com>
Co-authored-by: entorick <entorick11@qq.com>
Co-authored-by: liujianglc <liujianglc@163.com>
Co-authored-by: Fengrui Liu <liufengrui.work@bytedance.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-03-21 13:32:31 +08:00
committed by GitHub
parent 6d4b331db9
commit 9d27de154b
322 changed files with 9282 additions and 6498 deletions

View File

@@ -14,7 +14,7 @@ const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
w={w}
h={w}
p={'1px'}
src={src || LOGO_ICON}
src={src}
{...props}
/>
);

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { DispatchNodeResponseType } from '@fastgpt/global/core/module/runtime/type.d';
const ContextModal = ({

View File

@@ -1,6 +1,6 @@
import React, { useRef } from 'react';
import { ModalBody, Textarea, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { updateChatUserFeedback } from '@/web/core/chat/api';
@@ -9,6 +9,8 @@ const FeedbackModal = ({
appId,
chatId,
chatItemId,
teamId,
teamToken,
shareId,
outLinkUid,
onSuccess,
@@ -18,6 +20,8 @@ const FeedbackModal = ({
chatId: string;
chatItemId: string;
shareId?: string;
teamId?: string;
teamToken?: string;
outLinkUid?: string;
onSuccess: (e: string) => void;
onClose: () => void;
@@ -33,6 +37,8 @@ const FeedbackModal = ({
chatId,
chatItemId,
shareId,
teamId,
teamToken,
outLinkUid,
userBadFeedback: val
});

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import QuoteItem from '../core/dataset/QuoteItem';

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
const ReadFeedbackModal = ({

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@fastgpt/global/core/module/template/constants';
import Tabs from '../Tabs';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import Markdown from '../Markdown';

View File

@@ -9,9 +9,7 @@ import {
AccordionButton,
AccordionPanel,
AccordionIcon,
Button,
Image,
Grid
Image
} from '@chakra-ui/react';
import React, { useMemo } from 'react';
import ChatController, { type ChatControllerProps } from './ChatController';
@@ -88,7 +86,7 @@ const ChatItem = ({
return (
<>
{files.length > 0 && <FilesBlock files={files} />}
<Markdown source={text} isChatting={false} />
<Markdown source={text} />
</>
);
}
@@ -100,13 +98,26 @@ const ChatItem = ({
if (value.text) {
let source = value.text?.content || '';
if (isLastChild && !isChatting && questionGuides.length > 0) {
if (!source && chat.value.length > 1) return <></>;
if (
isLastChild &&
!isChatting &&
questionGuides.length > 0 &&
i === chat.value.length - 1
) {
source = `${source}
\`\`\`${CodeClassName.questionGuide}
${JSON.stringify(questionGuides)}`;
}
return <Markdown key={key} source={source} isChatting={isLastChild && isChatting} />;
return (
<Markdown
key={key}
source={source}
showAnimation={isLastChild && isChatting && i === chat.value.length - 1}
/>
);
}
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
return (
@@ -233,4 +244,4 @@ ${toolResponse}`}
);
};
export default ChatItem;
export default React.memo(ChatItem);

View File

@@ -18,7 +18,7 @@ const WelcomeBox = ({ appAvatar, welcomeText }: { appAvatar?: string; welcomeTex
bg={'white'}
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
>
<Markdown source={`~~~guide \n${welcomeText}`} isChatting={false} />
<Markdown source={`~~~guide \n${welcomeText}`} />
</Card>
</Box>
</Box>

View File

@@ -257,10 +257,7 @@ const ChatBox = (
};
return {
...item,
value:
lastValue && lastValue.text
? item.value.slice(0, -1).concat(val)
: item.value.concat(val)
value: item.value.concat(val)
};
} else if (
event === SseResponseEventEnum.toolParams &&
@@ -441,6 +438,7 @@ const ChatBox = (
chatController.current = abortSignal;
const messages = chats2GPTMessages({ messages: newChatList, reserveId: true });
const {
responseData,
responseText,
@@ -537,10 +535,9 @@ const ChatBox = (
setLoading(true);
const index = chatHistories.findIndex((item) => item.dataId === dataId);
const delHistory = chatHistories.slice(index);
try {
await Promise.all(
delHistory.map(async (item) => {
delHistory.map((item) => {
if (item.dataId) {
return onDelMessage({ contentId: item.dataId });
}
@@ -563,14 +560,29 @@ const ChatBox = (
},
[chatHistories, onDelMessage, sendPrompt, setLoading, toast]
);
// delete one message
// delete one message(One human and the ai response)
const delOneMessage = useCallback(
(dataId?: string) => {
if (!dataId || !onDelMessage) return;
return () => {
setChatHistories((state) => state.filter((chat) => chat.dataId !== dataId));
onDelMessage({
contentId: dataId
setChatHistories((state) => {
let aiIndex = -1;
return state.filter((chat, i) => {
if (chat.dataId === dataId) {
aiIndex = i + 1;
onDelMessage({
contentId: dataId
});
return false;
} else if (aiIndex === i && chat.obj === ChatRoleEnum.AI && chat.dataId) {
onDelMessage({
contentId: chat.dataId
});
return false;
}
return true;
});
});
};
},
@@ -630,6 +642,8 @@ const ChatBox = (
updateChatUserFeedback({
appId,
chatId,
teamId,
teamToken,
chatItemId: chat.dataId,
shareId,
outLinkUid,
@@ -654,6 +668,8 @@ const ChatBox = (
);
updateChatUserFeedback({
appId,
teamId,
teamToken,
chatId,
chatItemId: chat.dataId,
userGoodFeedback: undefined
@@ -687,6 +703,8 @@ const ChatBox = (
chatId,
chatItemId: chat.dataId,
shareId,
teamId,
teamToken,
outLinkUid
});
} catch (error) {}
@@ -869,7 +887,6 @@ const ChatBox = (
avatar={appAvatar}
chat={item}
isChatting={isChatting}
onDelete={delOneMessage(item.dataId)}
{...(item.obj === 'AI' && {
setChatHistories,
showVoiceIcon,
@@ -955,6 +972,8 @@ const ChatBox = (
{!!feedbackId && chatId && appId && (
<FeedbackModal
appId={appId}
teamId={teamId}
teamToken={teamToken}
chatId={chatId}
chatItemId={feedbackId}
shareId={shareId}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Button, ModalFooter, ModalBody } from '@chakra-ui/react';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import Markdown from '../Markdown';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -10,12 +10,7 @@ const CommunityModal = ({ onClose }: { onClose: () => void }) => {
const { feConfigs } = useSystemStore();
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/concat.svg"
title={t('home.Community')}
>
<MyModal isOpen={true} onClose={onClose} iconSrc="modal/concat" title={t('home.Community')}>
<ModalBody textAlign={'center'}>
<Markdown source={feConfigs?.concatMd || ''} />
</ModalBody>

View File

@@ -12,10 +12,8 @@ import dynamic from 'next/dynamic';
import Auth from './auth';
import Navbar from './navbar';
import NavbarPhone from './navbarPhone';
const UpdateInviteModal = dynamic(
() => import('@/components/support/user/team/UpdateInviteModal'),
{ ssr: false }
);
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'));
const pcUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
@@ -44,7 +42,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
const router = useRouter();
const { colorMode, setColorMode } = useColorMode();
const { Loading } = useLoading();
const { loading, setScreenWidth, isPc, feConfigs } = useSystemStore();
const { loading, setScreenWidth, isPc, feConfigs, isNotSufficientModal } = useSystemStore();
const { userInfo } = useUserStore();
const isChatPage = useMemo(
@@ -118,6 +116,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
)}
{!!userInfo && <UpdateInviteModal />}
{isNotSufficientModal && !isHideNavbar && <NotSufficientModal />}
</Box>
<Loading loading={loading} zIndex={999999} />
</>

View File

@@ -36,10 +36,10 @@ export enum CodeClassName {
const Markdown = ({
source = '',
isChatting = false
showAnimation = false
}: {
source?: string;
isChatting?: boolean;
showAnimation?: boolean;
}) => {
const components = useMemo<any>(
() => ({
@@ -60,7 +60,7 @@ const Markdown = ({
return (
<ReactMarkdown
className={`markdown ${styles.markdown}
${isChatting ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
${showAnimation ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
`}
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
rehypePlugins={[RehypeKatex]}

View File

@@ -1,42 +0,0 @@
import React from 'react';
import { ModalContentProps } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import CustomModal from '@fastgpt/web/components/common/CustomModal';
export interface MyModalProps extends ModalContentProps {
iconSrc?: string;
title?: any;
isCentered?: boolean;
isOpen: boolean;
onClose?: () => void;
}
const MyModal = ({
isOpen,
onClose,
iconSrc,
title,
children,
isCentered,
w = 'auto',
maxW = ['90vw', '600px'],
...props
}: MyModalProps) => {
const { isPc } = useSystemStore();
return (
<CustomModal
isOpen={isOpen}
onClose={onClose}
iconSrc={iconSrc}
title={title}
isCentered={isPc ? isCentered : true}
w={w}
maxW={maxW}
{...props}
>
{children}
</CustomModal>
);
};
export default MyModal;

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import MyModal from '../MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Box, Button, Flex, Grid, useTheme } from '@chakra-ui/react';
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
import { ModalBody, ModalFooter } from '@chakra-ui/react';

View File

@@ -0,0 +1,70 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
import MySelect, { SelectProps } from '@fastgpt/web/components/common/MySelect';
import { HUGGING_FACE_ICON, LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { Box, Flex } from '@chakra-ui/react';
import Avatar from '../Avatar';
const AIModelSelector = ({ list, ...props }: SelectProps) => {
const { t } = useTranslation();
const { feConfigs, llmModelList, vectorModelList } = useSystemStore();
const router = useRouter();
const avatarList = list.map((item) => {
const modelData =
llmModelList.find((model) => model.model === item.value) ||
vectorModelList.find((model) => model.model === item.value);
return {
value: item.value,
label: (
<Flex alignItems={'center'} py={1}>
<Avatar
borderRadius={'0'}
mr={2}
src={modelData?.avatar || HUGGING_FACE_ICON}
fallbackSrc={HUGGING_FACE_ICON}
w={'18px'}
/>
<Box>{item.label}</Box>
</Flex>
)
};
});
const expandList = useMemo(() => {
return feConfigs.show_pay
? avatarList.concat({
label: (
<Flex alignItems={'center'}>
<Avatar borderRadius={'0'} mr={2} src={LOGO_ICON} w={'18px'} />
<Box>{t('support.user.Price')}</Box>
</Flex>
),
value: 'price'
})
: avatarList;
}, [feConfigs.show_pay, avatarList, t]);
return (
<>
<MySelect
list={expandList}
{...props}
onchange={(e) => {
if (e === 'price') {
router.push(AI_POINT_USAGE_CARD_ROUTE);
return;
}
props.onchange?.(e);
}}
/>
</>
);
};
export default AIModelSelector;

View File

@@ -1,40 +0,0 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
import MySelect, { SelectProps } from '@fastgpt/web/components/common/MySelect';
const SelectAiModel = ({ list, ...props }: SelectProps) => {
const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const router = useRouter();
const expandList = useMemo(() => {
return feConfigs.show_pay
? list.concat({
label: t('support.user.Price'),
value: 'price'
})
: list;
}, [feConfigs.show_pay, list, t]);
return (
<>
<MySelect
list={expandList}
{...props}
onchange={(e) => {
if (e === 'price') {
router.push(AI_POINT_USAGE_CARD_ROUTE);
return;
}
props.onchange?.(e);
}}
/>
</>
);
};
export default SelectAiModel;

View File

@@ -10,8 +10,8 @@ type Props = BoxProps & {
const MyBox = ({ text, isLoading, children, ...props }: Props) => {
return (
<Box position={'relative'} {...props}>
{children}
{isLoading && <Loading fixed={false} text={text} />}
{children}
</Box>
);
};

View File

@@ -9,9 +9,16 @@ const ParentPaths = (props: {
rootName?: string;
FirstPathDom?: React.ReactNode;
onClick: (parentId: string) => void;
fontSize?: string;
}) => {
const { t } = useTranslation();
const { paths = [], rootName = t('common.folder.Root Path'), FirstPathDom, onClick } = props;
const {
paths = [],
rootName = t('common.folder.Root Path'),
FirstPathDom,
onClick,
fontSize
} = props;
const concatPaths = useMemo(
() => [
{
@@ -30,7 +37,7 @@ const ParentPaths = (props: {
{concatPaths.map((item, i) => (
<Flex key={item.parentId || i} alignItems={'center'}>
<Box
fontSize={['sm', 'lg']}
fontSize={['sm', fontSize || 'lg']}
py={1}
px={[1, 2]}
borderRadius={'md'}

View File

@@ -12,7 +12,7 @@ import {
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
type Props = TextareaProps & {
title?: string;

View File

@@ -0,0 +1,242 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import {
Box,
BoxProps,
Button,
Flex,
Link,
ModalBody,
ModalFooter,
Switch
} from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { SettingAIDataType } from '@fastgpt/global/core/module/node/type.d';
import { getDocPath } from '@/web/common/system/doc';
import AIModelSelector from '@/components/Select/AIModelSelector';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
const AIChatSettingsModal = ({
onClose,
onSuccess,
defaultData,
llmModels = []
}: {
onClose: () => void;
onSuccess: (e: SettingAIDataType) => void;
defaultData: SettingAIDataType;
llmModels?: LLMModelItemType[];
}) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { feConfigs, llmModelList } = useSystemStore();
const { handleSubmit, getValues, setValue, watch } = useForm({
defaultValues: defaultData
});
const model = watch('model');
const showResponseAnswerText = watch(ModuleInputKeyEnum.aiChatIsResponseText) !== undefined;
const showMaxHistoriesSlider = watch('maxHistories') !== undefined;
const selectedModel = llmModelList.find((item) => item.model === model) || llmModelList[0];
const tokenLimit = useMemo(() => {
return llmModelList.find((item) => item.model === model)?.maxResponse || 4096;
}, [llmModelList, model]);
const onChangeModel = (e: string) => {
setValue('model', e);
// update max tokens
const modelData = llmModelList.find((item) => item.model === e);
if (modelData) {
setValue('maxToken', modelData.maxResponse / 2);
}
setRefresh(!refresh);
};
const LabelStyles: BoxProps = {
display: 'flex',
alignItems: 'center',
fontSize: ['sm', 'md'],
width: ['80px', '90px']
};
return (
<MyModal
isOpen
iconSrc="/imgs/module/AI.png"
onClose={onClose}
title={
<>
{t('core.ai.AI settings')}
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/course/ai_settings/')}
target={'_blank'}
ml={1}
textDecoration={'underline'}
fontWeight={'normal'}
fontSize={'md'}
>
{t('common.Read intro')}
</Link>
)}
</>
}
w={'500px'}
>
<ModalBody overflowY={'auto'}>
<Flex alignItems={'center'}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Model')}
</Box>
<Box flex={'1 0 0'}>
<AIModelSelector
width={'100%'}
value={model}
list={llmModels.map((item) => ({
value: item.model,
label: item.name
}))}
onchange={onChangeModel}
/>
</Box>
</Flex>
{feConfigs && (
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Ai point price')}
</Box>
<Box flex={1} ml={'10px'}>
{t('support.wallet.Ai point every thousand tokens', {
points: selectedModel?.charsPointsPrice || 0
})}
</Box>
</Flex>
)}
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Max context')}
</Box>
<Box flex={1} ml={'10px'}>
{selectedModel?.maxContext || 4096}Tokens
</Box>
</Flex>
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.ai.Support tool')}
<QuestionTip ml={1} label={t('core.module.template.AI support tool tip')} />
</Box>
<Box flex={1} ml={'10px'}>
{selectedModel?.usedInToolCall ? '支持' : '不支持'}
</Box>
</Flex>
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.app.Max tokens')}
</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>
{showMaxHistoriesSlider && (
<Flex mt={8}>
<Box {...LabelStyles} mr={2}>
{t('core.app.Max histories')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: 0, value: 0 },
{ label: 30, value: 30 }
]}
width={'95%'}
min={0}
max={30}
value={getValues('maxHistories') ?? 6}
onChange={(e) => {
setValue('maxHistories', e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
)}
{showResponseAnswerText && (
<Flex mt={8} alignItems={'center'}>
<Box {...LabelStyles}>
{t('core.app.Ai response')}
<MyTooltip label={t('core.module.template.AI response switch tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} ml={'10px'}>
<Switch
isChecked={getValues(ModuleInputKeyEnum.aiChatIsResponseText)}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue(ModuleInputKeyEnum.aiChatIsResponseText, value);
setRefresh((state) => !state);
}}
/>
</Box>
</Flex>
)}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button ml={4} onClick={handleSubmit(onSuccess)}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default AIChatSettingsModal;

View File

@@ -0,0 +1,83 @@
import React, { useEffect } from 'react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { LLMModelTypeEnum, llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
import { Box, Button, useDisclosure } from '@chakra-ui/react';
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
import AISettingModal from '@/components/core/ai/AISettingModal';
import Avatar from '@/components/Avatar';
import { HUGGING_FACE_ICON } from '@fastgpt/global/common/system/constants';
type Props = {
llmModelType?: `${LLMModelTypeEnum}`;
defaultData: SettingAIDataType;
onChange: (e: SettingAIDataType) => void;
};
const SettingLLMModel = ({ llmModelType = LLMModelTypeEnum.all, defaultData, onChange }: Props) => {
const { llmModelList } = useSystemStore();
const model = defaultData.model;
const modelList = llmModelList.filter((model) => {
if (!llmModelType) return true;
const filterField = llmModelTypeFilterMap[llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
});
const selectedModel = modelList.find((item) => item.model === model) || modelList[0];
const {
isOpen: isOpenAIChatSetting,
onOpen: onOpenAIChatSetting,
onClose: onCloseAIChatSetting
} = useDisclosure();
useEffect(() => {
if (!model && modelList.length > 0) {
onChange({
...defaultData,
model: modelList[0].model
});
}
}, [defaultData, model, modelList, onChange]);
return (
<Box position={'relative'}>
<Button
w={'100%'}
justifyContent={'flex-start'}
variant={'whitePrimary'}
_active={{
transform: 'none'
}}
leftIcon={
<Avatar
borderRadius={'0'}
src={selectedModel.avatar || HUGGING_FACE_ICON}
fallbackSrc={HUGGING_FACE_ICON}
w={'18px'}
/>
}
pl={4}
onClick={onOpenAIChatSetting}
>
{selectedModel?.name}
</Button>
{isOpenAIChatSetting && (
<AISettingModal
onClose={onCloseAIChatSetting}
onSuccess={(e) => {
onChange(e);
onCloseAIChatSetting();
}}
defaultData={defaultData}
llmModels={modelList}
/>
)}
</Box>
);
};
export default React.memo(SettingLLMModel);

View File

@@ -50,7 +50,7 @@ const SearchParamsTip = ({
</Thead>
<Tbody>
<Tr color={'myGray.800'}>
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
<Flex alignItems={'center'}>
<MyIcon
name={DatasetSearchModeMap[searchMode]?.icon as any}
@@ -60,18 +60,18 @@ const SearchParamsTip = ({
{t(DatasetSearchModeMap[searchMode]?.title)}
</Flex>
</Td>
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{limit}
</Td>
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{hasSimilarityMode ? similarity : t('core.dataset.search.Nonsupport')}
</Td>
{hasReRankModel && (
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{usingReRank ? '✅' : '❌'}
</Td>
)}
<Td pt={0} pb={1}>
<Td pt={0} pb={2}>
{usingQueryExtension ? '✅' : '❌'}
</Td>
{hasEmptyResponseMode && <Th>{responseEmptyText !== '' ? '✅' : '❌'}</Th>}

View File

@@ -1,5 +1,5 @@
import { getDatasets, getDatasetPaths } from '@/web/core/dataset/api';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useQuery } from '@tanstack/react-query';
import React, { Dispatch, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';

View File

@@ -1,293 +0,0 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import {
Box,
BoxProps,
Button,
Flex,
Link,
ModalBody,
ModalFooter,
Switch
} from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
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';
import { getDocPath } from '@/web/common/system/doc';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
const PromptTemplate = dynamic(() => import('@/components/PromptTemplate'));
const AIChatSettingsModal = ({
isAdEdit,
onClose,
onSuccess,
defaultData,
pickerMenu = []
}: {
isAdEdit?: boolean;
onClose: () => void;
onSuccess: (e: AIChatModuleProps) => void;
defaultData: AIChatModuleProps;
pickerMenu?: EditorVariablePickerType[];
}) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { feConfigs, llmModelList } = useSystemStore();
const { handleSubmit, getValues, setValue, watch } = useForm({
defaultValues: defaultData
});
const aiChatQuoteTemplate = watch(ModuleInputKeyEnum.aiChatQuoteTemplate);
const aiChatQuotePrompt = watch(ModuleInputKeyEnum.aiChatQuotePrompt);
const [selectTemplateData, setSelectTemplateData] = useState<{
title: string;
templates: PromptTemplateItem[];
}>();
const tokenLimit = useMemo(() => {
return (
llmModelList.find((item) => item.model === getValues(ModuleInputKeyEnum.aiModel))
?.maxResponse || 4000
);
}, [getValues, llmModelList]);
const quoteTemplateVariables = (() => [
{
key: 'q',
label: 'q',
icon: 'core/app/simpleMode/variable'
},
{
key: 'a',
label: 'a',
icon: 'core/app/simpleMode/variable'
},
{
key: 'source',
label: t('core.dataset.search.Source name'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'sourceId',
label: t('core.dataset.search.Source id'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'index',
label: t('core.dataset.search.Quote index'),
icon: 'core/app/simpleMode/variable'
},
...pickerMenu
])();
const quotePromptVariables = (() => [
{
key: 'quote',
label: t('core.app.Quote templates'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'question',
label: t('core.module.input.label.user question'),
icon: 'core/app/simpleMode/variable'
},
...pickerMenu
])();
const LabelStyles: BoxProps = {
fontSize: ['sm', 'md']
};
const selectTemplateBtn: BoxProps = {
color: 'primary.500',
cursor: 'pointer'
};
return (
<MyModal
isOpen
iconSrc="/imgs/module/AI.png"
title={
<>
{t('common.More settings')}
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/use-cases/ai_settings/')}
target={'_blank'}
ml={1}
textDecoration={'underline'}
fontWeight={'normal'}
fontSize={'md'}
>
{t('common.Read intro')}
</Link>
)}
</>
}
isCentered
w={'700px'}
h={['90vh', 'auto']}
>
<ModalBody flex={['1 0 0', 'auto']} overflowY={'auto'} minH={'40vh'}>
{isAdEdit && (
<Flex alignItems={'center'}>
<Box {...LabelStyles} w={'80px'}>
{t('core.app.Ai response')}
</Box>
<Box flex={1} ml={'10px'}>
<Switch
isChecked={getValues(ModuleInputKeyEnum.aiChatIsResponseText)}
size={'lg'}
onChange={(e) => {
const value = e.target.checked;
setValue(ModuleInputKeyEnum.aiChatIsResponseText, value);
setRefresh((state) => !state);
}}
/>
</Box>
</Flex>
)}
<Flex mb={10} mt={isAdEdit ? 8 : 6}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Temperature')}
</Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: t('core.app.deterministic'), value: 0 },
{ label: t('core.app.Random'), value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={5} mb={5}>
<Box {...LabelStyles} mr={2} w={'80px'}>
{t('core.app.Max tokens')}
</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>
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<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: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptEditor
variables={quoteTemplateVariables}
h={160}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
value={aiChatQuoteTemplate}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, e);
// setRefresh(!refresh);
}}
/>
</Box>
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={230}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
value={aiChatQuotePrompt}
onChange={(e) => {
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, e);
}}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button ml={4} onClick={handleSubmit(onSuccess)}>
{t('common.Confirm')}
</Button>
</ModalFooter>
{!!selectTemplateData && (
<PromptTemplate
title={selectTemplateData.title}
templates={selectTemplateData.templates}
onClose={() => setSelectTemplateData(undefined)}
onSuccess={(e) => {
const quoteVal = e.value;
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
setValue(ModuleInputKeyEnum.aiChatQuoteTemplate, quoteVal);
setValue(ModuleInputKeyEnum.aiChatQuotePrompt, promptVal);
}}
/>
)}
</MyModal>
);
};
export default AIChatSettingsModal;

View File

@@ -15,7 +15,7 @@ import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -28,7 +28,7 @@ import Tabs from '@/components/Tabs';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import SelectAiModel from '@/components/Select/SelectAiModel';
import SelectAiModel from '@/components/Select/AIModelSelector';
export type DatasetParamsProps = {
searchMode: `${DatasetSearchModeEnum}`;
@@ -40,7 +40,6 @@ export type DatasetParamsProps = {
datasetSearchExtensionBg?: string;
maxTokens?: number; // limit max tokens
searchEmptyText?: string;
};
enum SearchSettingTabEnum {
searchMode = 'searchMode',
@@ -50,7 +49,6 @@ enum SearchSettingTabEnum {
const DatasetParamsModal = ({
searchMode = DatasetSearchModeEnum.embedding,
searchEmptyText,
limit,
similarity,
usingReRank,
@@ -71,11 +69,10 @@ const DatasetParamsModal = ({
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
defaultValues: {
searchEmptyText,
limit,
similarity,
searchMode,
usingReRank: !!usingReRank && !!teamPlanStatus?.standardConstants?.permissionReRank,
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
datasetSearchExtensionBg
@@ -272,21 +269,6 @@ const DatasetParamsModal = ({
</Box>
</Box>
)}
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 120px'} mb={[2, 0]}>
{t('core.dataset.search.Empty result response')}
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={t('core.dataset.search.Empty result response Tips')}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Box>
)}
</Box>
)}
{currentTabType === SearchSettingTabEnum.queryExtension && (

View File

@@ -8,10 +8,7 @@ import {
Connection,
addEdge
} from 'reactflow';
import type {
FlowModuleItemType,
FlowModuleTemplateType
} from '@fastgpt/global/core/module/type.d';
import type { FlowModuleItemType, FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
import type {
FlowNodeChangeProps,
FlowNodeInputItemType
@@ -64,7 +61,7 @@ export type useFlowProviderStoreType = {
onDelNode: (nodeId: string) => void;
onChangeNode: (e: FlowNodeChangeProps) => void;
onCopyNode: (nodeId: string) => void;
onResetNode: (e: { id: string; module: FlowModuleTemplateType }) => void;
onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void;
onDelEdge: (e: {
moduleId: string;
sourceHandle?: string | undefined;
@@ -417,7 +414,7 @@ export const FlowProvider = ({
// reset a node data. delete edge and replace it
const onResetNode = useCallback(
({ id, module }: { id: string; module: FlowModuleTemplateType }) => {
({ id, module }: { id: string; module: FlowNodeTemplateType }) => {
setNodes((state) =>
state.map((node) => {
if (node.id === id) {
@@ -549,12 +546,6 @@ export const onResetNode = (e: Parameters<useFlowProviderStoreType['onResetNode'
data: e
});
};
export const onDelNode = (nodeId: string) => {
eventBus.emit(EventNameEnum.requestFlowEvent, {
type: 'onDelNode',
data: nodeId
});
};
export const onDelConnect = (e: Parameters<useFlowProviderStoreType['onDelConnect']>[0]) => {
eventBus.emit(EventNameEnum.requestFlowEvent, {
type: 'onDelConnect',

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from './FlowProvider';

View File

@@ -1,13 +1,13 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import React, { useCallback, useMemo, useState } from 'react';
import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement } from '@chakra-ui/react';
import type {
FlowModuleTemplateType,
FlowNodeTemplateType,
moduleTemplateListType
} from '@fastgpt/global/core/module/type.d';
import { useViewport, XYPosition } from 'reactflow';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import Avatar from '@/components/Avatar';
import { getFlowStore, onSetNodes } from './FlowProvider';
import { onSetNodes, useFlowProviderStore } from './FlowProvider';
import { customAlphabet } from 'nanoid';
import { appModule2FlowNode } from '@/utils/adapt';
import { useTranslation } from 'next-i18next';
@@ -18,22 +18,83 @@ import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { moduleTemplatesList } from '@fastgpt/global/core/module/template/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@/web/common/hooks/useRequest';
import ParentPaths from '@/components/common/ParentPaths';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { useQuery } from '@tanstack/react-query';
import { debounce } from 'lodash';
export type ModuleTemplateProps = {
templates: FlowModuleTemplateType[];
};
type ModuleTemplateListProps = ModuleTemplateProps & {
type ModuleTemplateListProps = {
isOpen: boolean;
onClose: () => void;
};
type RenderListProps = {
templates: FlowModuleTemplateType[];
templates: FlowNodeTemplateType[];
onClose: () => void;
currentParent?: { parentId: string; parentName: string };
setCurrentParent: (e: { parentId: string; parentName: string }) => void;
};
const ModuleTemplateList = ({ templates, isOpen, onClose }: ModuleTemplateListProps) => {
enum TemplateTypeEnum {
'basic' = 'basic',
'systemPlugin' = 'systemPlugin',
'teamPlugin' = 'teamPlugin'
}
const sliderWidth = 380;
const ModuleTemplateList = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const { t } = useTranslation();
const router = useRouter();
const [currentParent, setCurrentParent] = useState<RenderListProps['currentParent']>();
const [searchKey, setSearchKey] = useState('');
const {
basicNodeTemplates,
systemNodeTemplates,
loadSystemNodeTemplates,
teamPluginNodeTemplates,
loadTeamPluginNodeTemplates
} = useWorkflowStore();
const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic);
const templates = useMemo(() => {
const map = {
[TemplateTypeEnum.basic]: basicNodeTemplates,
[TemplateTypeEnum.systemPlugin]: systemNodeTemplates,
[TemplateTypeEnum.teamPlugin]: teamPluginNodeTemplates.filter((item) =>
searchKey ? item.pluginType !== PluginTypeEnum.folder : true
)
};
return map[templateType];
}, [basicNodeTemplates, searchKey, systemNodeTemplates, teamPluginNodeTemplates, templateType]);
const { mutate: onChangeTab } = useRequest({
mutationFn: async (e: any) => {
const val = e as TemplateTypeEnum;
if (val === TemplateTypeEnum.systemPlugin) {
await loadSystemNodeTemplates();
} else if (val === TemplateTypeEnum.teamPlugin) {
await loadTeamPluginNodeTemplates({
parentId: currentParent?.parentId
});
}
setTemplateType(val);
},
errorToast: t('core.module.templates.Load plugin error')
});
useQuery(['teamNodeTemplate', currentParent?.parentId, searchKey], () =>
loadTeamPluginNodeTemplates({
parentId: currentParent?.parentId,
searchKey,
init: true
})
);
return (
<>
@@ -44,27 +105,108 @@ const ModuleTemplateList = ({ templates, isOpen, onClose }: ModuleTemplateListPr
top={0}
left={0}
bottom={0}
w={'360px'}
w={`${sliderWidth}px`}
onClick={onClose}
/>
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={'65px'}
top={'10px'}
left={0}
pt={2}
pt={'20px'}
pb={4}
h={isOpen ? 'calc(100% - 100px)' : '0'}
w={isOpen ? ['100%', '360px'] : '0'}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'20px'}
overflow={'hidden'}
borderRadius={'0 20px 20px 0'}
transition={'.2s ease'}
userSelect={'none'}
overflow={isOpen ? 'none' : 'hidden'}
>
<RenderList templates={templates} onClose={onClose} />
<Box mb={2} pl={'20px'} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
list={[
{
icon: 'core/modules/basicNode',
label: t('core.module.template.Basic Node'),
value: TemplateTypeEnum.basic
},
{
icon: 'core/modules/systemPlugin',
label: t('core.module.template.System Plugin'),
value: TemplateTypeEnum.systemPlugin
},
{
icon: 'core/modules/teamPlugin',
label: t('core.module.template.Team Plugin'),
value: TemplateTypeEnum.teamPlugin
}
]}
py={'5px'}
value={templateType}
onChange={onChangeTab}
/>
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'grayBase'}
aria-label={''}
onClick={onClose}
/>
</Flex>
{templateType === TemplateTypeEnum.teamPlugin && (
<Flex mt={2} alignItems={'center'} h={10}>
<InputGroup mr={4} h={'full'}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
</InputLeftElement>
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('plugin.Search plugin')}
onChange={debounce((e) => setSearchKey(e.target.value), 200)}
/>
</InputGroup>
<Box flex={1} />
<Flex
alignItems={'center'}
cursor={'pointer'}
_hover={{
color: 'primary.600'
}}
onClick={() => router.push('/plugin/list')}
>
<Box></Box>
<MyIcon name={'common/rightArrowLight'} w={'14px'} />
</Flex>
</Flex>
)}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex alignItems={'center'} mt={2}>
<ParentPaths
paths={[currentParent]}
FirstPathDom={null}
onClick={() => {
setCurrentParent(undefined);
}}
fontSize="md"
/>
</Flex>
)}
</Box>
<RenderList
templates={templates}
onClose={onClose}
currentParent={currentParent}
setCurrentParent={setCurrentParent}
/>
</Flex>
</>
);
@@ -72,12 +214,18 @@ const ModuleTemplateList = ({ templates, isOpen, onClose }: ModuleTemplateListPr
export default React.memo(ModuleTemplateList);
const RenderList = React.memo(function RenderList({ templates, onClose }: RenderListProps) {
const RenderList = React.memo(function RenderList({
templates,
onClose,
currentParent,
setCurrentParent
}: RenderListProps) {
const { t } = useTranslation();
const { isPc } = useSystemStore();
const { x, y, zoom } = useViewport();
const { setLoading } = useSystemStore();
const { toast } = useToast();
const { reactFlowWrapper, nodes } = useFlowProviderStore();
const formatTemplates = useMemo<moduleTemplateListType>(() => {
const copy: moduleTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList));
@@ -87,11 +235,11 @@ const RenderList = React.memo(function RenderList({ templates, onClose }: Render
copy[index].list.push(item);
});
return copy.filter((item) => item.list.length > 0);
}, [templates]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [templates, currentParent]);
const onAddNode = useCallback(
async ({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
const { reactFlowWrapper, nodes } = await getFlowStore();
async ({ template, position }: { template: FlowNodeTemplateType; position: XYPosition }) => {
if (!reactFlowWrapper?.current) return;
const templateModule = await (async () => {
@@ -131,34 +279,24 @@ const RenderList = React.memo(function RenderList({ templates, onClose }: Render
)
);
},
[setLoading, t, toast, x, y, zoom]
[nodes, reactFlowWrapper, setLoading, t, toast, x, y, zoom]
);
return templates.length === 0 ? (
<EmptyTip text={t('app.module.No Modules')} />
) : (
<Box flex={'1 0 0'} overflow={'overlay'}>
<Box w={['100%', '330px']} mx={'auto'}>
<Box flex={'1 0 0'} overflow={'overlay'} px={'20px'}>
<Box mx={'auto'}>
{formatTemplates.map((item, i) => (
<Box key={item.type}>
<Flex>
<Box fontWeight={'bold'} flex={1}>
{t(item.label)}
</Box>
{/* {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.label && (
<Flex>
<Box fontWeight={'bold'} flex={1}>
{t(item.label)}
</Box>
</Flex>
)}
<>
{item.list.map((template) => (
<Flex
@@ -168,21 +306,32 @@ const RenderList = React.memo(function RenderList({ templates, onClose }: Render
cursor={'pointer'}
_hover={{ bg: 'myWhite.600' }}
borderRadius={'sm'}
draggable
draggable={template.pluginType !== PluginTypeEnum.folder}
onDragEnd={(e) => {
if (e.clientX < 360) return;
if (e.clientX < sliderWidth) return;
onAddNode({
template: template,
position: { x: e.clientX, y: e.clientY }
});
}}
onClick={(e) => {
if (isPc) return;
onClose();
if (template.pluginType === PluginTypeEnum.folder) {
return setCurrentParent({
parentId: template.id,
parentName: template.name
});
}
if (isPc) {
return onAddNode({
template,
position: { x: sliderWidth * 1.5, y: 200 }
});
}
onAddNode({
template: template,
position: { x: e.clientX, y: e.clientY }
});
onClose();
}}
>
<Avatar

View File

@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { ModalBody, Flex, Box, useTheme, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useQuery } from '@tanstack/react-query';
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
import Avatar from '@/components/Avatar';

View File

@@ -8,7 +8,7 @@ import { TTSTypeEnum } from '@/constants/app';
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
import { useAudioPlay } from '@/web/common/utils/voice';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MySlider from '@/components/Slider';
import MySelect from '@fastgpt/web/components/common/MySelect';
@@ -67,7 +67,7 @@ const TTSSelect = ({
});
}
},
[onChange, value]
[audioSpeechModelList, onChange, value]
);
return (
@@ -79,17 +79,16 @@ const TTSSelect = ({
</MyTooltip>
<Box flex={1} />
<MyTooltip label={t('core.app.Select TTS')}>
<Box
cursor={'pointer'}
_hover={{ bg: 'myGray.100' }}
py={2}
px={3}
borderRadius={'md'}
<Button
variant={'transparentBase'}
iconSpacing={1}
size={'sm'}
fontSize={'md'}
mr={'-5px'}
onClick={onOpen}
color={'myGray.600'}
>
{formLabel}
</Box>
</Button>
</MyTooltip>
<MyModal
title={

View File

@@ -32,7 +32,7 @@ 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 MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@/components/MyTooltip';
import { variableTip } from '@fastgpt/global/core/module/template/tip';
import { useTranslation } from 'next-i18next';
@@ -83,16 +83,6 @@ const VariableEdit = ({
name: 'variable.enums'
});
const BoxBtnStyles: BoxProps = {
cursor: 'pointer',
px: 3,
py: 1,
borderRadius: 'md',
_hover: {
bg: 'myGray.150'
}
};
const formatVariables = useMemo(() => {
const results = formatEditorVariablePickerIcon(variables);
return results.map((item) => {
@@ -114,17 +104,20 @@ const VariableEdit = ({
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Flex
{...BoxBtnStyles}
alignItems={'center'}
<Button
variant={'transparentBase'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
mr={'-5px'}
fontSize={'md'}
onClick={() => {
resetEdit({ variable: addVariable() });
onOpenEdit();
}}
>
<SmallAddIcon />
{t('common.Add New')}
</Flex>
</Button>
</Flex>
{formatVariables.length > 0 && (
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">

View File

@@ -11,7 +11,7 @@ import {
} from '@chakra-ui/react';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { useForm } from 'react-hook-form';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { ModalBody, Button, ModalFooter, useDisclosure, Textarea, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { onChangeNode } from '../../../FlowProvider';

View File

@@ -41,6 +41,47 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
import RenderToolInput from '../../render/RenderToolInput';
const OpenApiImportModal = dynamic(() => import('./OpenApiImportModal'));
export const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
{ key: 'Accept', label: 'Accept' },
{ key: 'Accept-Charset', label: 'Accept-Charset' },
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
{ key: 'Accept-Language', label: 'Accept-Language' },
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' },
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
enum TabEnum {
params = 'params',
headers = 'headers',
@@ -342,47 +383,6 @@ const RenderForm = ({
const [shouldUpdateNode, setShouldUpdateNode] = useState(false);
const leftVariables = useMemo(() => {
const HttpHeaders = [
{ key: 'A-IM', label: 'A-IM' },
{ key: 'Accept', label: 'Accept' },
{ key: 'Accept-Charset', label: 'Accept-Charset' },
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
{ key: 'Accept-Language', label: 'Accept-Language' },
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' },
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
return (tabType === TabEnum.headers ? HttpHeaders : variables).filter((variable) => {
const existVariables = list.map((item) => item.key);
return !existVariables.includes(variable.key);
@@ -455,7 +455,7 @@ const RenderForm = ({
};
return (
<TableContainer>
<TableContainer overflowY={'visible'} overflowX={'unset'}>
<Table>
<Thead>
<Tr>

View File

@@ -62,44 +62,48 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
position={'relative'}
mb={7}
>
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
inputType: item.type,
valueType: item.valueType,
key: item.key,
label: item.label,
description: item.description,
required: item.required,
isToolInput: !!item.toolDescription
})
}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'red.500' }}
onClick={() => {
onChangeNode({
moduleId,
type: 'delInput',
key: item.key
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
{item.edit && (
<>
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
inputType: item.type,
valueType: item.valueType,
key: item.key,
label: item.label,
description: item.description,
required: item.required,
isToolInput: !!item.toolDescription
})
}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
cursor={'pointer'}
mr={3}
_hover={{ color: 'red.500' }}
onClick={() => {
onChangeNode({
moduleId,
type: 'delInput',
key: item.key
});
onChangeNode({
moduleId,
type: 'delOutput',
key: item.key
});
}}
/>
</>
)}
{item.description && (
<MyTooltip label={t(item.description)} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />

View File

@@ -10,7 +10,12 @@ import RenderToolInput from '../render/RenderToolInput';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../FlowProvider';
const NodeSimple = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
const NodeSimple = ({
data,
selected,
minW = '350px',
maxW
}: NodeProps<FlowModuleItemType> & { minW?: string | number; maxW?: string | number }) => {
const { t } = useTranslation();
const { splitToolInputs } = useFlowProviderStore();
const { moduleId, inputs, outputs } = data;
@@ -22,7 +27,7 @@ const NodeSimple = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
);
return (
<NodeCard minW={'350px'} selected={selected} {...data}>
<NodeCard minW={minW} maxW={maxW} selected={selected} {...data}>
{toolInputs.length > 0 && (
<>
<Divider text={t('core.module.tool.Tool input')} />

View File

@@ -10,10 +10,10 @@ import {
Textarea
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { DYNAMIC_INPUT_KEY, ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
import { useTranslation } from 'next-i18next';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import {
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum
@@ -265,10 +265,6 @@ const FieldEditModal = ({
placeholder="appointment/sql"
{...register('key', {
required: true,
pattern: {
value: /^[a-zA-Z]+[0-9]*$/,
message: '字段key必须是纯英文字母或数字并且不能以数字开头。'
},
onChange: (e) => {
const value = e.target.value;
// auto fill label

View File

@@ -6,13 +6,7 @@ import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
import { useTranslation } from 'next-i18next';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useToast } from '@fastgpt/web/hooks/useToast';
import {
onChangeNode,
onCopyNode,
onResetNode,
onDelNode,
useFlowProviderStore
} from '../../FlowProvider';
import { onChangeNode, onCopyNode, onResetNode, useFlowProviderStore } from '../../FlowProvider';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -26,6 +20,7 @@ import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
type Props = FlowModuleItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
minW?: string | number;
maxW?: string | number;
forbidMenu?: boolean;
selected?: boolean;
};
@@ -38,6 +33,7 @@ const NodeCard = (props: Props) => {
name = t('core.module.template.UnKnow Module'),
intro,
minW = '300px',
maxW = '600px',
moduleId,
flowType,
inputs,
@@ -48,21 +44,25 @@ const NodeCard = (props: Props) => {
const { toast } = useToast();
const { setLoading } = useSystemStore();
const { nodes, splitToolInputs } = useFlowProviderStore();
const { nodes, splitToolInputs, onDelNode } = useFlowProviderStore();
// edit intro
const { onOpenModal: onOpenIntroModal, EditModal: EditIntroModal } = useEditTextarea({
title: t('core.module.Edit intro'),
tip: '调整该模块会对工具调用时机有影响。\n你可以通过精确的描述该模块功能引导模型进行工具调用。',
canEmpty: false
});
// custom title edit
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
title: t('common.Custom Title'),
placeholder: t('app.module.Custom Title Tip') || ''
});
const { openConfirm, ConfirmModal } = useConfirm({
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: t('module.Confirm Sync Plugin')
});
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
content: t('core.module.Confirm Delete Node'),
type: 'delete'
});
const showToolHandle = useMemo(
() => isTool && !!nodes.find((item) => item.data?.flowType === FlowNodeTypeEnum.tools),
@@ -86,7 +86,7 @@ const NodeCard = (props: Props) => {
(item) => item.key === ModuleInputKeyEnum.pluginId
)?.value;
if (!pluginId) return;
openConfirm(async () => {
onOpenConfirmSync(async () => {
try {
setLoading(true);
const pluginModule = await getPreviewPluginModule(pluginId);
@@ -140,7 +140,7 @@ const NodeCard = (props: Props) => {
icon: 'delete',
label: t('common.Delete'),
variant: 'whiteDanger',
onClick: () => onDelNode(moduleId)
onClick: onOpenConfirmDeleteNode(() => onDelNode(moduleId))
}
];
@@ -211,21 +211,23 @@ const NodeCard = (props: Props) => {
</Box>
);
}, [
avatar,
flowType,
forbidMenu,
inputs,
intro,
moduleId,
moduleIsTool,
name,
onOpenIntroModal,
onOpenModal,
openConfirm,
setLoading,
showToolHandle,
t,
toast
onOpenConfirmDeleteNode,
showToolHandle,
moduleId,
avatar,
name,
forbidMenu,
intro,
moduleIsTool,
inputs,
onOpenConfirmSync,
setLoading,
toast,
onOpenModal,
onDelNode,
onOpenIntroModal
]);
const RenderModal = useMemo(() => {
@@ -233,15 +235,16 @@ const NodeCard = (props: Props) => {
<>
<EditTitleModal maxLength={20} />
{moduleIsTool && <EditIntroModal maxLength={500} />}
<ConfirmModal />
<ConfirmSyncModal />
<ConfirmDeleteModal />
</>
);
}, [ConfirmModal, EditIntroModal, EditTitleModal, moduleIsTool]);
}, [ConfirmDeleteModal, ConfirmSyncModal, EditIntroModal, EditTitleModal, moduleIsTool]);
return (
<Box
minW={minW}
maxW={'600px'}
maxW={maxW}
bg={'white'}
borderWidth={'1px'}
borderColor={selected ? 'primary.600' : 'borderColor.base'}

View File

@@ -42,12 +42,12 @@ const RenderList: {
Component: dynamic(() => import('./templates/SelectApp'))
},
{
types: [FlowNodeInputTypeEnum.aiSettings],
Component: dynamic(() => import('./templates/AiSetting'))
types: [FlowNodeInputTypeEnum.selectLLMModel],
Component: dynamic(() => import('./templates/SelectLLMModel'))
},
{
types: [FlowNodeInputTypeEnum.selectLLMModel],
Component: dynamic(() => import('./templates/SelectAiModel'))
types: [FlowNodeInputTypeEnum.settingLLMModel],
Component: dynamic(() => import('./templates/SettingLLMModel'))
},
{
types: [FlowNodeInputTypeEnum.selectDataset],
@@ -64,6 +64,10 @@ const RenderList: {
{
types: [FlowNodeInputTypeEnum.JSONEditor],
Component: dynamic(() => import('./templates/JsonEditor'))
},
{
types: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt],
Component: dynamic(() => import('./templates/SettingQuotePrompt'))
}
];
const UserChatInput = dynamic(() => import('./templates/UserChatInput'));

View File

@@ -1,63 +0,0 @@
import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next';
import { Button, useDisclosure } from '@chakra-ui/react';
import { AIChatModuleProps } from '@fastgpt/global/core/module/node/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import AIChatSettingsModal from '@/components/core/module/AIChatSettingsModal';
const AiSettingRender = ({ inputs = [], moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const chatModulesData = useMemo(() => {
const obj: Record<string, any> = {};
inputs.forEach((item) => {
obj[item.key] = item.value;
});
return obj as AIChatModuleProps;
}, [inputs]);
const {
isOpen: isOpenAIChatSetting,
onOpen: onOpenAIChatSetting,
onClose: onCloseAIChatSetting
} = useDisclosure();
return (
<>
<Button
variant={'whitePrimary'}
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
onClick={onOpenAIChatSetting}
>
{t('app.AI Settings')}
</Button>
{isOpenAIChatSetting && (
<AIChatSettingsModal
isAdEdit
onClose={onCloseAIChatSetting}
onSuccess={(e) => {
for (let key in e) {
const item = inputs.find((input) => input.key === key);
if (!item) continue;
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...item,
//@ts-ignore
value: e[key]
}
});
}
onCloseAIChatSetting();
}}
defaultData={chatModulesData}
/>
)}
</>
);
};
export default React.memo(AiSettingRender);

View File

@@ -1,83 +0,0 @@
import React, { useCallback, useEffect } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SelectAiModel from '@/components/Select/SelectAiModel';
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
const { llmModelList } = useSystemStore();
const modelList = llmModelList
.filter((model) => {
if (!item.llmModelType) return true;
const filterField = llmModelTypeFilterMap[item.llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
})
.map((item) => ({
model: item.model,
name: item.name,
maxResponse: item.maxResponse
}));
const onChangeModel = useCallback(
(e: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
// update max tokens
const model = modelList.find((item) => item.model === e) || modelList[0];
if (!model) return;
onChangeNode({
moduleId,
type: 'updateInput',
key: 'maxToken',
value: {
...inputs.find((input) => input.key === 'maxToken'),
markList: [
{ label: '100', value: 100 },
{ label: `${model.maxResponse}`, value: model.maxResponse }
],
max: model.maxResponse,
value: model.maxResponse / 2
}
});
},
[inputs, item, modelList, moduleId]
);
const list = modelList.map((item) => {
return {
value: item.model,
label: item.name
};
});
useEffect(() => {
if (!item.value && list.length > 0) {
onChangeModel(list[0].value);
}
}, [item.value, list, onChangeModel]);
return (
<SelectAiModel
minW={'350px'}
width={'100%'}
value={item.value}
list={list}
onchange={onChangeModel}
/>
);
};
export default React.memo(SelectAiModelRender);

View File

@@ -0,0 +1,54 @@
import React, { useCallback, useEffect } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
import AIModelSelector from '@/components/Select/AIModelSelector';
const SelectAiModelRender = ({ item, moduleId }: RenderInputProps) => {
const { llmModelList } = useSystemStore();
const modelList = llmModelList.filter((model) => {
if (!item.llmModelType) return true;
const filterField = llmModelTypeFilterMap[item.llmModelType];
if (!filterField) return true;
//@ts-ignore
return !!model[filterField];
});
const onChangeModel = useCallback(
(e: string) => {
onChangeNode({
moduleId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
},
[item, moduleId]
);
useEffect(() => {
if (!item.value && modelList.length > 0) {
onChangeModel(modelList[0].model);
}
}, [item.value, modelList, onChangeModel]);
return (
<AIModelSelector
minW={'350px'}
width={'100%'}
value={item.value}
list={modelList.map((item) => ({
value: item.model,
label: item.name
}))}
onchange={onChangeModel}
/>
);
};
export default React.memo(SelectAiModelRender);

View File

@@ -0,0 +1,49 @@
import React, { useCallback } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
const onChangeModel = useCallback(
(e: SettingAIDataType) => {
for (const key in e) {
const input = inputs.find((input) => input.key === key);
input &&
onChangeNode({
moduleId,
type: 'updateInput',
key,
value: {
...input,
// @ts-ignore
value: e[key]
}
});
}
},
[inputs, moduleId]
);
const llmModelData: SettingAIDataType = {
model: inputs.find((input) => input.key === ModuleInputKeyEnum.aiModel)?.value ?? '',
maxToken:
inputs.find((input) => input.key === ModuleInputKeyEnum.aiChatMaxToken)?.value ?? 2048,
temperature:
inputs.find((input) => input.key === ModuleInputKeyEnum.aiChatTemperature)?.value ?? 1,
isResponseAnswerText: inputs.find(
(input) => input.key === ModuleInputKeyEnum.aiChatIsResponseText
)?.value
};
return (
<SettingLLMModel
llmModelType={item.llmModelType}
defaultData={llmModelData}
onChange={onChangeModel}
/>
);
};
export default React.memo(SelectAiModelRender);

View File

@@ -0,0 +1,243 @@
import React, { useMemo, useState } from 'react';
import type { RenderInputProps } from '../type';
import { Box, BoxProps, Button, Flex, ModalFooter, useDisclosure } from '@chakra-ui/react';
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useForm } from 'react-hook-form';
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type';
import { useTranslation } from 'next-i18next';
import {
formatEditorVariablePickerIcon,
getGuideModule,
splitGuideModule
} from '@fastgpt/global/core/module/utils';
import { ModalBody } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import {
Prompt_QuotePromptList,
Prompt_QuoteTemplateList
} from '@fastgpt/global/core/ai/prompt/AIChat';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import PromptTemplate from '@/components/PromptTemplate';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
const SettingQuotePrompt = ({ inputs = [], moduleId }: RenderInputProps) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const { nodes } = useFlowProviderStore();
const { watch, setValue, handleSubmit } = useForm({
defaultValues: {
quoteTemplate: inputs.find((input) => input.key === 'quoteTemplate')?.value || '',
quotePrompt: inputs.find((input) => input.key === 'quotePrompt')?.value || ''
}
});
const aiChatQuoteTemplate = watch('quoteTemplate');
const aiChatQuotePrompt = watch('quotePrompt');
const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon(
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
);
const moduleVariables = formatEditorVariablePickerIcon(
inputs
.filter((input) => input.edit)
.map((item) => ({
key: item.key,
label: item.label
}))
);
const systemVariables = [
{
key: 'cTime',
label: t('core.module.http.Current time')
}
];
return [...globalVariables, ...moduleVariables, ...systemVariables];
}, [inputs, t]);
const [selectTemplateData, setSelectTemplateData] = useState<{
title: string;
templates: PromptTemplateItem[];
}>();
const quoteTemplateVariables = (() => [
{
key: 'q',
label: 'q',
icon: 'core/app/simpleMode/variable'
},
{
key: 'a',
label: 'a',
icon: 'core/app/simpleMode/variable'
},
{
key: 'source',
label: t('core.dataset.search.Source name'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'sourceId',
label: t('core.dataset.search.Source id'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'index',
label: t('core.dataset.search.Quote index'),
icon: 'core/app/simpleMode/variable'
},
...variables
])();
const quotePromptVariables = (() => [
{
key: 'quote',
label: t('core.app.Quote templates'),
icon: 'core/app/simpleMode/variable'
},
{
key: 'question',
label: t('core.module.input.label.user question'),
icon: 'core/app/simpleMode/variable'
},
...variables
])();
const LabelStyles: BoxProps = {
fontSize: ['sm', 'md']
};
const selectTemplateBtn: BoxProps = {
color: 'primary.500',
cursor: 'pointer'
};
const onSubmit = (data: { quoteTemplate: string; quotePrompt: string }) => {
const quoteTemplateInput = inputs.find(
(input) => input.key === ModuleInputKeyEnum.aiChatQuoteTemplate
);
const quotePromptInput = inputs.find(
(input) => input.key === ModuleInputKeyEnum.aiChatQuotePrompt
);
if (quoteTemplateInput) {
onChangeNode({
moduleId,
type: 'updateInput',
key: quoteTemplateInput.key,
value: {
...quoteTemplateInput,
value: data.quoteTemplate
}
});
}
if (quotePromptInput) {
onChangeNode({
moduleId,
type: 'updateInput',
key: quotePromptInput.key,
value: {
...quotePromptInput,
value: data.quotePrompt
}
});
}
onClose();
};
return (
<>
<Button variant={'whitePrimary'} size={'sm'} onClick={onOpen}>
{t('core.module.Setting quote prompt')}
</Button>
<MyModal
isOpen={isOpen}
iconSrc={'modal/edit'}
title={t('core.module.Quote prompt setting')}
w={'600px'}
>
<ModalBody>
<Box>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote templates')}
<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: t('core.app.Select quote template'),
templates: Prompt_QuoteTemplateList
})
}
>
{t('common.Select template')}
</Box>
</Flex>
<PromptEditor
variables={quoteTemplateVariables}
h={160}
title={t('core.app.Quote templates')}
placeholder={t('template.Quote Content Tip', {
default: Prompt_QuoteTemplateList[0].value
})}
value={aiChatQuoteTemplate}
onChange={(e) => {
setValue('quoteTemplate', e);
}}
/>
</Box>
<Box mt={4}>
<Flex {...LabelStyles} mb={1}>
{t('core.app.Quote prompt')}
<MyTooltip
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
forceShow
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<PromptEditor
variables={quotePromptVariables}
title={t('core.app.Quote prompt')}
h={280}
placeholder={t('template.Quote Prompt Tip', {
default: Prompt_QuotePromptList[0].value
})}
value={aiChatQuotePrompt}
onChange={(e) => {
setValue('quotePrompt', e);
}}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
{t('common.Close')}
</Button>
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
</ModalFooter>
</MyModal>
{!!selectTemplateData && (
<PromptTemplate
title={selectTemplateData.title}
templates={selectTemplateData.templates}
onClose={() => setSelectTemplateData(undefined)}
onSuccess={(e) => {
const quoteVal = e.value;
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
setValue('quoteTemplate', quoteVal);
setValue('quotePrompt', promptVal);
}}
/>
)}
</>
);
};
export default React.memo(SettingQuotePrompt);

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useRef } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import type { EditFieldModalProps } from './type.d';
import { useTranslation } from 'next-i18next';
import {

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Handle, Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Handle, OnConnect, Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';

View File

@@ -1,5 +1,5 @@
import MyTooltip from '@/components/MyTooltip';
import { FlowValueTypeMap } from '@/web/core/modules/constants/dataType';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { Box, BoxProps } from '@chakra-ui/react';
import {
ModuleIOValueTypeEnum,

View File

@@ -1,5 +1,11 @@
import React, { useCallback, useMemo } from 'react';
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider } from 'reactflow';
import ReactFlow, {
Background,
Connection,
Controls,
NodeProps,
ReactFlowProvider
} from 'reactflow';
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
@@ -7,12 +13,13 @@ import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/co
import dynamic from 'next/dynamic';
import ButtonEdge from './components/modules/ButtonEdge';
import ModuleTemplateList, { type ModuleTemplateProps } from './ModuleTemplateList';
import ModuleTemplateList from './ModuleTemplateList';
import { useFlowProviderStore } from './FlowProvider';
import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { FlowModuleItemType } from '@fastgpt/global/core/module/type';
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
@@ -34,7 +41,10 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
[FlowNodeTypeEnum.queryExtension]: NodeSimple,
[FlowNodeTypeEnum.tools]: dynamic(() => import('./components/nodes/NodeTools'))
[FlowNodeTypeEnum.tools]: dynamic(() => import('./components/nodes/NodeTools')),
[FlowNodeTypeEnum.stopTool]: (data: NodeProps<FlowModuleItemType>) => (
<NodeSimple {...data} minW={'100px'} maxW={'300px'} />
)
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge
@@ -99,11 +109,7 @@ const Container = React.memo(function Container() {
);
});
const Flow = ({
Header,
templates,
...data
}: ModuleTemplateProps & { Header: React.ReactNode }) => {
const Flow = ({ Header, ...data }: { Header: React.ReactNode }) => {
const {
isOpen: isOpenTemplate,
onOpen: onOpenTemplate,
@@ -143,14 +149,10 @@ const Flow = ({
<Container {...data} />
<ModuleTemplateList
templates={templates}
isOpen={isOpenTemplate}
onClose={onCloseTemplate}
/>
<ModuleTemplateList isOpen={isOpenTemplate} onClose={onCloseTemplate} />
</Box>
);
}, [data, isOpenTemplate, onCloseTemplate, onOpenTemplate, templates]);
}, [data, isOpenTemplate, onCloseTemplate, onOpenTemplate]);
return (
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>

View File

@@ -35,7 +35,7 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';
import MyTooltip from '@/components/MyTooltip';

View File

@@ -12,7 +12,9 @@ const PermissionIconText = ({
return PermissionTypeMap[permission] ? (
<Flex alignItems={'center'} {...props}>
<MyIcon name={PermissionTypeMap[permission]?.iconLight as any} w={'14px'} />
<Box ml={'2px'}>{t(PermissionTypeMap[permission]?.label)}</Box>
<Box ml={'2px'} lineHeight={1}>
{t(PermissionTypeMap[permission]?.label)}
</Box>
</Flex>
) : null;
};

View File

@@ -6,7 +6,7 @@ import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRequest } from '@/web/common/hooks/useRequest';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip';
import Avatar from '@/components/Avatar';

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
import TagTextarea from '@/components/common/Textarea/TagTextarea';

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { DragHandleIcon } from '@chakra-ui/icons';
@@ -194,7 +194,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
bg: 'myGray.100'
}
})}
onClick={() => onSwitchTeam(team.teamId)}
>
<Avatar src={team.avatar} w={['18px', '22px']} />
<Box

View File

@@ -1,5 +1,5 @@
import React from 'react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import {
Box,
Button,

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'next-i18next';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import {
Button,
ModalFooter,

View File

@@ -0,0 +1,35 @@
import React from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const NotSufficientModal = () => {
const { t } = useTranslation();
const router = useRouter();
const { setIsNotSufficientModal } = useSystemStore();
const onClose = () => setIsNotSufficientModal(false);
return (
<MyModal isOpen iconSrc="common/confirm/deleteTip" title={t('common.Warning')}>
<ModalBody>{t('support.wallet.Not sufficient')}</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
{t('common.Close')}
</Button>
<Button
onClick={() => {
router.push('/account');
onClose();
}}
>
{t('support.wallet.To read plan')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default NotSufficientModal;

View File

@@ -1,4 +1,4 @@
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import React, { useEffect } from 'react';
import { useTranslation } from 'next-i18next';
import { Box, ModalBody, ModalFooter } from '@chakra-ui/react';

View File

@@ -5,6 +5,10 @@ import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constant
import { Box, Flex, Grid } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
const StandardPlanContentList = ({
level,
@@ -15,6 +19,7 @@ const StandardPlanContentList = ({
}) => {
const { t } = useTranslation();
const { subPlans } = useSystemStore();
const router = useRouter();
const planContent = useMemo(() => {
const plan = subPlans?.standard?.[level];
@@ -87,6 +92,14 @@ const StandardPlanContentList = ({
amount: planContent.totalPoints
})}
</Box>
<MyTooltip label={t('support.wallet.subscription.AI points click to read tip')}>
<QuestionOutlineIcon
ml={'2px'}
onClick={() => {
router.push(AI_POINT_USAGE_CARD_ROUTE);
}}
/>
</MyTooltip>
</Flex>
</Flex>
<Flex alignItems={'center'}>

View File

@@ -6,7 +6,6 @@ export const defaultApp: AppDetailType = {
userId: 'userId',
name: '应用加载中',
type: 'simple',
simpleTemplateId: 'fastgpt-universal',
avatar: '/icon/logo.svg',
intro: '',
updateTime: Date.now(),

View File

@@ -7,7 +7,6 @@ import type {
} from '@fastgpt/global/core/ai/model.d';
import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type';
import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type';
export type InitDateResponse = {
@@ -19,5 +18,4 @@ export type InitDateResponse = {
feConfigs: FastGPTFeConfigsType;
subPlans?: SubPlanType;
systemVersion: string;
simpleModeTemplates: AppSimpleEditConfigTemplateType[];
};

View File

@@ -1,32 +0,0 @@
import { AppSimpleEditConfigTemplateType } from '@fastgpt/global/core/app/type.d';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateType = {
id: 'fastgpt-universal',
name: 'core.app.template.Common template',
desc: 'core.app.template.Common template tip',
systemForm: {
aiSettings: {
model: true,
systemPrompt: true,
temperature: true,
maxToken: true,
quoteTemplate: true,
quotePrompt: true
},
dataset: {
datasets: true,
similarity: true,
limit: true,
searchMode: DatasetSearchModeEnum.embedding,
usingReRank: true,
searchEmptyText: true
},
userGuide: {
welcomeText: true,
variables: true,
questionGuide: true,
tts: true
}
}
};

View File

@@ -19,7 +19,6 @@ export type CreateDatasetParams = {
avatar: string;
vectorModel?: string;
agentModel?: string;
type: `${DatasetTypeEnum}`;
};
/* ================= collection ===================== */

View File

@@ -1,116 +0,0 @@
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
{
title: '标准模板',
desc: '标准提示词,用于结构不固定的知识库。',
value: `{{q}}
{{a}}`
},
{
title: '问答模板',
desc: '适合 QA 问答结构的知识库可以让AI较为严格的按预设内容回答',
value: `<Question>
{{q}}
</Question>
<Answer>
{{a}}
</Answer>`
},
{
title: '标准严格模板',
desc: '在标准模板基础上,对模型的回答做更严格的要求。',
value: `{{q}}
{{a}}`
},
{
title: '严格问答模板',
desc: '在问答模板基础上,对模型的回答做更严格的要求。',
value: `<Question>
{{q}}
</Question>
<Answer>
{{a}}
</Answer>`
}
];
export const Prompt_QuotePromptList: PromptTemplateItem[] = [
{
title: '标准模板',
desc: '',
value: `使用 <Data></Data> 标记中的内容作为你的知识:
<Data>
{{quote}}
</Data>
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Data></Data> 获取的知识。
- 保持答案与 <Data></Data> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
},
{
title: '问答模板',
desc: '',
value: `使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>
回答要求:
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <答案></答案> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 QA 获取的知识,只需要回复答案。
问题:"""{{question}}"""`
},
{
title: '标准严格模板',
desc: '',
value: `忘记你已有的知识,仅使用 <Data></Data> 标记中的内容作为你的知识:
<Data>
{{quote}}
</Data>
思考流程:
1. 判断问题是否与 <Data></Data> 标记中的内容有关。
2. 如果有关,你按下面的要求回答。
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 <Data></Data> 获取的知识。
- 保持答案与 <Data></Data> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
},
{
title: '严格问答模板',
desc: '',
value: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>}
思考流程:
1. 判断问题是否与 <QA></QA> 标记中的内容有关。
2. 如果无关,你直接拒绝回答本次问题。
3. 判断是否有相近或相同的问题。
4. 如果有相同的问题,直接输出对应答案。
5. 如果只有相近的问题,请把相近的问题和答案一起输出。
最后,避免提及你是从 QA 获取的知识,只需要回复答案。
问题:"""{{question}}"""`
}
];

View File

@@ -1,62 +0,0 @@
export const Prompt_AgentQA = {
description: `<Context></Context> 标记中是一段文本,学习和分析它,并整理学习成果:
- 提出问题并给出每个问题的答案。
- 答案需详细完整,尽可能保留原文描述。
- 答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 Markdown 元素。
- 最多提出 30 个问题。
`,
fixedText: `请按以下格式整理学习成果:
<Context>
文本
</Context>
Q1: 问题。
A1: 答案。
Q2:
A2:
------
我们开始吧!
<Context>
{{text}}
<Context/>
`
};
export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录> 中提取指定 JSON 信息,你仅需返回 JSON 字符串,无需回答问题。
<提取要求>
{{description}}
</提取要求>
<字段说明>
1. 下面的 JSON 字符串均按照 JSON Schema 的规则描述。
2. key 代表字段名description 代表字段的描述enum 是可选值,代表可选的 value。
3. 如果没有可提取的内容,忽略该字段。
4. 本次需提取的JSON Schema{{json}}
</字段说明>
<对话记录>
{{text}}
</对话记录>
`;
export const Prompt_CQJson = `我会给你几个问题类型请参考背景知识可能为空和对话记录判断我“本次问题”的类型并返回一个问题“类型ID”:
<问题类型>
{{typeList}}
</问题类型>
<背景知识>
{{systemPrompt}}
</背景知识>
<对话记录>
{{history}}
</对话记录>
Human"{{question}}"
类型ID=
`;
export const Prompt_QuestionGuide = `我不太清楚问你什么问题,请帮我生成 3 个问题引导我继续提问。问题的长度应小于20个字符按 JSON 格式返回: ["问题1", "问题2", "问题3"]`;

View File

@@ -30,7 +30,7 @@ import MyBox from '@/components/common/MyBox';
import { useRequest } from '@/web/common/hooks/useRequest';
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
import MySelect from '@fastgpt/web/components/common/MySelect';
import MyModal from '@fastgpt/web/components/common/CustomModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { usePagination } from '@fastgpt/web/hooks/usePagination';
const BillTable = () => {

View File

@@ -16,6 +16,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { useUserStore } from '@/web/support/user/useUserStore';
import type { UserType } from '@fastgpt/global/support/user/type.d';
import { useQuery } from '@tanstack/react-query';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
@@ -29,16 +30,24 @@ import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tool
import { putUpdateMemberName } from '@/web/support/user/team/api';
import { getDocPath } from '@/web/common/system/doc';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import {
StandardSubLevelEnum,
standardSubLevelMap
} from '@fastgpt/global/support/wallet/sub/constants';
import { formatTime2YMD } from '@fastgpt/global/common/string/time';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
import {
AI_POINT_USAGE_CARD_ROUTE,
EXTRA_PLAN_CARD_ROUTE
} from '@/web/support/wallet/sub/constants';
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'));
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
const Account = () => {
const { isPc } = useSystemStore();
@@ -113,11 +122,11 @@ const MyInfo = () => {
});
reset(data);
toast({
title: '更新数据成功',
title: t('dataset.data.Update Success Tip'),
status: 'success'
});
},
[reset, toast, updateUserInfo]
[reset, t, toast, updateUserInfo]
);
const onSelectFile = useCallback(
@@ -184,7 +193,7 @@ const MyInfo = () => {
cursor={'pointer'}
onClick={onOpenSelectFile}
>
<MyTooltip label={'更换头像'}>
<MyTooltip label={t('common.avatar.Select Avatar')}>
<Box
w={['44px', '54px']}
h={['44px', '54px']}
@@ -269,7 +278,6 @@ const MyInfo = () => {
);
};
const PlanUsage = () => {
const { isPc } = useSystemStore();
const router = useRouter();
const { t } = useTranslation();
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
@@ -288,6 +296,21 @@ const PlanUsage = () => {
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
}, [teamPlanStatus?.standard?.currentSubLevel]);
const standardPlan = teamPlanStatus?.standard;
const isFreeTeam = useMemo(() => {
if (!teamPlanStatus || !teamPlanStatus?.standardConstants) return false;
const hasExtraDatasetSize =
teamPlanStatus.datasetMaxSize > teamPlanStatus.standardConstants.maxDatasetSize;
const hasExtraPoints =
teamPlanStatus.totalPoints > teamPlanStatus.standardConstants.totalPoints;
if (
teamPlanStatus?.standard?.currentSubLevel === StandardSubLevelEnum.free &&
!hasExtraDatasetSize &&
!hasExtraPoints
) {
return true;
}
return false;
}, [teamPlanStatus]);
useQuery(['init'], initUserInfo, {
onSuccess(res) {
@@ -374,6 +397,11 @@ const PlanUsage = () => {
<Box fontWeight={'bold'} fontSize="xl">
{t(planName)}
</Box>
{isFreeTeam && (
<Box mt="3" color={'#485264'} fontSize="sm">
15使
</Box>
)}
<Flex mt="3" color={'#485264'} fontSize="sm">
<Box>{t('common.Expired Time')}:</Box>
<Box ml={2}>{formatTime2YMD(standardPlan?.expiredTime)}</Box>
@@ -399,9 +427,29 @@ const PlanUsage = () => {
borderColor={'borderColor.low'}
borderRadius={'md'}
px={[5, 10]}
py={[4, 7]}
pt={[2, 4]}
pb={[4, 7]}
>
<Box width={'100%'}>
<Flex>
<Flex flex={'1 0 0'} alignItems={'flex-end'}>
<Box fontSize={'xl'}></Box>
<Box fontSize={'sm'} color={'myGray.500'}>
()
</Box>
</Flex>
<Link
href={EXTRA_PLAN_CARD_ROUTE}
transform={'translateX(15px)'}
display={'flex'}
alignItems={'center'}
color={'primary.600'}
cursor={'pointer'}
>
<MyIcon ml={1} name={'common/rightArrowLight'} w={'12px'} />
</Link>
</Flex>
<Box width={'100%'} mt={5}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{t('support.user.team.Dataset usage')}</Box>
@@ -426,7 +474,10 @@ const PlanUsage = () => {
<Box mt="9" width={'100%'}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points')}</Box>
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points usage')}</Box>
<MyTooltip label={t('support.wallet.subscription.AI points usage tip')}>
<QuestionOutlineIcon ml={'2px'} />
</MyTooltip>
<Box color={'myGray.600'} ml={2}>
{aiPointsUsageMap.used}/{aiPointsUsageMap.max}
</Box>
@@ -445,7 +496,6 @@ const PlanUsage = () => {
/>
</Box>
</Box>
<Flex></Flex>
</Box>
{isOpenStandardModal && <StandDetailModal onClose={onCloseStandardModal} />}
</Box>
@@ -462,6 +512,7 @@ const Other = () => {
});
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
const { isOpen: isOpenConcat, onClose: onCloseConcat, onOpen: onOpenConcat } = useDisclosure();
const onclickSave = useCallback(
async (data: UserType) => {
@@ -552,6 +603,17 @@ const Other = () => {
/>
</Flex>
)}
{feConfigs?.concatMd && (
<Button
variant={'whiteBase'}
justifyContent={'flex-start'}
leftIcon={<MyIcon name={'modal/concat'} w={'18px'} color={'myGray.600'} />}
onClick={onOpenConcat}
h={'48px'}
>
</Button>
)}
</Grid>
{isOpenOpenai && userInfo && (
@@ -566,6 +628,7 @@ const Other = () => {
onClose={onCloseOpenai}
/>
)}
{isOpenConcat && <CommunityModal onClose={onCloseConcat} />}
</Box>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';

View File

@@ -1,15 +1,16 @@
import React, { useState, useCallback, useMemo } from 'react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid, Link } from '@chakra-ui/react';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRouter } from 'next/router';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { EXTRA_PLAN_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
const PayModal = ({
onClose,
@@ -59,8 +60,12 @@ const PayModal = ({
return (
<MyModal isOpen={true} onClose={onClose} title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
<Box px={6} fontSize={'sm'} color={'myGray.600'} mb={2} maxW={'400px'}>
<Box px={6} fontSize={'sm'} mb={2} py={2} maxW={'400px'}>
<Link href={EXTRA_PLAN_CARD_ROUTE} color={'primary.600'} textDecoration={'underline'}>
</Link>
</Box>
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
{payList.map((item) => (

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';

View File

@@ -14,7 +14,7 @@ import {
import { UsageItemType } from '@fastgpt/global/support/wallet/usage/type.d';
import dayjs from 'dayjs';
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { formatNumber } from '@fastgpt/global/common/math/tools';

View File

@@ -11,7 +11,7 @@ import {
TableContainer,
ModalCloseButton
} from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { useLoading } from '@fastgpt/web/hooks/useLoading';

View File

@@ -1,70 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/common/string/jieba';
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();
success = 0;
console.log(
'total',
await MongoDatasetData.countDocuments({
fullTextToken: { $exists: false },
updateTime: { $lt: new Date() }
})
);
await initFullTextToken(limit, new Date());
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initFullTextToken(limit = 50, endDate: Date): Promise<any> {
try {
const dataList = await MongoDatasetData.find(
{ fullTextToken: { $exists: false }, updateTime: { $lt: endDate } },
'_id q a'
)
.limit(limit)
.lean();
if (dataList.length === 0) return;
const result = await Promise.allSettled(
dataList.map((item) => {
const text = item.q + (item.a || '');
const tokens = jiebaSplit({ text });
return MongoDatasetData.findByIdAndUpdate(item._id, {
$set: {
fullTextToken: tokens
}
});
})
);
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initFullTextToken(limit, endDate);
} catch (error) {
await delay(1000);
return initFullTextToken(limit, endDate);
}
}

View File

@@ -1,62 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/common/string/jieba';
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();
success = 0;
console.log('total', await MongoDatasetData.countDocuments({ inited: { $exists: false } }));
await initFullTextToken(limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initFullTextToken(limit = 50): Promise<any> {
try {
const dataList = await MongoDatasetData.find({ inited: { $exists: false } }, '_id q a')
.limit(limit)
.lean();
if (dataList.length === 0) return;
const result = await Promise.allSettled(
dataList.map((item) => {
const text = item.q + (item.a || '');
const tokens = jiebaSplit({ text });
return MongoDatasetData.findByIdAndUpdate(item._id, {
$set: {
inited: true,
fullTextToken: tokens
}
});
})
);
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initFullTextToken(limit);
} catch (error) {
await delay(1000);
return initFullTextToken(limit);
}
}

View File

@@ -1,109 +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 { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetStatusEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/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();
success = 0;
await MongoDatasetCollection.updateMany({ createTime: { $exists: false } }, [
{
$set: {
createTime: '$updateTime'
}
}
]);
await MongoDatasetCollection.updateMany({ trainingType: { $exists: false } }, [
{
$set: {
trainingType: {
$cond: {
if: { $ifNull: ['$a', false] },
then: TrainingModeEnum.qa,
else: TrainingModeEnum.chunk
}
}
}
}
]);
await MongoDatasetCollection.updateMany({ chunkSize: { $exists: false } }, [
{
$set: {
chunkSize: 0
}
}
]);
await MongoDatasetCollection.updateMany({ fileId: { $exists: false } }, [
{
$set: {
fileId: '$metadata.fileId'
}
}
]);
await MongoDatasetCollection.updateMany({ rawLink: { $exists: false } }, [
{
$set: {
rawLink: '$metadata.rawLink'
}
}
]);
await MongoDatasetData.updateMany(
{ chunkIndex: { $exists: false } },
{
chunkIndex: 0
}
);
await MongoDatasetData.updateMany(
{ updateTime: { $exists: false } },
{
updateTime: new Date()
}
);
await MongoDataset.updateMany(
{ status: { $exists: false } },
{
$set: {
status: DatasetStatusEnum.active
}
}
);
// dataset tags to intro
await MongoDataset.updateMany({ tags: { $exists: true } }, [
{
$set: {
intro: {
$reduce: {
input: '$tags',
initialValue: '',
in: { $concat: ['$$value', ' ', '$$this'] }
}
}
}
}
]);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -0,0 +1,49 @@
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 { MongoUsage } from '@fastgpt/service/support/wallet/usage/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
import { checkFiles } from '../timerTask/dataset/checkInValidDatasetFiles';
import { addHours } from 'date-fns';
import { checkInvalid as checkInvalidImg } from '../timerTask/dataset/checkInvalidDatasetImage';
import { checkInvalidCollection } from '../timerTask/dataset/checkInvalidMongoCollection';
import { checkInvalidVector } from '../timerTask/dataset/checkInvalidVector';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authCert({ req, authRoot: true });
await MongoPlugin.updateMany(
{ type: { $exists: false } },
{
$set: {
type: PluginTypeEnum.custom
}
}
);
await MongoPlugin.updateMany(
{ parentId: { $exists: false } },
{
$set: {
parentId: null
}
}
);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -30,8 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
})) || [],
whisperModel: global.whisperModel,
audioSpeechModels: global.audioSpeechModels,
systemVersion: global.systemVersion || '0.0.0',
simpleModeTemplates: global.simpleModeTemplates
systemVersion: global.systemVersion || '0.0.0'
}
});
}
@@ -43,7 +42,7 @@ const defaultFeConfigs: FastGPTFeConfigsType = {
openAPIDocUrl: 'https://doc.fastgpt.in/docs/development/openapi',
systemTitle: 'FastGPT',
concatMd:
'* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: ![](https://doc.fastgpt.in/wechat-fastgpt.webp)',
'* 项目开源地址: [FastGPT GitHub](https://github.com/labring/FastGPT)\n* 交流群: ![](https://oss.laf.run/htr4n1-images/fastgpt-qr-code.jpg)',
limit: {
exportDatasetLimitMinutes: 0,
websiteSyncLimitMinuted: 0
@@ -68,7 +67,6 @@ export async function getInitConfig() {
]);
console.log({
// simpleModeTemplates: global.simpleModeTemplates,
communityPlugins: global.communityPlugins
});
} catch (error) {
@@ -141,39 +139,6 @@ export function getSystemVersion() {
}
}
// async function getSimpleModeTemplates() {
// if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;
// try {
// const basePath =
// process.env.NODE_ENV === 'development' ? 'data/simpleTemplates' : '/app/data/simpleTemplates';
// // read data/simpleTemplates directory, get all json file
// const files = readdirSync(basePath);
// // filter json file
// 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];
// }
// }
function getSystemPlugin() {
if (global.communityPlugins && global.communityPlugins.length > 0) return;

View File

@@ -5,7 +5,6 @@ import type { CreateAppParams } from '@fastgpt/global/core/app/api.d';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -35,8 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
teamId,
tmbId,
modules,
type,
simpleTemplateId: SimpleModeTemplate_FastGPT_Universal.id
type
});
jsonRes(res, {

View File

@@ -1,620 +0,0 @@
/*
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 { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData, chatModelMaxToken } = 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
});
}
}
type Props = { formData: AppSimpleEditFormType; maxToken: number };
function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/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',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: 1,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 8,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}
function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'datasetSearch',
name: 'core.module.template.Dataset search',
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'datasets',
type: 'selectDataset',
label: '关联的知识库',
value: formData.dataset.datasets,
valueType: 'selectDataset',
list: [],
required: true,
showTargetInApp: false,
showTargetInPlugin: true,
connected: false
},
{
key: 'similarity',
type: 'hidden',
label: '最低相关性',
value: 0.15,
valueType: 'number',
min: 0,
max: 1,
step: 0.01,
markList: [
{
label: '0',
value: 0
},
{
label: '1',
value: 1
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'limit',
type: 'hidden',
label: '引用上限',
description: '单次搜索最大的 Tokens 数量中文约1字=1.7Tokens英文约1字=1Tokens',
value: 2000,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'searchMode',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: DatasetSearchModeEnum.mixedRecall,
connected: false
},
{
key: 'usingReRank',
type: 'hidden',
label: '',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: '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: []
},
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'hidden',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 2022.7264786978908,
y: 1006.3102431257475
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: 0,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: maxToken,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: '',
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
return modules;
}

View File

@@ -1,721 +0,0 @@
/*
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 { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { formData } = 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 simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
return [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/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',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: formData.aiSettings.temperature,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: formData.aiSettings.maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
}
function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
const modules: ModuleItemType[] = [
{
moduleId: 'userChatInput',
name: 'core.module.template.Chat entrance',
avatar: '/imgs/module/userChatInput.png',
flowType: 'questionInput',
position: {
x: 324.81436595478294,
y: 1527.0012457753612
},
inputs: [
{
key: 'userChatInput',
type: 'systemInput',
valueType: 'string',
label: 'core.module.input.label.user question',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
}
],
outputs: [
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'source',
valueType: 'string',
targets: [
{
moduleId: 'datasetSearch',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'datasetSearch',
name: 'core.module.template.Dataset search',
avatar: '/imgs/module/db.png',
flowType: 'datasetSearchNode',
showStatus: true,
position: {
x: 1351.5043753345153,
y: 947.0780385418003
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'datasets',
type: 'selectDataset',
label: '关联的知识库',
value: formData.dataset.datasets,
valueType: 'selectDataset',
list: [],
required: true,
showTargetInApp: false,
showTargetInPlugin: true,
connected: false
},
{
key: 'similarity',
type: 'hidden',
label: '最低相关性',
value: formData.dataset.similarity,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'limit',
type: 'hidden',
label: '引用上限',
description: '单次搜索最大的 Tokens 数量中文约1字=1.7Tokens英文约1字=1Tokens',
value: formData.dataset.limit,
valueType: 'number',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'searchMode',
type: 'hidden',
label: 'core.dataset.search.Mode',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.searchMode,
connected: false
},
{
key: 'usingReRank',
type: 'hidden',
label: '',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.usingReRank,
connected: false
},
{
key: 'datasetSearchUsingExtensionQuery',
type: 'hidden',
label: '',
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchUsingExtensionQuery,
connected: false
},
{
key: 'datasetSearchExtensionBg',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchExtensionBg,
connected: false
},
{
key: 'datasetSearchExtensionModel',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.datasetSearchExtensionModel,
connected: false
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'isEmpty',
label: '搜索结果为空',
type: 'source',
valueType: 'boolean',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: '6dtsvu',
key: 'switch'
}
]
: []
},
{
key: 'unEmpty',
label: '搜索结果不为空',
type: 'source',
valueType: 'boolean',
targets: formData.dataset.searchEmptyText
? [
{
moduleId: 'chatModule',
key: 'switch'
}
]
: []
},
{
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: []
},
{
key: 'userChatInput',
label: 'core.module.input.label.user question',
type: 'hidden',
valueType: 'string',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
]
},
{
moduleId: 'chatModule',
name: 'AI 对话',
avatar: '/imgs/module/AI.png',
flowType: 'chatNode',
showStatus: true,
position: {
x: 2022.7264786978908,
y: 1006.3102431257475
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: !!formData.dataset?.searchEmptyText
},
{
key: 'model',
type: 'selectLLMModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.model,
connected: false
},
{
key: 'temperature',
type: 'hidden',
label: '温度',
value: formData.aiSettings.temperature,
valueType: 'number',
min: 0,
max: 10,
step: 1,
markList: [
{
label: '严谨',
value: 0
},
{
label: '发散',
value: 10
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'maxToken',
type: 'hidden',
label: '回复上限',
value: formData.aiSettings.maxToken,
valueType: 'number',
min: 100,
max: 4000,
step: 50,
markList: [
{
label: '100',
value: 100
},
{
label: '4000',
value: 4000
}
],
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'isResponseAnswerText',
type: 'hidden',
label: '返回AI内容',
value: true,
valueType: 'boolean',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'quoteTemplate',
type: 'hidden',
label: '引用内容模板',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quoteTemplate,
connected: false
},
{
key: 'quotePrompt',
type: 'hidden',
label: '引用内容提示词',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.aiSettings.quotePrompt,
connected: false
},
{
key: 'aiSettings',
type: 'aiSettings',
label: '',
valueType: 'any',
showTargetInApp: false,
showTargetInPlugin: false,
connected: false
},
{
key: 'systemPrompt',
type: 'textarea',
label: 'core.ai.Prompt',
max: 300,
valueType: 'string',
description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
showTargetInApp: true,
showTargetInPlugin: true,
value: formData.aiSettings.systemPrompt,
connected: false
},
{
key: 'history',
type: 'numberInput',
label: 'core.module.input.label.chat history',
required: true,
min: 0,
max: 30,
valueType: 'chatHistory',
value: 6,
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: 'datasetQuote',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: 'core.module.input.label.user question',
required: true,
valueType: 'string',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
}
],
outputs: [
{
key: 'answerText',
label: 'AI回复',
description: '将在 stream 回复完毕后触发',
valueType: 'string',
type: 'source',
targets: []
},
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
},
{
key: 'history',
label: '新的上下文',
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
valueType: 'chatHistory',
type: 'source',
targets: []
}
]
}
];
if (formData.dataset?.searchEmptyText) {
modules.push({
moduleId: '6dtsvu',
name: '指定回复',
avatar: '/imgs/module/reply.png',
flowType: 'answerNode',
position: {
x: 2018.2744321961648,
y: 616.1220817209096
},
inputs: [
{
key: 'switch',
type: 'target',
label: 'core.module.input.label.switch',
valueType: 'any',
showTargetInApp: true,
showTargetInPlugin: true,
connected: true
},
{
key: 'text',
type: 'textarea',
value: formData.dataset.searchEmptyText,
valueType: 'any',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
placeholder:
'可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串',
showTargetInApp: true,
showTargetInPlugin: true,
connected: false
}
],
outputs: [
{
key: 'finish',
label: 'core.module.output.label.running done',
description: 'core.module.output.description.running done',
valueType: 'boolean',
type: 'source',
targets: []
}
]
});
}
return modules;
}

View File

@@ -12,7 +12,7 @@ import { getLLMModel } from '@fastgpt/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
const { name, avatar, type, intro, modules, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
@@ -29,7 +29,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
let maxTokens = 3000;
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.chatNode) {
if (
item.flowType === FlowNodeTypeEnum.chatNode ||
item.flowType === FlowNodeTypeEnum.tools
) {
const model =
item.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
@@ -61,7 +64,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
name,
type,
simpleTemplateId,
avatar,
intro,
permission,

View File

@@ -12,7 +12,7 @@ import { getLLMModel } from '@fastgpt/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
const { name, avatar, type, intro, modules, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
@@ -61,7 +61,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
name,
type,
simpleTemplateId,
avatar,
intro,
permission,

View File

@@ -8,10 +8,10 @@ import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import type { ChatItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { dispatchWorkFlow } from '@/service/moduleDispatch';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { setEntryEntries } from '@/service/moduleDispatch/utils';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatch/utils';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
export type Props = {

View File

@@ -7,8 +7,17 @@ import { autChatCrud } from '@/service/support/permission/auth/chat';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { appId, chatId, chatItemId, shareId, outLinkUid, userBadFeedback, userGoodFeedback } =
req.body as UpdateChatFeedbackProps;
const {
appId,
chatId,
chatItemId,
shareId,
teamId,
teamToken,
outLinkUid,
userBadFeedback,
userGoodFeedback
} = req.body as UpdateChatFeedbackProps;
try {
await connectToDatabase();
@@ -17,6 +26,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
req,
authToken: true,
appId,
teamId,
teamToken,
chatId,
shareId,
outLinkUid,

View File

@@ -9,12 +9,13 @@ import { autChatCrud } from '@/service/support/permission/auth/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, chatId, shareId, outLinkUid, customTitle, top } = req.body as UpdateHistoryProps;
const { appId, chatId, teamId, shareId, outLinkUid, customTitle, top } =
req.body as UpdateHistoryProps;
await autChatCrud({
req,
authToken: true,
appId,
teamId,
chatId,
shareId,
outLinkUid,

View File

@@ -14,7 +14,7 @@ import {
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
import { hashStr } from '@fastgpt/global/common/string/tools';
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
@@ -83,7 +83,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
]);
// 4. push chunks to training queue
const insertResults = await pushDataToTrainingQueue({
const insertResults = await pushDataListToTrainingQueue({
teamId,
tmbId,
collectionId,

View File

@@ -23,11 +23,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { collectionId, q, a, indexes } = req.body as InsertOneDatasetDataProps;
if (!q) {
return Promise.reject('q is required');
throw new Error('q is required');
}
if (!collectionId) {
return Promise.reject('collectionId is required');
throw new Error('collectionId is required');
}
// 凭证校验

View File

@@ -10,7 +10,7 @@ import type {
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -41,7 +41,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
jsonRes<PushDatasetDataResponse>(res, {
data: await pushDataToTrainingQueue({
data: await pushDataListToTrainingQueue({
...req.body,
teamId,
tmbId

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authDatasetFile } from '@fastgpt/service/support/permission/auth/dataset';
import { createFileToken } from '@fastgpt/service/support/permission/controller';
import { BucketNameEnum, FileBaseUrl } from '@fastgpt/global/common/file/constants';
import { BucketNameEnum, ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
jsonRes(res, {
data: `${FileBaseUrl}?token=${token}`
data: `${ReadFileBaseUrl}?token=${token}`
});
} catch (error) {
jsonRes(res, {

View File

@@ -5,7 +5,7 @@ import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/
import { connectToDatabase } from '@/service/mongo';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { searchDatasetData } from '@fastgpt/service/core/dataset/search/controller';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { getLLMModel } from '@fastgpt/service/core/ai/model';

View File

@@ -5,6 +5,8 @@ import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controll
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { checkTeamPluginLimit } from '@fastgpt/service/support/permission/teamLimit';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -12,17 +14,58 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
const body = req.body as CreateOnePluginParams;
await checkTeamPluginLimit(teamId);
// await checkTeamPluginLimit(teamId);
const { _id } = await MongoPlugin.create({
...body,
teamId,
tmbId
});
// create parent plugin and child plugin
if (body.metadata?.apiSchemaStr) {
const parentId = await mongoSessionRun(async (session) => {
const [{ _id: parentId }] = await MongoPlugin.create(
[
{
...body,
parentId: null,
teamId,
tmbId
}
],
{ session }
);
jsonRes(res, {
data: _id
});
const childrenPlugins = httpApiSchema2Plugins({
parentId,
apiSchemaStr: body.metadata?.apiSchemaStr,
customHeader: body.metadata?.customHeaders
});
await MongoPlugin.create(
childrenPlugins.map((item) => ({
...item,
metadata: {
pluginUid: item.name
},
teamId,
tmbId
})),
{
session
}
);
return parentId;
});
jsonRes(res, {
data: parentId
});
} else {
const { _id } = await MongoPlugin.create({
...body,
teamId,
tmbId
});
jsonRes(res, {
data: _id
});
}
} catch (err) {
jsonRes(res, {
code: 500,

View File

@@ -3,14 +3,32 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
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: 'owner' });
const { teamId } = await authUserNotVisitor({ req, authToken: true });
const { pluginId } = req.query as { pluginId: string };
await MongoPlugin.findByIdAndRemove(id);
if (!pluginId) {
throw new Error('缺少参数');
}
await authPluginCrud({ req, authToken: true, id: pluginId, per: 'owner' });
await mongoSessionRun(async (session) => {
await MongoPlugin.deleteMany(
{
teamId,
parentId: pluginId
},
{
session
}
);
await MongoPlugin.findByIdAndDelete(pluginId, { session });
});
jsonRes(res, {});
} catch (err) {

View File

@@ -6,7 +6,7 @@ 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 { FlowNodeTemplateType } 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>) {
@@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { teamId, tmbId } = await authCert({ req, authToken: true });
await authPluginCanUse({ id, teamId, tmbId });
jsonRes<FlowModuleTemplateType>(res, {
jsonRes<FlowNodeTemplateType>(res, {
data: await getPluginPreviewModule({ id })
});
} catch (err) {

View File

@@ -0,0 +1,20 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import * as SwaggerParser from '@apidevtools/swagger-parser';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const apiURL = req.body.url as string;
const api = await (SwaggerParser as any).validate(apiURL);
return jsonRes(res, {
data: api
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,16 +1,31 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { PluginListItemType } from '@fastgpt/global/core/plugin/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId, type } = req.query as { parentId?: string; type?: `${DatasetTypeEnum}` };
const { teamId } = await authCert({ req, authToken: true });
jsonRes(res, {
data: await MongoPlugin.find({ teamId })
const plugins = await MongoPlugin.find(
{
teamId,
...(parentId !== undefined && { parentId: parentId || null }),
...(type && { type })
},
'_id parentId type name avatar intro metadata'
)
.sort({ updateTime: -1 })
.lean();
jsonRes<PluginListItemType[]>(res, {
data: plugins
});
} catch (err) {
jsonRes(res, {

View File

@@ -0,0 +1,46 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type.d';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId } = req.query as { parentId: string };
if (!parentId) {
return jsonRes(res, {
data: []
});
}
await authCert({ req, authToken: true });
jsonRes<ParentTreePathItemType[]>(res, {
data: await getParents(parentId)
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
async function getParents(parentId?: string): Promise<ParentTreePathItemType[]> {
if (!parentId) {
return [];
}
const parent = await MongoPlugin.findById(parentId, 'name parentId');
if (!parent) return [];
const paths = await getParents(parent.parentId);
paths.push({ parentId, parentName: parent.name });
return paths;
}

View File

@@ -0,0 +1,37 @@
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 { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
await authCert({ req, authToken: true });
const data: FlowNodeTemplateType[] =
global.communityPlugins?.map((plugin) => ({
id: plugin.id,
templateType: plugin.templateType ?? FlowNodeTemplateTypeEnum.other,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: true,
isTool: plugin.isTool,
inputs: [],
outputs: []
})) || [];
jsonRes<FlowNodeTemplateType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,62 @@
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 { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId, searchKey } = req.query as { parentId?: string; searchKey?: string };
const { teamId } = await authCert({ req, authToken: true });
const userPlugins = await (async () => {
if (searchKey) {
return MongoPlugin.find({
teamId,
// search name or intro
$or: [
{ name: { $regex: searchKey, $options: 'i' } },
{ intro: { $regex: searchKey, $options: 'i' } }
]
})
.sort({
updateTime: -1
})
.lean();
} else {
return MongoPlugin.find({ teamId, parentId: parentId || null })
.sort({
updateTime: -1
})
.lean();
}
})();
const data: FlowNodeTemplateType[] = userPlugins.map((plugin) => ({
id: String(plugin._id),
parentId: String(plugin.parentId),
pluginType: plugin.type,
templateType: FlowNodeTemplateTypeEnum.personalPlugin,
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
showStatus: false,
inputs: [],
outputs: []
}));
jsonRes<FlowNodeTemplateType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,57 +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 { 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';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
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(),
FastGPTProUrl ? 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: plugin.templateType ?? ModuleTemplateTypeEnum.other,
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
});
}
}

View File

@@ -4,17 +4,37 @@ import { connectToDatabase } from '@/service/mongo';
import type { UpdatePluginParams } from '@fastgpt/global/core/plugin/controller';
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { ClientSession } from '@fastgpt/service/common/mongo';
import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id, ...props } = req.body as UpdatePluginParams;
const body = req.body as UpdatePluginParams;
await authPluginCrud({ req, authToken: true, id, per: 'owner' });
const { id, ...props } = body;
jsonRes(res, {
data: await MongoPlugin.findByIdAndUpdate(id, props)
});
const { teamId, tmbId } = await authPluginCrud({ req, authToken: true, id, per: 'owner' });
if (props.metadata?.apiSchemaStr) {
await mongoSessionRun(async (session) => {
// update children
await updateHttpChildrenPlugin({
teamId,
tmbId,
parent: body,
session
});
await MongoPlugin.findByIdAndUpdate(id, props, { session });
});
jsonRes(res, {});
} else {
jsonRes(res, {
data: await MongoPlugin.findByIdAndUpdate(id, props)
});
}
} catch (err) {
jsonRes(res, {
code: 500,
@@ -22,3 +42,64 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}
const updateHttpChildrenPlugin = async ({
teamId,
tmbId,
parent,
session
}: {
teamId: string;
tmbId: string;
parent: UpdatePluginParams;
session: ClientSession;
}) => {
if (!parent.metadata?.apiSchemaStr) return;
const dbPlugins = await MongoPlugin.find(
{
parentId: parent.id,
teamId
},
'_id metadata'
);
const schemaPlugins = httpApiSchema2Plugins({
parentId: parent.id,
apiSchemaStr: parent.metadata?.apiSchemaStr,
customHeader: parent.metadata?.customHeaders
});
// 数据库中存在schema不存在删除
for await (const plugin of dbPlugins) {
if (!schemaPlugins.find((p) => p.name === plugin.metadata?.pluginUid)) {
await MongoPlugin.deleteOne({ _id: plugin._id }, { session });
}
}
// 数据库中不存在schema存在新增
for await (const plugin of schemaPlugins) {
if (!dbPlugins.find((p) => p.metadata?.pluginUid === plugin.name)) {
await MongoPlugin.create(
[
{
...plugin,
metadata: {
pluginUid: plugin.name
},
teamId,
tmbId
}
],
{
session
}
);
}
}
// 数据库中存在schema存在更新
for await (const plugin of schemaPlugins) {
const dbPlugin = dbPlugins.find((p) => plugin.name === p.metadata?.pluginUid);
if (dbPlugin) {
await MongoPlugin.findByIdAndUpdate(dbPlugin._id, plugin, { session });
}
}
};

Some files were not shown because too many files have changed in this diff Show More