From dd119eb44f25687a853dea630061dbb3f3693ea9 Mon Sep 17 00:00:00 2001 From: yyh Date: Thu, 5 Mar 2026 09:14:55 +0800 Subject: [PATCH] fix(web): align UsagePrioritySection with Figma design and fix i18n key ordering - Single-row layout for icon, label, and option cards - Icon: arrow-up-double-line matching design spec - Buttons: flexible width with whitespace-nowrap instead of fixed w-[72px] - Add min-w-0 + truncate for text overflow, focus-visible ring for a11y - Sort modelProvider.card.* i18n keys alphabetically --- .../credits-fallback-alert.tsx | 26 ++++++++++ .../model-auth-dropdown/dropdown-content.tsx | 12 ++++- .../model-auth-dropdown/index.spec.tsx | 2 +- .../usage-priority-section.tsx | 50 +++++++++++-------- .../use-credential-panel-state.spec.ts | 3 +- .../use-credential-panel-state.ts | 10 ++-- web/i18n/en-US/common.json | 25 ++++++---- web/i18n/zh-Hans/common.json | 25 ++++++---- 8 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/credits-fallback-alert.tsx diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/credits-fallback-alert.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/credits-fallback-alert.tsx new file mode 100644 index 0000000000..6dfbc2d350 --- /dev/null +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/credits-fallback-alert.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from 'react-i18next' + +type CreditsFallbackAlertProps = { + hasCredentials: boolean +} + +export default function CreditsFallbackAlert({ hasCredentials }: CreditsFallbackAlertProps) { + const { t } = useTranslation() + + const titleKey = hasCredentials + ? 'modelProvider.card.apiKeyUnavailableFallback' + : 'modelProvider.card.noApiKeysFallback' + + return ( +
+
+ {t(titleKey, { ns: 'common' })} +
+ {hasCredentials && ( +
+ {t('modelProvider.card.apiKeyUnavailableFallbackDescription', { ns: 'common' })} +
+ )} +
+ ) +} diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/dropdown-content.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/dropdown-content.tsx index f0ffa5242e..433f956c96 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/dropdown-content.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-auth-dropdown/dropdown-content.tsx @@ -15,6 +15,7 @@ import { ConfigurationMethodEnum } from '../../declarations' import { useAuth } from '../../model-auth/hooks' import ApiKeySection from './api-key-section' import CreditsExhaustedAlert from './credits-exhausted-alert' +import CreditsFallbackAlert from './credits-fallback-alert' import UsagePrioritySection from './usage-priority-section' type DropdownContentProps = { @@ -66,9 +67,13 @@ function DropdownContent({ onClose() }, [handleOpenModal, onClose]) - const showCreditsAlert = state.isCreditsExhausted && state.supportsCredits + const showCreditsExhaustedAlert = state.isCreditsExhausted && state.supportsCredits const hasApiKeyFallback = state.variant === 'api-fallback' || (state.variant === 'api-active' && state.priority === 'apiKey') + const showCreditsFallbackAlert = state.priority === 'apiKey' + && state.supportsCredits + && !state.isCreditsExhausted + && state.variant !== 'api-active' return ( <> @@ -79,7 +84,10 @@ function DropdownContent({ onSelect={onChangePriority} /> )} - {showCreditsAlert && ( + {showCreditsFallbackAlert && ( + + )} + {showCreditsExhaustedAlert && ( )} -
- - {t('modelProvider.card.usagePriority', { ns: 'common' })} - -
-
- {options.map(option => ( - - ))} +
+
+
+ +
+
+ + {t('modelProvider.card.usagePriority', { ns: 'common' })} + + +
+
+ {options.map(option => ( + + ))} +
) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.spec.ts b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.spec.ts index 453fdc6c3a..d810ef635c 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.spec.ts +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.spec.ts @@ -2,6 +2,7 @@ import type { ModelProvider } from '../declarations' import { renderHook } from '@testing-library/react' import { ConfigurationMethodEnum, + CurrentSystemQuotaTypeEnum, CustomConfigurationStatusEnum, PreferredProviderTypeEnum, } from '../declarations' @@ -138,7 +139,7 @@ describe('useCredentialPanelState', () => { describe('apiKeyOnly priority (non-cloud / system disabled)', () => { it('should return apiKeyOnly when system config disabled', () => { const provider = createProvider({ - system_configuration: { enabled: false, current_quota_type: 'trial', quota_configurations: [] }, + system_configuration: { enabled: false, current_quota_type: CurrentSystemQuotaTypeEnum.trial, quota_configurations: [] }, }) const { result } = renderHook(() => useCredentialPanelState(provider)) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.ts b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.ts index fafb1c5558..85c518620d 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.ts +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/use-credential-panel-state.ts @@ -1,7 +1,6 @@ import type { ModelProvider } from '../declarations' import { useCredentialStatus } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks' import { - CustomConfigurationStatusEnum, PreferredProviderTypeEnum, } from '../declarations' import { useTrialCredits } from './use-trial-credits' @@ -43,6 +42,7 @@ function deriveVariant( isExhausted: boolean, hasCredential: boolean, authorized: boolean | undefined, + credentialName: string | undefined, ): CardVariant { if (priority === 'credits') { if (!isExhausted) @@ -57,7 +57,7 @@ function deriveVariant( if (hasCredential && authorized) return 'api-active' if (hasCredential && !authorized) - return 'api-unavailable' + return credentialName ? 'api-unavailable' : 'api-required-configure' return 'api-required-add' } @@ -70,11 +70,9 @@ export function useCredentialPanelState(provider: ModelProvider): CredentialPane } = useCredentialStatus(provider) const systemConfig = provider.system_configuration - const customConfig = provider.custom_configuration const preferredType = provider.preferred_provider_type const supportsCredits = systemConfig.enabled - const isCustomConfigured = customConfig.status === CustomConfigurationStatusEnum.active const priority: UsagePriority = !supportsCredits ? 'apiKeyOnly' @@ -82,9 +80,9 @@ export function useCredentialPanelState(provider: ModelProvider): CredentialPane ? 'credits' : 'apiKey' - const showPrioritySwitcher = supportsCredits && isCustomConfigured + const showPrioritySwitcher = supportsCredits - const variant = deriveVariant(priority, isExhausted, hasCredential, !!authorized) + const variant = deriveVariant(priority, isExhausted, hasCredential, !!authorized, current_credential_name) return { variant, diff --git a/web/i18n/en-US/common.json b/web/i18n/en-US/common.json index 250c72c9b6..bb5d5cfd00 100644 --- a/web/i18n/en-US/common.json +++ b/web/i18n/en-US/common.json @@ -341,11 +341,24 @@ "modelProvider.buyQuota": "Buy Quota", "modelProvider.callTimes": "Call times", "modelProvider.card.aiCreditsInUse": "AI credits in use", + "modelProvider.card.aiCreditsOption": "AI credits", + "modelProvider.card.apiKeyOption": "API Key", + "modelProvider.card.apiKeyRequired": "API key required", + "modelProvider.card.apiKeyUnavailableFallback": "API Key unavailable, now using AI credits", + "modelProvider.card.apiKeyUnavailableFallbackDescription": "Check your API key configuration to switch back", "modelProvider.card.buyQuota": "Buy Quota", "modelProvider.card.callTimes": "Call times", + "modelProvider.card.creditsExhaustedDescription": "Please {{upgradeLink}} or configure an API key", + "modelProvider.card.creditsExhaustedFallback": "AI credits exhausted, now using API key", + "modelProvider.card.creditsExhaustedFallbackDescription": "{{upgradeLink}} to resume AI credit priority.", + "modelProvider.card.creditsExhaustedMessage": "AI credits have been exhausted", "modelProvider.card.modelAPI": "{{modelName}} models are using the API Key.", "modelProvider.card.modelNotSupported": "{{modelName}} not installed", "modelProvider.card.modelSupported": "{{modelName}} models are using these credits.", + "modelProvider.card.noApiKeysDescription": "Add an API key to start using your own model credentials.", + "modelProvider.card.noApiKeysFallback": "No API keys, using AI credits instead", + "modelProvider.card.noApiKeysTitle": "No API keys configured yet", + "modelProvider.card.noAvailableUsage": "No available usage", "modelProvider.card.onTrial": "On Trial", "modelProvider.card.paid": "Paid", "modelProvider.card.priorityUse": "Priority use", @@ -354,20 +367,10 @@ "modelProvider.card.removeKey": "Remove API Key", "modelProvider.card.tip": "AI Credits supports models from {{modelNames}}. Priority will be given to the paid quota. The Trial quota will be used after the paid quota is exhausted.", "modelProvider.card.tokens": "Tokens", - "modelProvider.card.noAvailableUsage": "No available usage", - "modelProvider.card.apiKeyRequired": "API key required", "modelProvider.card.unavailable": "Unavailable", - "modelProvider.card.usagePriority": "Usage Priority", - "modelProvider.card.aiCreditsOption": "AI credits", - "modelProvider.card.apiKeyOption": "API Key", - "modelProvider.card.creditsExhaustedMessage": "AI credits have been exhausted", - "modelProvider.card.creditsExhaustedDescription": "Please {{upgradeLink}} or configure an API key", - "modelProvider.card.creditsExhaustedFallback": "AI credits exhausted, now using API key", - "modelProvider.card.creditsExhaustedFallbackDescription": "{{upgradeLink}} to resume AI credit priority.", "modelProvider.card.upgradePlan": "upgrade your plan", - "modelProvider.card.noApiKeysTitle": "No API keys configured yet", - "modelProvider.card.noApiKeysDescription": "Add an API key to start using your own model credentials.", "modelProvider.card.usageLabel": "Usage", + "modelProvider.card.usagePriority": "Usage Priority", "modelProvider.collapse": "Collapse", "modelProvider.config": "Config", "modelProvider.configLoadBalancing": "Config Load Balancing", diff --git a/web/i18n/zh-Hans/common.json b/web/i18n/zh-Hans/common.json index 2f0ce96872..4262864a3c 100644 --- a/web/i18n/zh-Hans/common.json +++ b/web/i18n/zh-Hans/common.json @@ -341,11 +341,24 @@ "modelProvider.buyQuota": "购买额度", "modelProvider.callTimes": "调用次数", "modelProvider.card.aiCreditsInUse": "AI 额度使用中", + "modelProvider.card.aiCreditsOption": "AI 额度", + "modelProvider.card.apiKeyOption": "API Key", + "modelProvider.card.apiKeyRequired": "需要配置 API Key", + "modelProvider.card.apiKeyUnavailableFallback": "API Key 不可用,正在使用 AI 额度", + "modelProvider.card.apiKeyUnavailableFallbackDescription": "检查你的 API Key 配置以切换回来", "modelProvider.card.buyQuota": "购买额度", "modelProvider.card.callTimes": "调用次数", + "modelProvider.card.creditsExhaustedDescription": "请{{upgradeLink}}或配置 API Key", + "modelProvider.card.creditsExhaustedFallback": "AI 额度已用尽,正在使用 API Key", + "modelProvider.card.creditsExhaustedFallbackDescription": "{{upgradeLink}}以恢复 AI 额度优先使用。", + "modelProvider.card.creditsExhaustedMessage": "AI 额度已用尽", "modelProvider.card.modelAPI": "{{modelName}} 模型正在使用 API Key。", "modelProvider.card.modelNotSupported": "{{modelName}} 未安装", "modelProvider.card.modelSupported": "{{modelName}} 模型正在使用此额度。", + "modelProvider.card.noApiKeysDescription": "添加 API Key 以使用自有模型凭证。", + "modelProvider.card.noApiKeysFallback": "未配置 API Key,正在使用 AI 额度", + "modelProvider.card.noApiKeysTitle": "尚未配置 API Key", + "modelProvider.card.noAvailableUsage": "无可用额度", "modelProvider.card.onTrial": "试用中", "modelProvider.card.paid": "已购买", "modelProvider.card.priorityUse": "优先使用", @@ -354,20 +367,10 @@ "modelProvider.card.removeKey": "删除 API 密钥", "modelProvider.card.tip": "AI Credits 支持使用 {{modelNames}} 的模型;试用额度会在付费额度用尽后才会消耗。", "modelProvider.card.tokens": "Tokens", - "modelProvider.card.noAvailableUsage": "无可用额度", - "modelProvider.card.apiKeyRequired": "需要配置 API Key", "modelProvider.card.unavailable": "不可用", - "modelProvider.card.usagePriority": "使用优先级", - "modelProvider.card.aiCreditsOption": "AI 额度", - "modelProvider.card.apiKeyOption": "API Key", - "modelProvider.card.creditsExhaustedMessage": "AI 额度已用尽", - "modelProvider.card.creditsExhaustedDescription": "请{{upgradeLink}}或配置 API Key", - "modelProvider.card.creditsExhaustedFallback": "AI 额度已用尽,正在使用 API Key", - "modelProvider.card.creditsExhaustedFallbackDescription": "{{upgradeLink}}以恢复 AI 额度优先使用。", "modelProvider.card.upgradePlan": "升级套餐", - "modelProvider.card.noApiKeysTitle": "尚未配置 API Key", - "modelProvider.card.noApiKeysDescription": "添加 API Key 以使用自有模型凭证。", "modelProvider.card.usageLabel": "用量", + "modelProvider.card.usagePriority": "使用优先级", "modelProvider.collapse": "收起", "modelProvider.config": "配置", "modelProvider.configLoadBalancing": "设置负载均衡",