From 0f952f328fd627ad7ef9c0e1f9a467e45e84f590 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Mon, 10 Nov 2025 17:59:48 +0800 Subject: [PATCH] feat: add api rate limit and trigger events billing card --- web/app/components/billing/config.ts | 7 ++++++ web/app/components/billing/plan/index.tsx | 18 +++++++++++++ web/app/components/billing/type.ts | 11 +++++++- .../components/billing/usage-info/index.tsx | 25 +++++++++++++++---- .../billing/usage-info/vector-space-info.tsx | 1 + web/app/components/billing/utils/index.ts | 15 +++++++++-- web/context/provider-context.tsx | 21 +++------------- web/i18n/en-US/billing.ts | 2 ++ web/i18n/ja-JP/billing.ts | 2 ++ web/i18n/zh-Hans/billing.ts | 2 ++ 10 files changed, 78 insertions(+), 26 deletions(-) diff --git a/web/app/components/billing/config.ts b/web/app/components/billing/config.ts index 1d5fbc7491..c0a21c1ebf 100644 --- a/web/app/components/billing/config.ts +++ b/web/app/components/billing/config.ts @@ -26,6 +26,7 @@ export const ALL_PLANS: Record = { apiRateLimit: 5000, documentProcessingPriority: Priority.standard, messageRequest: 200, + triggerEvents: 3000, annotatedResponse: 10, logHistory: 30, }, @@ -43,6 +44,7 @@ export const ALL_PLANS: Record = { apiRateLimit: NUM_INFINITE, documentProcessingPriority: Priority.priority, messageRequest: 5000, + triggerEvents: 20000, annotatedResponse: 2000, logHistory: NUM_INFINITE, }, @@ -60,6 +62,7 @@ export const ALL_PLANS: Record = { 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, }, } diff --git a/web/app/components/billing/plan/index.tsx b/web/app/components/billing/plan/index.tsx index dd3908635b..4b68fcfb15 100644 --- a/web/app/components/billing/plan/index.tsx +++ b/web/app/components/billing/plan/index.tsx @@ -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 = ({ 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 = ({ usage={usage.annotatedResponse} total={total.annotatedResponse} /> + + & { vectorSpace: number } +export type UsagePlanInfo = Pick & { 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 diff --git a/web/app/components/billing/usage-info/index.tsx b/web/app/components/billing/usage-info/index.tsx index 30b4bca776..0ed8775772 100644 --- a/web/app/components/billing/usage-info/index.tsx +++ b/web/app/components/billing/usage-info/index.tsx @@ -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 = ({ tooltip, usage, total, - unit = '', + unit, + unitPosition = 'suffix', }) => { const { t } = useTranslation() @@ -41,6 +43,12 @@ const UsageInfo: FC = ({ 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 (
@@ -56,10 +64,17 @@ const UsageInfo: FC = ({ /> )}
-
- {usage} -
/
-
{total === NUM_INFINITE ? t('billing.plansCommon.unlimited') : `${total}${unit}`}
+
+
+ {usage} +
/
+
{totalDisplay}
+
+ {showUnit && ( +
+ {unit} +
+ )}
= ({ usage={usage.vectorSpace} total={total.vectorSpace} unit='MB' + unitPosition='inline' /> ) } diff --git a/web/app/components/billing/utils/index.ts b/web/app/components/billing/utils/index.ts index 111f02e3cf..00ab7913b5 100644 --- a/web/app/components/billing/utils/index.ts +++ b/web/app/components/billing/utils/index.ts @@ -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), }, } } diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index 755131c859..90233fbc21 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -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({ 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, diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts index 927f41a21d..3a8fca4648 100644 --- a/web/i18n/en-US/billing.ts +++ b/web/i18n/en-US/billing.ts @@ -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: { diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts index e13cd3ade8..b679ae571a 100644 --- a/web/i18n/ja-JP/billing.ts +++ b/web/i18n/ja-JP/billing.ts @@ -7,6 +7,8 @@ const translation = { documentsUploadQuota: 'ドキュメント・アップロード・クォータ', vectorSpace: 'ナレッジベースのデータストレージ', vectorSpaceTooltip: '高品質インデックスモードのドキュメントは、ナレッジベースのデータストレージのリソースを消費します。ナレッジベースのデータストレージの上限に達すると、新しいドキュメントはアップロードされません。', + triggerEvents: 'トリガーイベント', + perMonth: '月あたり', }, upgradeBtn: { plain: 'プランをアップグレード', diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index e4e6e30f90..6fb1448d06 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -7,6 +7,8 @@ const translation = { documentsUploadQuota: '文档上传配额', vectorSpace: '知识库数据存储空间', vectorSpaceTooltip: '采用高质量索引模式的文档会消耗知识数据存储资源。当知识数据存储达到限制时,将不会上传新文档。', + triggerEvents: '触发事件', + perMonth: '每月', }, upgradeBtn: { plain: '查看套餐',