V4.8.15 feature (#3331)
* feat: add customize toolkit (#3205) * chaoyang * fix-auth * add toolkit * add order * plugin usage * fix * delete console: * Fix: Fix fullscreen preview top positioning and improve Markdown rendering logic (#3247) * 完成任务:修复全屏预览顶部固定问题,优化 Markdown 渲染逻辑 * 有问题修改 * 问题再修改 * 修正问题 * fix: plugin standalone display issue (#3254) * 4.8.15 test (#3246) * o1 config * perf: system plugin code * 调整系统插件代码。增加html 渲染安全配置。 (#3258) * perf: base64 picker * perf: list app or dataset * perf: plugin config code * 小窗适配等问题 (#3257) * 小窗适配等问题 * git问题 * 小窗剩余问题 * feat: system plugin auth and lock version (#3265) * feat: system plugin auth and lock version * update comment * 4.8.15 test (#3267) * tmp log * perf: login direct * perf: iframe html code * remove log * fix: plugin standalone display (#3277) * refactor: 页面拆分&i18n拆分 (#3281) * refactor: account组件拆成独立页面 * script: 新增i18n json文件创建脚本 * refactor: 页面i18n拆分 * i18n: add en&hant * 4.8.15 test (#3285) * tmp log * remove log * fix: watch avatar refresh * perf: i18n code * fix(plugin): use intro instead of userguide (#3290) * Universal SSO (#3292) * tmp log * remove log * feat: common oauth * readme * perf: sso provider * remove sso code * perf: refresh plugins * feat: add api dataset (#3272) * add api-dataset * fix api-dataset * fix api dataset * fix ts * perf: create collection code (#3301) * tmp log * remove log * perf: i18n change * update version doc * feat: question guide from chatId * perf: create collection code * fix: request api * fix: request api * fix: tts auth and response type (#3303) * perf: md splitter * fix: tts auth and response type * fix: api file dataset (#3307) * perf: api dataset init (#3310) * perf: collection schema * perf: api dataset init * refactor: 团队管理独立页面 (#3302) * ui: 团队管理独立页面 * 代码优化 * fix * perf: sync collection and ui check (#3314) * perf: sync collection * remove script * perf: update api server * perf: api dataset parent * perf: team ui * perf: team 18n * update team ui * perf: ui check * perf: i18n * fix: debug variables & cronjob & system plugin callback load (#3315) * fix: debug variables & cronjob & system plugin callback load * fix type * fix * fix * fix: plugin dataset quote;perf: system variables init (#3316) * fix: plugin dataset quote * perf: system variables init * perf: node templates ui;fix: dataset import ui (#3318) * fix: dataset import ui * perf: node templates ui * perf: ui refresh * feat:套餐改名和套餐跳转配置 (#3309) * fixing:except Sidebar * 去除了多余的代码 * 修正了套餐说明的代码 * 修正了误删除的show_git代码 * 修正了名字部分等代码 * 修正了问题,遗留了其他和ui讨论不一致的部分 * 4.8.15 test (#3319) * remove log * pref: bill ui * pref: bill ui * perf: log * html渲染文档 (#3270) * html渲染文档 * 文档有点小问题 * feat: doc (#3322) * 集合重训练 (#3282) * rebaser * 一点补充 * 小问题 * 其他问题修正,删除集合保留文件的参数还没找到... * reTraining * delete uesless * 删除了一行错误代码 * 集合重训练部分 * fixing * 删除console代码 * feat: navbar item config (#3326) * perf: custom navbar code;perf: retraining code;feat: api dataset and dataset api doc (#3329) * feat: api dataset and dataset api doc * perf: retraining code * perf: custom navbar code * fix: ts (#3330) * fix: ts * fix: ts * retraining ui * perf: api collection filter * perf: retrining button --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com> Co-authored-by: papapatrick <109422393+Patrickill@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ModalBody, Box, Button, VStack, HStack, Link } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { balanceConversion } from '@/web/support/wallet/bill/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { SUB_EXTRA_POINT_RATE } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const ConversionModal = ({
|
||||
onClose,
|
||||
onOpenContact
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onOpenContact: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
const points = useMemo(() => {
|
||||
if (!userInfo?.team?.balance) return 0;
|
||||
const balance = formatStorePrice2Read(userInfo?.team?.balance);
|
||||
|
||||
return Math.ceil((balance / 15) * SUB_EXTRA_POINT_RATE);
|
||||
}, []);
|
||||
|
||||
const { runAsync: onConvert, loading } = useRequest2(balanceConversion, {
|
||||
onSuccess() {
|
||||
router.reload();
|
||||
},
|
||||
successToast: t('account_info:exchange_success'),
|
||||
errorToast: t('account_info:exchange_failure')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="support/bill/wallet"
|
||||
iconColor="primary.600"
|
||||
title={t('account_info:usage_balance')}
|
||||
>
|
||||
<ModalBody maxW={'450px'}>
|
||||
<VStack px="2.25" gap={2} pb="6">
|
||||
<HStack px="4" py="2" color="primary.600" bgColor="primary.50" borderRadius="md">
|
||||
<Icon name="common/info" w="1rem" mr="1" />
|
||||
<Box fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('account_info:usage_balance_notice')}
|
||||
</Box>
|
||||
</HStack>
|
||||
<VStack mt={6}>
|
||||
<Box fontSize={'sm'} color="myGray.600" fontWeight="500">
|
||||
{t('account_info:current_token_price')}
|
||||
</Box>
|
||||
<Box fontSize={'xl'} fontWeight={'700'} color="myGray.900">
|
||||
¥15/1000 {t('account_info:tokens')}/{t('account_info:month')}
|
||||
</Box>
|
||||
</VStack>
|
||||
<VStack mt={6}>
|
||||
<Box fontSize={'sm'} color="myGray.600" fontWeight="500">
|
||||
{t('account_info:balance')}
|
||||
</Box>
|
||||
<Box fontSize={'xl'} fontWeight={'700'} color="myGray.900">
|
||||
¥{formatStorePrice2Read(userInfo?.team?.balance)?.toFixed(2)}
|
||||
</Box>
|
||||
</VStack>
|
||||
<VStack mt={6}>
|
||||
<Box fontSize={'sm'} color="myGray.600" fontWeight="500">
|
||||
{t('account_info:you_can_convert')}
|
||||
</Box>
|
||||
<Box fontSize={'xl'} fontWeight={'700'} color="myGray.900">
|
||||
{points} {t('account_info:tokens')}
|
||||
</Box>
|
||||
<Tag fontSize={'xs'} fontWeight={'500'}>
|
||||
{t('account_info:token_validity_period')}
|
||||
</Tag>
|
||||
</VStack>
|
||||
|
||||
<VStack mt="6">
|
||||
<Button
|
||||
variant={'primary'}
|
||||
alignItems={'center'}
|
||||
fontSize={'sm'}
|
||||
minW={'10rem'}
|
||||
onClick={onConvert}
|
||||
isLoading={loading}
|
||||
>
|
||||
{t('account_info:exchange')}
|
||||
</Button>
|
||||
<Link fontSize={'sm'} color="primary" mt="2" onClick={onOpenContact}>
|
||||
{t('account_info:contact_customer_service')}
|
||||
</Link>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConversionModal;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
|
||||
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';
|
||||
|
||||
const OpenAIAccountModal = ({
|
||||
defaultData,
|
||||
onSuccess,
|
||||
onClose
|
||||
}: {
|
||||
defaultData: UserType['openaiAccount'];
|
||||
onSuccess: (e: UserType['openaiAccount']) => Promise<any>;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const { mutate: onSubmit, isLoading } = useRequest({
|
||||
mutationFn: async (data: UserType['openaiAccount']) => onSuccess(data),
|
||||
onSuccess(res) {
|
||||
onClose();
|
||||
},
|
||||
errorToast: t('account_info:openai_account_setting_exception')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="common/openai"
|
||||
title={t('account_info:openai_account_configuration')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('account_info:open_api_notice')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 65px'}>API Key:</Box>
|
||||
<Input flex={1} {...register('key')}></Input>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 65px'}>BaseUrl:</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
{...register('baseUrl')}
|
||||
placeholder={t('account_info:request_address_notice')}
|
||||
></Input>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isLoading} onClick={handleSubmit((data) => onSubmit(data))}>
|
||||
{t('account_info:confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default OpenAIAccountModal;
|
||||
@@ -0,0 +1,120 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
Box,
|
||||
Flex,
|
||||
Input,
|
||||
ModalFooter,
|
||||
Button,
|
||||
HStack,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { updateNotificationAccount } from '@/web/support/user/api';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
type FormType = {
|
||||
account: string;
|
||||
verifyCode: string;
|
||||
};
|
||||
|
||||
const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { initUserInfo } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, handleSubmit, watch } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
account: '',
|
||||
verifyCode: ''
|
||||
}
|
||||
});
|
||||
const account = watch('account');
|
||||
const verifyCode = watch('verifyCode');
|
||||
|
||||
const { runAsync: onSubmit, loading: isLoading } = useRequest2(
|
||||
(data: FormType) => {
|
||||
return updateNotificationAccount(data);
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
initUserInfo();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('account_info:bind_notification_success'),
|
||||
errorToast: t('account_info:bind_notification_error')
|
||||
}
|
||||
);
|
||||
|
||||
const { SendCodeBox } = useSendCode({ type: 'bindNotification' });
|
||||
|
||||
const placeholder = feConfigs?.bind_notification_method
|
||||
?.map((item) => {
|
||||
switch (item) {
|
||||
case 'email':
|
||||
return t('account_info:email_label');
|
||||
case 'phone':
|
||||
return t('account_info:phone_label');
|
||||
}
|
||||
})
|
||||
.join('/');
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="common/settingLight"
|
||||
w={'32rem'}
|
||||
title={t('account_info:notification_receiving_hint')}
|
||||
>
|
||||
<ModalBody px={10}>
|
||||
<Flex flexDirection="column">
|
||||
<HStack px="6" py="3" color="primary.600" bgColor="primary.50" borderRadius="md">
|
||||
<Icon name="common/info" w="1rem" />
|
||||
<Box fontSize={'sm'}>{t('account_info:bind_notification_hint')}</Box>
|
||||
</HStack>
|
||||
<Flex mt="4" alignItems="center">
|
||||
<Box flex={'0 0 70px'}>{t('account_info:user_account')}</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
bg={'myGray.50'}
|
||||
{...register('account', { required: true })}
|
||||
placeholder={placeholder}
|
||||
></Input>
|
||||
</Flex>
|
||||
<Flex mt="6" alignItems="center" position={'relative'}>
|
||||
<Box flex={'0 0 70px'}>{t('account_info:verification_code_required')}</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
bg={'myGray.50'}
|
||||
{...register('verifyCode', { required: true })}
|
||||
placeholder={t('account_info:code_required')}
|
||||
></Input>
|
||||
<SendCodeBox username={account} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
isDisabled={!account || !verifyCode}
|
||||
onClick={handleSubmit((data) => onSubmit(data))}
|
||||
>
|
||||
{t('account_info:confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateNotificationModal;
|
||||
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
|
||||
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 { updatePasswordByOld } from '@/web/support/user/api';
|
||||
|
||||
type FormType = {
|
||||
oldPsw: string;
|
||||
newPsw: string;
|
||||
confirmPsw: string;
|
||||
};
|
||||
|
||||
const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
oldPsw: '',
|
||||
newPsw: '',
|
||||
confirmPsw: ''
|
||||
}
|
||||
});
|
||||
|
||||
const { mutate: onSubmit, isLoading } = useRequest({
|
||||
mutationFn: (data: FormType) => {
|
||||
if (data.newPsw !== data.confirmPsw) {
|
||||
return Promise.reject(t('account_info:password_mismatch'));
|
||||
}
|
||||
return updatePasswordByOld(data);
|
||||
},
|
||||
onSuccess() {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('account_info:password_update_success'),
|
||||
errorToast: t('account_info:password_update_error')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/password.svg"
|
||||
title={t('account_info:update_password')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('account_info:old_password') + ':'}</Box>
|
||||
<Input flex={1} type={'password'} {...register('oldPsw', { required: true })}></Input>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 70px'}>{t('account_info:new_password') + ':'}</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'password'}
|
||||
{...register('newPsw', {
|
||||
required: true,
|
||||
maxLength: {
|
||||
value: 60,
|
||||
message: t('account_info:password_length_error')
|
||||
}
|
||||
})}
|
||||
></Input>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 70px'}>{t('account_info:confirm_password') + ':'}</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'password'}
|
||||
{...register('confirmPsw', {
|
||||
required: true,
|
||||
maxLength: {
|
||||
value: 60,
|
||||
message: t('account_info:password_length_error')
|
||||
}
|
||||
})}
|
||||
></Input>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isLoading} onClick={handleSubmit((data) => onSubmit(data))}>
|
||||
{t('account_info:confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdatePswModal;
|
||||
@@ -0,0 +1,193 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
ModalCloseButton,
|
||||
HStack,
|
||||
Box,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { getTeamPlans } from '@/web/support/user/team/api';
|
||||
import {
|
||||
subTypeMap,
|
||||
standardSubLevelMap,
|
||||
SubTypeEnum
|
||||
} from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
type packageStatus = 'active' | 'inactive' | 'expired';
|
||||
|
||||
const StandDetailModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const { subPlans } = useSystemStore();
|
||||
const { data: teamPlans = [], loading: isLoading } = useRequest2(
|
||||
() =>
|
||||
getTeamPlans().then((res) => {
|
||||
return [
|
||||
...res.filter((plan) => plan.type === SubTypeEnum.standard),
|
||||
...res.filter((plan) => plan.type === SubTypeEnum.extraDatasetSize),
|
||||
...res.filter((plan) => plan.type === SubTypeEnum.extraPoints)
|
||||
].map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
status:
|
||||
new Date(item.expiredTime).getTime() < new Date().getTime()
|
||||
? 'expired'
|
||||
: item.type === SubTypeEnum.standard
|
||||
? index === 0
|
||||
? 'active'
|
||||
: 'inactive'
|
||||
: 'active'
|
||||
};
|
||||
});
|
||||
}),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
maxW={['90vw', '1200px']}
|
||||
iconSrc="modal/teamPlans"
|
||||
title={t('account_info:package_details')}
|
||||
isCentered
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody px={[4, 8]} py={[2, 6]}>
|
||||
<TableContainer mt={2} position={'relative'} minH={'300px'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_info:type')}</Th>
|
||||
<Th>{t('account_info:storage_capacity')}</Th>
|
||||
<Th>{t('account_info:ai_points')}</Th>
|
||||
<Th>{t('account_info:effective_time')}</Th>
|
||||
<Th>{t('account_info:expiration_time')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{teamPlans.map(
|
||||
({
|
||||
_id,
|
||||
type,
|
||||
currentSubLevel,
|
||||
currentExtraDatasetSize,
|
||||
surplusPoints = 0,
|
||||
totalPoints = 0,
|
||||
startTime,
|
||||
expiredTime,
|
||||
status
|
||||
}) => {
|
||||
const standardPlan = currentSubLevel
|
||||
? subPlans?.standard?.[currentSubLevel]
|
||||
: undefined;
|
||||
const datasetSize = standardPlan?.maxDatasetSize || currentExtraDatasetSize;
|
||||
|
||||
return (
|
||||
<Tr key={_id} fontWeight={500} fontSize={'mini'} color={'myGray.900'}>
|
||||
<Td>
|
||||
<Flex>
|
||||
<Flex align={'center'}>
|
||||
<MyIcon
|
||||
mr={2}
|
||||
name={subTypeMap[type]?.icon as any}
|
||||
w={'20px'}
|
||||
h={'20px'}
|
||||
color={'myGray.600'}
|
||||
fontWeight={500}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex align={'center'} color={'myGray.900'}>
|
||||
{t(subTypeMap[type]?.label as any)}
|
||||
{currentSubLevel &&
|
||||
`(${t(standardSubLevelMap[currentSubLevel]?.label as any)})`}
|
||||
</Flex>
|
||||
<StatusTag status={status as packageStatus} />
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{datasetSize ? `${datasetSize + t('account_info:group')}` : '-'}</Td>
|
||||
<Td>
|
||||
{totalPoints
|
||||
? `${Math.round(totalPoints - surplusPoints)} / ${totalPoints} ${t('account_info:ai_points_calculation_standard')}`
|
||||
: '-'}
|
||||
</Td>
|
||||
<Td color={'myGray.600'}>{formatTime2YMDHM(startTime)}</Td>
|
||||
<Td color={'myGray.600'}>{formatTime2YMDHM(expiredTime)}</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
)}
|
||||
<Tr key={'_id'}></Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</TableContainer>
|
||||
<HStack mt={4} color={'primary.700'}>
|
||||
<MyIcon name={'infoRounded'} w={'1rem'} />
|
||||
<Box fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('account_info:package_usage_rules')}
|
||||
</Box>
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
function StatusTag({ status }: { status: packageStatus }) {
|
||||
const { t } = useTranslation();
|
||||
const statusText = useMemo(() => {
|
||||
return {
|
||||
inactive: t('account_info:pending_usage'),
|
||||
active: t('account_info:active'),
|
||||
expired: t('account_info:expired')
|
||||
};
|
||||
}, [t]);
|
||||
const styleMap = useMemo(() => {
|
||||
return {
|
||||
inactive: {
|
||||
color: 'adora.600',
|
||||
bg: 'adora.50'
|
||||
},
|
||||
active: {
|
||||
color: 'green.600',
|
||||
bg: 'green.50'
|
||||
},
|
||||
expired: {
|
||||
color: 'myGray.700',
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<Box
|
||||
py={'0.25rem'}
|
||||
ml={'0.375rem'}
|
||||
px={'0.5rem'}
|
||||
fontSize={'0.625rem'}
|
||||
fontWeight={500}
|
||||
borderRadius={'sm'}
|
||||
bg={styleMap[status]?.bg}
|
||||
color={styleMap[status]?.color}
|
||||
>
|
||||
{statusText[status]}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default StandDetailModal;
|
||||
737
projects/app/src/pages/account/info/index.tsx
Normal file
737
projects/app/src/pages/account/info/index.tsx
Normal file
@@ -0,0 +1,737 @@
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useTheme,
|
||||
Input,
|
||||
Link,
|
||||
Progress,
|
||||
Grid,
|
||||
BoxProps,
|
||||
FlexProps
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { putUpdateMemberName } from '@/web/support/user/team/api';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import {
|
||||
StandardSubLevelEnum,
|
||||
standardSubLevelMap
|
||||
} from '@fastgpt/global/support/wallet/sub/constants';
|
||||
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 '@/web/common/utils/i18n';
|
||||
import { useRouter } from 'next/router';
|
||||
import TeamSelector from '../components/TeamSelector';
|
||||
|
||||
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 AiPointsModal = dynamic(() =>
|
||||
import('@/pages/price/components/Points').then((mod) => mod.AiPointsModal)
|
||||
);
|
||||
|
||||
const Info = () => {
|
||||
const { isPc } = useSystem();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standardConstants;
|
||||
const { isOpen: isOpenContact, onClose: onCloseContact, onOpen: onOpenContact } = useDisclosure();
|
||||
const { initUserInfo } = useUserStore();
|
||||
|
||||
useQuery(['init'], initUserInfo);
|
||||
|
||||
return (
|
||||
<AccountContainer>
|
||||
<Box py={[3, '28px']} px={[5, 10]} mx={'auto'}>
|
||||
{isPc ? (
|
||||
<Flex justifyContent={'center'} maxW={'1080px'}>
|
||||
<Box flex={'0 0 330px'}>
|
||||
<MyInfo onOpenContact={onOpenContact} />
|
||||
<Box mt={9}>
|
||||
<Other onOpenContact={onOpenContact} />
|
||||
</Box>
|
||||
</Box>
|
||||
{!!standardPlan && (
|
||||
<Box ml={'45px'} flex={'1'} maxW={'600px'}>
|
||||
<PlanUsage />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<MyInfo onOpenContact={onOpenContact} />
|
||||
{standardPlan && <PlanUsage />}
|
||||
<Other onOpenContact={onOpenContact} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{isOpenContact && <CommunityModal onClose={onCloseContact} />}
|
||||
</AccountContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
...(await serviceSideProps(content, ['account', 'account_info', 'user']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default React.memo(Info);
|
||||
|
||||
const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo } = useUserStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standardConstants;
|
||||
const { isPc } = useSystem();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
isOpen: isOpenConversionModal,
|
||||
onClose: onCloseConversionModal,
|
||||
onOpen: onOpenConversionModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenUpdatePsw,
|
||||
onClose: onCloseUpdatePsw,
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenUpdateNotification,
|
||||
onClose: onCloseUpdateNotification,
|
||||
onOpen: onOpenUpdateNotification
|
||||
} = useDisclosure();
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
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 onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
if (!file || !userInfo) return;
|
||||
try {
|
||||
const src = await compressImgFileAndUpload({
|
||||
type: MongoImageTypeEnum.userAvatar,
|
||||
file,
|
||||
maxW: 300,
|
||||
maxH: 300
|
||||
});
|
||||
|
||||
onclickSave({
|
||||
...userInfo,
|
||||
avatar: src
|
||||
});
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : t('account_info:avatar_selection_exception'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
},
|
||||
[onclickSave, t, toast, userInfo]
|
||||
);
|
||||
|
||||
const labelStyles: BoxProps = {
|
||||
flex: '0 0 80px',
|
||||
fontSize: 'sm',
|
||||
color: 'myGray.900'
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* user info */}
|
||||
{isPc && (
|
||||
<Flex alignItems={'center'} fontSize={'md'} h={'30px'}>
|
||||
<MyIcon mr={2} name={'support/user/userLight'} w={'1.25rem'} />
|
||||
{t('account_info:personal_information')}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box mt={[0, 6]} fontSize={'sm'}>
|
||||
{isPc ? (
|
||||
<Flex alignItems={'center'} cursor={'pointer'}>
|
||||
<Box {...labelStyles}>{t('account_info:avatar')}: </Box>
|
||||
|
||||
<MyTooltip label={t('account_info:select_avatar')}>
|
||||
<Box
|
||||
w={['44px', '56px']}
|
||||
h={['44px', '56px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<MyTooltip label={t('account_info:choose_avatar')}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('account_info:change')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex mt={[0, 4]} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:member_name')}: </Box>
|
||||
<Input
|
||||
flex={'1 0 0'}
|
||||
defaultValue={userInfo?.team?.memberName || 'Member'}
|
||||
title={t('account_info:click_modify_nickname')}
|
||||
borderColor={'transparent'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
const val = e.target.value;
|
||||
if (val === userInfo?.team?.memberName) return;
|
||||
try {
|
||||
putUpdateMemberName(val);
|
||||
} catch (error) {}
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} mt={6}>
|
||||
<Box {...labelStyles}>{t('account_info:user_account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
|
||||
{t('account_info:change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:notification_receiving')}: </Box>
|
||||
<Box
|
||||
flex={1}
|
||||
{...(!userInfo?.team.notificationAccount && userInfo?.permission.isOwner
|
||||
? { color: 'red.600' }
|
||||
: {})}
|
||||
>
|
||||
{userInfo?.team.notificationAccount
|
||||
? userInfo?.team.notificationAccount
|
||||
: userInfo?.permission.isOwner
|
||||
? t('account_info:please_bind_notification_receiving_path')
|
||||
: t('account_info:reminder_create_bound_notification_account')}
|
||||
</Box>
|
||||
|
||||
{userInfo?.permission.isOwner && (
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdateNotification}>
|
||||
{t('account_info:change')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:user_team_team_name')}: </Box>
|
||||
<Flex flex={'1 0 0'} w={0} align={'center'}>
|
||||
<TeamSelector height={'28px'} w={'100%'} showManage />
|
||||
</Flex>
|
||||
</Flex>
|
||||
{feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && (
|
||||
<Box mt={6} whiteSpace={'nowrap'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:team_balance')}: </Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong>{' '}
|
||||
{t('account_info:yuan')}
|
||||
</Box>
|
||||
|
||||
{userInfo?.permission.hasManagePer && !!standardPlan && (
|
||||
<Button variant={'primary'} size={'sm'} ml={5} onClick={onOpenConversionModal}>
|
||||
{t('account_info:exchange')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{isOpenConversionModal && (
|
||||
<ConversionModal onClose={onCloseConversionModal} onOpenContact={onOpenContact} />
|
||||
)}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenUpdateNotification && <UpdateNotification onClose={onCloseUpdateNotification} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PlanUsage = () => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
|
||||
const { subPlans } = useSystemStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
|
||||
const {
|
||||
isOpen: isOpenStandardModal,
|
||||
onClose: onCloseStandardModal,
|
||||
onOpen: onOpenStandardModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const planName = useMemo(() => {
|
||||
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
|
||||
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
|
||||
}, [teamPlanStatus?.standard?.currentSubLevel]);
|
||||
const standardPlan = teamPlanStatus?.standard;
|
||||
const isFreeTeam = useMemo(() => {
|
||||
if (!teamPlanStatus || !teamPlanStatus?.standardConstants) return false;
|
||||
const hasExtraDatasetSize =
|
||||
teamPlanStatus.datasetMaxSize > teamPlanStatus.standardConstants.maxDatasetSize;
|
||||
const hasExtraPoints =
|
||||
teamPlanStatus.totalPoints > teamPlanStatus.standardConstants.totalPoints;
|
||||
if (
|
||||
teamPlanStatus?.standard?.currentSubLevel === StandardSubLevelEnum.free &&
|
||||
!hasExtraDatasetSize &&
|
||||
!hasExtraPoints
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [teamPlanStatus]);
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
if (!teamPlanStatus) {
|
||||
return {
|
||||
colorScheme: 'green',
|
||||
value: 0,
|
||||
maxSize: t('account_info:unlimited'),
|
||||
usedSize: 0
|
||||
};
|
||||
}
|
||||
const rate = teamPlanStatus.usedDatasetSize / teamPlanStatus.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamPlanStatus.datasetMaxSize || t('account_info:unlimited'),
|
||||
usedSize: teamPlanStatus.usedDatasetSize
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
const aiPointsUsageMap = useMemo(() => {
|
||||
if (!teamPlanStatus) {
|
||||
return {
|
||||
colorScheme: 'green',
|
||||
value: 0,
|
||||
maxSize: t('account_info:unlimited'),
|
||||
usedSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
const rate = teamPlanStatus.usedPoints / teamPlanStatus.totalPoints;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
max: teamPlanStatus.totalPoints ? teamPlanStatus.totalPoints : t('account_info:unlimited'),
|
||||
used: teamPlanStatus.usedPoints ? Math.round(teamPlanStatus.usedPoints) : 0
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
|
||||
return standardPlan ? (
|
||||
<Box mt={[6, 0]}>
|
||||
<Flex fontSize={['md', 'lg']} h={'30px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<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>
|
||||
<Button ml={4} variant={'whitePrimary'} size={'sm'} onClick={onOpenStandardModal}>
|
||||
{t('account_info:package_details')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={[3, 6]}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Flex px={[5, 7]} pt={[3, 6]}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Box color={'myGray.600'} fontSize="sm">
|
||||
{t('account_info:current_package')}
|
||||
</Box>
|
||||
<Box fontWeight={'bold'} fontSize="lg">
|
||||
{t(planName as any)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Button
|
||||
onClick={() => {
|
||||
router.push(
|
||||
subPlans?.planDescriptionUrl ? getDocPath(subPlans.planDescriptionUrl) : '/price'
|
||||
);
|
||||
}}
|
||||
w={'8rem'}
|
||||
size="sm"
|
||||
>
|
||||
{t('account_info:upgrade_package')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box px={[5, 7]} pb={[3, 6]}>
|
||||
{isFreeTeam && (
|
||||
<Box mt="2" color={'#485264'} fontSize="sm">
|
||||
{t('account_info:account_knowledge_base_cleanup_warning')}
|
||||
</Box>
|
||||
)}
|
||||
{standardPlan.currentSubLevel !== StandardSubLevelEnum.free && (
|
||||
<Flex mt="2" color={'#485264'} fontSize="xs">
|
||||
<Box>{t('account_info:package_expiry_time')}:</Box>
|
||||
<Box ml={2}>{formatTime2YMD(standardPlan?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box py={3} borderTopWidth={'1px'} borderTopColor={'borderColor.base'}>
|
||||
<Box py={[0, 3]} px={[5, 7]} overflow={'auto'}>
|
||||
<StandardPlanContentList
|
||||
level={standardPlan?.currentSubLevel}
|
||||
mode={standardPlan.currentMode}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
mt={6}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
px={[5, 10]}
|
||||
pt={4}
|
||||
pb={[4, 7]}
|
||||
>
|
||||
<Flex>
|
||||
<Flex flex={'1 0 0'} alignItems={'flex-end'}>
|
||||
<Box fontSize={'md'} fontWeight={'bold'} color={'myGray.900'}>
|
||||
{t('account_info:resource_usage')}
|
||||
</Box>
|
||||
<Box ml={1} display={['none', 'block']} fontSize={'xs'} color={'myGray.500'}>
|
||||
{t('account_info:standard_package_and_extra_resource_package')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Link
|
||||
href={getWebReqUrl(getExtraPlanCardRoute())}
|
||||
transform={'translateX(15px)'}
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
color={'primary.600'}
|
||||
cursor={'pointer'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{t('account_info:purchase_extra_package')}
|
||||
<MyIcon ml={1} name={'common/rightArrowLight'} w={'12px'} />
|
||||
</Link>
|
||||
</Flex>
|
||||
<Box width={'100%'} mt={5} fontSize={'sm'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'} color={'myGray.900'}>
|
||||
{t('account_info:knowledge_base_capacity')}
|
||||
</Box>
|
||||
<Box color={'myGray.600'} ml={2}>
|
||||
{datasetUsageMap.usedSize}/{datasetUsageMap.maxSize}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
size={'sm'}
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt="9" width={'100%'} fontSize={'sm'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'} color={'myGray.900'}>
|
||||
{t('account_info:ai_points_usage')}
|
||||
</Box>
|
||||
<QuestionTip ml={1} label={t('account_info:ai_points_usage_tip')}></QuestionTip>
|
||||
<Box color={'myGray.600'} ml={2}>
|
||||
{aiPointsUsageMap.used}/{aiPointsUsageMap.max}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
size={'sm'}
|
||||
value={aiPointsUsageMap.value}
|
||||
colorScheme={aiPointsUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{isOpenStandardModal && <StandDetailModal onClose={onCloseStandardModal} />}
|
||||
{isOpenAiPointsModal && <AiPointsModal onClose={onCloseAiPointsModal} />}
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
const { userInfo, updateUserInfo } = useUserStore();
|
||||
|
||||
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'}
|
||||
>
|
||||
<MyIcon name={'common/courseLight'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('account_info:help_document')}
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!isPc &&
|
||||
feConfigs?.navbarItems
|
||||
?.filter((item) => item.isActive)
|
||||
.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
{...buttonStyles.current}
|
||||
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>
|
||||
)}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user