mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-02 12:48:30 +00:00
4.6.8-alpha (#804)
* perf: redirect request and err log replace perf: dataset openapi feat: session fix: retry input error feat: 468 doc sub page feat: standard sub perf: rerank tip perf: rerank tip perf: api sdk perf: openapi sub plan perf: sub ui fix: ts * perf: init log * fix: variable select * sub page * icon * perf: llm model config * perf: menu ux * perf: system store * perf: publish app name * fix: init data * perf: flow edit ux * fix: value type format and ux * fix prompt editor default value (#13) * fix prompt editor default value * fix prompt editor update when not focus * add key with variable --------- Co-authored-by: Archer <545436317@qq.com> * fix: value type * doc * i18n * import path * home page * perf: mongo session running * fix: ts * perf: use toast * perf: flow edit * perf: sse response * slider ui * fetch error * fix prompt editor rerender when not focus by key defaultvalue (#14) * perf: prompt editor * feat: dataset search concat * perf: doc * fix:ts * perf: doc * fix json editor onblur value (#15) * faq * vector model default config * ipv6 --------- Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
304
projects/app/src/pages/price/components/ExtraPlan.tsx
Normal file
304
projects/app/src/pages/price/components/ExtraPlan.tsx
Normal file
@@ -0,0 +1,304 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Grid,
|
||||
NumberDecrementStepper,
|
||||
NumberInput,
|
||||
NumberIncrementStepper,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Button,
|
||||
useDisclosure,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import MySelect from '@/components/Select';
|
||||
import {
|
||||
SubStatusEnum,
|
||||
SubTypeEnum,
|
||||
subSelectMap
|
||||
} from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import {
|
||||
posCheckTeamDatasetSizeSub,
|
||||
postUpdateTeamDatasetSizeSub,
|
||||
putTeamDatasetSubStatus
|
||||
} from '@/web/support/wallet/sub/api';
|
||||
import { SubDatasetSizePreviewCheckResponse } from '@fastgpt/global/support/wallet/sub/api.d';
|
||||
import { useRouter } from 'next/router';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyModal from '@/components/MyModal';
|
||||
|
||||
const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) => {
|
||||
const { t } = useTranslation();
|
||||
const { subPlans } = useSystemStore();
|
||||
const extraDatasetPrice = subPlans?.extraDatasetSize?.price || 0;
|
||||
const [datasetSize, setDatasetSize] = useState(0);
|
||||
const [isRenew, setIsRenew] = useState('false');
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const [confirmPayExtraDatasetSizeData, setConfirmPayExtraDatasetSizeData] =
|
||||
useState<SubDatasetSizePreviewCheckResponse>();
|
||||
|
||||
useEffect(() => {
|
||||
setDatasetSize((extraDatasetSize?.nextExtraDatasetSize || 0) / 1000);
|
||||
setIsRenew(extraDatasetSize?.status === SubStatusEnum.active ? 'true' : 'false');
|
||||
}, [extraDatasetSize]);
|
||||
|
||||
const { mutate: onUpdateExtraDatasetSizeStatus } = useRequest({
|
||||
mutationFn: (e: 'true' | 'false') => {
|
||||
setIsRenew(e);
|
||||
return putTeamDatasetSubStatus({
|
||||
status: subSelectMap[e],
|
||||
type: SubTypeEnum.extraDatasetSize
|
||||
});
|
||||
},
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
|
||||
const { mutate: onClickUpdateExtraDatasetPlan, isLoading: isPayingExtraDatasetSize } = useRequest(
|
||||
{
|
||||
mutationFn: () => postUpdateTeamDatasetSizeSub({ size: datasetSize }),
|
||||
onSuccess() {
|
||||
setTimeout(() => {
|
||||
router.reload();
|
||||
}, 100);
|
||||
},
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
}
|
||||
);
|
||||
const { mutate: onClickPreviewCheck, isLoading: isFetchingPreviewCheck } = useRequest({
|
||||
mutationFn: () =>
|
||||
posCheckTeamDatasetSizeSub({
|
||||
size: datasetSize
|
||||
}),
|
||||
onSuccess(res: SubDatasetSizePreviewCheckResponse) {
|
||||
if (!res.payForNewSub) {
|
||||
onClickUpdateExtraDatasetPlan('');
|
||||
return;
|
||||
} else {
|
||||
setConfirmPayExtraDatasetSizeData(res);
|
||||
}
|
||||
},
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
mt={['40px', '90px']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
{t('support.wallet.subscription.Extra plan')}
|
||||
</Box>
|
||||
<Box mt={8} mb={10} color={'myGray.500'} fontSize={'lg'}>
|
||||
{t('support.wallet.subscription.Extra plan tip')}
|
||||
</Box>
|
||||
<Grid mt={8} gridTemplateColumns={['1fr', '1fr']}>
|
||||
<Box
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
px={'32px'}
|
||||
py={'24px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
>
|
||||
<Flex w={['100%', '500px']} borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Box fontSize={'xl'} color={'primary.600'}>
|
||||
{t('support.wallet.subscription.Extra dataset size')}
|
||||
</Box>
|
||||
<Box mt={3} fontSize={['32px', '38px']} fontWeight={'bold'}>
|
||||
¥{extraDatasetPrice}/1k组{' '}
|
||||
<Box ml={1} as={'span'} fontSize={'lg'} color={'myGray.600'} fontWeight={'normal'}>
|
||||
/{t('common.month')}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<MyIcon
|
||||
transform={'translate(20px,-20px)'}
|
||||
name={'support/pay/extraDatasetsize'}
|
||||
fill={'none'}
|
||||
/>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Current dataset store')}:{' '}
|
||||
</Box>
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{extraDatasetSize?.currentExtraDatasetSize || 0}
|
||||
{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
{extraDatasetSize?.nextExtraDatasetSize !== undefined && (
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Next sub dataset size')}:
|
||||
</Box>
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{extraDatasetSize?.nextExtraDatasetSize || 0}
|
||||
{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!extraDatasetSize?.startTime && (
|
||||
<Flex mt={3}>
|
||||
<Box flex={'0 0 200px'}>订阅开始时间: </Box>
|
||||
<Box>{formatTime2YMDHM(extraDatasetSize?.startTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!extraDatasetSize?.expiredTime && (
|
||||
<Flex mt={3}>
|
||||
<Box flex={'0 0 200px'}>订阅到期时间: </Box>
|
||||
<Box>{formatTime2YMDHM(extraDatasetSize?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'0 0 200px'}>是否自动续费: </Box>
|
||||
<MySelect
|
||||
value={isRenew}
|
||||
size={'sm'}
|
||||
w={'180px'}
|
||||
bg={'myGray.50'}
|
||||
boxShadow={'none'}
|
||||
list={[
|
||||
{ label: '自动续费', value: 'true' },
|
||||
{ label: '不自动续费', value: 'false' }
|
||||
]}
|
||||
onchange={(e) => {
|
||||
if (!extraDatasetSize) return;
|
||||
onUpdateExtraDatasetSizeStatus(e);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Update extra dataset size')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
|
||||
<NumberInput
|
||||
size={'sm'}
|
||||
flex={1}
|
||||
min={0}
|
||||
max={10000}
|
||||
step={1}
|
||||
value={datasetSize}
|
||||
position={'relative'}
|
||||
onChange={(e) => {
|
||||
setDatasetSize(Number(e));
|
||||
}}
|
||||
>
|
||||
<NumberInputField pr={'30px'} value={datasetSize} step={1} min={0} max={10000} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
|
||||
000{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
isDisabled={datasetSize * 1000 === extraDatasetSize?.nextExtraDatasetSize}
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isPayingExtraDatasetSize || isFetchingPreviewCheck}
|
||||
onClick={onClickPreviewCheck}
|
||||
>
|
||||
{t('common.change')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* extra dataset size modal */}
|
||||
{!!confirmPayExtraDatasetSizeData && (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={() => setConfirmPayExtraDatasetSizeData(undefined)}
|
||||
title={t('support.wallet.Confirm pay')}
|
||||
iconSrc="common/confirm/rightTip"
|
||||
>
|
||||
<ModalBody px={8} py={5}>
|
||||
<Flex>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
当前额外容量
|
||||
</Box>
|
||||
<Box>{extraDatasetSize?.currentExtraDatasetSize || 0}条</Box>
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
新的额外容量
|
||||
</Box>
|
||||
<Box>{confirmPayExtraDatasetSizeData.newSubSize}条</Box>
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
新套餐价格
|
||||
</Box>
|
||||
<Box>{formatStorePrice2Read(confirmPayExtraDatasetSizeData.newPlanPrice)}元</Box>
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
有效时长
|
||||
</Box>
|
||||
<Box>30天</Box>
|
||||
</Flex>
|
||||
|
||||
{/* <Flex>
|
||||
<Box flex={'0 0 120px'}>账号余额:</Box>
|
||||
<Box>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}元</Box>
|
||||
</Flex> */}
|
||||
</ModalBody>
|
||||
<ModalFooter mx={8} px={0} borderTopWidth={'1px'} borderTopColor={'myGray.200'}>
|
||||
<Box color={'myGray.600'}>账号余额:</Box>
|
||||
{confirmPayExtraDatasetSizeData.balanceEnough ? (
|
||||
<>
|
||||
<Box flex={'1 0 0'}>
|
||||
{formatStorePrice2Read(userInfo?.team?.balance).toFixed(2)}元
|
||||
</Box>
|
||||
<Button
|
||||
isLoading={isPayingExtraDatasetSize}
|
||||
onClick={() => onClickUpdateExtraDatasetPlan('')}
|
||||
>
|
||||
支付{formatStorePrice2Read(confirmPayExtraDatasetSizeData.payPrice).toFixed(2)}元
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Box color={'red.600'} flex={'1 0 0'}>
|
||||
余额不足
|
||||
</Box>
|
||||
<Button
|
||||
isLoading={isPayingExtraDatasetSize}
|
||||
onClick={() => router.push('/account')}
|
||||
>
|
||||
去充值
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExtraPlan);
|
42
projects/app/src/pages/price/components/FAQ.tsx
Normal file
42
projects/app/src/pages/price/components/FAQ.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FAQ = () => {
|
||||
const { t } = useTranslation();
|
||||
const faqs = [{ title: '怎么付费', describe: '2222' }];
|
||||
|
||||
return (
|
||||
<Flex
|
||||
mt={['40px', '90px']}
|
||||
pb={'10vh'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
{t('support.wallet.subscription.FAQ')}
|
||||
</Box>
|
||||
<Grid mt={4} gridTemplateColumns={['1fr', '1fr 1fr']} gap={4} w={'100%'}>
|
||||
<Box
|
||||
py={2}
|
||||
px={4}
|
||||
borderRadius={'lg'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
怎么付费
|
||||
</Box>
|
||||
<Box color={'myGray.500'}>2222</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default FAQ;
|
122
projects/app/src/pages/price/components/Points.tsx
Normal file
122
projects/app/src/pages/price/components/Points.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const Points = () => {
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList, audioSpeechModelList, vectorModelList, whisperModel } = useSystemStore();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
mt={['40px', '90px']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
{t('support.wallet.subscription.Ai points')}
|
||||
</Box>
|
||||
<Grid gap={6} mt={['30px', '48px']} w={'100%'}>
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
borderRadius={'xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box
|
||||
flex={1}
|
||||
borderRightWidth={'1px'}
|
||||
borderRightColor={'myGray.150'}
|
||||
py={4}
|
||||
px={6}
|
||||
fontSize={'lg'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
AI语言模型
|
||||
</Box>
|
||||
<Box flex={4} textAlign={'center'}>
|
||||
{llmModelList?.map((item, i) => (
|
||||
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
|
||||
<Box flex={'1 0 0'}>{item.name}</Box>
|
||||
<Box flex={'1 0 0'}>5积分 / 1000字符</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
borderRadius={'xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box flex={1} borderRightWidth={'1px'} borderRightColor={'myGray.150'} py={4} px={6}>
|
||||
<Box fontSize={'lg'} fontWeight={'bold'}>
|
||||
索引模型
|
||||
</Box>
|
||||
<Box fontSize={'sm'} mt={1} color={'myGray.500'}>
|
||||
文档索引 & 对话索引
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={4} textAlign={'center'}>
|
||||
{vectorModelList?.map((item, i) => (
|
||||
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
|
||||
<Box flex={'1 0 0'}>{item.name}</Box>
|
||||
<Box flex={'1 0 0'}>5积分 / 1000字符</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
borderRadius={'xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box flex={1} borderRightWidth={'1px'} borderRightColor={'myGray.150'} py={4} px={6}>
|
||||
<Box fontSize={'lg'} fontWeight={'bold'}>
|
||||
语音播放
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={4} textAlign={'center'}>
|
||||
{audioSpeechModelList?.map((item, i) => (
|
||||
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
|
||||
<Box flex={'1 0 0'}>{item.name}</Box>
|
||||
<Box flex={'1 0 0'}>5积分 / 1000字符</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
borderRadius={'xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box flex={1} borderRightWidth={'1px'} borderRightColor={'myGray.150'} py={4} px={6}>
|
||||
<Box fontSize={'lg'} fontWeight={'bold'}>
|
||||
语音输入
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={4} textAlign={'center'} h={'100%'}>
|
||||
<Flex py={4}>
|
||||
<Box flex={'1 0 0'}>{whisperModel?.name}</Box>
|
||||
<Box flex={'1 0 0'}>{whisperModel?.inputPrice}积分 / 分钟</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Points);
|
340
projects/app/src/pages/price/components/Standard.tsx
Normal file
340
projects/app/src/pages/price/components/Standard.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { postCheckStandardSub, postUpdateStandardSub } from '@/web/support/wallet/sub/api';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { StandardSubPlanParams } from '@fastgpt/global/support/wallet/sub/api';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { StandardSubPlanUpdateResponse } from '@fastgpt/global/support/wallet/sub/api.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
|
||||
const Standard = ({
|
||||
standardPlan,
|
||||
refetchTeamSubPlan
|
||||
}: {
|
||||
standardPlan?: TeamSubSchema;
|
||||
refetchTeamSubPlan: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { subPlans, feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
|
||||
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month);
|
||||
|
||||
const standardSubList = useMemo(() => {
|
||||
return subPlans?.standard
|
||||
? Object.entries(subPlans.standard).map(([level, value]) => {
|
||||
return {
|
||||
price: value.price * (selectSubMode === SubModeEnum.month ? 1 : 10),
|
||||
level: level as `${StandardSubLevelEnum}`,
|
||||
...standardSubLevelMap[level as `${StandardSubLevelEnum}`],
|
||||
maxTeamMember: value.maxTeamMember,
|
||||
maxAppAmount: value.maxAppAmount,
|
||||
maxDatasetAmount: value.maxDatasetAmount,
|
||||
chatHistoryStoreDuration: value.chatHistoryStoreDuration,
|
||||
maxDatasetSize: value.maxDatasetSize,
|
||||
customApiKey: value.customApiKey,
|
||||
customCopyright: value.customCopyright,
|
||||
trainingWeight: value.trainingWeight,
|
||||
reRankWeight: value.reRankWeight,
|
||||
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
|
||||
websiteSyncInterval: value.websiteSyncInterval
|
||||
};
|
||||
})
|
||||
: [];
|
||||
}, [subPlans?.standard, selectSubMode]);
|
||||
|
||||
const { mutate: onclickUpdateStandardPlan, isLoading: isUpdatingStandardPlan } = useRequest({
|
||||
mutationFn: (data: StandardSubPlanParams) => postUpdateStandardSub(data),
|
||||
onSuccess() {
|
||||
refetchTeamSubPlan();
|
||||
},
|
||||
successToast: t('support.wallet.subscription.Standard update success'),
|
||||
errorToast: t('support.wallet.subscription.Standard update fail')
|
||||
});
|
||||
|
||||
const { mutate: onclickPreCheckStandPlan, isLoading: isCheckingStandardPlan } = useRequest({
|
||||
mutationFn: (data: StandardSubPlanParams) => postCheckStandardSub(data),
|
||||
onSuccess(res: StandardSubPlanUpdateResponse) {
|
||||
if (!res.balanceEnough) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('support.wallet.Balance not enough tip')
|
||||
});
|
||||
}
|
||||
if (res.payPrice === undefined) {
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
});
|
||||
} else if (res.payPrice > 0) {
|
||||
openConfirm(
|
||||
() =>
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}),
|
||||
undefined,
|
||||
t('support.wallet.subscription.Standard plan pay confirm', {
|
||||
payPrice: formatStorePrice2Read(res.payPrice).toFixed(2)
|
||||
})
|
||||
)();
|
||||
} else {
|
||||
openConfirm(
|
||||
() =>
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}),
|
||||
undefined,
|
||||
t('support.wallet.subscription.Refund plan and pay confirm', {
|
||||
amount: formatStorePrice2Read(Math.abs(res.payPrice)).toFixed(2)
|
||||
})
|
||||
)();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} alignItems={'center'} position={'relative'}>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
{t('support.wallet.subscription.Sub plan')}
|
||||
</Box>
|
||||
<Box mt={8} mb={10} color={'myGray.500'} fontSize={'lg'}>
|
||||
{t('support.wallet.subscription.Sub plan tip')}
|
||||
</Box>
|
||||
<Box>
|
||||
<RowTabs
|
||||
list={[
|
||||
{ label: t('support.wallet.subscription.mode.Month'), value: SubModeEnum.month },
|
||||
{
|
||||
label: (
|
||||
<Flex>
|
||||
{t('support.wallet.subscription.mode.Year')}
|
||||
<Box color={selectSubMode === SubModeEnum.month ? 'red.600' : 'auto'}>
|
||||
({t('support.wallet.subscription.mode.Year sale')})
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: SubModeEnum.year
|
||||
}
|
||||
]}
|
||||
value={selectSubMode}
|
||||
onChange={(e) => setSelectSubMode(e as `${SubModeEnum}`)}
|
||||
/>
|
||||
</Box>
|
||||
{/* card */}
|
||||
<Grid
|
||||
mt={[10, '48px']}
|
||||
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(4,1fr)']}
|
||||
gap={[4, 6, 8]}
|
||||
w={'100%'}
|
||||
>
|
||||
{standardSubList.map((item) => (
|
||||
<Box
|
||||
key={item.level}
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
p={'28px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
|
||||
¥{item.price}
|
||||
</Box>
|
||||
<Box color={'myGray.500'} h={'40px'}>
|
||||
{t(item.desc, { title: feConfigs?.systemTitle })}
|
||||
</Box>
|
||||
{(() => {
|
||||
if (item.level === StandardSubLevelEnum.free && selectSubMode === SubModeEnum.year) {
|
||||
return (
|
||||
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
|
||||
{t('support.wallet.subscription.Nonsupport')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.currentSubLevel &&
|
||||
selectSubMode === standardPlan?.currentMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Next plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Buy now')}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* function list */}
|
||||
<Grid gap={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max members', {
|
||||
amount: item.maxTeamMember
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max app', {
|
||||
amount: item.maxAppAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset', {
|
||||
amount: item.maxDatasetAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.History store', {
|
||||
amount: item.chatHistoryStoreDuration
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset size', {
|
||||
amount: item.maxDatasetSize
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Points', {
|
||||
amount: item.totalPoints
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.Training weight', {
|
||||
weight: item.trainingWeight
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!!item.customApiKey && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>个人API Key</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!item.websiteSyncInterval && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>{item.websiteSyncInterval} h/次 web站点同步</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<ConfirmModal />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Standard);
|
||||
|
||||
const RowTabs = ({
|
||||
list,
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
list: {
|
||||
icon?: string;
|
||||
label: string | React.ReactNode;
|
||||
value: string;
|
||||
}[];
|
||||
value: string;
|
||||
onChange: (e: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
display={'inline-flex'}
|
||||
px={'3px'}
|
||||
py={'3px'}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'primary.300'}
|
||||
bg={'primary.50'}
|
||||
gap={'4px'}
|
||||
>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.value}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
px={'12px'}
|
||||
py={'7px'}
|
||||
userSelect={'none'}
|
||||
w={['150px', '170px']}
|
||||
{...(value === item.value
|
||||
? {
|
||||
color: 'white',
|
||||
boxShadow: '1.5',
|
||||
bg: 'primary.600'
|
||||
}
|
||||
: {
|
||||
onClick: () => onChange(item.value)
|
||||
})}
|
||||
>
|
||||
{item.icon && <MyIcon name={item.icon as any} mr={1} w={'14px'} />}
|
||||
<Box>{item.label}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
57
projects/app/src/pages/price/index.tsx
Normal file
57
projects/app/src/pages/price/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { Box, Image } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamDatasetValidSub } from '@/web/support/wallet/sub/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import StandardPlan from './components/Standard';
|
||||
import ExtraPlan from './components/ExtraPlan';
|
||||
import PointsCard from './components/Points';
|
||||
import FAQ from './components/FAQ';
|
||||
|
||||
const PriceBox = () => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { data: teamSubPlan, refetch: refetchTeamSubPlan } = useQuery(
|
||||
['getTeamDatasetValidSub'],
|
||||
getTeamDatasetValidSub,
|
||||
{
|
||||
enabled: !!userInfo
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
h={'100%'}
|
||||
overflow={'overlay'}
|
||||
w={'100%'}
|
||||
px={['20px', '5vw']}
|
||||
py={['30px', '80px']}
|
||||
backgroundImage={'url(/imgs/priceBg.svg)'}
|
||||
backgroundSize={'cover'}
|
||||
backgroundRepeat={'no-repeat'}
|
||||
>
|
||||
{/* standard sub */}
|
||||
<StandardPlan standardPlan={teamSubPlan?.standard} refetchTeamSubPlan={refetchTeamSubPlan} />
|
||||
|
||||
<ExtraPlan extraDatasetSize={teamSubPlan?.extraDatasetSize} />
|
||||
|
||||
{/* points */}
|
||||
<PointsCard />
|
||||
|
||||
{/* question */}
|
||||
<FAQ />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PriceBox;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
return {
|
||||
props: { ...(await serviceSideProps(context)) }
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user