feat: add api rate limit and trigger events billing card

This commit is contained in:
lyzno1 2025-11-10 17:59:48 +08:00
parent 50619fba0a
commit 0f952f328f
No known key found for this signature in database
10 changed files with 78 additions and 26 deletions

View File

@ -26,6 +26,7 @@ export const ALL_PLANS: Record<BasicPlan, PlanInfo> = {
apiRateLimit: 5000,
documentProcessingPriority: Priority.standard,
messageRequest: 200,
triggerEvents: 3000,
annotatedResponse: 10,
logHistory: 30,
},
@ -43,6 +44,7 @@ export const ALL_PLANS: Record<BasicPlan, PlanInfo> = {
apiRateLimit: NUM_INFINITE,
documentProcessingPriority: Priority.priority,
messageRequest: 5000,
triggerEvents: 20000,
annotatedResponse: 2000,
logHistory: NUM_INFINITE,
},
@ -60,6 +62,7 @@ export const ALL_PLANS: Record<BasicPlan, PlanInfo> = {
apiRateLimit: NUM_INFINITE,
documentProcessingPriority: Priority.topPriority,
messageRequest: 10000,
triggerEvents: NUM_INFINITE,
annotatedResponse: 5000,
logHistory: NUM_INFINITE,
},
@ -74,6 +77,8 @@ export const defaultPlan = {
teamMembers: 1,
annotatedResponse: 1,
documentsUploadQuota: 0,
apiRateLimit: 0,
triggerEvents: 0,
},
total: {
documents: 50,
@ -82,5 +87,7 @@ export const defaultPlan = {
teamMembers: 1,
annotatedResponse: 10,
documentsUploadQuota: 0,
apiRateLimit: ALL_PLANS.sandbox.apiRateLimit,
triggerEvents: ALL_PLANS.sandbox.triggerEvents,
},
}

View File

