import React, { useCallback, useMemo } from 'react'; import { Box, Flex, Button, useDisclosure, useTheme, Input, Link, Progress, Grid, Image, BoxProps } 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 { useRouter } from 'next/router'; 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 { AI_POINT_USAGE_CARD_ROUTE, EXTRA_PLAN_CARD_ROUTE } 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'; const StandDetailModal = dynamic(() => import('./standardDetailModal')); const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu')); const ConversionModal = dynamic(() => import('./ConversionModal')); const UpdatePswModal = dynamic(() => import('./UpdatePswModal')); const UpdateNotification = dynamic(() => import('./UpdateNotificationModal')); const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal')); const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal')); const CommunityModal = dynamic(() => import('@/components/CommunityModal')); const Account = () => { 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 ( <> {isPc ? ( {!!standardPlan && ( )} ) : ( <> {standardPlan && } )} {isOpenContact && } ); }; export default React.memo(Account); const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { const theme = useTheme(); const { feConfigs } = useSystemStore(); const { t } = useTranslation(); const { userInfo, updateUserInfo } = useUserStore(); const { reset } = useForm({ defaultValues: userInfo as UserType }); const { teamPlanStatus } = useUserStore(); const standardPlan = teamPlanStatus?.standardConstants; const { isPc } = useSystem(); const { toast } = useToast(); 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('common:dataset.data.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('common:common.error.Select avatar failed'), status: 'warning' }); } }, [onclickSave, t, toast, userInfo] ); const labelStyles: BoxProps = { flex: '0 0 80px', fontSize: 'sm', color: 'myGray.900' }; return ( {/* user info */} {isPc && ( {t('common:support.user.User self info')} )} {isPc ? ( {t('common:support.user.Avatar')}:  ) : ( {t('common:user.Replace')} )} {feConfigs?.isPlus && ( {t('common:user.Member Name')}:  { const val = e.target.value; if (val === userInfo?.team?.memberName) return; try { putUpdateMemberName(val); } catch (error) {} }} /> )} {t('common:user.Account')}:  {userInfo?.username} {feConfigs?.isPlus && ( {t('common:user.Password')}:  ***** )} {feConfigs?.isPlus && ( {t('common:user.Notification Receive')}:  {userInfo?.team.notificationAccount ? userInfo?.team.notificationAccount : userInfo?.permission.isOwner ? t('common:user.Notification Receive Bind') : t('user:notification.remind_owner_bind')} {userInfo?.permission.isOwner && ( )} )} {t('common:user.Team')}:  {feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && ( {t('common:user.team.Balance')}:  {formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}{' '} {t('user:bill.yuan')} {userInfo?.permission.hasManagePer && !!standardPlan && ( )} )} {isOpenConversionModal && ( )} {isOpenUpdatePsw && } {isOpenUpdateNotification && } ); }; const PlanUsage = () => { const router = useRouter(); const { t } = useTranslation(); const { userInfo, initUserInfo, teamPlanStatus } = useUserStore(); const { reset } = useForm({ defaultValues: userInfo as UserType }); const { isOpen: isOpenStandardModal, onClose: onCloseStandardModal, onOpen: onOpenStandardModal } = 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('common:common.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('common:common.Unlimited'), usedSize: teamPlanStatus.usedDatasetSize }; }, [teamPlanStatus, t]); const aiPointsUsageMap = useMemo(() => { if (!teamPlanStatus) { return { colorScheme: 'green', value: 0, maxSize: t('common:common.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('common:common.Unlimited'), used: teamPlanStatus.usedPoints ? Math.round(teamPlanStatus.usedPoints) : 0 }; }, [teamPlanStatus, t]); return standardPlan ? ( {t('common:support.wallet.subscription.Team plan and usage')} {t('common:support.wallet.subscription.Current plan')} {t(planName as any)} {isFreeTeam && ( {t('common:info.free_plan')} )} {standardPlan.currentSubLevel !== StandardSubLevelEnum.free && ( {t('common:support.wallet.Plan expired time')}: {formatTime2YMD(standardPlan?.expiredTime)} )} {t('common:info.resource')} {t('common:info.include')} {t('common:info.buy_extra')} {t('common:support.user.team.Dataset usage')} {datasetUsageMap.usedSize}/{datasetUsageMap.maxSize} {t('common:support.wallet.subscription.AI points usage')} {aiPointsUsageMap.used}/{aiPointsUsageMap.max} {isOpenStandardModal && } ) : null; }; const Other = ({ onOpenContact }: { onOpenContact: () => void }) => { const theme = useTheme(); const { toast } = useToast(); const { feConfigs } = useSystemStore(); const { t } = useTranslation(); const { userInfo, updateUserInfo } = useUserStore(); const { reset } = useForm({ 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('common:dataset.data.Update Success Tip'), status: 'success' }); }, [reset, toast, updateUserInfo] ); return ( {feConfigs?.docUrl && ( {t('common:system.Help Document')} )} {feConfigs?.chatbotUrl && ( {t('common:common.system.Help Chatbot')} )} {feConfigs?.lafEnv && userInfo?.team.role === TeamMemberRoleEnum.owner && ( laf {'laf' + t('common:navbar.Account')} )} {feConfigs?.show_openai_account && ( {'OpenAI / OneAPI' + t('common:navbar.Account')} )} {feConfigs?.concatMd && ( )} {isOpenLaf && userInfo && ( )} {isOpenOpenai && userInfo && ( onclickSave({ ...userInfo, openaiAccount: data }) } onClose={onCloseOpenai} /> )} ); };