From 3b4b5b332c0ca65d205b6c2c003a67e477be8646 Mon Sep 17 00:00:00 2001 From: CodingOnStar Date: Mon, 19 Jan 2026 11:47:35 +0800 Subject: [PATCH] feat(billing): enhance usage info with storage threshold display - Add storageThreshold, storageTooltip, storageTotalDisplay props to UsageInfo - Implement indeterminate state in ProgressBar for usage below threshold - Update VectorSpaceInfo to calculate total based on plan type - Add i18n for storage threshold tooltip (en-US, ja-JP, zh-Hans) --- .../components/billing/progress-bar/index.tsx | 11 ++ .../components/billing/usage-info/index.tsx | 156 +++++++++++++++--- .../billing/usage-info/vector-space-info.tsx | 29 +++- web/eslint-suppressions.json | 5 - web/i18n/en-US/billing.json | 1 + web/i18n/ja-JP/billing.json | 1 + web/i18n/zh-Hans/billing.json | 1 + 7 files changed, 179 insertions(+), 25 deletions(-) diff --git a/web/app/components/billing/progress-bar/index.tsx b/web/app/components/billing/progress-bar/index.tsx index c41fc53310..a4fdff5cd0 100644 --- a/web/app/components/billing/progress-bar/index.tsx +++ b/web/app/components/billing/progress-bar/index.tsx @@ -3,12 +3,23 @@ import { cn } from '@/utils/classnames' type ProgressBarProps = { percent: number color: string + indeterminate?: boolean } const ProgressBar = ({ percent = 0, color = '#2970FF', + indeterminate = false, }: ProgressBarProps) => { + if (indeterminate) { + return ( +
+ ) + } + return (
name: string tooltip?: string usage: number @@ -19,6 +19,11 @@ type Props = { resetHint?: string resetInDays?: number hideIcon?: boolean + // Props for the 50MB threshold display logic + storageThreshold?: number + storageTooltip?: string + storageTotalDisplay?: string // e.g., "5GB" or "50MB" for formatted display + isSandboxPlan?: boolean } const WARNING_THRESHOLD = 80 @@ -35,30 +40,150 @@ const UsageInfo: FC = ({ resetHint, resetInDays, hideIcon = false, + storageThreshold = 50, + storageTooltip, + storageTotalDisplay, + isSandboxPlan = false, }) => { const { t } = useTranslation() + // Special display logic for usage below threshold + const isBelowThreshold = usage < storageThreshold + // Sandbox at full capacity (usage >= threshold and it's sandbox plan) + const isSandboxFull = isSandboxPlan && usage >= storageThreshold + const percent = usage / total * 100 - const color = percent >= 100 - ? 'bg-components-progress-error-progress' - : (percent >= WARNING_THRESHOLD ? 'bg-components-progress-warning-progress' : 'bg-components-progress-bar-progress-solid') + const getProgressColor = () => { + if (percent >= 100) + return 'bg-components-progress-error-progress' + if (percent >= WARNING_THRESHOLD) + return 'bg-components-progress-warning-progress' + return 'bg-components-progress-bar-progress-solid' + } + const color = getProgressColor() const isUnlimited = total === NUM_INFINITE let totalDisplay: string | number = isUnlimited ? t('plansCommon.unlimited', { ns: 'billing' }) : total if (!isUnlimited && unit && unitPosition === 'inline') totalDisplay = `${total}${unit}` const showUnit = !!unit && !isUnlimited && unitPosition === 'suffix' const resetText = resetHint ?? (typeof resetInDays === 'number' ? t('usagePage.resetsIn', { ns: 'billing', count: resetInDays }) : undefined) - const rightInfo = resetText - ? ( + + const renderRightInfo = () => { + if (resetText) { + return (
{resetText}
) - : (showUnit && ( + } + if (showUnit) { + return (
{unit}
- )) + ) + } + return null + } + + // Render usage display + const renderUsageDisplay = () => { + // Sandbox user at full capacity + if (isSandboxFull) { + return ( +
+ + {storageThreshold} + + / + + {storageThreshold} + {' '} + {unit} + +
+ ) + } + // Usage below threshold - show "< 50 MB" or "< 50 / 5GB" + if (isBelowThreshold) { + const totalText = storageTotalDisplay || totalDisplay + return ( +
+ + < + {' '} + {storageThreshold} + + {!isSandboxPlan && ( + <> + / + {totalText} + + )} + {isSandboxPlan && {unit}} +
+ ) + } + // Pro/Team users with usage >= threshold - show actual usage + const totalText = storageTotalDisplay || totalDisplay + return ( +
+ {usage} + / + {totalText} +
+ ) + } + + // Render progress bar with optional tooltip wrapper + const renderProgressBar = () => { + const progressBar = ( + + ) + + if (storageTooltip) { + return ( + + {storageTooltip} +
+ )} + asChild={false} + > +
{progressBar}
+ + ) + } + + return progressBar + } + + // Render usage text with optional tooltip wrapper + const renderUsageWithTooltip = () => { + const usageDisplay = renderUsageDisplay() + + if (storageTooltip) { + return ( + + {storageTooltip} +
+ )} + asChild={false} + > +
{usageDisplay}
+ + ) + } + + return usageDisplay + } return (
@@ -78,17 +203,10 @@ const UsageInfo: FC = ({ )}
-
- {usage} -
/
-
{totalDisplay}
-
- {rightInfo} + {renderUsageWithTooltip()} + {renderRightInfo()}
- + {renderProgressBar()}
) } diff --git a/web/app/components/billing/usage-info/vector-space-info.tsx b/web/app/components/billing/usage-info/vector-space-info.tsx index 11e3a6a1ae..a7f3c61f07 100644 --- a/web/app/components/billing/usage-info/vector-space-info.tsx +++ b/web/app/components/billing/usage-info/vector-space-info.tsx @@ -6,21 +6,44 @@ import { import * as React from 'react' import { useTranslation } from 'react-i18next' import { useProviderContext } from '@/context/provider-context' +import { Plan } from '../type' import UsageInfo from '../usage-info' type Props = { className?: string } +// Storage threshold in MB - usage below this shows as "< 50 MB" +const STORAGE_THRESHOLD_MB = 50 + const VectorSpaceInfo: FC = ({ className, }) => { const { t } = useTranslation() const { plan } = useProviderContext() const { + type, usage, total, } = plan + + // Determine total based on plan type (in MB) + const getTotalInMB = () => { + switch (type) { + case Plan.sandbox: + return STORAGE_THRESHOLD_MB // 50 MB + case Plan.professional: + return 5 * 1024 // 5 GB = 5120 MB + case Plan.team: + return 20 * 1024 // 20 GB = 20480 MB + default: + return total.vectorSpace + } + } + + const totalInMB = getTotalInMB() + const isSandbox = type === Plan.sandbox + return ( = ({ name={t('usagePage.vectorSpace', { ns: 'billing' })} tooltip={t('usagePage.vectorSpaceTooltip', { ns: 'billing' }) as string} usage={usage.vectorSpace} - total={total.vectorSpace} + total={totalInMB} unit="MB" unitPosition="inline" + storageThreshold={STORAGE_THRESHOLD_MB} + storageTooltip={t('usagePage.storageThresholdTooltip', { ns: 'billing' }) as string} + storageTotalDisplay={`${totalInMB}MB`} + isSandboxPlan={isSandbox} /> ) } diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 86b7095f2b..91a506dd51 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -1691,11 +1691,6 @@ "count": 3 } }, - "app/components/billing/usage-info/index.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "app/components/custom/custom-web-app-brand/index.spec.tsx": { "ts/no-explicit-any": { "count": 7 diff --git a/web/i18n/en-US/billing.json b/web/i18n/en-US/billing.json index 3242aa8e78..bfd82e1d67 100644 --- a/web/i18n/en-US/billing.json +++ b/web/i18n/en-US/billing.json @@ -172,6 +172,7 @@ "usagePage.documentsUploadQuota": "Documents Upload Quota", "usagePage.perMonth": "per month", "usagePage.resetsIn": "Resets in {{count,number}} days", + "usagePage.storageThresholdTooltip": "Detailed usage is shown once storage exceeds 50 MB.", "usagePage.teamMembers": "Team Members", "usagePage.triggerEvents": "Trigger Events", "usagePage.vectorSpace": "Knowledge Data Storage", diff --git a/web/i18n/ja-JP/billing.json b/web/i18n/ja-JP/billing.json index b23ae6c959..650022f955 100644 --- a/web/i18n/ja-JP/billing.json +++ b/web/i18n/ja-JP/billing.json @@ -172,6 +172,7 @@ "usagePage.documentsUploadQuota": "ドキュメント・アップロード・クォータ", "usagePage.perMonth": "月あたり", "usagePage.resetsIn": "{{count,number}}日後にリセット", + "usagePage.storageThresholdTooltip": "ストレージ使用量が 50 MB を超えると、詳細な使用状況が表示されます。", "usagePage.teamMembers": "チームメンバー", "usagePage.triggerEvents": "トリガーイベント数", "usagePage.vectorSpace": "ナレッジベースのデータストレージ", diff --git a/web/i18n/zh-Hans/billing.json b/web/i18n/zh-Hans/billing.json index 9111c1a6d1..997887fe6c 100644 --- a/web/i18n/zh-Hans/billing.json +++ b/web/i18n/zh-Hans/billing.json @@ -172,6 +172,7 @@ "usagePage.documentsUploadQuota": "文档上传配额", "usagePage.perMonth": "每月", "usagePage.resetsIn": "{{count,number}} 天后重置", + "usagePage.storageThresholdTooltip": "存储空间超过 50 MB 后,将显示详细使用情况。", "usagePage.teamMembers": "团队成员", "usagePage.triggerEvents": "触发器事件数", "usagePage.vectorSpace": "知识库数据存储空间",