fix: load balancing

This commit is contained in:
zxhlyh 2025-08-19 16:42:07 +08:00
parent 6463b3d051
commit 7bddf323e1
11 changed files with 135 additions and 67 deletions

View File

@ -86,6 +86,7 @@ export enum ModelStatusEnum {
quotaExceeded = 'quota-exceeded',
noPermission = 'no-permission',
disabled = 'disabled',
credentialRemoved = 'credential-removed',
}
export const MODEL_STATUS_TEXT: { [k: string]: TypeWithI18N } = {
@ -185,6 +186,8 @@ export type QuotaConfiguration = {
export type Credential = {
credential_id: string
credential_name?: string
from_enterprise?: boolean
allowed_to_use?: boolean
}
export type CustomModel = {
@ -316,4 +319,6 @@ export type ModelCredential = {
credentials: Record<string, any>
load_balancing: ModelLoadBalancingConfig
available_credentials: Credential[]
current_credential_id?: string
current_credential_name?: string
}

View File

@ -13,6 +13,7 @@ import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import cn from '@/utils/classnames'
import type { Credential } from '../../declarations'
import Badge from '@/app/components/base/badge'
type CredentialItemProps = {
credential: Credential
@ -49,7 +50,9 @@ const CredentialItem = ({
className={cn(
'group flex h-8 items-center rounded-lg p-1 hover:bg-state-base-hover',
)}
onClick={() => onItemClick?.(credential)}
onClick={() => {
onItemClick?.(credential)
}}
>
<div className='flex w-0 grow items-center space-x-1.5'>
{
@ -71,6 +74,13 @@ const CredentialItem = ({
{credential.credential_name}
</div>
</div>
{
credential.from_enterprise && (
<Badge className='shrink-0'>
Enterprise
</Badge>
)
}
{
showAction && (
<div className='ml-2 hidden shrink-0 items-center group-hover:flex'>

View File

@ -5,30 +5,32 @@ import {
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator'
import cn from '@/utils/classnames'
type ConfigModelProps = {
className?: string
onClick?: () => void
loadBalancingEnabled?: boolean
loadBalancingInvalid?: boolean
credentialRemoved?: boolean
}
const ConfigModel = ({
className,
onClick,
loadBalancingEnabled,
loadBalancingInvalid,
credentialRemoved,
}: ConfigModelProps) => {
const { t } = useTranslation()
if (loadBalancingEnabled && loadBalancingInvalid) {
if (loadBalancingEnabled && loadBalancingInvalid && !credentialRemoved) {
return (
<div
className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-text-warning bg-components-badge-bg-dimm px-1.5'
className='system-2xs-medium-uppercase relative flex h-[18px] items-center rounded-[5px] border border-text-warning bg-components-badge-bg-dimm px-1.5 text-text-warning'
onClick={onClick}
>
<RiScales3Line className='mr-0.5 h-3 w-3' />
{t('common.modelProvider.auth.authorizationError')}
<Indicator color='orange' className='absolute right-[-1px] top-[-1px] h-1.5 w-1.5' />
</div>
)
}
@ -38,13 +40,21 @@ const ConfigModel = ({
variant='secondary'
size='small'
className={cn(
'shrink-0',
className,
'hidden shrink-0 group-hover:flex',
credentialRemoved && 'flex',
)}
onClick={onClick}
>
{
!loadBalancingEnabled && (
credentialRemoved && (
<>
{t('common.modelProvider.auth.credentialRemoved')}
<Indicator color='red' className='ml-2' />
</>
)
}
{
!loadBalancingEnabled && !credentialRemoved && (
<>
<RiEqualizer2Line className='mr-1 h-4 w-4' />
{t('common.operation.config')}
@ -52,7 +62,7 @@ const ConfigModel = ({
)
}
{
loadBalancingEnabled && !loadBalancingInvalid && (
loadBalancingEnabled && !loadBalancingInvalid && !credentialRemoved && (
<>
<RiScales3Line className='mr-1 h-4 w-4' />
{t('common.modelProvider.auth.configLoadBalancing')}

View File

@ -7,40 +7,38 @@ import { useTranslation } from 'react-i18next'
import { RiArrowDownSLine } from '@remixicon/react'
import Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator'
import Badge from '@/app/components/base/badge'
import Authorized from './authorized'
import type {
ModelLoadBalancingConfig,
Credential,
CustomModel,
ModelProvider,
} from '../declarations'
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useCredentialStatus } from './hooks'
import cn from '@/utils/classnames'
type SwitchCredentialInLoadBalancingProps = {
provider: ModelProvider
draftConfig?: ModelLoadBalancingConfig
setDraftConfig: Dispatch<SetStateAction<ModelLoadBalancingConfig | undefined>>
model: CustomModel
credentials?: Credential[]
customModelCredential?: Credential
setCustomModelCredential: Dispatch<SetStateAction<Credential | undefined>>
}
const SwitchCredentialInLoadBalancing = ({
provider,
draftConfig,
model,
customModelCredential,
setCustomModelCredential,
credentials,
}: SwitchCredentialInLoadBalancingProps) => {
const { t } = useTranslation()
const {
available_credentials,
current_credential_name,
} = useCredentialStatus(provider)
const handleItemClick = useCallback(() => {
console.log('handleItemClick', draftConfig)
}, [])
const handleItemClick = useCallback((credential: Credential) => {
setCustomModelCredential(credential)
}, [setCustomModelCredential])
const renderTrigger = useCallback(() => {
const selectedCredentialId = draftConfig?.configs.find(config => config.name === '__inherit__')?.credential_id
const selectedCredential = available_credentials?.find(credential => credential.credential_id === selectedCredentialId)
const name = selectedCredential?.credential_name || current_credential_name
const authRemoved = !!selectedCredentialId && !selectedCredential
const selectedCredentialId = customModelCredential?.credential_id
const authRemoved = !selectedCredentialId && !!credentials?.length
return (
<Button
variant='secondary'
@ -54,17 +52,12 @@ const SwitchCredentialInLoadBalancing = ({
color={authRemoved ? 'red' : 'green'}
/>
{
authRemoved ? t('common.model.authRemoved') : name
}
{
!authRemoved && (
<Badge>enterprise</Badge>
)
authRemoved ? t('common.modelProvider.auth.authRemoved') : customModelCredential?.credential_name
}
<RiArrowDownSLine className='h-4 w-4' />
</Button>
)
}, [current_credential_name, t, draftConfig, available_credentials])
}, [customModelCredential, t, credentials])
return (
<Authorized
@ -72,17 +65,25 @@ const SwitchCredentialInLoadBalancing = ({
configurationMethod={ConfigurationMethodEnum.customizableModel}
items={[
{
model: {
model: t('common.modelProvider.modelCredentials'),
} as any,
credentials: available_credentials || [],
title: t('common.modelProvider.auth.modelCredentials'),
model,
credentials: credentials || [],
},
]}
renderTrigger={renderTrigger}
onItemClick={handleItemClick}
isModelCredential
enableAddModelCredential
bottomAddModelCredentialText={t('common.modelProvider.addModelCredential')}
bottomAddModelCredentialText={t('common.modelProvider.auth.addModelCredential')}
selectedCredential={
customModelCredential
? {
credential_id: customModelCredential?.credential_id || '',
credential_name: customModelCredential?.credential_name || '',
}
: undefined
}
showItemSelectedIcon
/>
)
}

View File

@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks'
import type { ModelItem, ModelProvider } from '../declarations'
import { ModelStatusEnum } from '../declarations'
import ModelBadge from '../model-badge'
import ModelIcon from '../model-icon'
import ModelName from '../model-name'
import classNames from '@/utils/classnames'
@ -15,6 +14,7 @@ import { disableModel, enableModel } from '@/service/common'
import { Plan } from '@/app/components/billing/type'
import { useAppContext } from '@/context/app-context'
import { ConfigModel } from '../model-auth'
import Badge from '@/app/components/base/badge'
export type ModelListItemProps = {
model: ModelItem
@ -63,21 +63,20 @@ const ModelListItem = ({ model, provider, isConfigurable, onModifyLoadBalancing
showMode
showContextSize
>
{modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && (
<ModelBadge className='ml-1 border-text-accent-secondary uppercase text-text-accent-secondary'>
<Balance className='mr-0.5 h-3 w-3' />
{t('common.modelProvider.loadBalancingHeadline')}
</ModelBadge>
)}
</ModelName>
<div className='flex shrink-0 items-center'>
{modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && (
<Badge className='mr-1 h-[18px] w-[18px] items-center justify-center border-text-accent-secondary p-0'>
<Balance className='h-3 w-3 text-text-accent-secondary' />
</Badge>
)}
{
(isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) && (
<ConfigModel
className='hidden group-hover:flex'
onClick={() => onModifyLoadBalancing?.(model)}
loadBalancingEnabled={model.load_balancing_enabled}
loadBalancingInvalid={model.has_invalid_load_balancing_configs}
credentialRemoved={model.status === ModelStatusEnum.credentialRemoved}
/>
)
}

View File

@ -1,5 +1,5 @@
import type { Dispatch, SetStateAction } from 'react'
import { useCallback } from 'react'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiDeleteBinLine,
@ -122,6 +122,20 @@ const ModelLoadBalancingConfigs = ({
})
}, [updateConfigEntry])
const validDraftConfigList = useMemo(() => {
if (!draftConfig)
return []
return draftConfig.configs.filter((config) => {
if (config.name === '__inherit__')
return true
if (config.credential_id)
return true
return false
})
}, [draftConfig])
if (!draftConfig)
return null
@ -165,15 +179,7 @@ const ModelLoadBalancingConfigs = ({
</div>
{draftConfig.enabled && (
<div className='flex flex-col gap-1 px-3 pb-3'>
{draftConfig.configs.filter((config) => {
if (config.name === '__inherit__')
return true
if (config.credential_id)
return true
return false
}).map((config, index) => {
{validDraftConfigList.map((config, index) => {
const isProviderManaged = config.name === '__inherit__'
return (
<div key={config.id || index} className='group flex h-10 items-center rounded-lg border border-components-panel-border bg-components-panel-on-panel-item-bg px-3 shadow-xs'>
@ -249,7 +255,7 @@ const ModelLoadBalancingConfigs = ({
</div>
)}
{
draftConfig.enabled && draftConfig.configs.length < 2 && (
draftConfig.enabled && validDraftConfigList.length < 2 && (
<div className='flex h-[34px] items-center rounded-b-xl border-t border-t-divider-subtle bg-components-panel-bg px-6 text-xs text-text-secondary'>
<AlertTriangle className='mr-1 h-3 w-3 text-[#f79009]' />
{t('common.modelProvider.loadBalancingLeastKeyWarning')}

View File

@ -19,7 +19,7 @@ import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Loading from '@/app/components/base/loading'
import { useToastContext } from '@/app/components/base/toast'
// import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
import { SwitchCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
import {
useGetModelCredential,
useUpdateModelLoadBalancingConfig,
@ -59,6 +59,9 @@ const ModelLoadBalancingModal = ({
const modelCredential = data
const {
load_balancing,
current_credential_id,
available_credentials,
current_credential_name,
} = modelCredential ?? {}
const originalConfig = load_balancing
const [draftConfig, setDraftConfig] = useState<ModelLoadBalancingConfig>()
@ -102,11 +105,21 @@ const ModelLoadBalancingModal = ({
}, [extendedSecretFormSchemas, originalConfigMap])
const { mutateAsync: updateModelLoadBalancingConfig } = useUpdateModelLoadBalancingConfig(provider.provider)
const initialCustomModelCredential = useMemo(() => {
if (!current_credential_id)
return undefined
return {
credential_id: current_credential_id,
credential_name: current_credential_name,
}
}, [current_credential_id, current_credential_name])
const [customModelCredential, setCustomModelCredential] = useState<Credential | undefined>(initialCustomModelCredential)
const handleSave = async () => {
try {
setLoading(true)
const res = await updateModelLoadBalancingConfig(
{
credential_id: customModelCredential?.credential_id || current_credential_id,
config_from: configFrom,
model: model.model,
model_type: model.model_type,
@ -178,14 +191,28 @@ const ModelLoadBalancingModal = ({
)}
</div>
<div className='grow'>
<div className='text-sm text-text-secondary'>{t('common.modelProvider.providerManaged')}</div>
<div className='text-xs text-text-tertiary'>{t('common.modelProvider.providerManagedDescription')}</div>
<div className='text-sm text-text-secondary'>{
providerFormSchemaPredefined
? t('common.modelProvider.auth.providerManaged')
: t('common.modelProvider.auth.specifyModelCredential')
}</div>
<div className='text-xs text-text-tertiary'>{
providerFormSchemaPredefined
? t('common.modelProvider.auth.providerManagedTip')
: t('common.modelProvider.auth.specifyModelCredentialTip')
}</div>
</div>
{/* <SwitchCredentialInLoadBalancing
draftConfig={draftConfig}
setDraftConfig={setDraftConfig}
provider={provider}
/> */}
{
!providerFormSchemaPredefined && (
<SwitchCredentialInLoadBalancing
provider={provider}
customModelCredential={initialCustomModelCredential ?? customModelCredential}
setCustomModelCredential={setCustomModelCredential}
model={model}
credentials={available_credentials}
/>
)
}
</div>
</div>
{

View File

@ -23,4 +23,5 @@ export type Credential = {
credentials?: Record<string, any>
isWorkspaceDefault?: boolean
from_enterprise?: boolean
allowed_to_use?: boolean
}

View File

@ -467,7 +467,7 @@ const translation = {
loadPresets: 'Load Presets',
parameters: 'PARAMETERS',
loadBalancing: 'Load balancing',
loadBalancingDescription: 'Reduce pressure with multiple sets of credentials.',
loadBalancingDescription: 'Configure multiple credentials for the model and invoke them automatically. ',
loadBalancingHeadline: 'Load Balancing',
configLoadBalancing: 'Config Load Balancing',
modelHasBeenDeprecated: 'This model has been deprecated',
@ -499,6 +499,10 @@ const translation = {
configModel: 'Config model',
configLoadBalancing: 'Config Load Balancing',
authorizationError: 'Authorization error',
specifyModelCredential: 'Specify model credential',
specifyModelCredentialTip: 'Use a configured model credential.',
providerManaged: 'Provider managed',
providerManagedTip: 'The current configuration is hosted by the provider.',
},
},
dataSource: {

View File

@ -466,7 +466,7 @@ const translation = {
loadPresets: '加载预设',
parameters: '参数',
loadBalancing: '负载均衡',
loadBalancingDescription: '为了减轻单组凭据的压力,您可以为模型调用配置多组凭据。',
loadBalancingDescription: '为模型配置多组凭据,并自动调用。',
loadBalancingHeadline: '负载均衡',
configLoadBalancing: '设置负载均衡',
modelHasBeenDeprecated: '该模型已废弃',
@ -499,6 +499,10 @@ const translation = {
configModel: '配置模型',
configLoadBalancing: '配置负载均衡',
authorizationError: '授权错误',
specifyModelCredential: '指定模型凭据',
specifyModelCredentialTip: '使用已配置的模型凭据。',
providerManaged: '由模型供应商管理',
providerManagedTip: '使用模型供应商提供的单组凭据。',
},
},
dataSource: {

View File

@ -147,6 +147,7 @@ export const useUpdateModelLoadBalancingConfig = (provider: string) => {
model: string
model_type: ModelTypeEnum
load_balancing: ModelLoadBalancingConfig
credential_id?: string
}) => post<{ result: string }>(`/workspaces/current/model-providers/${provider}/models`, {
body: data,
}),