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:
Archer
2024-12-27 20:05:12 +08:00
committed by GitHub
parent a209856d48
commit b520988c64
207 changed files with 2943 additions and 1378 deletions

View File

@@ -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'),

View File

@@ -92,7 +92,7 @@ const TeamSelector = ({
: []),
...teamList
];
}, [showManage, teamList, router]);
}, [showManage, t, teamList, router]);
return (
<Box w={'100%'}>

View File

@@ -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>
);
};

View File

@@ -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"

View File

@@ -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>

View 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);

View 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;