@ -6,8 +6,10 @@ import { useRouter } from 'next/navigation'
import {
RiBook2Line,
RiFileEditLine,
RiFlashlightLine,
RiGraduationCapLine,
RiGroupLine,
RiSpeedLine,
} from '@remixicon/react'
import { Plan, SelfHostedPlan } from '../type'
import VectorSpaceInfo from '../usage-info/vector-space-info'
@ -43,6 +45,8 @@ const PlanComp: FC<Props> = ({
usage,
total,
} = plan
const perMonthUnit = ` ${t('billing.usagePage.perMonth')}`
const triggerEventUnit = plan.type === Plan.sandbox ? undefined : perMonthUnit
const [showModal, setShowModal] = React.useState(false)
const { mutateAsync } = useEducationVerify()
@ -119,6 +123,20 @@ const PlanComp: FC<Props> = ({
usage={usage.annotatedResponse}
total={total.annotatedResponse}
/>
<UsageInfo
Icon={RiFlashlightLine}
name={t('billing.usagePage.triggerEvents')}
usage={usage.triggerEvents}
total={total.triggerEvents}
unit={triggerEventUnit}
/>
<UsageInfo
Icon={RiSpeedLine}
name={t('billing.plansCommon.apiRateLimit')}
usage={usage.apiRateLimit}
total={total.apiRateLimit}
unit={perMonthUnit}
/>
</div>
<VerifyStateModal

View File

@ -27,6 +27,7 @@ export type PlanInfo = {
documentProcessingPriority: Priority
logHistory: number
messageRequest: number
triggerEvents: number
annotatedResponse: number
}
@ -52,7 +53,7 @@ export type SelfHostedPlanInfo = {
annotatedResponse: number
}
export type UsagePlanInfo = Pick<PlanInfo, 'buildApps' | 'teamMembers' | 'annotatedResponse' | 'documentsUploadQuota'> & { vectorSpace: number }
export type UsagePlanInfo = Pick<PlanInfo, 'buildApps' | 'teamMembers' | 'annotatedResponse' | 'documentsUploadQuota' | 'apiRateLimit' | 'triggerEvents'> & { vectorSpace: number }
export enum DocumentProcessingPriority {
standard = 'standard',
@ -87,6 +88,14 @@ export type CurrentPlanInfoBackend = {
size: number
limit: number // total. 0 means unlimited
}
api_rate_limit?: {
size: number
limit: number // total. 0 means unlimited
}
trigger_events?: {
size: number
limit: number // total. 0 means unlimited
}
docs_processing: DocumentProcessingPriority
can_replace_logo: boolean
model_load_balancing_enabled: boolean

View File

@ -15,6 +15,7 @@ type Props = {
usage: number
total: number
unit?: string
unitPosition?: 'inline' | 'suffix'
}
const LOW = 50
@ -27,7 +28,8 @@ const UsageInfo: FC<Props> = ({
tooltip,
usage,
total,
unit = '',
unit,
unitPosition = 'suffix',
}) => {
const { t } = useTranslation()
@ -41,6 +43,12 @@ const UsageInfo: FC<Props> = ({
return 'bg-components-progress-error-progress'
})()
const isUnlimited = total === NUM_INFINITE
let totalDisplay: string | number = isUnlimited ? t('billing.plansCommon.unlimited') : total
if (!isUnlimited && unit && unitPosition === 'inline')
totalDisplay = `${total}${unit}`
const showUnit = !!unit && !isUnlimited && unitPosition === 'suffix'
return (
<div className={cn('flex flex-col gap-2 rounded-xl bg-components-panel-bg p-4', className)}>
<Icon className='h-4 w-4 text-text-tertiary' />
@ -56,10 +64,17 @@ const UsageInfo: FC<Props> = ({
/>
)}
</div>
<div className='system-md-semibold flex items-center gap-1 text-text-primary'>
{usage}
<div className='system-md-regular text-text-quaternary'>/</div>
<div>{total === NUM_INFINITE ? t('billing.plansCommon.unlimited') : `${total}${unit}`}</div>
<div className='system-md-semibold flex items-center gap-1 text-text-primary'>
<div className='flex items-center gap-1'>
{usage}
<div className='system-md-regular text-text-quaternary'>/</div>
<div>{totalDisplay}</div>
</div>
{showUnit && (
<div className='system-xs-medium ml-auto text-text-tertiary'>
{unit}
</div>
)}
</div>
<ProgressBar
percent={percent}

View File

@ -30,6 +30,7 @@ const VectorSpaceInfo: FC<Props> = ({
usage={usage.vectorSpace}
total={total.vectorSpace}
unit='MB'
unitPosition='inline'
/>
)
}

View File

@ -1,5 +1,5 @@
import type { CurrentPlanInfoBackend } from '../type'
import { NUM_INFINITE } from '@/app/components/billing/config'
import { ALL_PLANS, NUM_INFINITE } from '@/app/components/billing/config'
const parseLimit = (limit: number) => {
if (limit === 0)
@ -9,14 +9,23 @@ const parseLimit = (limit: number) => {
}
export const parseCurrentPlan = (data: CurrentPlanInfoBackend) => {
const planType = data.billing.subscription.plan
const planPreset = ALL_PLANS[planType]
const resolveLimit = (limit?: number, fallback?: number) => {
const value = limit ?? fallback ?? 0
return parseLimit(value)
}
return {
type: data.billing.subscription.plan,
type: planType,
usage: {
vectorSpace: data.vector_space.size,
buildApps: data.apps?.size || 0,
teamMembers: data.members.size,
annotatedResponse: data.annotation_quota_limit.size,
documentsUploadQuota: data.documents_upload_quota.size,
apiRateLimit: data.api_rate_limit?.size ?? 0,
triggerEvents: data.trigger_events?.size ?? 0,
},
total: {
vectorSpace: parseLimit(data.vector_space.limit),
@ -24,6 +33,8 @@ export const parseCurrentPlan = (data: CurrentPlanInfoBackend) => {
teamMembers: parseLimit(data.members.limit),
annotatedResponse: parseLimit(data.annotation_quota_limit.limit),
documentsUploadQuota: parseLimit(data.documents_upload_quota.limit),
apiRateLimit: resolveLimit(data.api_rate_limit?.limit, planPreset?.apiRateLimit ?? NUM_INFINITE),
triggerEvents: resolveLimit(data.trigger_events?.limit, planPreset?.triggerEvents),
},
}
}

View File

@ -17,7 +17,8 @@ import {
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Model, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { RETRIEVE_METHOD } from '@/types/app'
import { Plan, type UsagePlanInfo } from '@/app/components/billing/type'
import type { Plan } from '@/app/components/billing/type'
import type { UsagePlanInfo } from '@/app/components/billing/type'
import { fetchCurrentPlanInfo } from '@/service/billing'
import { parseCurrentPlan } from '@/app/components/billing/utils'
import { defaultPlan } from '@/app/components/billing/config'
@ -70,23 +71,7 @@ const ProviderContext = createContext<ProviderContextState>({
textGenerationModelList: [],
supportRetrievalMethods: [],
isAPIKeySet: true,
plan: {
type: Plan.sandbox,
usage: {
vectorSpace: 32,
buildApps: 12,
teamMembers: 1,
annotatedResponse: 1,
documentsUploadQuota: 50,
},
total: {
vectorSpace: 200,
buildApps: 50,
teamMembers: 1,
annotatedResponse: 10,
documentsUploadQuota: 500,
},
},
plan: defaultPlan,
isFetchedPlan: false,
enableBilling: false,
onPlanInfoChanged: noop,

View File

@ -7,6 +7,8 @@ const translation = {
documentsUploadQuota: 'Documents Upload Quota',
vectorSpace: 'Knowledge Data Storage',
vectorSpaceTooltip: 'Documents with the High Quality indexing mode will consume Knowledge Data Storage resources. When Knowledge Data Storage reaches the limit, new documents will not be uploaded.',
triggerEvents: 'Trigger Events',
perMonth: 'per month',
},
teamMembers: 'Team Members',
upgradeBtn: {

View File

@ -7,6 +7,8 @@ const translation = {
documentsUploadQuota: 'ドキュメント・アップロード・クォータ',
vectorSpace: 'ナレッジベースのデータストレージ',
vectorSpaceTooltip: '高品質インデックスモードのドキュメントは、ナレッジベースのデータストレージのリソースを消費します。ナレッジベースのデータストレージの上限に達すると、新しいドキュメントはアップロードされません。',
triggerEvents: 'トリガーイベント',
perMonth: '月あたり',
},
upgradeBtn: {
plain: 'プランをアップグレード',

View File

@ -7,6 +7,8 @@ const translation = {
documentsUploadQuota: '文档上传配额',
vectorSpace: '知识库数据存储空间',
vectorSpaceTooltip: '采用高质量索引模式的文档会消耗知识数据存储资源。当知识数据存储达到限制时,将不会上传新文档。',
triggerEvents: '触发事件',
perMonth: '每月',
},
upgradeBtn: {
plain: '查看套餐',