mirror of
https://github.com/langgenius/dify.git
synced 2026-05-01 07:58:02 +08:00
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
This commit is contained in:
@ -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 (
|
||||
<div className="mx-3 mb-1 mt-0.5 rounded-lg bg-background-section-burn p-3">
|
||||
<div className="text-text-primary system-xs-medium">
|
||||
{t(titleKey, { ns: 'common' })}
|
||||
</div>
|
||||
{hasCredentials && (
|
||||
<div className="mt-0.5 text-text-tertiary system-2xs-regular">
|
||||
{t('modelProvider.card.apiKeyUnavailableFallbackDescription', { ns: 'common' })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -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 && (
|
||||
<CreditsFallbackAlert hasCredentials={state.hasCredentials} />
|
||||
)}
|
||||
{showCreditsExhaustedAlert && (
|
||||
<CreditsExhaustedAlert hasApiKeyFallback={hasApiKeyFallback} />
|
||||
)}
|
||||
<ApiKeySection
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { CredentialPanelState } from '../use-credential-panel-state'
|
||||
import type { ModelProvider } from '../../declarations'
|
||||
import type { CredentialPanelState } from '../use-credential-panel-state'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../declarations'
|
||||
import ModelAuthDropdown from './index'
|
||||
|
||||
@ -20,28 +20,34 @@ export default function UsagePrioritySection({ value, onSelect }: UsagePriorityS
|
||||
: PreferredProviderTypeEnum.custom
|
||||
|
||||
return (
|
||||
<div className="border-b border-b-divider-subtle px-3 pb-2 pt-2.5">
|
||||
<div className="mb-1.5 flex items-center gap-1 text-text-tertiary system-xs-medium">
|
||||
<span className="i-ri-arrow-up-down-line h-4 w-4 shrink-0" />
|
||||
<span>{t('modelProvider.card.usagePriority', { ns: 'common' })}</span>
|
||||
<span className="i-ri-question-line h-3.5 w-3.5 shrink-0 text-text-quaternary" />
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
{options.map(option => (
|
||||
<button
|
||||
key={option.key}
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex-1 rounded-lg px-2 py-1 text-center transition-colors system-xs-medium',
|
||||
selectedKey === option.key
|
||||
? 'border-[1.5px] border-components-option-card-option-selected-border bg-components-panel-bg text-text-primary shadow-xs'
|
||||
: 'border border-components-option-card-option-border bg-components-option-card-option-bg text-text-secondary hover:bg-components-option-card-option-bg-hover',
|
||||
)}
|
||||
onClick={() => onSelect(option.key)}
|
||||
>
|
||||
{t(option.labelKey, { ns: 'common' })}
|
||||
</button>
|
||||
))}
|
||||
<div className="border-b border-b-divider-subtle p-1">
|
||||
<div className="flex items-center gap-1 rounded-lg p-1">
|
||||
<div className="shrink-0 px-0.5 py-1">
|
||||
<span className="i-ri-arrow-up-double-line block h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-0.5 py-0.5">
|
||||
<span className="truncate text-text-secondary system-sm-medium">
|
||||
{t('modelProvider.card.usagePriority', { ns: 'common' })}
|
||||
</span>
|
||||
<span className="i-ri-question-line h-3.5 w-3.5 shrink-0 text-text-quaternary" />
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{options.map(option => (
|
||||
<button
|
||||
key={option.key}
|
||||
type="button"
|
||||
className={cn(
|
||||
'shrink-0 whitespace-nowrap rounded-md px-2 py-1 text-center transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-components-button-primary-border',
|
||||
selectedKey === option.key
|
||||
? 'border-[1.5px] border-components-option-card-option-selected-border bg-components-panel-bg text-text-primary shadow-xs system-xs-medium'
|
||||
: 'border border-components-option-card-option-border bg-components-option-card-option-bg text-text-secondary system-xs-regular hover:bg-components-option-card-option-bg-hover',
|
||||
)}
|
||||
onClick={() => onSelect(option.key)}
|
||||
>
|
||||
{t(option.labelKey, { ns: 'common' })}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "设置负载均衡",
|
||||
|
||||
Reference in New Issue
Block a user