V4.8.17 feature (#3485)
* feat: add third party account config (#3443) * temp * editor workflow variable style * add team to dispatch * i18n * delete console * change openai account position * fix * fix * fix * fix * fix * 4.8.17 test (#3461) * perf: external provider config * perf: ui * feat: add template config (#3434) * change template position * template config * delete console * delete * fix * fix * perf: Mongo visutal field (#3464) * remve invalid code * perf: team member visutal code * perf: virtual search; perf: search test data * fix: ts * fix: image response headers * perf: template code * perf: auth layout;perf: auto save (#3472) * perf: auth layout * perf: auto save * perf: auto save * fix: template guide display & http input support external variables (#3475) * fix: template guide display * http editor support external workflow variables * perf: auto save;fix: ifelse checker line break; (#3478) * perf: auto save * perf: auto save * fix: ifelse checker line break * perf: doc * perf: doc * fix: update var type error * 4.8.17 test (#3479) * perf: auto save * perf: auto save * perf: template code * 4.8.17 test (#3480) * perf: auto save * perf: auto save * perf: model price model * feat: add react memo * perf: model provider filter * fix: ts (#3481) * perf: auto save * perf: auto save * fix: ts * simple app tool select (#3473) * workflow plugin userguide & simple tool ui * simple tool filter * reuse component * change component to hook * fix * perf: too selector modal (#3484) * perf: auto save * perf: auto save * perf: markdown render * perf: too selector * fix: app version require tmbId * perf: templates refresh * perf: templates refresh * hide auto save error tip * perf: toolkit guide --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -17,7 +17,7 @@ const unAuthPage: { [key: string]: boolean } = {
|
||||
'/price': true
|
||||
};
|
||||
|
||||
const Auth = ({ children }: { children: JSX.Element }) => {
|
||||
const Auth = ({ children }: { children: JSX.Element | React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -80,14 +80,14 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
{isHideNavbar ? (
|
||||
<Auth>{children}</Auth>
|
||||
) : (
|
||||
<>
|
||||
<Auth>
|
||||
<Box h={'100%'} position={'fixed'} left={0} top={0} w={navbarWidth}>
|
||||
<Navbar unread={unread} />
|
||||
</Box>
|
||||
<Box h={'100%'} ml={navbarWidth} overflow={'overlay'}>
|
||||
<Auth>{children}</Auth>
|
||||
{children}
|
||||
</Box>
|
||||
</>
|
||||
</Auth>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
@@ -96,14 +96,16 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
{phoneUnShowLayoutRoute[router.pathname] || isChatPage ? (
|
||||
<Auth>{children}</Auth>
|
||||
) : (
|
||||
<Flex h={'100%'} flexDirection={'column'}>
|
||||
<Box flex={'1 0 0'} h={0}>
|
||||
<Auth>{children}</Auth>
|
||||
</Box>
|
||||
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
|
||||
<NavbarPhone unread={unread} />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Auth>
|
||||
<Flex h={'100%'} flexDirection={'column'}>
|
||||
<Box flex={'1 0 0'} h={0}>
|
||||
{children}
|
||||
</Box>
|
||||
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
|
||||
<NavbarPhone unread={unread} />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Auth>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -82,6 +82,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
'/account/info',
|
||||
'/account/team',
|
||||
'/account/usage',
|
||||
'/account/thirdParty',
|
||||
'/account/apikey',
|
||||
'/account/setting',
|
||||
'/account/inform',
|
||||
|
||||
@@ -28,17 +28,22 @@ const IframeHtmlCodeBlock = dynamic(() => import('./codeBlock/iframe-html'), { s
|
||||
const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false });
|
||||
const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false });
|
||||
|
||||
const Markdown = ({
|
||||
source = '',
|
||||
showAnimation = false,
|
||||
isDisabled = false,
|
||||
forbidZhFormat = false
|
||||
}: {
|
||||
type Props = {
|
||||
source?: string;
|
||||
showAnimation?: boolean;
|
||||
isDisabled?: boolean;
|
||||
forbidZhFormat?: boolean;
|
||||
}) => {
|
||||
};
|
||||
const Markdown = (props: Props) => {
|
||||
const source = props.source || '';
|
||||
|
||||
if (source.length < 200000) {
|
||||
return <MarkdownRender {...props} />;
|
||||
}
|
||||
|
||||
return <Box whiteSpace={'pre-wrap'}>{source}</Box>;
|
||||
};
|
||||
const MarkdownRender = ({ source = '', showAnimation, isDisabled, forbidZhFormat }: Props) => {
|
||||
const components = useMemo<any>(
|
||||
() => ({
|
||||
img: Image,
|
||||
|
||||
@@ -24,12 +24,6 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs, llmModelList, vectorModelList } = useSystemStore();
|
||||
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const avatarSize = useMemo(() => {
|
||||
const size = {
|
||||
sm: '1rem',
|
||||
@@ -74,17 +68,6 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
: avatarList;
|
||||
}, [feConfigs.show_pay, avatarList, avatarSize, t]);
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: string) => {
|
||||
if (e === 'price') {
|
||||
onOpenAiPointsModal();
|
||||
return;
|
||||
}
|
||||
return onchange?.(e);
|
||||
},
|
||||
[onOpenAiPointsModal, onchange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
@@ -94,30 +77,32 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={disableTip}>
|
||||
<MySelect
|
||||
className="nowheel"
|
||||
isDisabled={!!disableTip}
|
||||
list={expandList}
|
||||
{...props}
|
||||
onchange={onSelect}
|
||||
/>
|
||||
<ModelPriceModal>
|
||||
{({ onOpen }) => (
|
||||
<MySelect
|
||||
className="nowheel"
|
||||
isDisabled={!!disableTip}
|
||||
list={expandList}
|
||||
{...props}
|
||||
onchange={(e) => {
|
||||
if (e === 'price') {
|
||||
onOpen();
|
||||
return;
|
||||
}
|
||||
return onchange?.(e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ModelPriceModal>
|
||||
</MyTooltip>
|
||||
|
||||
{isOpenAiPointsModal && <ModelPriceModal onClose={onCloseAiPointsModal} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs, llmModelList, vectorModelList } = useSystemStore();
|
||||
const { llmModelList, vectorModelList } = useSystemStore();
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const avatarSize = useMemo(() => {
|
||||
const size = {
|
||||
sm: '1rem',
|
||||
@@ -211,8 +196,6 @@ const MultipleRowSelector = ({ list, onchange, disableTip, ...props }: Props) =>
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
|
||||
{isOpenAiPointsModal && <ModelPriceModal onClose={onCloseAiPointsModal} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
46
projects/app/src/components/common/Modal/UseGuideModal.tsx
Normal file
46
projects/app/src/components/common/Modal/UseGuideModal.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, ModalBody, useDisclosure } from '@chakra-ui/react';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import React from 'react';
|
||||
|
||||
const UseGuideModal = ({
|
||||
children,
|
||||
title,
|
||||
iconSrc,
|
||||
text,
|
||||
link
|
||||
}: {
|
||||
children: ({ onClick }: { onClick: () => void }) => React.ReactNode;
|
||||
title?: string;
|
||||
iconSrc?: string;
|
||||
text?: string;
|
||||
link?: string;
|
||||
}) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const onClick = () => {
|
||||
if (link) {
|
||||
return window.open(getDocPath(link), '_blank');
|
||||
}
|
||||
if (text) {
|
||||
return onOpen();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{children({ onClick })}
|
||||
{isOpen && (
|
||||
<MyModal isOpen iconSrc={iconSrc} title={title} onClose={onClose} minW={'600px'}>
|
||||
<ModalBody>
|
||||
<Box border={'base'} borderRadius={'10px'} p={4} minH={'500px'}>
|
||||
<Markdown source={text} />
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UseGuideModal);
|
||||
@@ -100,12 +100,6 @@ const AIChatSettingsModal = ({
|
||||
setRefresh(!refresh);
|
||||
};
|
||||
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
@@ -160,10 +154,11 @@ const AIChatSettingsModal = ({
|
||||
<Th fontSize={'mini'} pb={2}>
|
||||
<HStack spacing={1}>
|
||||
<Box> {t('app:ai_point_price')}</Box>
|
||||
<QuestionTip
|
||||
label={t('app:look_ai_point_price')}
|
||||
onClick={onOpenAiPointsModal}
|
||||
/>
|
||||
<ModelPriceModal>
|
||||
{({ onOpen }) => (
|
||||
<QuestionTip label={t('app:look_ai_point_price')} onClick={onOpen} />
|
||||
)}
|
||||
</ModelPriceModal>
|
||||
</HStack>
|
||||
</Th>
|
||||
<Th fontSize={'mini'} pb={2}>
|
||||
@@ -327,8 +322,6 @@ const AIChatSettingsModal = ({
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
{isOpenAiPointsModal && <ModelPriceModal onClose={onCloseAiPointsModal} />}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr
|
||||
Tr,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
@@ -26,6 +26,9 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const MyModal = dynamic(() => import('@fastgpt/web/components/common/MyModal'));
|
||||
|
||||
const ModelTable = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -156,6 +159,19 @@ const ModelTable = () => {
|
||||
search
|
||||
]);
|
||||
|
||||
const filterProviderList = useMemo(() => {
|
||||
const allProviderIds: string[] = [
|
||||
...llmModelList,
|
||||
...vectorModelList,
|
||||
...audioSpeechModelList,
|
||||
whisperModel
|
||||
].map((model) => model.provider);
|
||||
|
||||
return providerList.current.filter(
|
||||
(item) => allProviderIds.includes(item.value) || item.value === ''
|
||||
);
|
||||
}, [audioSpeechModelList, llmModelList, vectorModelList, whisperModel]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
<Flex>
|
||||
@@ -168,7 +184,7 @@ const ModelTable = () => {
|
||||
bg={'myGray.50'}
|
||||
value={provider}
|
||||
onchange={setProvider}
|
||||
list={providerList.current}
|
||||
list={filterProviderList}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack flexShrink={0} ml={6}>
|
||||
@@ -228,24 +244,34 @@ const ModelTable = () => {
|
||||
|
||||
export default ModelTable;
|
||||
|
||||
export const ModelPriceModal = ({ onClose }: { onClose: () => void }) => {
|
||||
export const ModelPriceModal = ({
|
||||
children
|
||||
}: {
|
||||
children: ({ onOpen }: { onOpen: () => void }) => React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isCentered
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('common:support.wallet.subscription.Ai points')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
maxW={'90vw'}
|
||||
maxH={'90vh'}
|
||||
>
|
||||
<ModalBody flex={'1 0 0'}>
|
||||
<ModelTable />
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
<>
|
||||
{children({ onOpen })}
|
||||
{isOpen && (
|
||||
<MyModal
|
||||
isCentered
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('common:support.wallet.subscription.Ai points')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
maxW={'90vw'}
|
||||
maxH={'90vh'}
|
||||
>
|
||||
<ModalBody flex={'1 0 0'}>
|
||||
<ModelTable />
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -226,8 +226,8 @@ const DatasetParamsModal = ({
|
||||
{limit !== undefined && (
|
||||
<Box display={['block', 'flex']}>
|
||||
<Flex flex={'0 0 120px'} alignItems={'center'} mb={[5, 0]}>
|
||||
<FormLabel>{t('app:max_quote_tokens')}</FormLabel>
|
||||
<QuestionTip label={t('app:max_quote_tokens_tips')} />
|
||||
<FormLabel>{t('common:max_quote_tokens')}</FormLabel>
|
||||
<QuestionTip label={t('common:max_quote_tokens_tips')} />
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'}>
|
||||
<InputSlider
|
||||
@@ -245,8 +245,8 @@ const DatasetParamsModal = ({
|
||||
)}
|
||||
<Box display={['block', 'flex']} mt={[6, 10]} mb={4}>
|
||||
<Flex flex={'0 0 120px'} alignItems={'center'} mb={[5, 0]}>
|
||||
<FormLabel>{t('app:min_similarity')}</FormLabel>
|
||||
<QuestionTip label={t('app:min_similarity_tip')} />
|
||||
<FormLabel>{t('common:min_similarity')}</FormLabel>
|
||||
<QuestionTip label={t('common:min_similarity_tip')} />
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'}>
|
||||
{showSimilarity ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Button, Flex, Switch, Textarea } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Switch, Textarea, useDisclosure } from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
@@ -20,6 +20,8 @@ import { isEqual } from 'lodash';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { PluginRunContext } from '../context';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
@@ -152,6 +154,7 @@ const RenderPluginInput = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const inputType = input.renderTypeList[0];
|
||||
const { llmModelList } = useSystemStore();
|
||||
|
||||
const render = (() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.customVariable) {
|
||||
@@ -167,7 +170,19 @@ const RenderPluginInput = ({
|
||||
<FileSelector onChange={onChange} input={input} setUploading={setUploading} value={value} />
|
||||
);
|
||||
}
|
||||
|
||||
if (inputType === FlowNodeInputTypeEnum.selectLLMModel) {
|
||||
return (
|
||||
<AIModelSelector
|
||||
w={'100%'}
|
||||
value={value}
|
||||
list={llmModelList.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.string) {
|
||||
return (
|
||||
<Textarea
|
||||
|
||||
@@ -62,8 +62,8 @@ const SearchParamsTip = ({
|
||||
<Thead>
|
||||
<Tr bg={'transparent !important'}>
|
||||
<Th fontSize={'mini'}>{t('common:core.dataset.search.search mode')}</Th>
|
||||
<Th fontSize={'mini'}>{t('app:max_quote_tokens')}</Th>
|
||||
<Th fontSize={'mini'}>{t('app:min_similarity')}</Th>
|
||||
<Th fontSize={'mini'}>{t('common:max_quote_tokens')}</Th>
|
||||
<Th fontSize={'mini'}>{t('common:min_similarity')}</Th>
|
||||
{hasReRankModel && <Th fontSize={'mini'}>{t('common:core.dataset.search.ReRank')}</Th>}
|
||||
<Th fontSize={'mini'}>{t('common:core.module.template.Query extension')}</Th>
|
||||
{hasEmptyResponseMode && (
|
||||
|
||||
@@ -96,7 +96,7 @@ const LafAccountModal = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal isOpen iconSrc="/imgs/workflow/laf.png" title={t('common:user.Laf Account Setting')}>
|
||||
<MyModal isOpen iconSrc="support/account/laf" title={t('common:user.Laf Account Setting')}>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
<Box>{t('common:support.user.Laf account intro')}</Box>
|
||||
@@ -107,7 +107,7 @@ const LafAccountModal = ({
|
||||
</Box>
|
||||
<Box>
|
||||
<Link textDecoration={'underline'} href={`${feConfigs.lafEnv}/`} isExternal>
|
||||
{t('support.user.Go laf env', {
|
||||
{t('common:support.user.Go laf env', {
|
||||
env: feConfigs.lafEnv?.split('//')[1]
|
||||
})}
|
||||
</Link>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import React, { useMemo } from 'react';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { Box, Flex, Grid, useDisclosure } from '@chakra-ui/react';
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
@@ -22,12 +22,6 @@ const StandardPlanContentList = ({
|
||||
const { t } = useTranslation();
|
||||
const { subPlans } = useSystemStore();
|
||||
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const planContent = useMemo(() => {
|
||||
const plan = subPlans?.standard?.[level];
|
||||
|
||||
@@ -100,11 +94,15 @@ const StandardPlanContentList = ({
|
||||
amount: planContent.totalPoints
|
||||
})}
|
||||
</Box>
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.wallet.subscription.AI points click to read tip')}
|
||||
onClick={onOpenAiPointsModal}
|
||||
></QuestionTip>
|
||||
<ModelPriceModal>
|
||||
{({ onOpen }) => (
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.wallet.subscription.AI points click to read tip')}
|
||||
onClick={onOpen}
|
||||
/>
|
||||
)}
|
||||
</ModelPriceModal>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
@@ -127,7 +125,6 @@ const StandardPlanContentList = ({
|
||||
<Box color={'myGray.600'}>{t('common:support.wallet.subscription.web_site_sync')}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{isOpenAiPointsModal && <ModelPriceModal onClose={onCloseAiPointsModal} />}
|
||||
</Grid>
|
||||
) : null;
|
||||
};
|
||||
|
||||
1
projects/app/src/global/core/app/api.d.ts
vendored
1
projects/app/src/global/core/app/api.d.ts
vendored
@@ -20,6 +20,7 @@ export type PostPublishAppProps = {
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
isPublish?: boolean;
|
||||
versionName?: string;
|
||||
autoSave?: boolean; // If it is automatically saved, only one copy of the entire app will be stored, overwriting the old version
|
||||
};
|
||||
|
||||
export type PostRevertAppProps = {
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function register() {
|
||||
const [
|
||||
{ connectMongo },
|
||||
{ systemStartCb },
|
||||
{ initGlobalVariables, getInitConfig, initSystemPlugins },
|
||||
{ initGlobalVariables, getInitConfig, initSystemPluginGroups, initAppTemplateTypes },
|
||||
{ initVectorStore },
|
||||
{ initRootUser },
|
||||
{ getSystemPluginCb },
|
||||
@@ -37,8 +37,10 @@ export async function register() {
|
||||
await connectMongo();
|
||||
|
||||
//init system config;init vector database;init root user
|
||||
await Promise.all([getInitConfig(), initVectorStore(), initRootUser(), initSystemPlugins()]);
|
||||
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]);
|
||||
|
||||
initSystemPluginGroups();
|
||||
initAppTemplateTypes();
|
||||
getSystemPluginCb();
|
||||
startMongoWatch();
|
||||
startCron();
|
||||
|
||||
@@ -17,6 +17,8 @@ export enum TabEnum {
|
||||
'bill' = 'bill',
|
||||
'inform' = 'inform',
|
||||
'setting' = 'setting',
|
||||
'thirdParty' = 'thirdParty',
|
||||
'individuation' = 'individuation',
|
||||
'apikey' = 'apikey',
|
||||
'loginout' = 'loginout',
|
||||
'team' = 'team',
|
||||
@@ -70,6 +72,11 @@ const AccountContainer = ({
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
icon: 'common/thirdParty',
|
||||
label: t('account:third_party'),
|
||||
value: TabEnum.thirdParty
|
||||
},
|
||||
{
|
||||
icon: 'common/model',
|
||||
label: t('account:model_provider'),
|
||||
|
||||
@@ -92,7 +92,7 @@ const TeamSelector = ({
|
||||
: []),
|
||||
...teamList
|
||||
];
|
||||
}, [showManage, teamList, router]);
|
||||
}, [showManage, t, teamList, router]);
|
||||
|
||||
return (
|
||||
<Box w={'100%'}>
|
||||
|
||||
@@ -38,10 +38,8 @@ import { formatTime2YMD } from '@fastgpt/global/common/string/time';
|
||||
import { getExtraPlanCardRoute } from '@/web/support/wallet/sub/constants';
|
||||
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import AccountContainer from '../components/AccountContainer';
|
||||
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
|
||||
@@ -52,8 +50,6 @@ const StandDetailModal = dynamic(() => import('./components/standardDetailModal'
|
||||
const ConversionModal = dynamic(() => import('./components/ConversionModal'));
|
||||
const UpdatePswModal = dynamic(() => import('./components/UpdatePswModal'));
|
||||
const UpdateNotification = dynamic(() => import('./components/UpdateNotificationModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./components/OpenAIAccountModal'));
|
||||
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
|
||||
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
|
||||
|
||||
const ModelPriceModal = dynamic(() =>
|
||||
@@ -144,8 +140,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
timezone: data.timezone
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
@@ -353,11 +348,6 @@ const PlanUsage = () => {
|
||||
onClose: onCloseStandardModal,
|
||||
onOpen: onOpenStandardModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const planName = useMemo(() => {
|
||||
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
|
||||
@@ -443,9 +433,13 @@ const PlanUsage = () => {
|
||||
<MyIcon mr={2} name={'support/account/plans'} w={'20px'} />
|
||||
{t('account_info:package_and_usage')}
|
||||
</Flex>
|
||||
<Button ml={4} size={'sm'} onClick={onOpenAiPointsModal}>
|
||||
{t('account_info:billing_standard')}
|
||||
</Button>
|
||||
<ModelPriceModal>
|
||||
{({ onOpen }) => (
|
||||
<Button ml={4} size={'sm'} onClick={onOpen}>
|
||||
{t('account_info:billing_standard')}
|
||||
</Button>
|
||||
)}
|
||||
</ModelPriceModal>
|
||||
<Button ml={4} variant={'whitePrimary'} size={'sm'} onClick={onOpenStandardModal}>
|
||||
{t('account_info:package_details')}
|
||||
</Button>
|
||||
@@ -584,14 +578,24 @@ const PlanUsage = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
{isOpenStandardModal && <StandDetailModal onClose={onCloseStandardModal} />}
|
||||
{isOpenAiPointsModal && <ModelPriceModal onClose={onCloseAiPointsModal} />}
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const ButtonStyles = {
|
||||
bg: 'white',
|
||||
py: 3,
|
||||
px: 6,
|
||||
border: 'sm',
|
||||
borderWidth: '1.5px',
|
||||
borderRadius: 'md',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none' as any,
|
||||
fontSize: 'sm'
|
||||
};
|
||||
const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
@@ -600,56 +604,16 @@ const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { isOpen: isOpenLaf, onClose: onCloseLaf, onOpen: onOpenLaf } = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
title: t('account_info:update_success_tip'),
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
[reset, t, toast, updateUserInfo]
|
||||
);
|
||||
|
||||
const buttonStyles = useRef<FlexProps>({
|
||||
bg: 'white',
|
||||
py: 3,
|
||||
px: 6,
|
||||
border: theme.borders.sm,
|
||||
borderWidth: '1.5px',
|
||||
borderRadius: 'md',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
fontSize: 'sm'
|
||||
});
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid gridGap={4} mt={3}>
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
bg={'white'}
|
||||
href={getDocPath('/docs/intro')}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
fontSize={'sm'}
|
||||
{...ButtonStyles}
|
||||
>
|
||||
<MyIcon name={'common/courseLight'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
@@ -662,76 +626,22 @@ const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
feConfigs?.navbarItems
|
||||
?.filter((item) => item.isActive)
|
||||
.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
{...buttonStyles.current}
|
||||
onClick={() => window.open(item.url, '_blank')}
|
||||
>
|
||||
<Flex key={item.id} {...ButtonStyles} onClick={() => window.open(item.url, '_blank')}>
|
||||
<Avatar src={item.avatar} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
{feConfigs?.lafEnv && userInfo?.team.role === TeamMemberRoleEnum.owner && (
|
||||
<Flex {...buttonStyles.current} onClick={onOpenLaf}>
|
||||
<MyImage src="/imgs/workflow/laf.png" w={'18px'} alt="laf" />
|
||||
<Box ml={2} flex={1}>
|
||||
{'laf' + t('account_info:account_duplicate')}
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.team.lafAccount?.token ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{feConfigs?.show_openai_account && (
|
||||
<Flex {...buttonStyles.current} onClick={onOpenOpenai}>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{'OpenAI / OneAPI' + t('account_info:account_duplicate')}
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{feConfigs?.concatMd && (
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
justifyContent={'flex-start'}
|
||||
leftIcon={<MyIcon name={'modal/concat'} w={'18px'} color={'myGray.600'} />}
|
||||
onClick={onOpenContact}
|
||||
h={'48px'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{t('account_info:contact_us')}
|
||||
</Button>
|
||||
<Flex onClick={onOpenContact} {...ButtonStyles}>
|
||||
<MyIcon name={'modal/concat'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('account_info:contact_us')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{isOpenLaf && userInfo && (
|
||||
<LafAccountModal defaultData={userInfo?.team.lafAccount} onClose={onCloseLaf} />
|
||||
)}
|
||||
{isOpenOpenai && userInfo && (
|
||||
<OpenAIAccountModal
|
||||
defaultData={userInfo?.openaiAccount}
|
||||
onSuccess={(data) =>
|
||||
onclickSave({
|
||||
...userInfo,
|
||||
openaiAccount: data
|
||||
})
|
||||
}
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -130,7 +130,7 @@ const Team = () => {
|
||||
<Flex align={'center'} ml={6}>
|
||||
<TeamSelector height={'28px'} />
|
||||
</Flex>
|
||||
{userInfo?.team.role === TeamMemberRoleEnum.owner && (
|
||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner && (
|
||||
<Flex align={'center'} justify={'center'} ml={2} p={'0.44rem'}>
|
||||
<MyIcon
|
||||
name="edit"
|
||||
|
||||
@@ -3,41 +3,51 @@ import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/rea
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import type { OpenaiAccountType } from '@fastgpt/global/support/user/team/type';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { putUpdateTeam } from '@/web/support/user/team/api';
|
||||
|
||||
const OpenAIAccountModal = ({
|
||||
defaultData,
|
||||
onSuccess,
|
||||
onClose
|
||||
}: {
|
||||
defaultData: UserType['openaiAccount'];
|
||||
onSuccess: (e: UserType['openaiAccount']) => Promise<any>;
|
||||
defaultData?: OpenaiAccountType;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const { mutate: onSubmit, isLoading } = useRequest({
|
||||
mutationFn: async (data: UserType['openaiAccount']) => onSuccess(data),
|
||||
onSuccess(res) {
|
||||
onClose();
|
||||
const { runAsync: onSubmit, loading } = useRequest2(
|
||||
async (data: OpenaiAccountType) => {
|
||||
if (!userInfo?.team.teamId) return;
|
||||
return putUpdateTeam({
|
||||
openaiAccount: data
|
||||
});
|
||||
},
|
||||
errorToast: t('account_info:openai_account_setting_exception')
|
||||
});
|
||||
{
|
||||
onSuccess: () => {
|
||||
initUserInfo();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Update Success'),
|
||||
errorToast: t('common:common.Update Failed')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="common/openai"
|
||||
title={t('account_info:openai_account_configuration')}
|
||||
title={t('account_thirdParty:openai_account_configuration')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('account_info:open_api_notice')}
|
||||
{t('account_thirdParty:open_api_notice')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 65px'}>API Key:</Box>
|
||||
@@ -48,16 +58,16 @@ const OpenAIAccountModal = ({
|
||||
<Input
|
||||
flex={1}
|
||||
{...register('baseUrl')}
|
||||
placeholder={t('account_info:request_address_notice')}
|
||||
></Input>
|
||||
placeholder={t('account_thirdParty:request_address_notice')}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isLoading} onClick={handleSubmit((data) => onSubmit(data))}>
|
||||
{t('account_info:confirm')}
|
||||
<Button isLoading={loading} onClick={handleSubmit(onSubmit)}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
81
projects/app/src/pages/account/thirdParty/components/WorkflowVariableModal.tsx
vendored
Normal file
81
projects/app/src/pages/account/thirdParty/components/WorkflowVariableModal.tsx
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React from 'react';
|
||||
import { ThirdPartyAccountType } from '../index';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { putUpdateTeam } from '@/web/support/user/team/api';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const WorkflowVariableModal = ({
|
||||
defaultData,
|
||||
onClose
|
||||
}: {
|
||||
defaultData: ThirdPartyAccountType;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
value: '',
|
||||
key: defaultData.key || ''
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: onSubmit, loading } = useRequest2(
|
||||
async (data: { key: string; value: string }) => {
|
||||
if (!userInfo?.team.teamId) return;
|
||||
|
||||
await putUpdateTeam({
|
||||
externalWorkflowVariable: data
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
initUserInfo();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Update Success'),
|
||||
errorToast: t('common:common.Update Failed')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal title={`${defaultData.name} 配置`} iconSrc={'edit'} iconColor={'primary.600'}>
|
||||
<ModalBody w={'420px'}>
|
||||
<Box fontSize={'14px'} color={'myGray.900'}>
|
||||
{defaultData.intro}
|
||||
</Box>
|
||||
<Box h={'1px'} bg={'myGray.150'} my={4}></Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'14px'} color={'myGray.900'} fontWeight={'medium'}>
|
||||
{t('common:core.workflow.value')}
|
||||
</Box>
|
||||
<Input
|
||||
ml={8}
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('account_thirdParty:value_placeholder')}
|
||||
flex={1}
|
||||
{...register('value')}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mt={1} color={'myGray.500'} fontSize={'xs'}>
|
||||
{t('account_thirdParty:value_not_return_tip')}
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={loading} onClick={handleSubmit(onSubmit)}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(WorkflowVariableModal);
|
||||
263
projects/app/src/pages/account/thirdParty/index.tsx
vendored
Normal file
263
projects/app/src/pages/account/thirdParty/index.tsx
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
import AccountContainer from '../components/AccountContainer';
|
||||
import { Box, Flex, Grid, Progress, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useState, useMemo } from 'react';
|
||||
import WorkflowVariableModal from './components/WorkflowVariableModal';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { serviceSideProps } from '@fastgpt/web/common/system/nextjs';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { GET } from '@/web/common/api/request';
|
||||
import type { checkUsageResponse } from '@/pages/api/support/user/team/thirtdParty/checkUsage';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./components/OpenAIAccountModal'));
|
||||
|
||||
export type ThirdPartyAccountType = {
|
||||
name: string;
|
||||
icon: string;
|
||||
iconColor?: string;
|
||||
key?: string;
|
||||
intro: string;
|
||||
onClick?: () => void;
|
||||
isOpen?: boolean;
|
||||
active: boolean;
|
||||
usage?: {
|
||||
used: number;
|
||||
total: number;
|
||||
};
|
||||
};
|
||||
|
||||
const ThirdParty = () => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
const { isOpen: isOpenLaf, onClose: onCloseLaf, onOpen: onOpenLaf } = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
|
||||
const [workflowVariable, setWorkflowVariable] = useState<ThirdPartyAccountType>();
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const isOwner = userInfo?.team?.role === TeamMemberRoleEnum.owner;
|
||||
|
||||
const defaultAccountList: ThirdPartyAccountType[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: t('account_thirdParty:laf_account'),
|
||||
icon: 'support/account/laf',
|
||||
intro: t('common:support.user.Laf account intro'),
|
||||
onClick: onOpenLaf,
|
||||
isOpen: !!feConfigs?.lafEnv,
|
||||
active: !!userInfo?.team?.lafAccount?.appid
|
||||
},
|
||||
{
|
||||
name: t('account_thirdParty:openai_account_configuration'),
|
||||
iconColor: 'black',
|
||||
icon: 'common/openai',
|
||||
intro: t('account_thirdParty:open_api_notice'),
|
||||
onClick: onOpenOpenai,
|
||||
isOpen: feConfigs?.show_openai_account,
|
||||
active: userInfo?.team?.openaiAccount?.key !== undefined
|
||||
}
|
||||
],
|
||||
[
|
||||
feConfigs?.lafEnv,
|
||||
feConfigs?.show_openai_account,
|
||||
onOpenLaf,
|
||||
onOpenOpenai,
|
||||
t,
|
||||
userInfo?.team?.lafAccount?.appid,
|
||||
userInfo?.team?.openaiAccount?.key
|
||||
]
|
||||
);
|
||||
|
||||
const { data: workflowVariables = [], loading } = useRequest2(
|
||||
async (): Promise<ThirdPartyAccountType[]> => {
|
||||
return Promise.all(
|
||||
(feConfigs?.externalProviderWorkflowVariables || []).map(async (item) => {
|
||||
const usage = await (async () => {
|
||||
try {
|
||||
return await GET<checkUsageResponse>('/support/user/team/thirtdParty/checkUsage', {
|
||||
key: item.key
|
||||
});
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
})();
|
||||
|
||||
const account = {
|
||||
key: item.key,
|
||||
name: item.name,
|
||||
active: userInfo?.team?.externalWorkflowVariables?.[item.key] !== undefined,
|
||||
icon: 'common/variable',
|
||||
iconColor: 'primary.600',
|
||||
intro: item.intro || t('account_thirdParty:no_intro')
|
||||
};
|
||||
|
||||
return {
|
||||
...account,
|
||||
usage,
|
||||
onClick: () => setWorkflowVariable(account),
|
||||
isOpen: item.isOpen
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [
|
||||
feConfigs?.externalProviderWorkflowVariables,
|
||||
userInfo?.team?.externalWorkflowVariables
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
const accountList = useMemo(
|
||||
() => [...defaultAccountList, ...workflowVariables],
|
||||
[defaultAccountList, workflowVariables]
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountContainer>
|
||||
<MyBox isLoading={loading} px={[4, 8]} py={[4, 6]} bg={'white'} h={'full'}>
|
||||
<Flex>
|
||||
<MyIcon name={'common/thirdParty'} w={'24px'} color={'myGray.900'} />
|
||||
<Box ml={3}>
|
||||
<Box fontSize={'md'} color={'myGray.900'}>
|
||||
{t('account_thirdParty:third_party_account')}
|
||||
</Box>
|
||||
<Box fontSize={'mini'} color={'myGray.500'}>
|
||||
{t('account_thirdParty:third_party_account_desc')}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Grid
|
||||
gridTemplateColumns={[
|
||||
'1fr',
|
||||
'repeat(2,1fr)',
|
||||
'repeat(3,1fr)',
|
||||
'repeat(3,1fr)',
|
||||
'repeat(4,1fr)'
|
||||
]}
|
||||
gridGap={4}
|
||||
alignItems={'stretch'}
|
||||
mt={5}
|
||||
pb={5}
|
||||
>
|
||||
{accountList
|
||||
.filter((item) => item.isOpen)
|
||||
.map((item) => (
|
||||
<Flex
|
||||
key={item.name}
|
||||
flexDirection={'column'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
pt={4}
|
||||
px={5}
|
||||
borderRadius={'10px'}
|
||||
h={'146px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
borderColor: 'primary.600'
|
||||
}}
|
||||
onClick={
|
||||
isOwner
|
||||
? item.onClick
|
||||
: () =>
|
||||
toast({
|
||||
title: t('account_thirdParty:error.no_permission'),
|
||||
status: 'warning'
|
||||
})
|
||||
}
|
||||
position={'relative'}
|
||||
>
|
||||
<Flex>
|
||||
<MyIcon name={item.icon as any} w={'24px'} color={item.iconColor} />
|
||||
<Box ml={2} flex={1} fontWeight={'medium'} fontSize={'16px'} color={'myGray.900'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
<Box
|
||||
color={item.active ? 'green.600' : 'myGray.700'}
|
||||
bg={item.active ? 'green.50' : 'myGray.100'}
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius={'sm'}
|
||||
fontSize={'10px'}
|
||||
>
|
||||
{item.active
|
||||
? t('account_thirdParty:configured')
|
||||
: t('account_thirdParty:not_configured')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box
|
||||
className="textEllipsis2"
|
||||
mt={3}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.500'}
|
||||
lineHeight={'18px'}
|
||||
>
|
||||
{item.intro}
|
||||
</Box>
|
||||
<Box flex={1} />
|
||||
{item.active && item.usage && (
|
||||
<Box w={'full'} mb={4}>
|
||||
<Flex fontSize={'mini'} color={'myGray.500'}>
|
||||
<Box>{t('account_thirdParty:usage')}</Box>
|
||||
{item.usage?.total ? (
|
||||
<Box ml={1}>
|
||||
{item.usage.used}/{item.usage.total}
|
||||
</Box>
|
||||
) : (
|
||||
<Box ml={1}>{t('account_thirdParty:unavailable')}</Box>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={1} w={'full'}>
|
||||
<Progress
|
||||
size={'sm'}
|
||||
value={(item.usage.used / item.usage.total) * 100}
|
||||
colorScheme={'blue'}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'low'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
</MyBox>
|
||||
|
||||
{isOpenLaf && userInfo && (
|
||||
<LafAccountModal defaultData={userInfo?.team?.lafAccount} onClose={onCloseLaf} />
|
||||
)}
|
||||
{isOpenOpenai && userInfo && (
|
||||
<OpenAIAccountModal defaultData={userInfo?.team?.openaiAccount} onClose={onCloseOpenai} />
|
||||
)}
|
||||
{workflowVariable && (
|
||||
<WorkflowVariableModal
|
||||
defaultData={workflowVariable}
|
||||
onClose={() => setWorkflowVariable(undefined)}
|
||||
/>
|
||||
)}
|
||||
</AccountContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
...(await serviceSideProps(content, ['account', 'account_thirdParty']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ThirdParty;
|
||||
39
projects/app/src/pages/api/admin/initv4817.ts
Normal file
39
projects/app/src/pages/api/admin/initv4817.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await authCert({ req, authRoot: true });
|
||||
|
||||
const users = await MongoUser.find(
|
||||
{ openaiAccount: { $exists: true, $ne: null } },
|
||||
'_id openaiAccount'
|
||||
);
|
||||
|
||||
console.log(`共 ${users.length} 个用户需要更新`);
|
||||
let count = 0;
|
||||
for (const user of users) {
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoTeam.updateOne(
|
||||
{ ownerId: user._id },
|
||||
{
|
||||
$set: { openaiAccount: (user as any).openaiAccount }
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
user.openaiAccount = undefined;
|
||||
await user.save({ session });
|
||||
});
|
||||
count++;
|
||||
console.log(`已更新 ${count} 个用户`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -94,6 +94,7 @@ async function initHttp(teamId?: string): Promise<any> {
|
||||
await MongoAppVersion.create(
|
||||
[
|
||||
{
|
||||
tmbId: plugin.tmbId,
|
||||
appId: newPluginId,
|
||||
nodes: item.modules,
|
||||
edges: item.edges
|
||||
@@ -166,6 +167,7 @@ async function initPlugin(teamId?: string): Promise<any> {
|
||||
await MongoAppVersion.create(
|
||||
[
|
||||
{
|
||||
tmbId: plugin.tmbId,
|
||||
appId: newPluginId,
|
||||
nodes: plugin.modules,
|
||||
edges: plugin.edges
|
||||
|
||||
@@ -40,6 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
res.setHeader('Content-Type', `${file.contentType}; charset=${encoding}`);
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
||||
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(file.filename)}"`);
|
||||
res.setHeader('Content-Length', file.length);
|
||||
|
||||
stream.pipe(res);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
res.setHeader('Content-Type', `${file.contentType}; charset=${encoding}`);
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
||||
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(filename)}"`);
|
||||
res.setHeader('Content-Length', file.length);
|
||||
|
||||
stream.pipe(res);
|
||||
|
||||
|
||||
@@ -45,8 +45,9 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
const tmb = await MongoTeamMember.findById({ _id: tmbId });
|
||||
const user = await MongoUser.findById({ _id: tmb?.userId });
|
||||
const tmb = await MongoTeamMember.findById({ _id: tmbId }, 'userId').populate<{
|
||||
user: { avatar: string; username: string };
|
||||
}>('user', 'avatar username');
|
||||
|
||||
// 创建app
|
||||
const appId = await onCreateApp({
|
||||
@@ -59,8 +60,8 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
chatConfig,
|
||||
teamId,
|
||||
tmbId,
|
||||
userAvatar: user?.avatar,
|
||||
username: user?.username
|
||||
userAvatar: tmb?.user?.avatar,
|
||||
username: tmb?.user?.username
|
||||
});
|
||||
|
||||
pushTrack.createApp({
|
||||
@@ -132,6 +133,7 @@ export const onCreateApp = async ({
|
||||
await MongoAppVersion.create(
|
||||
[
|
||||
{
|
||||
tmbId,
|
||||
appId,
|
||||
nodes: modules,
|
||||
edges,
|
||||
|
||||
@@ -45,7 +45,8 @@ async function handler(
|
||||
currentCost: plugin.currentCost,
|
||||
hasTokenFee: plugin.hasTokenFee,
|
||||
author: plugin.author,
|
||||
instructions: plugin.userGuide
|
||||
instructions: plugin.userGuide,
|
||||
courseUrl: plugin.courseUrl
|
||||
}))
|
||||
.filter((item) => {
|
||||
if (searchKey) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { TemplateMarketItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { getTemplateMarketItemDetail } from '@/service/core/app/template';
|
||||
import { AppTemplateSchemaType } from '@fastgpt/global/core/app/type';
|
||||
import { getAppTemplatesAndLoadThem } from '@fastgpt/templates/register';
|
||||
|
||||
type Props = {
|
||||
templateId: string;
|
||||
@@ -11,11 +11,13 @@ type Props = {
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<TemplateMarketItemType | undefined> {
|
||||
): Promise<AppTemplateSchemaType | undefined> {
|
||||
await authCert({ req, authToken: true });
|
||||
const { templateId } = req.query as Props;
|
||||
|
||||
return getTemplateMarketItemDetail(templateId);
|
||||
const templateMarketItems: AppTemplateSchemaType[] = await getAppTemplatesAndLoadThem();
|
||||
|
||||
return templateMarketItems.find((item) => item.templateId === templateId);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,16 +1,53 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { TemplateMarketListItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { getTemplateMarketItemList } from '@/service/core/app/template';
|
||||
import { getAppTemplatesAndLoadThem } from '@fastgpt/templates/register';
|
||||
import { AppTemplateSchemaType } from '@fastgpt/global/core/app/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
export type ListParams = {
|
||||
isQuickTemplate?: boolean;
|
||||
type?: AppTypeEnum | 'all';
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
req: ApiRequestProps<ListParams>,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<TemplateMarketListItemType[]> {
|
||||
): Promise<AppTemplateSchemaType[]> {
|
||||
await authCert({ req, authToken: true });
|
||||
|
||||
return getTemplateMarketItemList();
|
||||
const { isQuickTemplate = false, type = 'all' } = req.query;
|
||||
|
||||
const templateMarketItems = await getAppTemplatesAndLoadThem();
|
||||
|
||||
let filteredItems = templateMarketItems.filter((item) => {
|
||||
if (!item.isActive) return false;
|
||||
if (type === 'all') return true;
|
||||
return item.type === type;
|
||||
});
|
||||
|
||||
if (isQuickTemplate) {
|
||||
if (filteredItems.some((item) => item.isQuickTemplate !== undefined)) {
|
||||
filteredItems = filteredItems.filter((item) => item.isQuickTemplate);
|
||||
} else {
|
||||
filteredItems = filteredItems.slice(0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredItems.map((item) => {
|
||||
return {
|
||||
templateId: item.templateId,
|
||||
name: item.name,
|
||||
intro: item.intro,
|
||||
avatar: item.avatar,
|
||||
tags: item.tags,
|
||||
type: item.type,
|
||||
author: item.author,
|
||||
userGuide: item.userGuide,
|
||||
workflow: {}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -11,12 +11,9 @@ import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<PostPublishAppProps>,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<{}> {
|
||||
async function handler(req: ApiRequestProps<PostPublishAppProps>, res: NextApiResponse<any>) {
|
||||
const { appId } = req.query as { appId: string };
|
||||
const { nodes = [], edges = [], chatConfig, isPublish, versionName } = req.body;
|
||||
const { nodes = [], edges = [], chatConfig, isPublish, versionName, autoSave } = req.body;
|
||||
|
||||
const { app, tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
@@ -25,6 +22,15 @@ async function handler(
|
||||
isPlugin: app.type === AppTypeEnum.plugin
|
||||
});
|
||||
|
||||
if (autoSave) {
|
||||
return MongoApp.findByIdAndUpdate(appId, {
|
||||
modules: formatNodes,
|
||||
edges,
|
||||
chatConfig,
|
||||
updateTime: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
// create version histories
|
||||
const [{ _id }] = await MongoAppVersion.create(
|
||||
@@ -70,8 +76,6 @@ async function handler(
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'
|
||||
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@fastgpt/service/support/permission/auth/team';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import {
|
||||
concatHistories,
|
||||
@@ -115,7 +115,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
})();
|
||||
|
||||
const limit = getMaxHistoryLimitFromNodes(nodes);
|
||||
const [{ histories }, chatDetail, { user }] = await Promise.all([
|
||||
const [{ histories }, chatDetail, { timezone, externalProvider }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
@@ -158,7 +158,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'test',
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
uid: tmbId,
|
||||
|
||||
runningAppInfo: {
|
||||
@@ -204,7 +205,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
|
||||
|
||||
const newTitle = isPlugin
|
||||
? variables.cTime ?? getSystemTime(user.timezone)
|
||||
? variables.cTime ?? getSystemTime(timezone)
|
||||
: getChatTitleFromChatMessage(userQuestion);
|
||||
|
||||
const aiResponse: AIChatItemType & { dataId?: string } = {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
|
||||
@@ -20,7 +21,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
// auth app permission
|
||||
const [tmb, chat, app] = await Promise.all([
|
||||
MongoTeamMember.findById(outLinkConfig.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoTeamMember.findById(outLinkConfig.tmbId, '_id userId')
|
||||
.populate<{ user: UserModelSchema }>('user', 'avatar')
|
||||
.lean(),
|
||||
MongoChat.findOne({ appId, chatId, shareId }).lean(),
|
||||
MongoApp.findById(appId).lean()
|
||||
]);
|
||||
@@ -45,8 +48,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
chatId,
|
||||
appId: app._id,
|
||||
title: chat?.title,
|
||||
//@ts-ignore
|
||||
userAvatar: tmb?.userId?.avatar,
|
||||
userAvatar: tmb?.user?.avatar,
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
|
||||
@@ -66,9 +66,9 @@ async function handler(
|
||||
return {
|
||||
type: DatasetSourceReadTypeEnum.apiFile,
|
||||
sourceId: collection.apiFileId,
|
||||
apiServer: collection.datasetId.apiServer,
|
||||
feishuServer: collection.datasetId.feishuServer,
|
||||
yuqueServer: collection.datasetId.yuqueServer
|
||||
apiServer: collection.dataset.apiServer,
|
||||
feishuServer: collection.dataset.feishuServer,
|
||||
yuqueServer: collection.dataset.yuqueServer
|
||||
};
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.externalFile) {
|
||||
@@ -90,12 +90,12 @@ async function handler(
|
||||
|
||||
return mongoSessionRun(async (session) => {
|
||||
const { collectionId } = await createCollectionAndInsertData({
|
||||
dataset: collection.datasetId,
|
||||
dataset: collection.dataset,
|
||||
rawText,
|
||||
createCollectionParams: {
|
||||
teamId: collection.teamId,
|
||||
tmbId: collection.tmbId,
|
||||
datasetId: collection.datasetId._id,
|
||||
datasetId: collection.dataset._id,
|
||||
name: collection.name,
|
||||
type: collection.type,
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ async function handler(req: NextApiRequest) {
|
||||
// find all delete id
|
||||
const collections = await findCollectionAndChild({
|
||||
teamId,
|
||||
datasetId: collection.datasetId._id,
|
||||
datasetId: collection.datasetId,
|
||||
collectionId,
|
||||
fields: '_id teamId datasetId fileId metadata'
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ async function handler(req: NextApiRequest): Promise<DatasetCollectionItemType>
|
||||
...collection,
|
||||
...getCollectionSourceData(collection),
|
||||
tags: await collectionTagsToTagLabel({
|
||||
datasetId: collection.datasetId._id,
|
||||
datasetId: collection.datasetId,
|
||||
tags: collection.tags
|
||||
}),
|
||||
permission,
|
||||
|
||||
@@ -148,9 +148,9 @@ async function handler(
|
||||
return collection.rawLink;
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.apiFile && collection.apiFileId) {
|
||||
const apiServer = collection.datasetId.apiServer;
|
||||
const feishuServer = collection.datasetId.feishuServer;
|
||||
const yuqueServer = collection.datasetId.yuqueServer;
|
||||
const apiServer = collection.dataset.apiServer;
|
||||
const feishuServer = collection.dataset.feishuServer;
|
||||
const yuqueServer = collection.dataset.yuqueServer;
|
||||
|
||||
if (apiServer) {
|
||||
return useApiDatasetRequest({ apiServer }).getFilePreviewUrl({
|
||||
@@ -170,11 +170,8 @@ async function handler(
|
||||
return '';
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.externalFile) {
|
||||
if (collection.externalFileId && collection.datasetId.externalReadUrl) {
|
||||
return collection.datasetId.externalReadUrl.replace(
|
||||
'{{fileId}}',
|
||||
collection.externalFileId
|
||||
);
|
||||
if (collection.externalFileId && collection.dataset.externalReadUrl) {
|
||||
return collection.dataset.externalReadUrl.replace('{{fileId}}', collection.externalFileId);
|
||||
}
|
||||
if (collection.externalFileUrl) {
|
||||
return collection.externalFileUrl;
|
||||
|
||||
@@ -100,7 +100,7 @@ async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
|
||||
const collectionTags = await createOrGetCollectionTags({
|
||||
tags,
|
||||
teamId,
|
||||
datasetId: collection.datasetId._id,
|
||||
datasetId: collection.datasetId,
|
||||
session
|
||||
});
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ async function handler(req: NextApiRequest) {
|
||||
// auth collection and get dataset
|
||||
const [
|
||||
{
|
||||
datasetId: { _id: datasetId, vectorModel }
|
||||
dataset: { _id: datasetId, vectorModel }
|
||||
}
|
||||
] = await Promise.all([getCollectionWithDataset(collectionId)]);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ async function handler(
|
||||
const queryReg = new RegExp(`${replaceRegChars(searchText)}`, 'i');
|
||||
const match = {
|
||||
teamId,
|
||||
datasetId: collection.datasetId._id,
|
||||
datasetId: collection.datasetId,
|
||||
collectionId,
|
||||
...(searchText.trim()
|
||||
? {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/* push data to training queue */
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type {
|
||||
PushDatasetDataProps,
|
||||
PushDatasetDataResponse
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import type { PushDatasetDataProps } from '@fastgpt/global/core/dataset/api.d';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
|
||||
@@ -42,9 +39,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
...body,
|
||||
teamId,
|
||||
tmbId,
|
||||
datasetId: collection.datasetId._id,
|
||||
agentModel: collection.datasetId.agentModel,
|
||||
vectorModel: collection.datasetId.vectorModel
|
||||
datasetId: collection.datasetId,
|
||||
agentModel: collection.dataset.agentModel,
|
||||
vectorModel: collection.dataset.vectorModel
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ async function handler(req: ApiRequestProps<UpdateDatasetDataProps>) {
|
||||
// auth data permission
|
||||
const {
|
||||
collection: {
|
||||
datasetId: { vectorModel }
|
||||
dataset: { vectorModel }
|
||||
},
|
||||
teamId,
|
||||
tmbId
|
||||
|
||||
@@ -32,7 +32,7 @@ async function handler(
|
||||
const queryReg = new RegExp(`${replaceRegChars(searchText)}`, 'i');
|
||||
const match = {
|
||||
teamId,
|
||||
datasetId: collection.datasetId._id,
|
||||
datasetId: collection.datasetId,
|
||||
collectionId,
|
||||
...(searchText.trim()
|
||||
? {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
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 { getUserChatInfoAndAuthTeamPoints } from '@fastgpt/service/support/permission/auth/team';
|
||||
import { PostWorkflowDebugProps, PostWorkflowDebugResponse } from '@/global/core/workflow/api';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
@@ -37,7 +37,7 @@ async function handler(
|
||||
]);
|
||||
|
||||
// auth balance
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
|
||||
/* start process */
|
||||
const { flowUsages, flowResponses, debugResponse, newVariables } = await dispatchWorkFlow({
|
||||
@@ -50,7 +50,8 @@ async function handler(
|
||||
tmbId
|
||||
},
|
||||
uid: tmbId,
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
runtimeNodes: nodes,
|
||||
runtimeEdges: edges,
|
||||
variables,
|
||||
|
||||
@@ -2,14 +2,39 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserDetail } from '@fastgpt/service/support/user/controller';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { UserType } from '@fastgpt/global/support/user/type';
|
||||
|
||||
export type TokenLoginQuery = {};
|
||||
export type TokenLoginBody = {};
|
||||
export type TokenLoginResponse = {};
|
||||
export type TokenLoginResponse = UserType;
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<TokenLoginBody, TokenLoginQuery>,
|
||||
_res: ApiResponseType<any>
|
||||
): Promise<TokenLoginResponse> {
|
||||
const { tmbId } = await authCert({ req, authToken: true });
|
||||
return getUserDetail({ tmbId });
|
||||
const user = await getUserDetail({ tmbId });
|
||||
|
||||
// Remove sensitive information
|
||||
if (user.team.lafAccount) {
|
||||
user.team.lafAccount = {
|
||||
appid: user.team.lafAccount.appid,
|
||||
token: '',
|
||||
pat: ''
|
||||
};
|
||||
}
|
||||
if (user.team.openaiAccount) {
|
||||
user.team.openaiAccount = {
|
||||
key: '',
|
||||
baseUrl: user.team.openaiAccount.baseUrl
|
||||
};
|
||||
}
|
||||
if (user.team.externalWorkflowVariables) {
|
||||
user.team.externalWorkflowVariables = Object.fromEntries(
|
||||
Object.entries(user.team.externalWorkflowVariables).map(([key, value]) => [key, ''])
|
||||
);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { getAIApi, openaiBaseUrl } from '@fastgpt/service/core/ai/config';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
|
||||
/* update user info */
|
||||
@@ -14,7 +13,7 @@ async function handler(
|
||||
req: ApiRequestProps<UserAccountUpdateBody, UserAccountUpdateQuery>,
|
||||
_res: ApiResponseType<any>
|
||||
): Promise<UserAccountUpdateResponse> {
|
||||
const { avatar, timezone, openaiAccount, lafAccount } = req.body;
|
||||
const { avatar, timezone } = req.body;
|
||||
|
||||
const { tmbId } = await authCert({ req, authToken: true });
|
||||
const tmb = await MongoTeamMember.findById(tmbId);
|
||||
@@ -22,25 +21,6 @@ async function handler(
|
||||
throw new Error('can not find it');
|
||||
}
|
||||
const userId = tmb.userId;
|
||||
// auth key
|
||||
if (openaiAccount?.key) {
|
||||
console.log('auth user openai key', openaiAccount?.key);
|
||||
const baseUrl = openaiAccount?.baseUrl || openaiBaseUrl;
|
||||
openaiAccount.baseUrl = baseUrl;
|
||||
|
||||
const ai = getAIApi({
|
||||
userKey: openaiAccount
|
||||
});
|
||||
|
||||
const response = await ai.chat.completions.create({
|
||||
model: 'gpt-4o-mini',
|
||||
max_tokens: 1,
|
||||
messages: [{ role: 'user', content: 'hi' }]
|
||||
});
|
||||
if (response?.choices?.[0]?.message?.content === undefined) {
|
||||
throw new Error('Key response is empty');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新对应的记录
|
||||
await MongoUser.updateOne(
|
||||
@@ -49,9 +29,7 @@ async function handler(
|
||||
},
|
||||
{
|
||||
...(avatar && { avatar }),
|
||||
...(timezone && { timezone }),
|
||||
openaiAccount: openaiAccount?.key ? openaiAccount : null,
|
||||
lafAccount: lafAccount?.token ? lafAccount : null
|
||||
...(timezone && { timezone })
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import axios from 'axios';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
|
||||
export type checkUsageQuery = { key: string };
|
||||
|
||||
export type checkUsageBody = {};
|
||||
|
||||
export type checkUsageResponse =
|
||||
| {
|
||||
total: number;
|
||||
used: number;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<checkUsageBody, checkUsageQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<checkUsageResponse> {
|
||||
try {
|
||||
const { key } = req.query;
|
||||
|
||||
const { tmb } = await authUserPer({ req, authToken: true, per: ReadPermissionVal });
|
||||
|
||||
const url = global.feConfigs.externalProviderWorkflowVariables?.find(
|
||||
(item) => item.key === key
|
||||
)?.url;
|
||||
if (!url || !tmb.externalWorkflowVariables?.[key]) return undefined;
|
||||
|
||||
const { data } = await axios.get<checkUsageResponse>(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tmb.externalWorkflowVariables[key]}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!data) return undefined;
|
||||
|
||||
return {
|
||||
total: data.total || 0,
|
||||
used: data.used || 0
|
||||
};
|
||||
} catch (error) {
|
||||
addLog.debug('checkUsage error', { error });
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
removeEmptyUserInput
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@fastgpt/service/support/permission/auth/team';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||
@@ -59,6 +59,7 @@ import { rewriteNodeOutputByHistories } from '@fastgpt/global/core/workflow/runt
|
||||
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
|
||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { ExternalProviderType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
type FastGptWebChatProps = {
|
||||
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
|
||||
@@ -80,7 +81,8 @@ export type Props = ChatCompletionCreateParams &
|
||||
type AuthResponseType = {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
user: UserModelSchema;
|
||||
timezone: string;
|
||||
externalProvider: ExternalProviderType;
|
||||
app: AppSchema;
|
||||
responseDetail?: boolean;
|
||||
showNodeStatus?: boolean;
|
||||
@@ -154,7 +156,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
app,
|
||||
responseDetail,
|
||||
authType,
|
||||
@@ -269,7 +272,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'chat',
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
@@ -314,7 +318,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
|
||||
|
||||
const newTitle = isPlugin
|
||||
? variables.cTime ?? getSystemTime(user.timezone)
|
||||
? variables.cTime ?? getSystemTime(timezone)
|
||||
: getChatTitleFromChatMessage(userQuestion);
|
||||
|
||||
const aiResponse: AIChatItemType & { dataId?: string } = {
|
||||
@@ -459,8 +463,18 @@ const authShareChat = async ({
|
||||
shareId: string;
|
||||
chatId?: string;
|
||||
}): Promise<AuthResponseType> => {
|
||||
const { teamId, tmbId, user, appId, authType, responseDetail, showNodeStatus, uid, sourceName } =
|
||||
await authOutLinkChatStart(data);
|
||||
const {
|
||||
teamId,
|
||||
tmbId,
|
||||
timezone,
|
||||
externalProvider,
|
||||
appId,
|
||||
authType,
|
||||
responseDetail,
|
||||
showNodeStatus,
|
||||
uid,
|
||||
sourceName
|
||||
} = await authOutLinkChatStart(data);
|
||||
const app = await MongoApp.findById(appId).lean();
|
||||
|
||||
if (!app) {
|
||||
@@ -477,8 +491,9 @@ const authShareChat = async ({
|
||||
sourceName,
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
timezone,
|
||||
externalProvider,
|
||||
apikey: '',
|
||||
authType,
|
||||
responseAllData: false,
|
||||
@@ -508,7 +523,7 @@ const authTeamSpaceChat = async ({
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
const [chat, { user }] = await Promise.all([
|
||||
const [chat, { timezone, externalProvider }] = await Promise.all([
|
||||
MongoChat.findOne({ appId, chatId }).lean(),
|
||||
getUserChatInfoAndAuthTeamPoints(app.tmbId)
|
||||
]);
|
||||
@@ -520,8 +535,9 @@ const authTeamSpaceChat = async ({
|
||||
return {
|
||||
teamId,
|
||||
tmbId: app.tmbId,
|
||||
user,
|
||||
app,
|
||||
timezone,
|
||||
externalProvider,
|
||||
authType: AuthUserTypeEnum.outLink,
|
||||
apikey: '',
|
||||
responseAllData: false,
|
||||
@@ -588,7 +604,7 @@ const authHeaderRequest = async ({
|
||||
}
|
||||
})();
|
||||
|
||||
const [{ user }, chat] = await Promise.all([
|
||||
const [{ timezone, externalProvider }, chat] = await Promise.all([
|
||||
getUserChatInfoAndAuthTeamPoints(tmbId),
|
||||
MongoChat.findOne({ appId, chatId }).lean()
|
||||
]);
|
||||
@@ -605,7 +621,8 @@ const authHeaderRequest = async ({
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
app,
|
||||
apikey,
|
||||
authType,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
Button,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -25,20 +24,18 @@ import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/ut
|
||||
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
|
||||
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
|
||||
import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
|
||||
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
import { workflowSystemVariables } from '@/web/core/app/utils';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
|
||||
import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
import ToolSelect from './components/ToolSelect';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
|
||||
const ToolSelectModal = dynamic(() => import('./components/ToolSelectModal'));
|
||||
const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'));
|
||||
const QGConfig = dynamic(() => import('@/components/core/app/QGConfig'));
|
||||
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'));
|
||||
@@ -70,7 +67,6 @@ const EditForm = ({
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
@@ -95,11 +91,6 @@ const EditForm = ({
|
||||
onOpen: onOpenDatasetParams,
|
||||
onClose: onCloseDatasetParams
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenToolsSelect,
|
||||
onOpen: onOpenToolsSelect,
|
||||
onClose: onCloseToolsSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const formatVariables = useMemo(
|
||||
() =>
|
||||
@@ -278,67 +269,7 @@ const EditForm = ({
|
||||
|
||||
{/* tool choice */}
|
||||
<Box {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<MyIcon name={'core/app/toolCall'} w={'20px'} />
|
||||
<FormLabel ml={2}>{appT('plugin_dispatch')}</FormLabel>
|
||||
<QuestionTip ml={1} label={appT('plugin_dispatch_tip')} />
|
||||
</Flex>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
mr={'-5px'}
|
||||
size={'sm'}
|
||||
fontSize={'sm'}
|
||||
onClick={onOpenToolsSelect}
|
||||
>
|
||||
{t('common:common.Choose')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Grid
|
||||
mt={appForm.selectedTools.length > 0 ? 2 : 0}
|
||||
gridTemplateColumns={'repeat(2, minmax(0, 1fr))'}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{appForm.selectedTools.map((item) => (
|
||||
<MyTooltip key={item.id} label={item.intro}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2.5}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
_hover={{
|
||||
...hoverDeleteStyles,
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
<DeleteIcon
|
||||
onClick={() => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
<ToolSelect appForm={appForm} setAppForm={setAppForm} />
|
||||
</Box>
|
||||
|
||||
{/* File select */}
|
||||
@@ -494,24 +425,6 @@ const EditForm = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenToolsSelect && (
|
||||
<ToolSelectModal
|
||||
selectedTools={appForm.selectedTools}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: [...state.selectedTools, e]
|
||||
}));
|
||||
}}
|
||||
onRemoveTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((item) => item.pluginId !== e.id)
|
||||
}));
|
||||
}}
|
||||
onClose={onCloseToolsSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -76,10 +76,12 @@ const Header = ({
|
||||
const { runAsync: onClickSave, loading } = useRequest2(
|
||||
async ({
|
||||
isPublish,
|
||||
versionName = formatTime2YMDHMS(new Date())
|
||||
versionName = formatTime2YMDHMS(new Date()),
|
||||
autoSave
|
||||
}: {
|
||||
isPublish?: boolean;
|
||||
versionName?: string;
|
||||
autoSave?: boolean;
|
||||
}) => {
|
||||
const { nodes, edges } = form2AppWorkflow(appForm, t);
|
||||
await onSaveApp({
|
||||
@@ -87,7 +89,8 @@ const Header = ({
|
||||
edges,
|
||||
chatConfig: appForm.chatConfig,
|
||||
isPublish,
|
||||
versionName
|
||||
versionName,
|
||||
autoSave
|
||||
});
|
||||
setPast((prevPast) =>
|
||||
prevPast.map((item, index) =>
|
||||
@@ -157,7 +160,7 @@ const Header = ({
|
||||
if (isSaved) return;
|
||||
try {
|
||||
console.log('Leave auto save');
|
||||
return onClickSave({ isPublish: false, versionName: t('app:auto_save') });
|
||||
return onClickSave({ isPublish: false, autoSave: true });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Button, HStack, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { childAppSystemKey } from './ToolSelectModal';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import RenderPluginInput from '@/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||
|
||||
const ConfigToolModal = ({
|
||||
configTool,
|
||||
onCloseConfigTool,
|
||||
onAddTool
|
||||
}: {
|
||||
configTool: AppSimpleEditFormType['selectedTools'][number];
|
||||
onCloseConfigTool: () => void;
|
||||
onAddTool: (tool: AppSimpleEditFormType['selectedTools'][number]) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
defaultValues: configTool
|
||||
? configTool.inputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.value || input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
: {}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
isCentered
|
||||
title={t('common:core.app.ToolCall.Parameter setting')}
|
||||
iconSrc="core/app/toolCall"
|
||||
overflow={'auto'}
|
||||
>
|
||||
<ModalBody>
|
||||
<HStack mb={4} spacing={1} fontSize={'sm'}>
|
||||
<MyIcon name={'common/info'} w={'1.25rem'} />
|
||||
<Box flex={1}>{t('app:tool_input_param_tip')}</Box>
|
||||
{!!(configTool?.courseUrl || configTool?.userGuide) && (
|
||||
<UseGuideModal
|
||||
title={configTool?.name}
|
||||
iconSrc={configTool?.avatar}
|
||||
text={configTool?.userGuide}
|
||||
link={configTool?.courseUrl}
|
||||
>
|
||||
{({ onClick }) => (
|
||||
<Box cursor={'pointer'} color={'primary.500'} onClick={onClick}>
|
||||
{t('app:workflow.Input guide')}
|
||||
</Box>
|
||||
)}
|
||||
</UseGuideModal>
|
||||
)}
|
||||
</HStack>
|
||||
{configTool.inputs
|
||||
.filter(
|
||||
(input) =>
|
||||
!input.toolDescription &&
|
||||
!childAppSystemKey.includes(input.key) &&
|
||||
!input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) &&
|
||||
!input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
|
||||
)
|
||||
.map((input) => {
|
||||
return (
|
||||
<Controller
|
||||
key={input.key}
|
||||
control={control}
|
||||
name={input.key}
|
||||
rules={{
|
||||
validate: (value) => {
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return value !== undefined;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
}}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<RenderPluginInput
|
||||
value={value}
|
||||
isInvalid={errors && Object.keys(errors).includes(input.key)}
|
||||
onChange={onChange}
|
||||
input={input}
|
||||
setUploading={() => {}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ModalBody>
|
||||
<ModalFooter gap={6}>
|
||||
<Button onClick={onCloseConfigTool} variant={'whiteBase'}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'primary'}
|
||||
onClick={handleSubmit((data) => {
|
||||
onAddTool({
|
||||
...configTool,
|
||||
inputs: configTool.inputs.map((input) => ({
|
||||
...input,
|
||||
value: data[input.key] ?? input.value
|
||||
}))
|
||||
});
|
||||
onCloseConfigTool();
|
||||
})}
|
||||
>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ConfigToolModal);
|
||||
@@ -0,0 +1,157 @@
|
||||
import { Box, Button, Flex, Grid, useDisclosure } from '@chakra-ui/react';
|
||||
import React, { useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { theme } from '@fastgpt/web/styles/theme';
|
||||
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
|
||||
import ToolSelectModal, { childAppSystemKey } from './ToolSelectModal';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
|
||||
const ToolSelect = ({
|
||||
appForm,
|
||||
setAppForm
|
||||
}: {
|
||||
appForm: AppSimpleEditFormType;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [configTool, setConfigTool] = useState<
|
||||
AppSimpleEditFormType['selectedTools'][number] | null
|
||||
>(null);
|
||||
|
||||
const {
|
||||
isOpen: isOpenToolsSelect,
|
||||
onOpen: onOpenToolsSelect,
|
||||
onClose: onCloseToolsSelect
|
||||
} = useDisclosure();
|
||||
const selectedModel = getWebLLMModel(appForm.aiSettings.model);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<MyIcon name={'core/app/toolCall'} w={'20px'} />
|
||||
<FormLabel ml={2}>{t('common:core.app.Tool call')}</FormLabel>
|
||||
<QuestionTip ml={1} label={t('app:plugin_dispatch_tip')} />
|
||||
</Flex>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
mr={'-5px'}
|
||||
size={'sm'}
|
||||
fontSize={'sm'}
|
||||
onClick={onOpenToolsSelect}
|
||||
>
|
||||
{t('common:common.Choose')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Grid
|
||||
mt={appForm.selectedTools.length > 0 ? 2 : 0}
|
||||
gridTemplateColumns={'repeat(2, minmax(0, 1fr))'}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{appForm.selectedTools.map((item) => (
|
||||
<MyTooltip key={item.id} label={item.intro}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2.5}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
_hover={{
|
||||
...hoverDeleteStyles,
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
if (
|
||||
item.inputs
|
||||
.filter((input) => !childAppSystemKey.includes(input.key))
|
||||
.every(
|
||||
(input) =>
|
||||
input.toolDescription ||
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) ||
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setConfigTool(item);
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'1.5rem'} h={'1.5rem'} borderRadius={'sm'} />
|
||||
<Box
|
||||
ml={2}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className={'textEllipsis'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
<DeleteIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setAppForm((state: AppSimpleEditFormType) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id)
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{isOpenToolsSelect && (
|
||||
<ToolSelectModal
|
||||
selectedTools={appForm.selectedTools}
|
||||
chatConfig={appForm.chatConfig}
|
||||
selectedModel={selectedModel}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: [...state.selectedTools, e]
|
||||
}));
|
||||
}}
|
||||
onRemoveTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((item) => item.pluginId !== e.id)
|
||||
}));
|
||||
}}
|
||||
onClose={onCloseToolsSelect}
|
||||
/>
|
||||
)}
|
||||
{configTool && (
|
||||
<ConfigToolModal
|
||||
configTool={configTool}
|
||||
onCloseConfigTool={() => setConfigTool(null)}
|
||||
onAddTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.map((item) =>
|
||||
item.pluginId === configTool.pluginId ? e : item
|
||||
)
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ToolSelect);
|
||||
@@ -1,54 +1,63 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box,
|
||||
Button,
|
||||
css,
|
||||
Flex,
|
||||
HStack,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
Grid
|
||||
} from '@chakra-ui/react';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import {
|
||||
FlowNodeTemplateType,
|
||||
NodeTemplateListItemType
|
||||
NodeTemplateListItemType,
|
||||
NodeTemplateListType
|
||||
} from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
getPluginGroups,
|
||||
getPreviewPluginNode,
|
||||
getSystemPlugTemplates,
|
||||
getSystemPluginPaths
|
||||
} from '@/web/core/app/api/plugin';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { getTeamPlugTemplates } from '@/web/core/app/api/plugin';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import FolderPath from '@/components/common/folder/Path';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import CostTooltip from '@/components/core/app/plugin/CostTooltip';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import RenderPluginInput from '@/components/core/chat/ChatContainer/PluginRunBox/components/renderPluginInput';
|
||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../../context';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import MyAvatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { workflowStartNodeId } from '@/web/core/app/constants';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
|
||||
type Props = {
|
||||
selectedTools: FlowNodeTemplateType[];
|
||||
chatConfig: AppSimpleEditFormType['chatConfig'];
|
||||
selectedModel: LLMModelItemType;
|
||||
onAddTool: (tool: FlowNodeTemplateType) => void;
|
||||
onRemoveTool: (tool: NodeTemplateListItemType) => void;
|
||||
};
|
||||
|
||||
const childAppSystemKey: string[] = [
|
||||
export const childAppSystemKey: string[] = [
|
||||
NodeInputKeyEnum.forbidStream,
|
||||
NodeInputKeyEnum.history,
|
||||
NodeInputKeyEnum.historyMaxAmount,
|
||||
@@ -64,7 +73,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const [templateType, setTemplateType] = useState(TemplateTypeEnum.teamPlugin);
|
||||
const [templateType, setTemplateType] = useState(TemplateTypeEnum.systemPlugin);
|
||||
const [parentId, setParentId] = useState<ParentIdType>('');
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
@@ -142,14 +151,14 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{
|
||||
icon: 'core/modules/teamPlugin',
|
||||
label: t('common:core.app.ToolCall.Team'),
|
||||
value: TemplateTypeEnum.teamPlugin
|
||||
icon: 'phoneTabbar/tool',
|
||||
label: t('common:navbar.Toolkit'),
|
||||
value: TemplateTypeEnum.systemPlugin
|
||||
},
|
||||
{
|
||||
icon: 'core/modules/systemPlugin',
|
||||
label: t('common:core.app.ToolCall.System'),
|
||||
value: TemplateTypeEnum.systemPlugin
|
||||
icon: 'core/modules/teamPlugin',
|
||||
label: t('common:core.module.template.Team app'),
|
||||
value: TemplateTypeEnum.teamPlugin
|
||||
}
|
||||
]}
|
||||
py={'5px'}
|
||||
@@ -162,35 +171,29 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
|
||||
})
|
||||
}
|
||||
/>
|
||||
<InputGroup w={300}>
|
||||
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
|
||||
<MyIcon name={'common/searchLight'} w={'16px'} color={'myGray.500'} ml={3} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('common:plugin.Search plugin')}
|
||||
<Box w={300}>
|
||||
<SearchInput
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
placeholder={
|
||||
templateType === TemplateTypeEnum.systemPlugin
|
||||
? t('common:plugin.Search plugin')
|
||||
: t('app:search_app')
|
||||
}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* route components */}
|
||||
{!searchKey && parentId && (
|
||||
<Flex mt={2} px={[3, 6]}>
|
||||
<FolderPath
|
||||
paths={paths}
|
||||
FirstPathDom={null}
|
||||
onClick={() => {
|
||||
onUpdateParentId(null);
|
||||
}}
|
||||
/>
|
||||
<FolderPath paths={paths} FirstPathDom={null} onClick={() => onUpdateParentId(null)} />
|
||||
</Flex>
|
||||
)}
|
||||
<MyBox isLoading={isLoading} mt={2} px={[3, 6]} pb={3} flex={'1 0 0'} overflowY={'auto'}>
|
||||
<RenderList
|
||||
templates={templates}
|
||||
isLoadingData={isLoading}
|
||||
type={templateType}
|
||||
setParentId={onUpdateParentId}
|
||||
showCost={templateType === TemplateTypeEnum.systemPlugin}
|
||||
{...props}
|
||||
/>
|
||||
</MyBox>
|
||||
@@ -202,55 +205,116 @@ export default React.memo(ToolSelectModal);
|
||||
|
||||
const RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
selectedTools,
|
||||
isLoadingData,
|
||||
type,
|
||||
onAddTool,
|
||||
onRemoveTool,
|
||||
setParentId,
|
||||
showCost
|
||||
selectedTools,
|
||||
chatConfig,
|
||||
selectedModel
|
||||
}: Props & {
|
||||
templates: NodeTemplateListItemType[];
|
||||
isLoadingData: boolean;
|
||||
type: TemplateTypeEnum;
|
||||
setParentId: (parentId: ParentIdType) => any;
|
||||
showCost?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const [configTool, setConfigTool] = useState<FlowNodeTemplateType>();
|
||||
const onCloseConfigTool = useCallback(() => setConfigTool(undefined), []);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
control,
|
||||
formState: { errors }
|
||||
} = useForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (configTool) {
|
||||
const defaultValues = configTool.inputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
reset(defaultValues);
|
||||
}
|
||||
}, [configTool, reset]);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { runAsync: onClickAdd, loading: isLoading } = useRequest2(
|
||||
async (template: NodeTemplateListItemType) => {
|
||||
const res = await getPreviewPluginNode({ appId: template.id });
|
||||
|
||||
// All input is tool params
|
||||
/* Invalid plugin check
|
||||
1. Reference type. but not tool description;
|
||||
2. Has dataset select
|
||||
3. Has dynamic external data
|
||||
*/
|
||||
const oneFileInput =
|
||||
res.inputs.filter((input) =>
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
|
||||
).length === 1;
|
||||
const canUploadFile =
|
||||
chatConfig?.fileSelectConfig?.canSelectFile || chatConfig?.fileSelectConfig?.canSelectImg;
|
||||
const invalidFileInput = oneFileInput && !!canUploadFile;
|
||||
if (
|
||||
res.inputs.every((input) => childAppSystemKey.includes(input.key) || input.toolDescription)
|
||||
res.inputs.some(
|
||||
(input) =>
|
||||
(input.renderTypeList.length === 1 &&
|
||||
input.renderTypeList[0] === FlowNodeInputTypeEnum.reference &&
|
||||
!input.toolDescription) ||
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectDataset) ||
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.addInputParam) ||
|
||||
(input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect) && !invalidFileInput)
|
||||
)
|
||||
) {
|
||||
onAddTool(res);
|
||||
return toast({
|
||||
title: t('app:simple_tool_tips'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
|
||||
// 判断是否可以直接添加工具,满足以下任一条件:
|
||||
// 1. 有工具描述
|
||||
// 2. 是模型选择类型
|
||||
// 3. 是文件上传类型且:已开启文件上传、非必填、只有一个文件上传输入
|
||||
const hasInputForm =
|
||||
res.inputs.length > 0 &&
|
||||
res.inputs.some((input) => {
|
||||
if (input.toolDescription) {
|
||||
return false;
|
||||
}
|
||||
if (input.key === NodeInputKeyEnum.forbidStream) {
|
||||
return false;
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.input)) {
|
||||
return true;
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.textarea)) {
|
||||
return true;
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.numberInput)) {
|
||||
return true;
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.switch)) {
|
||||
return true;
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.select)) {
|
||||
return true;
|
||||
}
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.JSONEditor)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 构建默认表单数据
|
||||
const defaultForm = {
|
||||
...res,
|
||||
inputs: res.inputs.map((input) => {
|
||||
// 如果是模型选择类型,使用当前选中的模型
|
||||
// if (input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel)) {
|
||||
// return {
|
||||
// ...input,
|
||||
// value: selectedModel.model
|
||||
// };
|
||||
// }
|
||||
// 如果是文件上传类型,设置为从工作流开始节点获取用户文件
|
||||
if (input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)) {
|
||||
return {
|
||||
...input,
|
||||
value: [[workflowStartNodeId, NodeOutputKeyEnum.userFiles]]
|
||||
};
|
||||
}
|
||||
return input;
|
||||
})
|
||||
};
|
||||
|
||||
if (hasInputForm) {
|
||||
setConfigTool(defaultForm);
|
||||
} else {
|
||||
reset();
|
||||
setConfigTool(res);
|
||||
onAddTool(defaultForm);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -258,165 +322,229 @@ const RenderList = React.memo(function RenderList({
|
||||
}
|
||||
);
|
||||
|
||||
return templates.length === 0 && !isLoadingData ? (
|
||||
<EmptyTip text={t('common:core.app.ToolCall.No plugin')} />
|
||||
) : (
|
||||
<MyBox>
|
||||
{templates.map((item, i) => {
|
||||
const selected = selectedTools.some((tool) => tool.pluginId === item.id);
|
||||
const { data: pluginGroups = [] } = useRequest2(getPluginGroups, {
|
||||
manual: false
|
||||
});
|
||||
|
||||
return (
|
||||
<MyTooltip
|
||||
key={item.id}
|
||||
placement={'bottom'}
|
||||
shouldWrapChildren={false}
|
||||
label={
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar
|
||||
src={item.avatar}
|
||||
w={'1.75rem'}
|
||||
objectFit={'contain'}
|
||||
borderRadius={'sm'}
|
||||
/>
|
||||
<Box fontWeight={'bold'} ml={2} color={'myGray.900'}>
|
||||
{t(item.name as any)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={2} color={'myGray.500'} maxH={'100px'} overflow={'hidden'}>
|
||||
{t(item.intro as any) || t('common:core.workflow.Not intro')}
|
||||
</Box>
|
||||
{showCost && <CostTooltip cost={item.currentCost} />}
|
||||
</Box>
|
||||
const formatTemplatesArray = useMemo(() => {
|
||||
const data = (() => {
|
||||
if (type === TemplateTypeEnum.systemPlugin) {
|
||||
return pluginGroups.map((group) => {
|
||||
const copy: NodeTemplateListType = group.groupTypes.map((type) => ({
|
||||
list: [],
|
||||
type: type.typeId,
|
||||
label: type.typeName
|
||||
}));
|
||||
templates.forEach((item) => {
|
||||
const index = copy.findIndex((template) => template.type === item.templateType);
|
||||
if (index === -1) return;
|
||||
copy[index].list.push(item);
|
||||
});
|
||||
return {
|
||||
label: group.groupName,
|
||||
list: copy.filter((item) => item.list.length > 0)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
list: [
|
||||
{
|
||||
list: templates,
|
||||
type: '',
|
||||
label: ''
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
p={[4, 5]}
|
||||
_notLast={{
|
||||
borderBottomWidth: '1px',
|
||||
borderBottomColor: 'myGray.150'
|
||||
}}
|
||||
_hover={{
|
||||
bg: 'myGray.50'
|
||||
}}
|
||||
],
|
||||
label: ''
|
||||
}
|
||||
];
|
||||
})();
|
||||
|
||||
return data.filter(({ list }) => list.length > 0);
|
||||
}, [pluginGroups, templates, type]);
|
||||
|
||||
const gridStyle = useMemo(() => {
|
||||
if (type === TemplateTypeEnum.teamPlugin) {
|
||||
return {
|
||||
gridTemplateColumns: ['1fr', '1fr'],
|
||||
py: 2,
|
||||
avatarSize: '2rem'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
gridTemplateColumns: ['1fr', '1fr 1fr'],
|
||||
py: 3,
|
||||
avatarSize: '1.75rem'
|
||||
};
|
||||
}, [type]);
|
||||
|
||||
const PluginListRender = useMemoizedFn(({ list = [] }: { list: NodeTemplateListType }) => {
|
||||
return (
|
||||
<>
|
||||
{list.map((item, i) => {
|
||||
return (
|
||||
<Box
|
||||
key={item.type}
|
||||
css={css({
|
||||
span: {
|
||||
display: 'block'
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'2rem'} objectFit={'contain'} borderRadius={'md'} />
|
||||
|
||||
<Box ml={3} flex={'1 0 0'} color={'myGray.900'}>
|
||||
{t(item.name as any)}
|
||||
</Box>
|
||||
{item.author !== undefined && (
|
||||
<Box fontSize={'xs'} mr={3}>
|
||||
{`By ${item.author || feConfigs.systemTitle}`}
|
||||
<Flex>
|
||||
<Box fontSize={'sm'} my={2} fontWeight={'500'} flex={1} color={'myGray.900'}>
|
||||
{t(item.label as any)}
|
||||
</Box>
|
||||
)}
|
||||
{selected ? (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'grayDanger'}
|
||||
leftIcon={<MyIcon name={'delete'} w={'14px'} />}
|
||||
onClick={() => onRemoveTool(item)}
|
||||
>
|
||||
{t('common:common.Remove')}
|
||||
</Button>
|
||||
) : item.isFolder ? (
|
||||
<Button size={'sm'} variant={'whiteBase'} onClick={() => setParentId(item.id)}>
|
||||
{t('common:common.Open')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
isLoading={isLoading}
|
||||
onClick={() => onClickAdd(item)}
|
||||
>
|
||||
{t('common:common.Add')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
<Grid gridTemplateColumns={gridStyle.gridTemplateColumns} rowGap={2} columnGap={3}>
|
||||
{item.list.map((template) => {
|
||||
const selected = selectedTools.some((tool) => tool.pluginId === template.id);
|
||||
|
||||
{/* Plugin input config */}
|
||||
{!!configTool && (
|
||||
<MyModal
|
||||
isOpen
|
||||
isCentered
|
||||
title={t('common:core.app.ToolCall.Parameter setting')}
|
||||
iconSrc="core/app/toolCall"
|
||||
overflow={'auto'}
|
||||
>
|
||||
<ModalBody>
|
||||
<HStack mb={4} spacing={1} fontSize={'sm'}>
|
||||
<MyIcon name={'common/info'} w={'1.25rem'} />
|
||||
<Box flex={1}>{t('app:tool_input_param_tip')}</Box>
|
||||
{configTool.courseUrl && (
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
color={'primary.500'}
|
||||
onClick={() => window.open(configTool.courseUrl, '_blank')}
|
||||
>
|
||||
{t('app:workflow.Input guide')}
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
{configTool.inputs
|
||||
.filter((item) => !item.toolDescription && !childAppSystemKey.includes(item.key))
|
||||
.map((input) => {
|
||||
return (
|
||||
<Controller
|
||||
key={input.key}
|
||||
control={control}
|
||||
name={input.key}
|
||||
rules={{
|
||||
validate: (value) => {
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return value !== undefined;
|
||||
}
|
||||
return !!value;
|
||||
return (
|
||||
<MyTooltip
|
||||
key={template.id}
|
||||
placement={'right'}
|
||||
label={
|
||||
<Box py={2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyAvatar
|
||||
src={template.avatar}
|
||||
w={'1.75rem'}
|
||||
objectFit={'contain'}
|
||||
borderRadius={'sm'}
|
||||
/>
|
||||
<Box fontWeight={'bold'} ml={3} color={'myGray.900'}>
|
||||
{t(template.name as any)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={2} color={'myGray.500'} maxH={'100px'} overflow={'hidden'}>
|
||||
{t(template.intro as any) || t('common:core.workflow.Not intro')}
|
||||
</Box>
|
||||
{type === TemplateTypeEnum.systemPlugin && (
|
||||
<CostTooltip
|
||||
cost={template.currentCost}
|
||||
hasTokenFee={template.hasTokenFee}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
}}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<RenderPluginInput
|
||||
value={value}
|
||||
isInvalid={errors && Object.keys(errors).includes(input.key)}
|
||||
onChange={onChange}
|
||||
input={input}
|
||||
setUploading={() => {}}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
py={gridStyle.py}
|
||||
px={3}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'sm'}
|
||||
whiteSpace={'nowrap'}
|
||||
overflow={'hidden'}
|
||||
textOverflow={'ellipsis'}
|
||||
>
|
||||
<MyAvatar
|
||||
src={template.avatar}
|
||||
w={gridStyle.avatarSize}
|
||||
objectFit={'contain'}
|
||||
borderRadius={'sm'}
|
||||
flexShrink={0}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ModalBody>
|
||||
<ModalFooter gap={6}>
|
||||
<Button onClick={onCloseConfigTool} variant={'whiteBase'}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'primary'}
|
||||
onClick={handleSubmit((data) => {
|
||||
onAddTool({
|
||||
...configTool,
|
||||
inputs: configTool.inputs.map((input) => ({
|
||||
...input,
|
||||
value: data[input.key] ?? input.value
|
||||
}))
|
||||
});
|
||||
onCloseConfigTool();
|
||||
})}
|
||||
>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
<Box
|
||||
color={'myGray.900'}
|
||||
fontWeight={'500'}
|
||||
fontSize={'sm'}
|
||||
flex={'1 0 0'}
|
||||
ml={3}
|
||||
className="textEllipsis"
|
||||
>
|
||||
{t(template.name as any)}
|
||||
</Box>
|
||||
|
||||
{selected ? (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'grayDanger'}
|
||||
leftIcon={<MyIcon name={'delete'} w={'16px'} mr={-1} />}
|
||||
onClick={() => onRemoveTool(template)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:common.Remove')}
|
||||
</Button>
|
||||
) : template.isFolder ? (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<MyIcon name={'common/arrowRight'} w={'16px'} mr={-1.5} />}
|
||||
onClick={() => setParentId(template.id)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:common.Open')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'primaryOutline'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'16px'} mr={-1.5} />}
|
||||
isLoading={isLoading}
|
||||
onClick={() => onClickAdd(template)}
|
||||
px={2}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{t('common:common.Add')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
return templates.length === 0 ? (
|
||||
<EmptyTip text={t('app:module.No Modules')} />
|
||||
) : (
|
||||
<Box flex={'1 0 0'} overflow={'overlay'}>
|
||||
<Accordion defaultIndex={[0]} allowMultiple reduceMotion>
|
||||
{formatTemplatesArray.length > 1 ? (
|
||||
<>
|
||||
{formatTemplatesArray.map(({ list, label }, index) => (
|
||||
<AccordionItem key={index} border={'none'}>
|
||||
<AccordionButton
|
||||
fontSize={'sm'}
|
||||
fontWeight={'500'}
|
||||
color={'myGray.900'}
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
>
|
||||
{t(label as any)}
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={0}>
|
||||
<PluginListRender list={list} />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<PluginListRender list={formatTemplatesArray?.[0]?.list} />
|
||||
)}
|
||||
</Accordion>
|
||||
|
||||
{!!configTool && (
|
||||
<ConfigToolModal
|
||||
configTool={configTool}
|
||||
onCloseConfigTool={onCloseConfigTool}
|
||||
onAddTool={onAddTool}
|
||||
/>
|
||||
)}
|
||||
</MyBox>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -50,6 +50,7 @@ import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/consta
|
||||
import { getEditorVariables } from '../../../utils';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
|
||||
|
||||
const defaultFormBody = {
|
||||
@@ -87,6 +88,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
|
||||
|
||||
const requestMethods = inputs.find(
|
||||
@@ -168,6 +170,15 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
});
|
||||
}, [nodeId, nodeList, edges, appDetail, t]);
|
||||
|
||||
const externalProviderWorkflowVariables = useMemo(() => {
|
||||
return (
|
||||
feConfigs?.externalProviderWorkflowVariables?.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.name
|
||||
})) || []
|
||||
);
|
||||
}, [feConfigs?.externalProviderWorkflowVariables]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
|
||||
@@ -235,7 +246,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
}
|
||||
value={requestUrl?.value || ''}
|
||||
variableLabels={variables}
|
||||
variables={variables}
|
||||
variables={externalProviderWorkflowVariables}
|
||||
onBlur={onBlurUrl}
|
||||
onChange={onChangeUrl}
|
||||
minH={40}
|
||||
@@ -263,6 +274,7 @@ export function RenderHttpProps({
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
|
||||
const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
|
||||
@@ -276,6 +288,15 @@ export function RenderHttpProps({
|
||||
const headersLength = headers?.value?.length || 0;
|
||||
|
||||
// get variable
|
||||
const externalProviderWorkflowVariables = useMemo(() => {
|
||||
return (
|
||||
feConfigs?.externalProviderWorkflowVariables?.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.name
|
||||
})) || []
|
||||
);
|
||||
}, [feConfigs?.externalProviderWorkflowVariables]);
|
||||
|
||||
const variables = useCreation(() => {
|
||||
return getEditorVariables({
|
||||
nodeId,
|
||||
@@ -298,13 +319,15 @@ export function RenderHttpProps({
|
||||
params,
|
||||
headers,
|
||||
jsonBody,
|
||||
variables
|
||||
variables,
|
||||
externalProviderWorkflowVariables
|
||||
}),
|
||||
[headers, jsonBody, params, variables]
|
||||
[externalProviderWorkflowVariables, headers, jsonBody, params, variables]
|
||||
);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
const { params, headers, jsonBody, variables } = JSON.parse(stringifyVariables);
|
||||
const { params, headers, jsonBody, variables, externalProviderWorkflowVariables } =
|
||||
JSON.parse(stringifyVariables);
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'} mb={2} fontWeight={'medium'} color={'myGray.600'}>
|
||||
@@ -347,18 +370,31 @@ export function RenderHttpProps({
|
||||
headers &&
|
||||
jsonBody &&
|
||||
{
|
||||
[TabEnum.params]: <RenderForm nodeId={nodeId} input={params} variables={variables} />,
|
||||
[TabEnum.params]: (
|
||||
<RenderForm
|
||||
nodeId={nodeId}
|
||||
input={params}
|
||||
variables={variables}
|
||||
externalProviderWorkflowVariables={externalProviderWorkflowVariables}
|
||||
/>
|
||||
),
|
||||
[TabEnum.body]: (
|
||||
<RenderBody
|
||||
nodeId={nodeId}
|
||||
variables={variables}
|
||||
externalProviderWorkflowVariables={externalProviderWorkflowVariables}
|
||||
jsonBody={jsonBody}
|
||||
formBody={formBody}
|
||||
typeInput={contentType}
|
||||
/>
|
||||
),
|
||||
[TabEnum.headers]: (
|
||||
<RenderForm nodeId={nodeId} input={headers} variables={variables} />
|
||||
<RenderForm
|
||||
nodeId={nodeId}
|
||||
input={headers}
|
||||
variables={variables}
|
||||
externalProviderWorkflowVariables={externalProviderWorkflowVariables}
|
||||
/>
|
||||
)
|
||||
}[selectedTab]}
|
||||
</Box>
|
||||
@@ -436,11 +472,16 @@ const RenderHttpTimeout = ({
|
||||
const RenderForm = ({
|
||||
nodeId,
|
||||
input,
|
||||
variables
|
||||
variables,
|
||||
externalProviderWorkflowVariables
|
||||
}: {
|
||||
nodeId: string;
|
||||
input: FlowNodeInputItemType;
|
||||
variables: EditorVariableLabelPickerType[];
|
||||
externalProviderWorkflowVariables: {
|
||||
key: string;
|
||||
label: string;
|
||||
}[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@@ -547,7 +588,7 @@ const RenderForm = ({
|
||||
placeholder={t('common:textarea_variable_picker_tip')}
|
||||
value={item.key}
|
||||
variableLabels={variables}
|
||||
variables={variables}
|
||||
variables={externalProviderWorkflowVariables}
|
||||
onBlur={(val) => {
|
||||
handleKeyChange(index, val);
|
||||
|
||||
@@ -565,7 +606,7 @@ const RenderForm = ({
|
||||
<HttpInput
|
||||
placeholder={t('common:textarea_variable_picker_tip')}
|
||||
value={item.value}
|
||||
variables={variables}
|
||||
variables={externalProviderWorkflowVariables}
|
||||
variableLabels={variables}
|
||||
onBlur={(val) => {
|
||||
setList((prevList) =>
|
||||
@@ -597,7 +638,16 @@ const RenderForm = ({
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
}, [handleAddNewProps, handleKeyChange, input.key, list, t, updateTrigger, variables]);
|
||||
}, [
|
||||
externalProviderWorkflowVariables,
|
||||
handleAddNewProps,
|
||||
handleKeyChange,
|
||||
input.key,
|
||||
list,
|
||||
t,
|
||||
updateTrigger,
|
||||
variables
|
||||
]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
@@ -606,13 +656,18 @@ const RenderBody = ({
|
||||
jsonBody,
|
||||
formBody,
|
||||
typeInput,
|
||||
variables
|
||||
variables,
|
||||
externalProviderWorkflowVariables
|
||||
}: {
|
||||
nodeId: string;
|
||||
jsonBody: FlowNodeInputItemType;
|
||||
formBody: FlowNodeInputItemType;
|
||||
typeInput: FlowNodeInputItemType | undefined;
|
||||
variables: EditorVariableLabelPickerType[];
|
||||
externalProviderWorkflowVariables: {
|
||||
key: string;
|
||||
label: string;
|
||||
}[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
@@ -684,7 +739,12 @@ const RenderBody = ({
|
||||
</Flex>
|
||||
{(typeInput?.value === ContentTypes.formData ||
|
||||
typeInput?.value === ContentTypes.xWwwFormUrlencoded) && (
|
||||
<RenderForm nodeId={nodeId} input={formBody} variables={variables} />
|
||||
<RenderForm
|
||||
nodeId={nodeId}
|
||||
input={formBody}
|
||||
variables={variables}
|
||||
externalProviderWorkflowVariables={externalProviderWorkflowVariables}
|
||||
/>
|
||||
)}
|
||||
{typeInput?.value === ContentTypes.json && (
|
||||
<PromptEditor
|
||||
@@ -729,7 +789,16 @@ const RenderBody = ({
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}, [typeInput?.value, t, nodeId, formBody, variables, jsonBody, onChangeNode]);
|
||||
}, [
|
||||
typeInput?.value,
|
||||
nodeId,
|
||||
formBody,
|
||||
variables,
|
||||
externalProviderWorkflowVariables,
|
||||
jsonBody,
|
||||
t,
|
||||
onChangeNode
|
||||
]);
|
||||
return Render;
|
||||
};
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
|
||||
const token = userInfo?.team.lafAccount?.token;
|
||||
const appid = userInfo?.team.lafAccount?.appid;
|
||||
const token = userInfo?.team?.lafAccount?.token;
|
||||
const appid = userInfo?.team?.lafAccount?.appid;
|
||||
|
||||
const {
|
||||
data: lafData,
|
||||
@@ -322,7 +322,7 @@ const ConfigLaf = () => {
|
||||
</Button>
|
||||
|
||||
{isOpenLafConfig && feConfigs?.lafEnv && (
|
||||
<LafAccountModal defaultData={userInfo?.team.lafAccount} onClose={onCloseLafConfig} />
|
||||
<LafAccountModal defaultData={userInfo?.team?.lafAccount} onClose={onCloseLafConfig} />
|
||||
)}
|
||||
</Center>
|
||||
) : (
|
||||
|
||||
@@ -190,6 +190,19 @@ const FieldEditModal = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (data.renderTypeList[0] === FlowNodeInputTypeEnum.addInputParam) {
|
||||
if (
|
||||
!data.customInputConfig?.selectValueTypeList ||
|
||||
!data.customInputConfig?.selectValueTypeList.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.module.edit.Field Value Type Cannot Be Empty')
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get toolDescription and removes the types of some unusable tools
|
||||
if (data.toolDescription && data.renderTypeList.includes(FlowNodeInputTypeEnum.reference)) {
|
||||
data.toolDescription = data.description;
|
||||
|
||||
@@ -351,12 +351,6 @@ const InputTypeConfig = ({
|
||||
|
||||
{inputType === FlowNodeInputTypeEnum.addInputParam && (
|
||||
<>
|
||||
{/* <Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 132px'} fontWeight={'medium'}>
|
||||
{t('common:core.module.Input Type')}
|
||||
</FormLabel>
|
||||
<Box fontSize={'14px'}>{t('workflow:only_the_reference_type_is_supported')}</Box>
|
||||
</Flex> */}
|
||||
<Box>
|
||||
<HStack mb={1}>
|
||||
<FormLabel fontWeight={'medium'}>{t('workflow:optional_value_type')}</FormLabel>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
@@ -35,6 +35,8 @@ import { useCreation, useMemoizedFn } from 'ahooks';
|
||||
import { getEditorVariables } from '../../utils';
|
||||
import { isArray } from 'lodash';
|
||||
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
|
||||
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { inputs = [], nodeId } = data;
|
||||
@@ -67,7 +69,17 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
t
|
||||
});
|
||||
}, [nodeId, nodeList, edges, appDetail, t]);
|
||||
const { feConfigs } = useSystemStore();
|
||||
const externalProviderWorkflowVariables = useMemo(() => {
|
||||
return (
|
||||
feConfigs?.externalProviderWorkflowVariables?.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.name
|
||||
})) || []
|
||||
);
|
||||
}, [feConfigs?.externalProviderWorkflowVariables]);
|
||||
|
||||
// Node inputs
|
||||
const updateList = useMemo(
|
||||
() =>
|
||||
(inputs.find((input) => input.key === NodeInputKeyEnum.updateList)
|
||||
@@ -104,7 +116,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
(item) => item.renderType === updateItem.renderType
|
||||
);
|
||||
|
||||
const handleUpdate = (newValue?: ReferenceValueType | string) => {
|
||||
const onUpdateNewValue = (newValue?: ReferenceValueType | string) => {
|
||||
if (typeof newValue === 'string') {
|
||||
onUpdateList(
|
||||
updateList.map((update, i) =>
|
||||
@@ -134,7 +146,11 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
return {
|
||||
...update,
|
||||
value: ['', ''],
|
||||
valueType,
|
||||
valueType: getRefData({
|
||||
variable: value as ReferenceItemValueType,
|
||||
nodeList,
|
||||
chatConfig: appDetail.chatConfig
|
||||
}).valueType,
|
||||
variable: value as ReferenceItemValueType
|
||||
};
|
||||
}
|
||||
@@ -206,7 +222,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
nodeId={nodeId}
|
||||
variable={updateItem.value}
|
||||
valueType={valueType}
|
||||
onSelect={handleUpdate}
|
||||
onSelect={onUpdateNewValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -218,9 +234,10 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
<Box w={'300px'}>
|
||||
<PromptEditor
|
||||
value={inputValue || ''}
|
||||
onChange={handleUpdate}
|
||||
onChange={onUpdateNewValue}
|
||||
showOpenModal={false}
|
||||
variableLabels={variables}
|
||||
variables={[...variables, ...externalProviderWorkflowVariables]}
|
||||
minH={100}
|
||||
/>
|
||||
</Box>
|
||||
@@ -228,20 +245,18 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
}
|
||||
if (valueType === WorkflowIOValueTypeEnum.number) {
|
||||
return (
|
||||
<NumberInput value={Number(inputValue) || 0}>
|
||||
<NumberInputField bg="white" onChange={(e) => handleUpdate(e.target.value)} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
<MyNumberInput
|
||||
bg={'white'}
|
||||
value={Number(inputValue) || 0}
|
||||
onChange={(e) => onUpdateNewValue(String(e || 0))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return (
|
||||
<Switch
|
||||
defaultChecked={inputValue === 'true'}
|
||||
onChange={(e) => handleUpdate(String(e.target.checked))}
|
||||
onChange={(e) => onUpdateNewValue(String(e.target.checked))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -250,9 +265,10 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
<Box w={'300px'}>
|
||||
<PromptEditor
|
||||
value={inputValue || ''}
|
||||
onChange={handleUpdate}
|
||||
onChange={onUpdateNewValue}
|
||||
showOpenModal={false}
|
||||
variableLabels={variables}
|
||||
variables={[...variables, ...externalProviderWorkflowVariables]}
|
||||
minH={100}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Box, Button, Card, Flex, FlexProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
@@ -24,11 +24,11 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useWorkflowUtils } from '../../hooks/useUtils';
|
||||
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
|
||||
import { WorkflowEventContext } from '../../../context/workflowEventContext';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||
|
||||
type Props = FlowNodeItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -136,6 +136,7 @@ const NodeCard = (props: Props) => {
|
||||
} = useConfirm({
|
||||
content: t('workflow:Confirm_sync_node')
|
||||
});
|
||||
|
||||
const hasNewVersion = nodeTemplate && nodeTemplate.version !== node?.version;
|
||||
|
||||
const { runAsync: onClickSyncVersion } = useRequest2(
|
||||
@@ -166,7 +167,7 @@ const NodeCard = (props: Props) => {
|
||||
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
|
||||
|
||||
{/* avatar and name */}
|
||||
<Flex alignItems={'center'} mb={intro ? 1 : 0}>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
@@ -271,15 +272,19 @@ const NodeCard = (props: Props) => {
|
||||
{!!nodeTemplate?.diagram && node?.courseUrl && (
|
||||
<Box bg={'myGray.300'} w={'1px'} h={'12px'} ml={1} mr={0.5} />
|
||||
)}
|
||||
{node?.courseUrl && !hasNewVersion && (
|
||||
<MyTooltip label={t('workflow:Node.Open_Node_Course')}>
|
||||
<MyIconButton
|
||||
ml={1}
|
||||
icon="book"
|
||||
color={'primary.600'}
|
||||
onClick={() => window.open(getDocPath(node.courseUrl || ''), '_blank')}
|
||||
/>
|
||||
</MyTooltip>
|
||||
{!!(node?.courseUrl || nodeTemplate?.userGuide) && !hasNewVersion && (
|
||||
<UseGuideModal
|
||||
title={nodeTemplate?.name}
|
||||
iconSrc={nodeTemplate?.avatar}
|
||||
text={nodeTemplate?.userGuide}
|
||||
link={nodeTemplate?.courseUrl}
|
||||
>
|
||||
{({ onClick }) => (
|
||||
<MyTooltip label={t('workflow:Node.Open_Node_Course')}>
|
||||
<MyIconButton ml={1} icon="book" color={'primary.600'} onClick={onClick} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</UseGuideModal>
|
||||
)}
|
||||
</Flex>
|
||||
<NodeIntro nodeId={nodeId} intro={intro} />
|
||||
@@ -291,6 +296,7 @@ const NodeCard = (props: Props) => {
|
||||
);
|
||||
}, [
|
||||
node?.flowNodeType,
|
||||
node?.courseUrl,
|
||||
showToolHandle,
|
||||
nodeId,
|
||||
isFolded,
|
||||
@@ -301,7 +307,10 @@ const NodeCard = (props: Props) => {
|
||||
onOpenConfirmSync,
|
||||
onClickSyncVersion,
|
||||
nodeTemplate?.diagram,
|
||||
node?.courseUrl,
|
||||
nodeTemplate?.userGuide,
|
||||
nodeTemplate?.name,
|
||||
nodeTemplate?.avatar,
|
||||
nodeTemplate?.courseUrl,
|
||||
intro,
|
||||
menuForbid,
|
||||
nodeList,
|
||||
|
||||
@@ -8,8 +8,9 @@ import { useCreation } from 'ahooks';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { getEditorVariables } from '../../../../../utils';
|
||||
import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
const TextareaRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
@@ -17,6 +18,8 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
// get variable
|
||||
const variables = useCreation(() => {
|
||||
return getEditorVariables({
|
||||
@@ -28,6 +31,15 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
});
|
||||
}, [nodeId, nodeList, edges, appDetail, t]);
|
||||
|
||||
const externalProviderWorkflowVariables = useMemo(() => {
|
||||
return (
|
||||
feConfigs?.externalProviderWorkflowVariables?.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.name
|
||||
})) || []
|
||||
);
|
||||
}, [feConfigs?.externalProviderWorkflowVariables]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
@@ -47,7 +59,7 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
return (
|
||||
<PromptEditor
|
||||
variableLabels={variables}
|
||||
variables={variables}
|
||||
variables={[...variables, ...externalProviderWorkflowVariables]}
|
||||
title={t(item.label as any)}
|
||||
maxLength={item.maxLength}
|
||||
minH={100}
|
||||
@@ -57,7 +69,16 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}, [item.label, item.maxLength, item.placeholder, item.value, onChange, t, variables]);
|
||||
}, [
|
||||
externalProviderWorkflowVariables,
|
||||
item.label,
|
||||
item.maxLength,
|
||||
item.placeholder,
|
||||
item.value,
|
||||
onChange,
|
||||
t,
|
||||
variables
|
||||
]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useDebounceEffect, useMemoizedFn } from 'ahooks';
|
||||
import React, { ReactNode, useMemo, useRef, useState } from 'react';
|
||||
import { useDebounceEffect, useLockFn, useMemoizedFn } from 'ahooks';
|
||||
import React, { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowInitContext, WorkflowNodeEdgeContext } from './workflowInitContext';
|
||||
import { WorkflowContext } from '.';
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_Node_Width
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
import { isProduction } from '@fastgpt/global/common/system/constants';
|
||||
|
||||
type WorkflowStatusContextType = {
|
||||
isSaved: boolean;
|
||||
@@ -67,20 +68,28 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
// Lead check before unload
|
||||
const flowData2StoreData = useContextSelector(WorkflowContext, (v) => v.flowData2StoreData);
|
||||
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
|
||||
const autoSaveFn = useLockFn(async () => {
|
||||
if (isSaved || !leaveSaveSign.current) return;
|
||||
console.log('Leave auto save');
|
||||
const data = flowData2StoreData();
|
||||
if (!data || data.nodes.length === 0) return;
|
||||
await onSaveApp({
|
||||
...data,
|
||||
isPublish: false,
|
||||
chatConfig: appDetail.chatConfig,
|
||||
autoSave: true
|
||||
});
|
||||
});
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (isProduction) {
|
||||
autoSaveFn();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
useBeforeunload({
|
||||
tip: t('common:core.common.tip.leave page'),
|
||||
callback: async () => {
|
||||
if (isSaved || !leaveSaveSign.current) return;
|
||||
console.log('Leave auto save');
|
||||
const data = flowData2StoreData();
|
||||
if (!data || data.nodes.length === 0) return;
|
||||
await onSaveApp({
|
||||
...data,
|
||||
isPublish: false,
|
||||
versionName: t('app:unusual_leave_auto_save'),
|
||||
chatConfig: appDetail.chatConfig
|
||||
});
|
||||
}
|
||||
callback: autoSaveFn
|
||||
});
|
||||
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useDisclosure } from '@chakra-ui/react';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
|
||||
const InfoModal = dynamic(() => import('./InfoModal'));
|
||||
const TagsEditModal = dynamic(() => import('./TagsEditModal'));
|
||||
@@ -150,13 +151,20 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
});
|
||||
|
||||
const { runAsync: onSaveApp } = useRequest2(async (data: PostPublishAppProps) => {
|
||||
await postPublishApp(appId, data);
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
...data,
|
||||
modules: data.nodes || state.modules
|
||||
}));
|
||||
reloadAppLatestVersion();
|
||||
try {
|
||||
await postPublishApp(appId, data);
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
...data,
|
||||
modules: data.nodes || state.modules
|
||||
}));
|
||||
reloadAppLatestVersion();
|
||||
} catch (error: any) {
|
||||
if (error.statusText == AppErrEnum.unExist) {
|
||||
return;
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { Box, Flex, Button, ModalFooter, ModalBody, Input, Grid, Card } from '@chakra-ui/react';
|
||||
import { Box, Flex, Button, ModalBody, Input, Grid, Card } from '@chakra-ui/react';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getTemplateMarketItemList
|
||||
} from '@/web/core/app/api/template';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { AppTemplateSchemaType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
type FormType = {
|
||||
avatar: string;
|
||||
@@ -70,13 +71,12 @@ const CreateModal = ({
|
||||
}
|
||||
});
|
||||
const typeData = typeMap.current[type];
|
||||
|
||||
const { data: templateList = [] } = useRequest2(getTemplateMarketItemList, {
|
||||
manual: false
|
||||
});
|
||||
const filterTemplates = useMemo(() => {
|
||||
return templateList.filter((item) => item.type === type).slice(0, 3);
|
||||
}, [templateList, type]);
|
||||
const { data: templateList = [], loading: isRequestTemplates } = useRequest2(
|
||||
() => getTemplateMarketItemList({ isQuickTemplate: true, type }),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
@@ -127,13 +127,12 @@ const CreateModal = ({
|
||||
});
|
||||
}
|
||||
|
||||
const templateDetail = await getTemplateMarketItemDetail({ templateId: templateId });
|
||||
|
||||
const templateDetail = await getTemplateMarketItemDetail(templateId);
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: data.avatar || templateDetail.avatar,
|
||||
name: data.name,
|
||||
type: templateDetail.type,
|
||||
type: templateDetail.type as AppTypeEnum,
|
||||
modules: templateDetail.workflow.nodes || [],
|
||||
edges: templateDetail.workflow.edges || [],
|
||||
chatConfig: templateDetail.workflow.chatConfig
|
||||
@@ -158,7 +157,7 @@ const CreateModal = ({
|
||||
onClose={onClose}
|
||||
isCentered={!isPc}
|
||||
maxW={['90vw', '40rem']}
|
||||
isLoading={isCreating}
|
||||
isLoading={isCreating || isRequestTemplates}
|
||||
>
|
||||
<ModalBody px={9} pb={8}>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
@@ -205,7 +204,7 @@ const CreateModal = ({
|
||||
</Flex>
|
||||
<Grid
|
||||
userSelect={'none'}
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']}
|
||||
gridTemplateColumns={templateList.length > 0 ? ['repeat(1,1fr)', 'repeat(2,1fr)'] : '1fr'}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
<Card
|
||||
@@ -231,9 +230,9 @@ const CreateModal = ({
|
||||
{typeData.emptyCreateText}
|
||||
</Box>
|
||||
</Card>
|
||||
{filterTemplates.map((item) => (
|
||||
{templateList.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
key={item.templateId}
|
||||
p={4}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
@@ -271,17 +270,17 @@ const CreateModal = ({
|
||||
h={'full'}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bottom={1}
|
||||
height={'40px'}
|
||||
bg={'white'}
|
||||
zIndex={1}
|
||||
>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
h={'1.75rem'}
|
||||
borderRadius={'xl'}
|
||||
h={6}
|
||||
borderRadius={'sm'}
|
||||
w={'40%'}
|
||||
onClick={handleSubmit((data) => onclickCreate(data, item.id))}
|
||||
onClick={handleSubmit((data) => onclickCreate(data, item.templateId))}
|
||||
>
|
||||
{t('app:templateMarket.Use')}
|
||||
</Button>
|
||||
|
||||
@@ -12,16 +12,16 @@ import {
|
||||
ModalOverlay
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import AppTypeTag from './TypeTag';
|
||||
import { AppTemplateTypeEnum, AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
getTemplateMarketItemDetail,
|
||||
getTemplateMarketItemList
|
||||
getTemplateMarketItemList,
|
||||
getTemplateTagList
|
||||
} from '@/web/core/app/api/template';
|
||||
import { TemplateMarketListItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { postCreateApp } from '@/web/core/app/api';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppListContext } from './context';
|
||||
@@ -34,9 +34,18 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput/index'
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { webPushTrack } from '@/web/common/middle/tracks/utils';
|
||||
import { AppTemplateSchemaType, TemplateTypeSchemaType } from '@fastgpt/global/core/app/type';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||
|
||||
type TemplateAppType = AppTypeEnum | 'all';
|
||||
|
||||
const recommendTag: TemplateTypeSchemaType = {
|
||||
typeId: AppTemplateTypeEnum.recommendation,
|
||||
typeName: i18nT('app:templateMarket.templateTags.Recommendation'),
|
||||
typeOrder: 0
|
||||
};
|
||||
|
||||
const TemplateMarketModal = ({
|
||||
defaultType = 'all',
|
||||
onClose
|
||||
@@ -46,63 +55,57 @@ const TemplateMarketModal = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const templateTags = [
|
||||
{
|
||||
id: AppTemplateTypeEnum.recommendation,
|
||||
label: t('app:templateMarket.templateTags.Recommendation')
|
||||
},
|
||||
{
|
||||
id: AppTemplateTypeEnum.writing,
|
||||
label: t('app:templateMarket.templateTags.Writing')
|
||||
},
|
||||
{
|
||||
id: AppTemplateTypeEnum.imageGeneration,
|
||||
label: t('app:templateMarket.templateTags.Image_generation')
|
||||
},
|
||||
{
|
||||
id: AppTemplateTypeEnum.webSearch,
|
||||
label: t('app:templateMarket.templateTags.Web_search')
|
||||
},
|
||||
{
|
||||
id: AppTemplateTypeEnum.roleplay,
|
||||
label: t('app:templateMarket.templateTags.Roleplay')
|
||||
},
|
||||
{
|
||||
id: AppTemplateTypeEnum.officeServices,
|
||||
label: t('app:templateMarket.templateTags.Office_services')
|
||||
}
|
||||
];
|
||||
|
||||
const { parentId } = useContextSelector(AppListContext, (v) => v);
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const [currentTag, setCurrentTag] = useState(templateTags[0].id);
|
||||
const [currentTag, setCurrentTag] = useState<string>(AppTemplateTypeEnum.recommendation);
|
||||
const [currentAppType, setCurrentAppType] = useState<TemplateAppType>(defaultType);
|
||||
const [currentSearch, setCurrentSearch] = useState('');
|
||||
|
||||
const { data: templateList = [], loading: isLoadingTemplates } = useRequest2(
|
||||
getTemplateMarketItemList,
|
||||
() => getTemplateMarketItemList({ type: currentAppType }),
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [currentAppType]
|
||||
}
|
||||
);
|
||||
|
||||
const { data: templateTags = [], loading: isLoadingTags } = useRequest2(
|
||||
() => getTemplateTagList().then((res) => [recommendTag, ...res]),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
// Batch by tags
|
||||
const filterTemplateTags = useMemo(() => {
|
||||
return templateTags
|
||||
.map((tag) => {
|
||||
const templates = templateList.filter((template) => template.tags.includes(tag.typeId));
|
||||
return {
|
||||
...tag,
|
||||
templates
|
||||
};
|
||||
})
|
||||
.filter((item) => item.templates.length > 0);
|
||||
}, [templateList, templateTags]);
|
||||
|
||||
const { runAsync: onUseTemplate, loading: isCreating } = useRequest2(
|
||||
async (id: string) => {
|
||||
const templateDetail = await getTemplateMarketItemDetail({ templateId: id });
|
||||
async (template: AppTemplateSchemaType) => {
|
||||
const templateDetail = await getTemplateMarketItemDetail(template.templateId);
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: templateDetail.avatar,
|
||||
name: templateDetail.name,
|
||||
type: templateDetail.type,
|
||||
avatar: template.avatar,
|
||||
name: template.name,
|
||||
type: template.type as AppTypeEnum,
|
||||
modules: templateDetail.workflow.nodes || [],
|
||||
edges: templateDetail.workflow.edges || [],
|
||||
chatConfig: templateDetail.workflow.chatConfig
|
||||
}).then((res) => {
|
||||
webPushTrack.useAppTemplate({
|
||||
id,
|
||||
name: templateDetail.name
|
||||
id: res,
|
||||
name: template.name
|
||||
});
|
||||
|
||||
return res;
|
||||
@@ -122,9 +125,9 @@ const TemplateMarketModal = ({
|
||||
async () => {
|
||||
let firstVisibleTitle: any = null;
|
||||
|
||||
templateTags
|
||||
.map((type) => type.id)
|
||||
.forEach((type: string) => {
|
||||
filterTemplateTags
|
||||
.map((type) => type.typeId)
|
||||
.forEach((type) => {
|
||||
const element = document.getElementById(type);
|
||||
if (!element) return;
|
||||
|
||||
@@ -144,17 +147,18 @@ const TemplateMarketModal = ({
|
||||
}
|
||||
},
|
||||
{
|
||||
throttleWait: 100
|
||||
throttleWait: 100,
|
||||
refreshDeps: [filterTemplateTags.length]
|
||||
}
|
||||
);
|
||||
|
||||
const TemplateCard = useCallback(
|
||||
({ item }: { item: TemplateMarketListItemType }) => {
|
||||
({ item }: { item: AppTemplateSchemaType }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
key={item.id}
|
||||
key={item.templateId}
|
||||
lineHeight={1.5}
|
||||
h="100%"
|
||||
pt={4}
|
||||
@@ -181,7 +185,7 @@ const TemplateMarketModal = ({
|
||||
{item.name}
|
||||
</Box>
|
||||
<Box mr={'-1rem'}>
|
||||
<AppTypeTag type={item.type} />
|
||||
<AppTypeTag type={item.type as AppTypeEnum} />
|
||||
</Box>
|
||||
</HStack>
|
||||
<Box
|
||||
@@ -198,7 +202,7 @@ const TemplateMarketModal = ({
|
||||
|
||||
<Box w={'full'} fontSize={'mini'}>
|
||||
<Box color={'myGray.500'}>{`by ${item.author || feConfigs.systemTitle}`}</Box>
|
||||
<Box
|
||||
<Flex
|
||||
className="buttons"
|
||||
display={'none'}
|
||||
justifyContent={'center'}
|
||||
@@ -209,32 +213,49 @@ const TemplateMarketModal = ({
|
||||
h={'full'}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
bottom={1}
|
||||
height={'40px'}
|
||||
bg={'white'}
|
||||
zIndex={1}
|
||||
gap={2}
|
||||
>
|
||||
{((item.userGuide?.type === 'markdown' && item.userGuide?.content) ||
|
||||
(item.userGuide?.type === 'link' && item.userGuide?.link)) && (
|
||||
<UseGuideModal
|
||||
title={item.name}
|
||||
iconSrc={item.avatar}
|
||||
text={item.userGuide?.content}
|
||||
link={item.userGuide?.link}
|
||||
>
|
||||
{({ onClick }) => (
|
||||
<Button variant={'whiteBase'} h={6} rounded={'sm'} onClick={onClick}>
|
||||
{t('app:templateMarket.template_guide')}
|
||||
</Button>
|
||||
)}
|
||||
</UseGuideModal>
|
||||
)}
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
h={'1.75rem'}
|
||||
borderRadius={'xl'}
|
||||
w={'40%'}
|
||||
onClick={() => onUseTemplate(item.id)}
|
||||
h={6}
|
||||
rounded={'sm'}
|
||||
onClick={() => onUseTemplate(item)}
|
||||
>
|
||||
{t('app:templateMarket.Use')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
</MyBox>
|
||||
);
|
||||
},
|
||||
[onUseTemplate]
|
||||
[feConfigs.systemTitle, onUseTemplate]
|
||||
);
|
||||
|
||||
const isLoading = isLoadingTags || isLoadingTemplates || isCreating;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
onClose={() => onClose && onClose()}
|
||||
onClose={onClose}
|
||||
autoFocus={false}
|
||||
blockScrollOnMount={false}
|
||||
closeOnOverlayClick={false}
|
||||
@@ -263,11 +284,11 @@ const TemplateMarketModal = ({
|
||||
|
||||
<Box flex={'1'} />
|
||||
|
||||
<MySelect
|
||||
<MySelect<TemplateAppType>
|
||||
h={'8'}
|
||||
value={currentAppType}
|
||||
onchange={(value) => {
|
||||
setCurrentAppType(value as AppTypeEnum | 'all');
|
||||
setCurrentAppType(value);
|
||||
}}
|
||||
bg={'myGray.100'}
|
||||
minW={'7rem'}
|
||||
@@ -302,25 +323,24 @@ const TemplateMarketModal = ({
|
||||
</Box>
|
||||
)}
|
||||
</ModalHeader>
|
||||
<MyBox isLoading={isCreating || isLoadingTemplates} flex={'1 0 0'} overflow={'overlay'}>
|
||||
<MyBox isLoading={isLoading} flex={'1 0 0'} h="0">
|
||||
<ModalBody
|
||||
h={'100%'}
|
||||
display={'flex'}
|
||||
bg={'myGray.100'}
|
||||
overflow={'auto'}
|
||||
gap={5}
|
||||
onScroll={handleScroll}
|
||||
px={0}
|
||||
pt={5}
|
||||
>
|
||||
{isPc && (
|
||||
<Flex pl={5} flexDirection={'column'} gap={3}>
|
||||
{templateTags.map((item) => {
|
||||
{filterTemplateTags.map((item) => {
|
||||
return (
|
||||
<Box
|
||||
key={item.id}
|
||||
key={item.typeId}
|
||||
cursor={'pointer'}
|
||||
{...(item.id === currentTag && !currentSearch
|
||||
{...(item.typeId === currentTag && !currentSearch
|
||||
? {
|
||||
bg: 'primary.1',
|
||||
color: 'primary.600'
|
||||
@@ -336,14 +356,14 @@ const TemplateMarketModal = ({
|
||||
fontSize={'sm'}
|
||||
fontWeight={500}
|
||||
onClick={() => {
|
||||
setCurrentTag(item.id);
|
||||
const anchor = document.getElementById(item.id);
|
||||
setCurrentTag(item.typeId);
|
||||
const anchor = document.getElementById(item.typeId);
|
||||
if (anchor) {
|
||||
anchor.scrollIntoView({ behavior: 'auto', block: 'start' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.typeName as any)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
@@ -370,7 +390,15 @@ const TemplateMarketModal = ({
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box pl={[3, 0]} pr={[3, 5]} pt={1} flex={'1'} h={'100%'} overflow={'auto'}>
|
||||
<Box
|
||||
pl={[3, 0]}
|
||||
pr={[3, 5]}
|
||||
pt={1}
|
||||
flex={'1'}
|
||||
h={'100%'}
|
||||
overflow={'auto'}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{currentSearch ? (
|
||||
<>
|
||||
<Box fontSize={'lg'} color={'myGray.900'} mb={4}>
|
||||
@@ -396,7 +424,7 @@ const TemplateMarketModal = ({
|
||||
pb={5}
|
||||
>
|
||||
{templates.map((item) => (
|
||||
<TemplateCard key={item.id} item={item} />
|
||||
<TemplateCard key={item.templateId} item={item} />
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
@@ -407,26 +435,17 @@ const TemplateMarketModal = ({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{templateTags.map((item) => {
|
||||
const currentTemplates = templateList
|
||||
?.filter((template) => template.tags.includes(item.id))
|
||||
.filter((template) => {
|
||||
if (currentAppType === 'all') return true;
|
||||
return template.type === currentAppType;
|
||||
});
|
||||
|
||||
if (currentTemplates.length === 0) return null;
|
||||
|
||||
{filterTemplateTags.map((item) => {
|
||||
return (
|
||||
<Box key={item.id}>
|
||||
<Box key={item.typeId}>
|
||||
<Box
|
||||
id={item.id}
|
||||
id={item.typeId}
|
||||
fontSize={['md', 'lg']}
|
||||
color={'myGray.900'}
|
||||
mb={4}
|
||||
fontWeight={500}
|
||||
>
|
||||
{item.label}
|
||||
{t(item.typeName as any)}
|
||||
</Box>
|
||||
<Grid
|
||||
gridTemplateColumns={[
|
||||
@@ -440,8 +459,8 @@ const TemplateMarketModal = ({
|
||||
alignItems={'stretch'}
|
||||
pb={5}
|
||||
>
|
||||
{currentTemplates.map((item) => (
|
||||
<TemplateCard key={item.id} item={item} />
|
||||
{item.templates.map((item) => (
|
||||
<TemplateCard key={item.templateId} item={item} />
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
@@ -16,7 +16,6 @@ import { useTranslation } from 'next-i18next';
|
||||
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
@@ -37,6 +36,7 @@ import ChatRecordContextProvider, {
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
|
||||
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
|
||||
|
||||
@@ -361,15 +361,14 @@ export async function getServerSideProps(context: any) {
|
||||
const app = await (async () => {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const app = (await MongoOutLink.findOne(
|
||||
return MongoOutLink.findOne(
|
||||
{
|
||||
shareId
|
||||
},
|
||||
'appId showRawSource showNodeStatus'
|
||||
)
|
||||
.populate('appId', 'name avatar intro')
|
||||
.lean()) as OutLinkWithAppType;
|
||||
return app;
|
||||
.populate<{ associatedApp: AppSchema }>('associatedApp', 'name avatar intro')
|
||||
.lean();
|
||||
} catch (error) {
|
||||
addLog.error('getServerSideProps', error);
|
||||
return undefined;
|
||||
@@ -378,10 +377,10 @@ export async function getServerSideProps(context: any) {
|
||||
|
||||
return {
|
||||
props: {
|
||||
appId: String(app?.appId?._id) ?? '',
|
||||
appName: app?.appId?.name ?? 'AI',
|
||||
appAvatar: app?.appId?.avatar ?? '',
|
||||
appIntro: app?.appId?.intro ?? 'AI',
|
||||
appId: String(app?.appId) ?? '',
|
||||
appName: app?.associatedApp?.name ?? 'AI',
|
||||
appAvatar: app?.associatedApp?.avatar ?? '',
|
||||
appIntro: app?.associatedApp?.intro ?? 'AI',
|
||||
showRawSource: app?.showRawSource ?? false,
|
||||
showNodeStatus: app?.showNodeStatus ?? false,
|
||||
shareId: shareId ?? '',
|
||||
|
||||
@@ -158,11 +158,11 @@ const InputDataModal = ({
|
||||
|
||||
const maxToken = useMemo(() => {
|
||||
const vectorModel =
|
||||
vectorModelList.find((item) => item.model === collection.datasetId.vectorModel) ||
|
||||
vectorModelList.find((item) => item.model === collection.dataset.vectorModel) ||
|
||||
vectorModelList[0];
|
||||
|
||||
return vectorModel?.maxToken || 3000;
|
||||
}, [collection.datasetId.vectorModel, vectorModelList]);
|
||||
}, [collection.dataset.vectorModel, vectorModelList]);
|
||||
|
||||
// import new data
|
||||
const { mutate: sureImportData, isLoading: isImporting } = useRequest({
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex, HStack, ModalBody } from '@chakra-ui/react';
|
||||
import { Box, Flex, HStack } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { PluginGroupSchemaType } from '@fastgpt/service/core/app/plugin/type';
|
||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||
|
||||
const PluginCard = ({
|
||||
item,
|
||||
@@ -20,8 +19,6 @@ const PluginCard = ({
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const [currentPlugin, setCurrentPlugin] = useState<NodeTemplateListItemType | null>(null);
|
||||
|
||||
const type = groups.reduce<string | undefined>((acc, group) => {
|
||||
const foundType = group.groupTypes.find((type) => type.typeId === item.templateType);
|
||||
return foundType ? foundType.typeName : acc;
|
||||
@@ -82,51 +79,33 @@ const PluginCard = ({
|
||||
|
||||
<Flex w={'full'} fontSize={'mini'}>
|
||||
<Flex flex={1}>
|
||||
{item.instructions && (
|
||||
<Flex
|
||||
color={'primary.700'}
|
||||
alignItems={'center'}
|
||||
gap={1}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setCurrentPlugin(item)}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
{(item.instructions || item.courseUrl) && (
|
||||
<UseGuideModal
|
||||
title={item.name}
|
||||
iconSrc={item.avatar}
|
||||
text={item.instructions}
|
||||
link={item.courseUrl}
|
||||
>
|
||||
<MyIcon name={'book'} w={'14px'} />
|
||||
{t('app:plugin.Instructions')}
|
||||
</Flex>
|
||||
{({ onClick }) => (
|
||||
<Flex
|
||||
color={'primary.700'}
|
||||
alignItems={'center'}
|
||||
gap={1}
|
||||
cursor={'pointer'}
|
||||
onClick={onClick}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
>
|
||||
<MyIcon name={'book'} w={'14px'} />
|
||||
{t('app:plugin.Instructions')}
|
||||
</Flex>
|
||||
)}
|
||||
</UseGuideModal>
|
||||
)}
|
||||
</Flex>
|
||||
<Box color={'myGray.500'}>{`by ${item.author || feConfigs.systemTitle}`}</Box>
|
||||
</Flex>
|
||||
{currentPlugin && (
|
||||
<InstructionModal currentPlugin={currentPlugin} onClose={() => setCurrentPlugin(null)} />
|
||||
)}
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
const InstructionModal = ({
|
||||
currentPlugin,
|
||||
onClose
|
||||
}: {
|
||||
currentPlugin: NodeTemplateListItemType;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc={currentPlugin.avatar}
|
||||
title={currentPlugin.name}
|
||||
onClose={onClose}
|
||||
minW={'600px'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box border={'base'} borderRadius={'10px'} p={4} minH={'500px'}>
|
||||
<Markdown source={currentPlugin.instructions} />
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(PluginCard);
|
||||
|
||||
@@ -4,14 +4,14 @@ import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/i
|
||||
import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/index.d';
|
||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import { getFastGPTConfigFromDB } from '@fastgpt/service/common/system/config/controller';
|
||||
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
|
||||
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
|
||||
import { isProduction } from '@fastgpt/global/common/system/constants';
|
||||
import { initFastGPTConfig } from '@fastgpt/service/common/system/tools';
|
||||
import json5 from 'json5';
|
||||
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { defaultGroup } from '@fastgpt/web/core/workflow/constants';
|
||||
import { defaultGroup, defaultTemplateTypes } from '@fastgpt/web/core/workflow/constants';
|
||||
import { MongoPluginGroups } from '@fastgpt/service/core/app/plugin/pluginGroupSchema';
|
||||
import { MongoTemplateTypes } from '@fastgpt/service/core/app/templates/templateTypeSchema';
|
||||
|
||||
export const readConfigData = (name: string) => {
|
||||
const splitName = name.split('.');
|
||||
@@ -164,7 +164,7 @@ function getSystemPlugin() {
|
||||
global.communityPlugins = fileTemplates;
|
||||
}
|
||||
|
||||
export async function initSystemPlugins() {
|
||||
export async function initSystemPluginGroups() {
|
||||
try {
|
||||
const { groupOrder, ...restDefaultGroup } = defaultGroup;
|
||||
await MongoPluginGroups.updateOne(
|
||||
@@ -182,3 +182,27 @@ export async function initSystemPlugins() {
|
||||
console.error('Error initializing system plugins:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function initAppTemplateTypes() {
|
||||
try {
|
||||
await Promise.all(
|
||||
defaultTemplateTypes.map((templateType) => {
|
||||
const { typeOrder, ...rest } = templateType;
|
||||
|
||||
return MongoTemplateTypes.updateOne(
|
||||
{
|
||||
typeId: templateType.typeId
|
||||
},
|
||||
{
|
||||
$set: rest
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error initializing system templates:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import { createDatasetTrainingMongoWatch } from '@/service/core/dataset/training
|
||||
import { MongoSystemConfigs } from '@fastgpt/service/common/system/config/schema';
|
||||
import { MongoSystemPlugin } from '@fastgpt/service/core/app/plugin/systemPluginSchema';
|
||||
import { debounce } from 'lodash';
|
||||
import { MongoAppTemplate } from '@fastgpt/service/core/app/templates/templateSchema';
|
||||
import { getAppTemplatesAndLoadThem } from '@fastgpt/templates/register';
|
||||
|
||||
export const startMongoWatch = async () => {
|
||||
reloadConfigWatch();
|
||||
refetchSystemPlugins();
|
||||
createDatasetTrainingMongoWatch();
|
||||
refetchAppTemplates();
|
||||
};
|
||||
|
||||
const reloadConfigWatch = () => {
|
||||
@@ -38,3 +41,18 @@ const refetchSystemPlugins = () => {
|
||||
}, 500)
|
||||
);
|
||||
};
|
||||
|
||||
const refetchAppTemplates = () => {
|
||||
const changeStream = MongoAppTemplate.watch();
|
||||
|
||||
changeStream.on(
|
||||
'change',
|
||||
debounce(async (change) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
getAppTemplatesAndLoadThem(true);
|
||||
} catch (error) {}
|
||||
}, 5000);
|
||||
}, 500)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { isProduction } from '@fastgpt/global/common/system/constants';
|
||||
import { readdirSync, readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Get template from memory or file system
|
||||
const loadTemplateMarketItems = async () => {
|
||||
if (isProduction && global.appMarketTemplates) return global.appMarketTemplates;
|
||||
|
||||
const templatesDir = path.join(process.cwd(), 'public', 'appMarketTemplates');
|
||||
const templateNames = readdirSync(templatesDir);
|
||||
|
||||
global.appMarketTemplates = templateNames.map((name) => {
|
||||
try {
|
||||
const filePath = path.join(templatesDir, name, 'template.json');
|
||||
const fileContent = readFileSync(filePath, 'utf-8');
|
||||
const data = JSON.parse(fileContent);
|
||||
return {
|
||||
id: name,
|
||||
...data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error fetching template ${name}:`, error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
global.appMarketTemplates.sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0));
|
||||
|
||||
return global.appMarketTemplates;
|
||||
};
|
||||
|
||||
export const getTemplateMarketItemDetail = async (id: string) => {
|
||||
const templateMarketItems = await loadTemplateMarketItems();
|
||||
return templateMarketItems.find((item) => item.id === id);
|
||||
};
|
||||
|
||||
export const getTemplateMarketItemList = async () => {
|
||||
const templateMarketItems = await loadTemplateMarketItems();
|
||||
return templateMarketItems.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
avatar: item.avatar,
|
||||
intro: item.intro,
|
||||
author: item.author,
|
||||
tags: item.tags,
|
||||
type: item.type
|
||||
}));
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@fastgpt/service/support/permission/auth/team';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
@@ -39,7 +39,7 @@ export const getScheduleTriggerApp = async () => {
|
||||
if (!app.scheduledTriggerConfig) return;
|
||||
// random delay 0 ~ 60s
|
||||
await delay(Math.floor(Math.random() * 60 * 1000));
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(app.tmbId);
|
||||
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(app.tmbId);
|
||||
|
||||
// Get app latest version
|
||||
const { nodes, edges, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
@@ -57,7 +57,8 @@ export const getScheduleTriggerApp = async () => {
|
||||
const { flowUsages, assistantResponses, flowResponses } = await retryFn(() => {
|
||||
return dispatchWorkFlow({
|
||||
chatId,
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
mode: 'chat',
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from '@fastgpt/global/support/outLink/api.d';
|
||||
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { authOutLinkValid } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@fastgpt/service/support/permission/auth/team';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
|
||||
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
|
||||
@@ -57,7 +57,7 @@ export async function authOutLinkChatStart({
|
||||
const { outLinkConfig, appId } = await authOutLinkValid({ shareId });
|
||||
|
||||
// check ai points and chat limit
|
||||
const [{ user }, { uid }] = await Promise.all([
|
||||
const [{ timezone, externalProvider }, { uid }] = await Promise.all([
|
||||
getUserChatInfoAndAuthTeamPoints(outLinkConfig.tmbId),
|
||||
authOutLinkChatLimit({ outLink: outLinkConfig, ip, outLinkUid, question })
|
||||
]);
|
||||
@@ -69,7 +69,8 @@ export async function authOutLinkChatStart({
|
||||
authType: AuthUserTypeEnum.token,
|
||||
responseDetail: outLinkConfig.responseDetail,
|
||||
showNodeStatus: outLinkConfig.showNodeStatus,
|
||||
user,
|
||||
timezone,
|
||||
externalProvider,
|
||||
appId,
|
||||
uid
|
||||
};
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
|
||||
import { TeamMemberWithUserSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { checkTeamAIPoints } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { GET } from '@fastgpt/service/common/api/plusRequest';
|
||||
import {
|
||||
AuthTeamTagTokenProps,
|
||||
@@ -9,20 +6,6 @@ import {
|
||||
} from '@fastgpt/global/support/user/team/tag';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
|
||||
export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
|
||||
const tmb = (await MongoTeamMember.findById(tmbId, 'teamId userId').populate(
|
||||
'userId',
|
||||
'timezone openaiAccount'
|
||||
)) as TeamMemberWithUserSchema;
|
||||
if (!tmb) return Promise.reject(UserErrEnum.unAuthUser);
|
||||
|
||||
await checkTeamAIPoints(tmb.teamId);
|
||||
|
||||
return {
|
||||
user: tmb.userId
|
||||
};
|
||||
}
|
||||
|
||||
export function authTeamTagToken(data: AuthTeamTagTokenProps) {
|
||||
return GET<AuthTokenFromTeamDomainResponse['data']>('/support/user/team/tag/authTeamToken', data);
|
||||
}
|
||||
|
||||
3
projects/app/src/types/user.d.ts
vendored
3
projects/app/src/types/user.d.ts
vendored
@@ -1,11 +1,8 @@
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||
import { LafAccountType } from '@fastgpt/global/support/user/team/type.d';
|
||||
|
||||
export interface UserUpdateParams {
|
||||
balance?: number;
|
||||
avatar?: string;
|
||||
timezone?: string;
|
||||
openaiAccount?: UserModelSchema['openaiAccount'];
|
||||
lafAccount?: LafAccountType;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ function checkMaxQuantity({ url, maxQuantity }: { url: string; maxQuantity?: num
|
||||
|
||||
if (item) {
|
||||
if (item.amount >= maxQuantity) {
|
||||
item.sign?.abort?.();
|
||||
!item.sign?.signal?.aborted && item.sign?.abort?.();
|
||||
maxQuantityMap[url] = {
|
||||
amount: 1,
|
||||
sign: controller
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AppLogsListItemType } from '@/types/app';
|
||||
import { PagingData } from '@/types';
|
||||
|
||||
/**
|
||||
* 获取模型列表
|
||||
* 获取应用列表
|
||||
*/
|
||||
export const getMyApps = (data?: ListAppBody) =>
|
||||
POST<AppListItemType[]>('/core/app/list', data, {
|
||||
@@ -16,23 +16,23 @@ export const getMyApps = (data?: ListAppBody) =>
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建一个模型
|
||||
* 创建一个应用
|
||||
*/
|
||||
export const postCreateApp = (data: CreateAppBody) => POST<string>('/core/app/create', data);
|
||||
|
||||
export const getMyAppsByTags = (data: {}) => POST(`/proApi/core/chat/team/getApps`, data);
|
||||
/**
|
||||
* 根据 ID 删除模型
|
||||
* 根据 ID 删除应用
|
||||
*/
|
||||
export const delAppById = (id: string) => DELETE(`/core/app/del?appId=${id}`);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取模型
|
||||
* 根据 ID 获取应用
|
||||
*/
|
||||
export const getAppDetailById = (id: string) => GET<AppDetailType>(`/core/app/detail?appId=${id}`);
|
||||
|
||||
/**
|
||||
* 根据 ID 更新模型
|
||||
* 根据 ID 更新应用
|
||||
*/
|
||||
export const putAppById = (id: string, data: AppUpdateParams) =>
|
||||
PUT(`/core/app/update?appId=${id}`, data);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ListParams } from '@/pages/api/core/app/template/list';
|
||||
import { GET } from '@/web/common/api/request';
|
||||
import {
|
||||
TemplateMarketItemType,
|
||||
TemplateMarketListItemType
|
||||
} from '@fastgpt/global/core/workflow/type';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { AppTemplateSchemaType, TemplateTypeSchemaType } from '@fastgpt/global/core/app/type';
|
||||
import { defaultTemplateTypes } from '@fastgpt/web/core/workflow/constants';
|
||||
|
||||
export const getTemplateMarketItemList = () =>
|
||||
GET<TemplateMarketListItemType[]>('/core/app/template/list');
|
||||
export const getTemplateMarketItemList = (data: ListParams) =>
|
||||
GET<AppTemplateSchemaType[]>(`/core/app/template/list`, data);
|
||||
|
||||
export const getTemplateMarketItemDetail = (data: { templateId: string }) =>
|
||||
GET<TemplateMarketItemType>(`/core/app/template/detail`, data);
|
||||
export const getTemplateMarketItemDetail = (templateId: string) =>
|
||||
GET<AppTemplateSchemaType>(`/core/app/template/detail?templateId=${templateId}`);
|
||||
|
||||
export const getTemplateTagList = () => {
|
||||
return useSystemStore.getState()?.feConfigs?.isPlus
|
||||
? GET<TemplateTypeSchemaType[]>('/proApi/core/app/template/getTemplateTypes')
|
||||
: Promise.resolve(defaultTemplateTypes);
|
||||
};
|
||||
|
||||
@@ -56,3 +56,5 @@ export enum TTSTypeEnum {
|
||||
web = 'web',
|
||||
model = 'model'
|
||||
}
|
||||
|
||||
export const workflowStartNodeId = 'workflowStartNodeId';
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
Input_Template_File_Link_Prompt,
|
||||
Input_Template_UserChatInput
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
import { workflowStartNodeId } from './constants';
|
||||
|
||||
type WorkflowType = {
|
||||
nodes: StoreNodeItemType[];
|
||||
@@ -50,7 +51,6 @@ export function form2AppWorkflow(
|
||||
): WorkflowType & {
|
||||
chatConfig: AppChatConfigType;
|
||||
} {
|
||||
const workflowStartNodeId = 'workflowStartNodeId';
|
||||
const datasetNodeId = 'iKBoX2vIzETU';
|
||||
const aiChatNodeId = '7BdojPlukIQw';
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ export const defaultCollectionDetail: DatasetCollectionItemType = {
|
||||
_id: '',
|
||||
teamId: '',
|
||||
tmbId: '',
|
||||
datasetId: {
|
||||
datasetId: '',
|
||||
dataset: {
|
||||
_id: '',
|
||||
parentId: '',
|
||||
userId: '',
|
||||
|
||||
Reference in New Issue
Block a user