From 0f952f328fd627ad7ef9c0e1f9a467e45e84f590 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Mon, 10 Nov 2025 17:59:48 +0800 Subject: [PATCH 1/4] 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: '查看套餐', From 81afd087f6f321d7f0de04c1822173dc5b80a7a3 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Tue, 11 Nov 2025 15:00:15 +0800 Subject: [PATCH 2/4] feat: add trigger events, workflow execution, and start nodes to billing plan features - Add three new feature items to cloud plan list: - Trigger Events (varies by plan: 3K for sandbox, 20K/month for pro, unlimited for team) - Workflow Execution (standard/faster/priority based on plan) - Start Nodes (limited to 2 for sandbox, unlimited for pro/team) - Add i18n translations for en-US and zh-Hans - Position new items below document processing priority and above divider --- .../plans/cloud-plan-item/list/index.tsx | 25 +++++++++++++++++++ web/i18n/en-US/billing.ts | 14 +++++++++++ web/i18n/zh-Hans/billing.ts | 14 +++++++++++ 3 files changed, 53 insertions(+) diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx index 0420bfc7a3..0b35ee7e97 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx @@ -56,6 +56,31 @@ const List = ({ + + + Date: Tue, 11 Nov 2025 15:15:40 +0800 Subject: [PATCH 3/4] feat: add URL parameter support for pricing modal using action=showPricing --- web/app/education-apply/constants.ts | 1 + web/context/modal-context.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/web/app/education-apply/constants.ts b/web/app/education-apply/constants.ts index 2c42d2b263..75b6da67e5 100644 --- a/web/app/education-apply/constants.ts +++ b/web/app/education-apply/constants.ts @@ -2,3 +2,4 @@ export const EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION = 'getEducationVerify' export const EDUCATION_VERIFYING_LOCALSTORAGE_ITEM = 'educationVerifying' export const EDUCATION_PRICING_SHOW_ACTION = 'educationPricing' export const EDUCATION_RE_VERIFY_ACTION = 'educationReVerify' +export const SHOW_PRICING_MODAL_ACTION = 'showPricing' diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index 5baadc934b..708a951dde 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -3,7 +3,7 @@ import type { Dispatch, SetStateAction } from 'react' import { useCallback, useState } from 'react' import { createContext, useContext, useContextSelector } from 'use-context-selector' -import { useRouter, useSearchParams } from 'next/navigation' +import { useSearchParams } from 'next/navigation' import type { ConfigurationMethodEnum, Credential, @@ -13,6 +13,7 @@ import type { } from '@/app/components/header/account-setting/model-provider-page/declarations' import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, + SHOW_PRICING_MODAL_ACTION, } from '@/app/education-apply/constants' import type { ModerationConfig, PromptVariable } from '@/models/debug' import type { @@ -151,8 +152,9 @@ export const ModalContextProvider = ({ const [showEducationExpireNoticeModal, setShowEducationExpireNoticeModal] = useState | null>(null) const searchParams = useSearchParams() - const router = useRouter() - const [showPricingModal, setShowPricingModal] = useState(searchParams.get('show-pricing') === '1') + const [showPricingModal, setShowPricingModal] = useState( + searchParams.get('action') === SHOW_PRICING_MODAL_ACTION, + ) const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false) const handleCancelAccountSettingModal = () => { const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) @@ -308,8 +310,6 @@ export const ModalContextProvider = ({ { !!showPricingModal && ( { - if (searchParams.get('show-pricing') === '1') - router.push(location.pathname, { forceOptimisticNavigation: true } as any) removeSpecificQueryParam('action') setShowPricingModal(false) }} /> From 9de0e3c3a782bee99d3c16c3d528969617754004 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Tue, 11 Nov 2025 15:18:14 +0800 Subject: [PATCH 4/4] fix: add missing TimePicker type definitions for notClearable, triggerFullWidth, showTimezone and placement --- web/app/components/base/date-and-time-picker/types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/app/components/base/date-and-time-picker/types.ts b/web/app/components/base/date-and-time-picker/types.ts index 483913a62e..f79fb83ace 100644 --- a/web/app/components/base/date-and-time-picker/types.ts +++ b/web/app/components/base/date-and-time-picker/types.ts @@ -1,4 +1,5 @@ import type { Dayjs } from 'dayjs' +import type { Placement } from '@floating-ui/react' export enum ViewType { date = 'date', @@ -65,6 +66,10 @@ export type TimePickerProps = { title?: string minuteFilter?: (minutes: string[]) => string[] popupClassName?: string + notClearable?: boolean + triggerFullWidth?: boolean + showTimezone?: boolean + placement?: Placement } export type TimePickerFooterProps = {