mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 23:18:05 +08:00
feat: refactor model provider quota handling and improve type safety
- Moved ModelProviderQuotaGetPaid enum to a new file for better organization. - Updated imports across components to use the new type definition. - Enhanced type safety in utility functions by specifying more precise types. - Simplified provider configuration by deriving provider list from a shared constant.
This commit is contained in:
@ -12,29 +12,32 @@ import InstallFromMarketplace from '@/app/components/plugins/install-plugin/inst
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { ModelProviderQuotaGetPaid } from '@/types/model-provider'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import { PreferredProviderTypeEnum } from '../declarations'
|
||||
import { useMarketplaceAllPlugins } from '../hooks'
|
||||
import { modelNameMap, ModelProviderQuotaGetPaid } from '../utils'
|
||||
import { MODEL_PROVIDER_QUOTA_GET_PAID, modelNameMap } from '../utils'
|
||||
|
||||
type ProviderConfig = {
|
||||
key: ModelProviderQuotaGetPaid
|
||||
Icon: ComponentType<{ className?: string }>
|
||||
// Icon map for each provider - single source of truth for provider icons
|
||||
const providerIconMap: Record<ModelProviderQuotaGetPaid, ComponentType<{ className?: string }>> = {
|
||||
[ModelProviderQuotaGetPaid.OPENAI]: OpenaiSmall,
|
||||
[ModelProviderQuotaGetPaid.ANTHROPIC]: AnthropicShortLight,
|
||||
[ModelProviderQuotaGetPaid.GEMINI]: Gemini,
|
||||
[ModelProviderQuotaGetPaid.X]: Grok,
|
||||
[ModelProviderQuotaGetPaid.DEEPSEEK]: Deepseek,
|
||||
[ModelProviderQuotaGetPaid.TONGYI]: Tongyi,
|
||||
}
|
||||
|
||||
const allProviders: ProviderConfig[] = [
|
||||
{ key: ModelProviderQuotaGetPaid.OPENAI, Icon: OpenaiSmall },
|
||||
{ key: ModelProviderQuotaGetPaid.ANTHROPIC, Icon: AnthropicShortLight },
|
||||
{ key: ModelProviderQuotaGetPaid.GEMINI, Icon: Gemini },
|
||||
{ key: ModelProviderQuotaGetPaid.X, Icon: Grok },
|
||||
{ key: ModelProviderQuotaGetPaid.DEEPSEEK, Icon: Deepseek },
|
||||
{ key: ModelProviderQuotaGetPaid.TONGYI, Icon: Tongyi },
|
||||
]
|
||||
// Derive allProviders from the shared constant
|
||||
const allProviders = MODEL_PROVIDER_QUOTA_GET_PAID.map(key => ({
|
||||
key,
|
||||
Icon: providerIconMap[key],
|
||||
}))
|
||||
|
||||
// Map provider key to plugin ID
|
||||
// provider key format: langgenius/provider/model, plugin ID format: langgenius/provider
|
||||
const providerKeyToPluginId: Record<string, string> = {
|
||||
const providerKeyToPluginId: Record<ModelProviderQuotaGetPaid, string> = {
|
||||
[ModelProviderQuotaGetPaid.OPENAI]: 'langgenius/openai',
|
||||
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'langgenius/anthropic',
|
||||
[ModelProviderQuotaGetPaid.GEMINI]: 'langgenius/gemini',
|
||||
@ -43,15 +46,6 @@ const providerKeyToPluginId: Record<string, string> = {
|
||||
[ModelProviderQuotaGetPaid.TONGYI]: 'langgenius/tongyi',
|
||||
}
|
||||
|
||||
const providerNameMap = {
|
||||
[ModelProviderQuotaGetPaid.OPENAI]: 'OpenAI',
|
||||
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'Anthropic',
|
||||
[ModelProviderQuotaGetPaid.GEMINI]: 'Gemini',
|
||||
[ModelProviderQuotaGetPaid.X]: 'xAI',
|
||||
[ModelProviderQuotaGetPaid.DEEPSEEK]: 'DeepSeek',
|
||||
[ModelProviderQuotaGetPaid.TONGYI]: 'Tongyi',
|
||||
}
|
||||
|
||||
type QuotaPanelProps = {
|
||||
providers: ModelProvider[]
|
||||
isLoading?: boolean
|
||||
@ -78,7 +72,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
}] = useBoolean(false)
|
||||
const selectedPluginIdRef = useRef<string | null>(null)
|
||||
|
||||
const handleIconClick = useCallback((key: string) => {
|
||||
const handleIconClick = useCallback((key: ModelProviderQuotaGetPaid) => {
|
||||
const providerType = providerMap.get(key)
|
||||
if (!providerType && allPlugins) {
|
||||
const pluginId = providerKeyToPluginId[key]
|
||||
@ -113,7 +107,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
<div className={cn('my-2 min-w-[72px] shrink-0 rounded-xl border-[0.5px] pb-2.5 pl-4 pr-2.5 pt-3 shadow-xs', credits <= 0 ? 'border-state-destructive-border hover:bg-state-destructive-hover' : 'border-components-panel-border bg-third-party-model-bg-default')}>
|
||||
<div className="system-xs-medium-uppercase mb-2 flex h-4 items-center text-text-tertiary">
|
||||
{t('modelProvider.quota', { ns: 'common' })}
|
||||
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common', modelNames: trial_models.map(key => providerNameMap[key as keyof typeof providerNameMap]).filter(Boolean).join(', ') })} />
|
||||
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common', modelNames: trial_models.map(key => modelNameMap[key as keyof typeof modelNameMap]).filter(Boolean).join(', ') })} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1 text-xs text-text-tertiary">
|
||||
@ -135,7 +129,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
: null}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{allProviders.map(({ key, Icon }) => {
|
||||
{allProviders.filter(({ key }) => trial_models.includes(key)).map(({ key, Icon }) => {
|
||||
const providerType = providerMap.get(key)
|
||||
const usingQuota = providerType === PreferredProviderTypeEnum.system
|
||||
const getTooltipKey = () => {
|
||||
@ -146,22 +140,21 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
return 'modelProvider.card.modelNotSupported'
|
||||
}
|
||||
return (
|
||||
trial_models.includes(key) && (
|
||||
<Tooltip
|
||||
key={key}
|
||||
popupContent={t(getTooltipKey(), { modelName: modelNameMap[key], ns: 'common' })}
|
||||
<Tooltip
|
||||
key={key}
|
||||
popupContent={t(getTooltipKey(), { modelName: modelNameMap[key], ns: 'common' })}
|
||||
>
|
||||
<div
|
||||
className={cn('relative h-6 w-6', !providerType && 'cursor-pointer hover:opacity-80')}
|
||||
onClick={() => handleIconClick(key)}
|
||||
>
|
||||
<div
|
||||
className={cn('relative h-6 w-6', !providerType && 'cursor-pointer hover:opacity-80')}
|
||||
onClick={() => handleIconClick(key)}
|
||||
>
|
||||
<Icon className="h-6 w-6 rounded-lg" />
|
||||
{!usingQuota && (
|
||||
<div className="absolute inset-0 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge opacity-30" />
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
))
|
||||
<Icon className="h-6 w-6 rounded-lg" />
|
||||
{!usingQuota && (
|
||||
<div className="absolute inset-0 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge opacity-30" />
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type {
|
||||
CredentialFormSchemaSelect,
|
||||
CredentialFormSchemaTextInput,
|
||||
FormValue,
|
||||
ModelLoadBalancingConfig,
|
||||
@ -9,6 +10,7 @@ import {
|
||||
validateModelLoadBalancingCredentials,
|
||||
validateModelProvider,
|
||||
} from '@/service/common'
|
||||
import { ModelProviderQuotaGetPaid } from '@/types/model-provider'
|
||||
import { ValidatedStatus } from '../key-validator/declarations'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
@ -17,15 +19,8 @@ import {
|
||||
ModelTypeEnum,
|
||||
} from './declarations'
|
||||
|
||||
export enum ModelProviderQuotaGetPaid {
|
||||
ANTHROPIC = 'langgenius/anthropic/anthropic',
|
||||
OPENAI = 'langgenius/openai/openai',
|
||||
// AZURE_OPENAI = 'langgenius/azure_openai/azure_openai',
|
||||
GEMINI = 'langgenius/gemini/google',
|
||||
X = 'langgenius/x/x',
|
||||
DEEPSEEK = 'langgenius/deepseek/deepseek',
|
||||
TONGYI = 'langgenius/tongyi/tongyi',
|
||||
}
|
||||
export { ModelProviderQuotaGetPaid } from '@/types/model-provider'
|
||||
|
||||
export const MODEL_PROVIDER_QUOTA_GET_PAID = [ModelProviderQuotaGetPaid.ANTHROPIC, ModelProviderQuotaGetPaid.OPENAI, ModelProviderQuotaGetPaid.GEMINI, ModelProviderQuotaGetPaid.X, ModelProviderQuotaGetPaid.DEEPSEEK, ModelProviderQuotaGetPaid.TONGYI]
|
||||
|
||||
export const modelNameMap = {
|
||||
@ -37,7 +32,7 @@ export const modelNameMap = {
|
||||
[ModelProviderQuotaGetPaid.TONGYI]: 'Tongyi',
|
||||
}
|
||||
|
||||
export const isNullOrUndefined = (value: any) => {
|
||||
export const isNullOrUndefined = (value: unknown): value is null | undefined => {
|
||||
return value === undefined || value === null
|
||||
}
|
||||
|
||||
@ -66,8 +61,9 @@ export const validateCredentials = async (predefined: boolean, provider: string,
|
||||
else
|
||||
return Promise.resolve({ status: ValidatedStatus.Error, message: res.error || 'error' })
|
||||
}
|
||||
catch (e: any) {
|
||||
return Promise.resolve({ status: ValidatedStatus.Error, message: e.message })
|
||||
catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Unknown error'
|
||||
return Promise.resolve({ status: ValidatedStatus.Error, message })
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,8 +86,9 @@ export const validateLoadBalancingCredentials = async (predefined: boolean, prov
|
||||
else
|
||||
return Promise.resolve({ status: ValidatedStatus.Error, message: res.error || 'error' })
|
||||
}
|
||||
catch (e: any) {
|
||||
return Promise.resolve({ status: ValidatedStatus.Error, message: e.message })
|
||||
catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Unknown error'
|
||||
return Promise.resolve({ status: ValidatedStatus.Error, message })
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,7 +174,7 @@ export const modelTypeFormat = (modelType: ModelTypeEnum) => {
|
||||
return modelType.toLocaleUpperCase()
|
||||
}
|
||||
|
||||
export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]) => {
|
||||
export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]): Omit<CredentialFormSchemaSelect, 'name'> => {
|
||||
return {
|
||||
type: FormTypeEnum.select,
|
||||
label: {
|
||||
@ -198,10 +195,10 @@ export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]) => {
|
||||
show_on: [],
|
||||
}
|
||||
}),
|
||||
} as any
|
||||
}
|
||||
}
|
||||
|
||||
export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInput, 'label' | 'placeholder'>) => {
|
||||
export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInput, 'label' | 'placeholder'>): Omit<CredentialFormSchemaTextInput, 'name'> => {
|
||||
return {
|
||||
type: FormTypeEnum.textInput,
|
||||
label: model?.label || {
|
||||
@ -215,5 +212,5 @@ export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInpu
|
||||
zh_Hans: '请输入模型名称',
|
||||
en_US: 'Please enter model name',
|
||||
},
|
||||
} as any
|
||||
}
|
||||
}
|
||||
|
||||
@ -2164,11 +2164,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/model-provider-page/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"app/components/header/account-setting/plugin-page/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ModelProviderQuotaGetPaid } from '@/app/components/header/account-setting/model-provider-page/utils'
|
||||
import type { ModelProviderQuotaGetPaid } from './model-provider'
|
||||
|
||||
export enum SSOProtocol {
|
||||
SAML = 'saml',
|
||||
|
||||
13
web/types/model-provider.ts
Normal file
13
web/types/model-provider.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Model provider quota types - shared type definitions for API responses
|
||||
* These represent the provider identifiers that support paid/trial quotas
|
||||
*/
|
||||
export enum ModelProviderQuotaGetPaid {
|
||||
ANTHROPIC = 'langgenius/anthropic/anthropic',
|
||||
OPENAI = 'langgenius/openai/openai',
|
||||
// AZURE_OPENAI = 'langgenius/azure_openai/azure_openai',
|
||||
GEMINI = 'langgenius/gemini/google',
|
||||
X = 'langgenius/x/x',
|
||||
DEEPSEEK = 'langgenius/deepseek/deepseek',
|
||||
TONGYI = 'langgenius/tongyi/tongyi',
|
||||
}
|
||||
Reference in New Issue
Block a user