mirror of
https://github.com/langgenius/dify.git
synced 2026-03-05 07:37:07 +08:00
refactor(web): extract SystemQuotaCard compound component and shared useTrialCredits hook
Extract trial credits calculation into a shared useTrialCredits hook to prevent logic drift between QuotaPanel and CredentialPanel. Add SystemQuotaCard compound component with explicit default/destructive variants for the system quota UI state in provider cards, replacing inline conditional styling with composable Label and Actions slots. Remove unnecessary useMemo for simple derived values.
This commit is contained in:
@ -2,7 +2,6 @@ import type {
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { ConfigProvider } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
@ -25,6 +24,8 @@ import {
|
||||
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index'
|
||||
import PrioritySelector from './priority-selector'
|
||||
import PriorityUseTip from './priority-use-tip'
|
||||
import SystemQuotaCard from './system-quota-card'
|
||||
import { useTrialCredits } from './use-trial-credits'
|
||||
|
||||
type CredentialPanelProps = {
|
||||
provider: ModelProvider
|
||||
@ -53,6 +54,8 @@ const CredentialPanel = ({
|
||||
} = useCredentialStatus(provider)
|
||||
|
||||
const showPrioritySelector = systemConfig.enabled && isCustomConfigured && IS_CLOUD_EDITION
|
||||
const isUsingSystemQuota = systemConfig.enabled && priorityUseType === PreferredProviderTypeEnum.system && IS_CLOUD_EDITION
|
||||
const { isExhausted } = useTrialCredits()
|
||||
|
||||
const handleChangePriority = async (key: PreferredProviderTypeEnum) => {
|
||||
const res = await changeModelProviderPriority({
|
||||
@ -80,24 +83,40 @@ const CredentialPanel = ({
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
const credentialLabel = useMemo(() => {
|
||||
if (!hasCredential)
|
||||
return t('modelProvider.auth.unAuthorized', { ns: 'common' })
|
||||
if (authorized)
|
||||
return current_credential_name
|
||||
if (authRemoved)
|
||||
return t('modelProvider.auth.authRemoved', { ns: 'common' })
|
||||
const credentialLabel = !hasCredential
|
||||
? t('modelProvider.auth.unAuthorized', { ns: 'common' })
|
||||
: authorized
|
||||
? current_credential_name
|
||||
: authRemoved
|
||||
? t('modelProvider.auth.authRemoved', { ns: 'common' })
|
||||
: ''
|
||||
|
||||
return ''
|
||||
}, [authorized, authRemoved, current_credential_name, hasCredential, t])
|
||||
const color = (authRemoved || !hasCredential)
|
||||
? 'red'
|
||||
: notAllowedToUse
|
||||
? 'gray'
|
||||
: 'green'
|
||||
|
||||
const color = useMemo(() => {
|
||||
if (authRemoved || !hasCredential)
|
||||
return 'red'
|
||||
if (notAllowedToUse)
|
||||
return 'gray'
|
||||
return 'green'
|
||||
}, [authRemoved, notAllowedToUse, hasCredential])
|
||||
if (isUsingSystemQuota) {
|
||||
return (
|
||||
<SystemQuotaCard variant={isExhausted ? 'destructive' : 'default'}>
|
||||
<SystemQuotaCard.Label>
|
||||
{isExhausted
|
||||
? t('modelProvider.card.quotaExhausted', { ns: 'common' })
|
||||
: t('modelProvider.card.aiCreditsInUse', { ns: 'common' })}
|
||||
</SystemQuotaCard.Label>
|
||||
<SystemQuotaCard.Actions>
|
||||
<ConfigProvider provider={provider} />
|
||||
{showPrioritySelector && (
|
||||
<PrioritySelector
|
||||
value={priorityUseType}
|
||||
onSelect={handleChangePriority}
|
||||
/>
|
||||
)}
|
||||
</SystemQuotaCard.Actions>
|
||||
</SystemQuotaCard>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -108,7 +127,7 @@ const CredentialPanel = ({
|
||||
authRemoved && 'border-state-destructive-border bg-state-destructive-hover',
|
||||
)}
|
||||
>
|
||||
<div className="system-xs-medium mb-1 flex h-5 items-center justify-between pl-2 pr-[7px] pt-1 text-text-tertiary">
|
||||
<div className="mb-1 flex h-5 items-center justify-between pl-2 pr-[7px] pt-1 text-text-tertiary system-xs-medium">
|
||||
<div
|
||||
className={cn(
|
||||
'grow truncate',
|
||||
|
||||
@ -11,7 +11,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/u
|
||||
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
|
||||
import { useSystemFeaturesQuery } from '@/context/global-public-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useCurrentWorkspace } from '@/service/use-common'
|
||||
import { ModelProviderQuotaGetPaid } from '@/types/model-provider'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
@ -19,6 +18,7 @@ import { PreferredProviderTypeEnum } from '../declarations'
|
||||
import { useMarketplaceAllPlugins } from '../hooks'
|
||||
import { MODEL_PROVIDER_QUOTA_GET_PAID, modelNameMap } from '../utils'
|
||||
import styles from './quota-panel.module.css'
|
||||
import { useTrialCredits } from './use-trial-credits'
|
||||
|
||||
const providerIconMap: Record<ModelProviderQuotaGetPaid, ComponentType<{ className?: string }>> = {
|
||||
[ModelProviderQuotaGetPaid.OPENAI]: OpenaiSmall,
|
||||
@ -50,11 +50,9 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
providers,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: currentWorkspace, isPending: isPendingWorkspace } = useCurrentWorkspace()
|
||||
const { credits, isExhausted, isLoading, nextCreditResetDate } = useTrialCredits()
|
||||
const { data: systemFeatures } = useSystemFeaturesQuery()
|
||||
const trialModels = systemFeatures?.trial_models ?? []
|
||||
const credits = Math.max(((currentWorkspace?.trial_credits ?? 0) - (currentWorkspace?.trial_credits_used ?? 0)) || 0, 0)
|
||||
const isLoading = isPendingWorkspace && !currentWorkspace
|
||||
const providerMap = useMemo(() => new Map(
|
||||
providers.map(p => [p.provider, p.preferred_provider_type]),
|
||||
), [providers])
|
||||
@ -111,7 +109,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative my-2 min-w-[72px] shrink-0 overflow-hidden rounded-xl border-[0.5px] pb-2.5 pl-4 pr-2.5 pt-3 shadow-xs',
|
||||
credits <= 0
|
||||
isExhausted
|
||||
? 'border-state-destructive-border hover:bg-state-destructive-hover'
|
||||
: 'border-components-panel-border bg-third-party-model-bg-default',
|
||||
)}
|
||||
@ -140,14 +138,14 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
{credits > 0
|
||||
? <span className="mr-0.5 text-text-secondary system-xl-semibold">{formatNumber(credits)}</span>
|
||||
: <span className="mr-0.5 text-text-destructive system-xl-semibold">{t('modelProvider.card.quotaExhausted', { ns: 'common' })}</span>}
|
||||
{currentWorkspace?.next_credit_reset_date
|
||||
{nextCreditResetDate
|
||||
? (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span>
|
||||
{t('modelProvider.resetDate', {
|
||||
ns: 'common',
|
||||
date: formatTime(currentWorkspace.next_credit_reset_date, t('dateFormat', { ns: 'appLog' })),
|
||||
date: formatTime(nextCreditResetDate!, t('dateFormat', { ns: 'appLog' })),
|
||||
interpolation: { escapeValue: false },
|
||||
})}
|
||||
</span>
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { createContext, useContext } from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import styles from './quota-panel.module.css'
|
||||
|
||||
type Variant = 'default' | 'destructive'
|
||||
|
||||
const VariantContext = createContext<Variant>('default')
|
||||
|
||||
const containerVariants: Record<Variant, string> = {
|
||||
default: 'border-components-panel-border bg-white/[0.18]',
|
||||
destructive: 'border-state-destructive-border bg-state-destructive-hover',
|
||||
}
|
||||
|
||||
const labelVariants: Record<Variant, string> = {
|
||||
default: 'text-text-secondary',
|
||||
destructive: 'text-text-destructive',
|
||||
}
|
||||
|
||||
type SystemQuotaCardProps = {
|
||||
variant?: Variant
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const SystemQuotaCard = ({
|
||||
variant = 'default',
|
||||
children,
|
||||
}: SystemQuotaCardProps) => {
|
||||
return (
|
||||
<VariantContext.Provider value={variant}>
|
||||
<div className={cn(
|
||||
'relative isolate ml-1 flex w-[128px] shrink-0 flex-col justify-between rounded-lg border-[0.5px] p-1 shadow-xs',
|
||||
containerVariants[variant],
|
||||
)}
|
||||
>
|
||||
<div className={cn('pointer-events-none absolute inset-0 rounded-[7px]', styles.gridBg)} />
|
||||
{children}
|
||||
</div>
|
||||
</VariantContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const Label = ({ children }: { children: ReactNode }) => {
|
||||
const variant = useContext(VariantContext)
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative z-[1] truncate px-1.5 pt-1 system-xs-medium',
|
||||
labelVariants[variant],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Actions = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<div className="relative z-[1] flex items-center gap-0.5">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SystemQuotaCard.Label = Label
|
||||
SystemQuotaCard.Actions = Actions
|
||||
|
||||
export default SystemQuotaCard
|
||||
@ -0,0 +1,13 @@
|
||||
import { useCurrentWorkspace } from '@/service/use-common'
|
||||
|
||||
export const useTrialCredits = () => {
|
||||
const { data: currentWorkspace, isPending } = useCurrentWorkspace()
|
||||
const credits = Math.max(((currentWorkspace?.trial_credits ?? 0) - (currentWorkspace?.trial_credits_used ?? 0)) || 0, 0)
|
||||
|
||||
return {
|
||||
credits,
|
||||
isExhausted: credits <= 0,
|
||||
isLoading: isPending && !currentWorkspace,
|
||||
nextCreditResetDate: currentWorkspace?.next_credit_reset_date,
|
||||
}
|
||||
}
|
||||
@ -4858,9 +4858,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
|
||||
@ -340,6 +340,7 @@
|
||||
"modelProvider.auth.unAuthorized": "Unauthorized",
|
||||
"modelProvider.buyQuota": "Buy Quota",
|
||||
"modelProvider.callTimes": "Call times",
|
||||
"modelProvider.card.aiCreditsInUse": "AI credits in use",
|
||||
"modelProvider.card.buyQuota": "Buy Quota",
|
||||
"modelProvider.card.callTimes": "Call times",
|
||||
"modelProvider.card.modelAPI": "{{modelName}} models are using the API Key.",
|
||||
|
||||
@ -340,6 +340,7 @@
|
||||
"modelProvider.auth.unAuthorized": "未授权",
|
||||
"modelProvider.buyQuota": "购买额度",
|
||||
"modelProvider.callTimes": "调用次数",
|
||||
"modelProvider.card.aiCreditsInUse": "AI 额度使用中",
|
||||
"modelProvider.card.buyQuota": "购买额度",
|
||||
"modelProvider.card.callTimes": "调用次数",
|
||||
"modelProvider.card.modelAPI": "{{modelName}} 模型正在使用 API Key。",
|
||||
|
||||
Reference in New Issue
Block a user