mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 08:25:07 +00:00
V4.8.20 feature (#3686)
* Aiproxy (#3649) * model config * feat: model config ui * perf: rename variable * feat: custom request url * perf: model buffer * perf: init model * feat: json model config * auto login * fix: ts * update packages * package * fix: dockerfile * feat: usage filter & export & dashbord (#3538) * feat: usage filter & export & dashbord * adjust ui * fix tmb scroll * fix code & selecte all * merge * perf: usages list;perf: move components (#3654) * perf: usages list * team sub plan load * perf: usage dashboard code * perf: dashboard ui * perf: move components * add default model config (#3653) * 4.8.20 test (#3656) * provider * perf: model config * model perf (#3657) * fix: model * dataset quote * perf: model config * model tag * doubao model config * perf: config model * feat: model test * fix: POST 500 error on dingtalk bot (#3655) * feat: default model (#3662) * move model config * feat: default model * fix: false triggerd org selection (#3661) * export usage csv i18n (#3660) * export usage csv i18n * fix build * feat: markdown extension (#3663) * feat: markdown extension * media cros * rerank test * default price * perf: default model * fix: cannot custom provider * fix: default model select * update bg * perf: default model selector * fix: usage export * i18n * fix: rerank * update init extension * perf: ip limit check * doubao model order * web default modle * perf: tts selector * perf: tts error * qrcode package * reload buffer (#3665) * reload buffer * reload buffer * tts selector * fix: err tip (#3666) * fix: err tip * perf: training queue * doc * fix interactive edge (#3659) * fix interactive edge * fix * comment * add gemini model * fix: chat model select * perf: supplement assistant empty response (#3669) * perf: supplement assistant empty response * check array * perf: max_token count;feat: support resoner output;fix: member scroll (#3681) * perf: supplement assistant empty response * check array * perf: max_token count * feat: support resoner output * member scroll * update provider order * i18n * fix: stream response (#3682) * perf: supplement assistant empty response * check array * fix: stream response * fix: model config cannot set to null * fix: reasoning response (#3684) * perf: supplement assistant empty response * check array * fix: reasoning response * fix: reasoning response * doc (#3685) * perf: supplement assistant empty response * check array * doc * lock * animation * update doc * update compose * doc * doc --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com>
This commit is contained in:
105
projects/app/src/pageComponents/account/info/ConversionModal.tsx
Normal file
105
projects/app/src/pageComponents/account/info/ConversionModal.tsx
Normal file
@@ -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;
|
107
projects/app/src/pageComponents/account/info/UpdatePswModal.tsx
Normal file
107
projects/app/src/pageComponents/account/info/UpdatePswModal.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
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 { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { updatePasswordByOld } from '@/web/support/user/api';
|
||||
import { PasswordRule } from '@/web/support/user/login/constants';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
type FormType = {
|
||||
oldPsw: string;
|
||||
newPsw: string;
|
||||
confirmPsw: string;
|
||||
};
|
||||
|
||||
const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { register, handleSubmit, getValues } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
oldPsw: '',
|
||||
newPsw: '',
|
||||
confirmPsw: ''
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: onSubmit, loading: isLoading } = useRequest2(updatePasswordByOld, {
|
||||
onSuccess() {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('account_info:password_update_success'),
|
||||
errorToast: t('account_info:password_update_error')
|
||||
});
|
||||
const onSubmitErr = (err: Record<string, any>) => {
|
||||
const val = Object.values(err)[0];
|
||||
if (!val) return;
|
||||
if (val.message) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: val.message,
|
||||
duration: 3000,
|
||||
isClosable: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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'} fontSize={'sm'}>
|
||||
{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'} fontSize={'sm'}>
|
||||
{t('account_info:new_password') + ':'}
|
||||
</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'password'}
|
||||
placeholder={t('account_info:password_tip')}
|
||||
{...register('newPsw', {
|
||||
required: true,
|
||||
pattern: {
|
||||
value: PasswordRule,
|
||||
message: t('account_info:password_tip')
|
||||
}
|
||||
})}
|
||||
></Input>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 70px'} fontSize={'sm'}>
|
||||
{t('account_info:confirm_password') + ':'}
|
||||
</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
type={'password'}
|
||||
placeholder={t('user:password.confirm')}
|
||||
{...register('confirmPsw', {
|
||||
required: true,
|
||||
validate: (val) => (getValues('newPsw') === val ? true : t('user:password.not_match'))
|
||||
})}
|
||||
></Input>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isLoading} onClick={handleSubmit((data) => onSubmit(data), onSubmitErr)}>
|
||||
{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;
|
Reference in New Issue
Block a user