V4.14.4 features (#6036)

* feat: add query optimize and bill (#6021)

* add query optimize and bill

* perf: query extension

* fix: embe model

* remove log

* remove log

* fix: test

---------

Co-authored-by: xxyyh <2289112474@qq>
Co-authored-by: archer <545436317@qq.com>

* feat: notice (#6013)

* feat: record user's language

* feat: notice points/dataset indexes; support count limit; update docker-compose.yml

* fix: ts error

* feat: send auth code i18n

* chore: dataset notice limit

* chore: adjust

* fix: ts

* fix: countLimit race condition; i18n en-prefix locale fallback to en

---------

Co-authored-by: archer <545436317@qq.com>

* perf: comment

* perf: send inform code

* fix: type error (#6029)

* feat: add ip region for chat logs (#6010)

* feat: add ip region for chat logs

* refactor: use Geolite2.mmdb

* fix: export chat logs

* fix: return location directly

* test: add unit test

* perf: log show ip data

* adjust commercial plans (#6008)

* plan frontend

* plan limit

* coupon

* discount coupon

* fix

* type

* fix audit

* type

* plan name

* legacy plan

* track

* feat: add discount coupon

* fix

* fix discount coupon

* openapi

* type

* type

* env

* api type

* fix

* fix: simple agent plugin input & agent dashboard card (#6034)

* refactor: remove gridfs (#6031)

* fix: replace gridfs multer operations with s3 compatible ops

* wip: s3 features

* refactor: remove gridfs

* fix

* perf: mock test

* doc

* doc

* doc

* fix: test

* fix: s3

* fix: mock s3

* remove invalid config

* fix: init query extension

* initv4144 (#6037)

* chore: initv4144

* fix

* version

* fix: new plans (#6039)

* fix: new plans

* qr modal tip

* fix: buffer raw text filename (#6040)

* fix: initv4144 (#6041)

* fix: pay refresh (#6042)

* fix: migration shell

* rename collection

* clear timerlock

* clear timerlock

* perf: faq

* perf: bill schema

* fix: openapi

* doc

* fix: share var render

* feat: delete dataset queue

* plan usage display (#6043)

* plan usage display

* text

* fix

* fix: ts

* perf: remove invalid code

* perf: init shell

* doc

* perf: rename field

* perf: avatar presign

* init

* custom plan text (#6045)

* fix plans

* fix

* fixed

* computed

---------

Co-authored-by: archer <545436317@qq.com>

* init shell

* plan text & price page back button (#6046)

* init

* index

* delete dataset

* delete dataset

* perf: delete dataset

* init

---------

Co-authored-by: YeYuheng <57035043+YYH211@users.noreply.github.com>
Co-authored-by: xxyyh <2289112474@qq>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: Roy <whoeverimf5@gmail.com>
Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2025-12-08 01:44:15 +08:00
committed by GitHub
parent 9d72f238c0
commit 2ccb5b50c6
247 changed files with 7342 additions and 3819 deletions

View File

@@ -35,10 +35,6 @@ const Auth = ({ children }: { children: JSX.Element | React.ReactNode }) => {
{
refetchInterval: 10 * 60 * 1000,
onError(error) {
console.log('error->', error);
router.replace(
`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`
);
toast({
status: 'warning',
title: t('common:support.user.Need to login')

View File

@@ -2,13 +2,29 @@ import { Box, Flex } from '@chakra-ui/react';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import { useTranslation } from 'next-i18next';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import type { LangEnum } from '@fastgpt/global/common/i18n/type';
import { langMap } from '@fastgpt/global/common/i18n/type';
import { useUserStore } from '@/web/support/user/useUserStore';
const I18nLngSelector = () => {
const { i18n } = useTranslation();
const { onChangeLng } = useI18nLng();
const { onChangeLng: onChangeLngI18n } = useI18nLng();
const { userInfo, updateUserInfo } = useUserStore();
const onChangeLng = useCallback(
async (lng: `${LangEnum}`) => {
if (userInfo?.username) {
// logined
await updateUserInfo({
language: lng
});
}
await onChangeLngI18n(lng);
},
[userInfo?.username, onChangeLngI18n, updateUserInfo]
);
const list = useMemo(() => {
return Object.entries(langMap).map(([key, lang]) => ({

View File

@@ -122,9 +122,6 @@ const FileSelector = ({
Object.entries(fields).forEach(([k, v]) => formData.set(k, v));
formData.set('file', file.rawFile);
await POST(url, formData, {
headers: {
'Content-Type': 'multipart/form-data; charset=utf-8'
},
onUploadProgress: (e) => {
if (!e.total) return;
const percent = Math.round((e.loaded / e.total) * 100);

View File

@@ -188,9 +188,6 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
Object.entries(fields).forEach(([k, v]) => formData.set(k, v));
formData.set('file', copyFile.rawFile);
await POST(url, formData, {
headers: {
'Content-Type': 'multipart/form-data; charset=utf-8'
},
onUploadProgress: (e) => {
if (!e.total) return;
const percent = Math.round((e.loaded / e.total) * 100);

View File

@@ -5,7 +5,7 @@ import {
DatasetSearchModeMap
} from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
import React, { useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { getWebLLMModel } from '@/web/common/system/utils';
@@ -27,16 +27,15 @@ const SearchParamsTip = ({
queryExtensionModel?: string;
}) => {
const { t } = useTranslation();
const { reRankModelList, llmModelList } = useSystemStore();
const { reRankModelList } = useSystemStore();
const hasReRankModel = reRankModelList.length > 0;
const hasEmptyResponseMode = responseEmptyText !== undefined;
const hasSimilarityMode = usingReRank || searchMode === DatasetSearchModeEnum.embedding;
const extensionModelName = useMemo(
() =>
datasetSearchUsingExtensionQuery ? getWebLLMModel(queryExtensionModel)?.name : undefined,
[datasetSearchUsingExtensionQuery, queryExtensionModel, llmModelList]
() => getWebLLMModel(queryExtensionModel)?.name,
[queryExtensionModel]
);
return (

View File

@@ -11,6 +11,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useMount } from 'ahooks';
import { useRouter } from 'next/router';
const NotSufficientModal = () => {
const { t } = useTranslation();
@@ -62,7 +63,7 @@ const NotSufficientModal = () => {
export default NotSufficientModal;
const RechargeModal = ({
export const RechargeModal = ({
onClose,
onPaySuccess
}: {
@@ -70,7 +71,9 @@ const RechargeModal = ({
onPaySuccess: () => void;
}) => {
const { t } = useTranslation();
const router = useRouter();
const { teamPlanStatus, initTeamPlanStatus } = useUserStore();
const { subPlans } = useSystemStore();
useMount(() => {
initTeamPlanStatus();
@@ -78,8 +81,11 @@ const RechargeModal = ({
const planName = useMemo(() => {
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
}, [teamPlanStatus?.standard?.currentSubLevel]);
return (
subPlans?.standard?.[teamPlanStatus.standard.currentSubLevel]?.name ||
t(standardSubLevelMap[teamPlanStatus.standard.currentSubLevel]?.label as any)
);
}, [teamPlanStatus?.standard?.currentSubLevel, subPlans?.standard, t]);
const [tab, setTab] = useState<'standard' | 'extra'>('standard');
@@ -96,28 +102,87 @@ const RechargeModal = ({
>
<ModalBody px={'52px'}>
<Flex alignItems={'center'} mb={6}>
<FormLabel fontSize={'16px'} fontWeight={'medium'}>
{t('common:support.wallet.subscription.Current plan')}
</FormLabel>
<Box fontSize={'14px'} ml={5} color={'myGray.900'}>
{t(planName as any)}
</Box>
<Flex>
<FormLabel fontSize={'16px'} fontWeight={'medium'} color={'myGray.900'}>
{t('common:support.wallet.subscription.Current plan')}
</FormLabel>
<Box fontSize={'14px'} ml={5} color={'myGray.900'}>
{t(planName as any)}
</Box>
</Flex>
<Box flex={1} />
<Button
size={'md'}
variant={'transparentBase'}
color={'primary.700'}
onClick={() => {
router.push('/account/usage');
onClose();
onPaySuccess();
}}
>
{t('common:usage_records')}
</Button>
</Flex>
<Flex alignItems={'center'} mb={6}>
<FormLabel fontSize={'16px'} fontWeight={'medium'}>
{t('common:info.resource')}
</FormLabel>
<Flex fontSize={'14px'} ml={5} color={'myGray.900'}>
<Box>{`${t('common:support.user.team.Dataset usage')}:`}</Box>
<Box
ml={2}
>{`${teamPlanStatus?.usedDatasetIndexSize} / ${teamPlanStatus?.datasetMaxSize || t('account_info:unlimited')}`}</Box>
<Box ml={5}>{`${t('common:support.wallet.subscription.AI points usage')}:`}</Box>
<Box
ml={2}
>{`${Math.round(teamPlanStatus?.usedPoints || 0)} / ${teamPlanStatus?.totalPoints || t('account_info:unlimited')}`}</Box>
</Flex>
<Flex mb={6} gap={8} w={'100%'}>
<Box flex={1}>
<Flex gap={4} alignItems={'center'} mb={2}>
<Box fontSize={'16px'} fontWeight={'medium'} color={'myGray.900'}>
{t('common:support.wallet.subscription.AI points usage')}
</Box>
<Box
fontSize={'14px'}
fontWeight={'medium'}
>{`${teamPlanStatus?.usedPoints || 0} / ${teamPlanStatus?.totalPoints ?? t('common:Unlimited')}`}</Box>
</Flex>
<Flex h={2} w={'full'} p={0.5} bg={'primary.50'} borderRadius={'md'}>
<Box
borderRadius={'sm'}
transition="width 0.3s"
w={`${teamPlanStatus?.totalPoints ? Math.max((teamPlanStatus.usedPoints / teamPlanStatus.totalPoints) * 100, 0) : 0}%`}
bg={`${
teamPlanStatus?.totalPoints
? (teamPlanStatus.usedPoints / teamPlanStatus.totalPoints) * 100 < 50
? 'primary'
: (teamPlanStatus.usedPoints / teamPlanStatus.totalPoints) * 100 < 80
? 'yellow'
: 'red'
: 'primary'
}.500`}
/>
</Flex>
</Box>
<Box flex={1}>
<Flex gap={4} alignItems={'center'} mb={2}>
<Box fontSize={'16px'} fontWeight={'medium'} color={'myGray.900'}>
{t('common:support.user.team.Dataset usage')}
</Box>
<Box
fontSize={'14px'}
fontWeight={'medium'}
>{`${teamPlanStatus?.usedDatasetIndexSize || 0} / ${teamPlanStatus?.datasetMaxSize ?? t('common:Unlimited')}`}</Box>
</Flex>
<Flex h={2} w={'full'} p={0.5} bg={'primary.50'} borderRadius={'md'}>
<Box
borderRadius={'sm'}
transition="width 0.3s"
w={`${teamPlanStatus?.datasetMaxSize ? Math.max((teamPlanStatus.usedDatasetIndexSize / teamPlanStatus.datasetMaxSize) * 100, 0) : 0}%`}
bg={`${
teamPlanStatus?.datasetMaxSize
? (teamPlanStatus.usedDatasetIndexSize / teamPlanStatus.datasetMaxSize) * 100 <
50
? 'primary'
: (teamPlanStatus.usedDatasetIndexSize / teamPlanStatus.datasetMaxSize) *
100 <
80
? 'yellow'
: 'red'
: 'primary'
}.500`}
/>
</Flex>
</Box>
</Flex>
<FillRowTabs

View File

@@ -15,10 +15,11 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import Markdown from '@/components/Markdown';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { type CreateBillResponse } from '@fastgpt/global/support/wallet/bill/api';
import type { CreateBillResponseType } from '@fastgpt/global/openapi/support/wallet/bill/api';
export type QRPayProps = CreateBillResponse & {
export type QRPayProps = CreateBillResponseType & {
tip?: string;
discountCouponName?: string;
};
const QRCodePayModal = ({
@@ -29,8 +30,14 @@ const QRCodePayModal = ({
qrCode,
iframeCode,
markdown,
onSuccess
}: QRPayProps & { tip?: string; onSuccess?: () => any }) => {
onSuccess,
discountCouponName,
onClose
}: QRPayProps & {
tip?: string;
onSuccess?: () => any;
onClose?: () => void;
}) => {
const { t } = useTranslation();
const canvasRef = useRef<HTMLDivElement>(null);
const toast = useToast();
@@ -161,13 +168,25 @@ const QRCodePayModal = ({
title={t('common:user.Pay')}
iconSrc="/imgs/modal/wallet.svg"
w={'600px'}
onClose={onClose}
>
<ModalBody textAlign={'center'} padding={['16px 24px', '32px 52px']}>
{tip && <LightTip text={tip} mb={6} textAlign={'left'} />}
<Box>{t('common:pay_money')}</Box>
<Box color="primary.600" fontSize="32px" fontWeight="600" lineHeight="40px" mb={6}>
<Box
color="primary.600"
fontSize="32px"
fontWeight="600"
lineHeight="40px"
mb={discountCouponName ? 1 : 6}
>
¥{readPrice.toFixed(2)}
</Box>
{discountCouponName && (
<Box color={'myGray.900'} fontSize={'14px'} fontWeight={'500'} mb={6}>
{t('common:discount_coupon_used') + t(discountCouponName)}
</Box>
)}
{renderPaymentContent()}

View File

@@ -34,29 +34,66 @@ const StandardPlanContentList = ({
price: plan.price * (mode === SubModeEnum.month ? 1 : 10),
level: level as `${StandardSubLevelEnum}`,
...standardSubLevelMap[level as `${StandardSubLevelEnum}`],
totalPoints:
standplan?.totalPoints || plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
requestsPerMinute: standplan?.requestsPerMinute || plan.requestsPerMinute || 2000,
maxTeamMember: standplan?.maxTeamMember || plan.maxTeamMember,
maxAppAmount: standplan?.maxApp || plan.maxAppAmount,
maxDatasetAmount: standplan?.maxDataset || plan.maxDatasetAmount,
chatHistoryStoreDuration: plan.chatHistoryStoreDuration,
maxDatasetSize: plan.maxDatasetSize,
permissionCustomApiKey: plan.permissionCustomApiKey,
permissionCustomCopyright: plan.permissionCustomCopyright,
trainingWeight: plan.trainingWeight,
totalPoints: plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
permissionWebsiteSync: plan.permissionWebsiteSync,
permissionTeamOperationLog: plan.permissionTeamOperationLog
maxDatasetSize: standplan?.maxDatasetSize || plan.maxDatasetSize,
websiteSyncPerDataset: standplan?.websiteSyncPerDataset || plan.websiteSyncPerDataset,
chatHistoryStoreDuration:
standplan?.chatHistoryStoreDuration || plan.chatHistoryStoreDuration,
auditLogStoreDuration: standplan?.auditLogStoreDuration || plan.auditLogStoreDuration,
appRegistrationCount: standplan?.appRegistrationCount || plan.appRegistrationCount,
ticketResponseTime: standplan?.ticketResponseTime || plan.ticketResponseTime
};
}, [
subPlans?.standard,
level,
mode,
standplan?.totalPoints,
standplan?.requestsPerMinute,
standplan?.maxTeamMember,
standplan?.maxApp,
standplan?.maxDataset
standplan?.maxDataset,
standplan?.maxDatasetSize,
standplan?.websiteSyncPerDataset,
standplan?.chatHistoryStoreDuration,
standplan?.auditLogStoreDuration,
standplan?.appRegistrationCount,
standplan?.ticketResponseTime
]);
return planContent ? (
<Grid gap={4} fontSize={'sm'} fontWeight={500}>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Flex alignItems={'center'}>
<Box fontWeight={'bold'} color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Points', {
amount: planContent.totalPoints
})}
</Box>
<ModelPriceModal>
{({ onOpen }) => (
<QuestionTip
ml={1}
label={t('common:support.wallet.subscription.AI points click to read tip')}
onClick={onOpen}
/>
)}
</ModelPriceModal>
</Flex>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box fontWeight={'bold'} color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Max dataset size', {
amount: planContent.maxDatasetSize
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
@@ -89,52 +126,52 @@ const StandardPlanContentList = ({
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box fontWeight={'bold'} color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Max dataset size', {
amount: planContent.maxDatasetSize
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Flex alignItems={'center'}>
<Box fontWeight={'bold'} color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Points', {
amount: planContent.totalPoints
})}
</Box>
<ModelPriceModal>
{({ onOpen }) => (
<QuestionTip
ml={1}
label={t('common:support.wallet.subscription.AI points click to read tip')}
onClick={onOpen}
/>
)}
</ModelPriceModal>
</Flex>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('common:support.wallet.subscription.Training weight', {
weight: planContent.trainingWeight
})}
</Box>
</Flex>
{!!planContent.permissionWebsiteSync && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>{t('common:support.wallet.subscription.web_site_sync')}</Box>
</Flex>
)}
{!!planContent.permissionTeamOperationLog && (
{!!planContent.auditLogStoreDuration && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('common:support.wallet.subscription.team_operation_log')}
{t('common:support.wallet.subscription.function.Audit log store duration', {
amount: planContent.auditLogStoreDuration
})}
</Box>
</Flex>
)}
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Requests per minute', {
amount: planContent.requestsPerMinute
})}
</Box>
<QuestionTip ml={1} label={t('common:support.wallet.subscription.function.qpm tip')} />
</Flex>
{!!planContent.websiteSyncPerDataset && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box fontWeight={'bold'} color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Website sync per dataset', {
amount: planContent.websiteSyncPerDataset
})}
</Box>
</Flex>
)}
{!!planContent.ticketResponseTime && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Ticket response time', {
amount: planContent.ticketResponseTime
})}
</Box>
</Flex>
)}
{!!planContent.appRegistrationCount && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('common:support.wallet.subscription.function.App registration count', {
amount: planContent.appRegistrationCount
})}
</Box>
</Flex>
)}