Skip to content

Commit dd988d4

Browse files
CodingOnStarCodingOnStar
andauthored
feat: enhance quota panel to support additional model providers and integrate trial models feature (langgenius#31443)
Co-authored-by: CodingOnStar <[email protected]>
1 parent a43d2ec commit dd988d4

File tree

27 files changed

+79
-60
lines changed

27 files changed

+79
-60
lines changed

web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
1-
import type { FC } from 'react'
1+
import type { ComponentType, FC } from 'react'
22
import type { ModelProvider } from '../declarations'
33
import type { Plugin } from '@/app/components/plugins/types'
44
import { useBoolean } from 'ahooks'
55
import * as React from 'react'
66
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
77
import { useTranslation } from 'react-i18next'
8-
import { OpenaiSmall } from '@/app/components/base/icons/src/public/llm'
8+
import { AnthropicShortLight, Deepseek, Gemini, Grok, OpenaiSmall, Tongyi } from '@/app/components/base/icons/src/public/llm'
99
import Loading from '@/app/components/base/loading'
1010
import Tooltip from '@/app/components/base/tooltip'
1111
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
1212
import { useAppContext } from '@/context/app-context'
13+
import { useGlobalPublicStore } from '@/context/global-public-context'
1314
import useTimestamp from '@/hooks/use-timestamp'
15+
import { ModelProviderQuotaGetPaid } from '@/types/model-provider'
1416
import { cn } from '@/utils/classnames'
1517
import { formatNumber } from '@/utils/format'
1618
import { PreferredProviderTypeEnum } from '../declarations'
1719
import { useMarketplaceAllPlugins } from '../hooks'
18-
import { modelNameMap, ModelProviderQuotaGetPaid } from '../utils'
20+
import { MODEL_PROVIDER_QUOTA_GET_PAID, modelNameMap } from '../utils'
1921

20-
const allProviders = [
21-
{ key: ModelProviderQuotaGetPaid.OPENAI, Icon: OpenaiSmall },
22-
// { key: ModelProviderQuotaGetPaid.ANTHROPIC, Icon: AnthropicShortLight },
23-
// { key: ModelProviderQuotaGetPaid.GEMINI, Icon: Gemini },
24-
// { key: ModelProviderQuotaGetPaid.X, Icon: Grok },
25-
// { key: ModelProviderQuotaGetPaid.DEEPSEEK, Icon: Deepseek },
26-
// { key: ModelProviderQuotaGetPaid.TONGYI, Icon: Tongyi },
27-
] as const
22+
// Icon map for each provider - single source of truth for provider icons
23+
const providerIconMap: Record<ModelProviderQuotaGetPaid, ComponentType<{ className?: string }>> = {
24+
[ModelProviderQuotaGetPaid.OPENAI]: OpenaiSmall,
25+
[ModelProviderQuotaGetPaid.ANTHROPIC]: AnthropicShortLight,
26+
[ModelProviderQuotaGetPaid.GEMINI]: Gemini,
27+
[ModelProviderQuotaGetPaid.X]: Grok,
28+
[ModelProviderQuotaGetPaid.DEEPSEEK]: Deepseek,
29+
[ModelProviderQuotaGetPaid.TONGYI]: Tongyi,
30+
}
31+
32+
// Derive allProviders from the shared constant
33+
const allProviders = MODEL_PROVIDER_QUOTA_GET_PAID.map(key => ({
34+
key,
35+
Icon: providerIconMap[key],
36+
}))
2837

2938
// Map provider key to plugin ID
3039
// provider key format: langgenius/provider/model, plugin ID format: langgenius/provider
31-
const providerKeyToPluginId: Record<string, string> = {
40+
const providerKeyToPluginId: Record<ModelProviderQuotaGetPaid, string> = {
3241
[ModelProviderQuotaGetPaid.OPENAI]: 'langgenius/openai',
3342
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'langgenius/anthropic',
3443
[ModelProviderQuotaGetPaid.GEMINI]: 'langgenius/gemini',
@@ -47,6 +56,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
4756
}) => {
4857
const { t } = useTranslation()
4958
const { currentWorkspace } = useAppContext()
59+
const { trial_models } = useGlobalPublicStore(s => s.systemFeatures)
5060
const credits = Math.max((currentWorkspace.trial_credits - currentWorkspace.trial_credits_used) || 0, 0)
5161
const providerMap = useMemo(() => new Map(
5262
providers.map(p => [p.provider, p.preferred_provider_type]),
@@ -62,7 +72,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
6272
}] = useBoolean(false)
6373
const selectedPluginIdRef = useRef<string | null>(null)
6474

65-
const handleIconClick = useCallback((key: string) => {
75+
const handleIconClick = useCallback((key: ModelProviderQuotaGetPaid) => {
6676
const providerType = providerMap.get(key)
6777
if (!providerType && allPlugins) {
6878
const pluginId = providerKeyToPluginId[key]
@@ -97,7 +107,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
97107
<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')}>
98108
<div className="system-xs-medium-uppercase mb-2 flex h-4 items-center text-text-tertiary">
99109
{t('modelProvider.quota', { ns: 'common' })}
100-
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common' })} />
110+
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common', modelNames: trial_models.map(key => modelNameMap[key as keyof typeof modelNameMap]).filter(Boolean).join(', ') })} />
101111
</div>
102112
<div className="flex items-center justify-between">
103113
<div className="flex items-center gap-1 text-xs text-text-tertiary">
@@ -119,7 +129,7 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
119129
: null}
120130
</div>
121131
<div className="flex items-center gap-1">
122-
{allProviders.map(({ key, Icon }) => {
132+
{allProviders.filter(({ key }) => trial_models.includes(key)).map(({ key, Icon }) => {
123133
const providerType = providerMap.get(key)
124134
const usingQuota = providerType === PreferredProviderTypeEnum.system
125135
const getTooltipKey = () => {

web/app/components/header/account-setting/model-provider-page/utils.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
CredentialFormSchemaSelect,
23
CredentialFormSchemaTextInput,
34
FormValue,
45
ModelLoadBalancingConfig,
@@ -9,6 +10,7 @@ import {
910
validateModelLoadBalancingCredentials,
1011
validateModelProvider,
1112
} from '@/service/common'
13+
import { ModelProviderQuotaGetPaid } from '@/types/model-provider'
1214
import { ValidatedStatus } from '../key-validator/declarations'
1315
import {
1416
ConfigurationMethodEnum,
@@ -17,15 +19,8 @@ import {
1719
ModelTypeEnum,
1820
} from './declarations'
1921

20-
export enum ModelProviderQuotaGetPaid {
21-
ANTHROPIC = 'langgenius/anthropic/anthropic',
22-
OPENAI = 'langgenius/openai/openai',
23-
// AZURE_OPENAI = 'langgenius/azure_openai/azure_openai',
24-
GEMINI = 'langgenius/gemini/google',
25-
X = 'langgenius/x/x',
26-
DEEPSEEK = 'langgenius/deepseek/deepseek',
27-
TONGYI = 'langgenius/tongyi/tongyi',
28-
}
22+
export { ModelProviderQuotaGetPaid } from '@/types/model-provider'
23+
2924
export const MODEL_PROVIDER_QUOTA_GET_PAID = [ModelProviderQuotaGetPaid.ANTHROPIC, ModelProviderQuotaGetPaid.OPENAI, ModelProviderQuotaGetPaid.GEMINI, ModelProviderQuotaGetPaid.X, ModelProviderQuotaGetPaid.DEEPSEEK, ModelProviderQuotaGetPaid.TONGYI]
3025

3126
export const modelNameMap = {
@@ -37,7 +32,7 @@ export const modelNameMap = {
3732
[ModelProviderQuotaGetPaid.TONGYI]: 'Tongyi',
3833
}
3934

40-
export const isNullOrUndefined = (value: any) => {
35+
export const isNullOrUndefined = (value: unknown): value is null | undefined => {
4136
return value === undefined || value === null
4237
}
4338

@@ -66,8 +61,9 @@ export const validateCredentials = async (predefined: boolean, provider: string,
6661
else
6762
return Promise.resolve({ status: ValidatedStatus.Error, message: res.error || 'error' })
6863
}
69-
catch (e: any) {
70-
return Promise.resolve({ status: ValidatedStatus.Error, message: e.message })
64+
catch (e: unknown) {
65+
const message = e instanceof Error ? e.message : 'Unknown error'
66+
return Promise.resolve({ status: ValidatedStatus.Error, message })
7167
}
7268
}
7369

@@ -90,8 +86,9 @@ export const validateLoadBalancingCredentials = async (predefined: boolean, prov
9086
else
9187
return Promise.resolve({ status: ValidatedStatus.Error, message: res.error || 'error' })
9288
}
93-
catch (e: any) {
94-
return Promise.resolve({ status: ValidatedStatus.Error, message: e.message })
89+
catch (e: unknown) {
90+
const message = e instanceof Error ? e.message : 'Unknown error'
91+
return Promise.resolve({ status: ValidatedStatus.Error, message })
9592
}
9693
}
9794

@@ -177,7 +174,7 @@ export const modelTypeFormat = (modelType: ModelTypeEnum) => {
177174
return modelType.toLocaleUpperCase()
178175
}
179176

180-
export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]) => {
177+
export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]): Omit<CredentialFormSchemaSelect, 'name'> => {
181178
return {
182179
type: FormTypeEnum.select,
183180
label: {
@@ -198,10 +195,10 @@ export const genModelTypeFormSchema = (modelTypes: ModelTypeEnum[]) => {
198195
show_on: [],
199196
}
200197
}),
201-
} as any
198+
}
202199
}
203200

204-
export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInput, 'label' | 'placeholder'>) => {
201+
export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInput, 'label' | 'placeholder'>): Omit<CredentialFormSchemaTextInput, 'name'> => {
205202
return {
206203
type: FormTypeEnum.textInput,
207204
label: model?.label || {
@@ -215,5 +212,5 @@ export const genModelNameFormSchema = (model?: Pick<CredentialFormSchemaTextInpu
215212
zh_Hans: '请输入模型名称',
216213
en_US: 'Please enter model name',
217214
},
218-
} as any
215+
}
219216
}

web/eslint-suppressions.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,11 +2156,6 @@
21562156
"count": 3
21572157
}
21582158
},
2159-
"app/components/header/account-setting/model-provider-page/utils.ts": {
2160-
"ts/no-explicit-any": {
2161-
"count": 5
2162-
}
2163-
},
21642159
"app/components/header/account-setting/plugin-page/utils.ts": {
21652160
"ts/no-explicit-any": {
21662161
"count": 4

web/i18n/ar-TN/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "حصة",
352352
"modelProvider.card.quotaExhausted": "نفدت الحصة",
353353
"modelProvider.card.removeKey": "إزالة مفتاح API",
354-
"modelProvider.card.tip": "تدعم أرصدة الرسائل نماذج من OpenAI. ستعطى الأولوية للحصة المدفوعة. سيتم استخدام الحصة المجانية بعد نفاد الحصة المدفوعة.",
354+
"modelProvider.card.tip": "تدعم أرصدة الرسائل نماذج من {{modelNames}}. ستعطى الأولوية للحصة المدفوعة. سيتم استخدام الحصة المجانية بعد نفاد الحصة المدفوعة.",
355355
"modelProvider.card.tokens": "رموز",
356356
"modelProvider.collapse": "طي",
357357
"modelProvider.config": "تكوين",

web/i18n/de-DE/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "KONTINGENT",
352352
"modelProvider.card.quotaExhausted": "Kontingent erschöpft",
353353
"modelProvider.card.removeKey": "API-Schlüssel entfernen",
354-
"modelProvider.card.tip": "Nachrichtenguthaben unterstützen Modelle von OpenAI. Der bezahlten Kontingent wird Vorrang gegeben. Das kostenlose Kontingent wird nach dem Verbrauch des bezahlten Kontingents verwendet.",
354+
"modelProvider.card.tip": "Nachrichtenguthaben unterstützen Modelle von {{modelNames}}. Der bezahlten Kontingent wird Vorrang gegeben. Das kostenlose Kontingent wird nach dem Verbrauch des bezahlten Kontingents verwendet.",
355355
"modelProvider.card.tokens": "Token",
356356
"modelProvider.collapse": "Einklappen",
357357
"modelProvider.config": "Konfigurieren",

web/i18n/en-US/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "QUOTA",
352352
"modelProvider.card.quotaExhausted": "Quota exhausted",
353353
"modelProvider.card.removeKey": "Remove API Key",
354-
"modelProvider.card.tip": "Message Credits supports models from OpenAI. Priority will be given to the paid quota. The free quota will be used after the paid quota is exhausted.",
354+
"modelProvider.card.tip": "Message Credits supports models from {{modelNames}}. Priority will be given to the paid quota. The free quota will be used after the paid quota is exhausted.",
355355
"modelProvider.card.tokens": "Tokens",
356356
"modelProvider.collapse": "Collapse",
357357
"modelProvider.config": "Config",

web/i18n/es-ES/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "CUOTA",
352352
"modelProvider.card.quotaExhausted": "Cuota agotada",
353353
"modelProvider.card.removeKey": "Eliminar CLAVE API",
354-
"modelProvider.card.tip": "Créditos de mensajes admite modelos de OpenAI. Se dará prioridad a la cuota pagada. La cuota gratuita se utilizará después de que se agote la cuota pagada.",
354+
"modelProvider.card.tip": "Créditos de mensajes admite modelos de {{modelNames}}. Se dará prioridad a la cuota pagada. La cuota gratuita se utilizará después de que se agote la cuota pagada.",
355355
"modelProvider.card.tokens": "Tokens",
356356
"modelProvider.collapse": "Colapsar",
357357
"modelProvider.config": "Configurar",

web/i18n/fa-IR/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "سهمیه",
352352
"modelProvider.card.quotaExhausted": "سهمیه تمام شده",
353353
"modelProvider.card.removeKey": "حذف کلید API",
354-
"modelProvider.card.tip": "اعتبار پیام از مدل‌های OpenAI پشتیبانی می‌کند. اولویت به سهمیه پرداخت شده داده می‌شود. سهمیه رایگان پس از اتمام سهمیه پرداخت شده استفاده خواهد شد.",
354+
"modelProvider.card.tip": "اعتبار پیام از مدل‌های {{modelNames}} پشتیبانی می‌کند. اولویت به سهمیه پرداخت شده داده می‌شود. سهمیه رایگان پس از اتمام سهمیه پرداخت شده استفاده خواهد شد.",
355355
"modelProvider.card.tokens": "توکن‌ها",
356356
"modelProvider.collapse": "جمع کردن",
357357
"modelProvider.config": "پیکربندی",

web/i18n/fr-FR/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "QUOTA",
352352
"modelProvider.card.quotaExhausted": "Quota épuisé",
353353
"modelProvider.card.removeKey": "Supprimer la clé API",
354-
"modelProvider.card.tip": "Les crédits de messages prennent en charge les modèles d'OpenAI. La priorité sera donnée au quota payant. Le quota gratuit sera utilisé après épuisement du quota payant.",
354+
"modelProvider.card.tip": "Les crédits de messages prennent en charge les modèles de {{modelNames}}. La priorité sera donnée au quota payant. Le quota gratuit sera utilisé après épuisement du quota payant.",
355355
"modelProvider.card.tokens": "Jetons",
356356
"modelProvider.collapse": "Effondrer",
357357
"modelProvider.config": "Configuration",

web/i18n/hi-IN/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@
351351
"modelProvider.card.quota": "कोटा",
352352
"modelProvider.card.quotaExhausted": "कोटा समाप्त",
353353
"modelProvider.card.removeKey": "API कुंजी निकालें",
354-
"modelProvider.card.tip": "संदेश क्रेडिट OpenAI के मॉडल का समर्थन करते हैं। भुगतान किए गए कोटा को प्राथमिकता दी जाएगी। भुगतान किए गए कोटा के समाप्त होने के बाद मुफ्त कोटा का उपयोग किया जाएगा।",
354+
"modelProvider.card.tip": "संदेश क्रेडिट {{modelNames}} के मॉडल का समर्थन करते हैं। भुगतान किए गए कोटा को प्राथमिकता दी जाएगी। भुगतान किए गए कोटा के समाप्त होने के बाद मुफ्त कोटा का उपयोग किया जाएगा।",
355355
"modelProvider.card.tokens": "टोकन",
356356
"modelProvider.collapse": "संक्षिप्त करें",
357357
"modelProvider.config": "कॉन्फ़िग",

0 commit comments

Comments
 (0)