286 lines
11 KiB
TypeScript
286 lines
11 KiB
TypeScript
import type { Dispatch, SetStateAction } from 'react'
|
|
import { useCallback, useMemo } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import {
|
|
RiIndeterminateCircleLine,
|
|
} from '@remixicon/react'
|
|
import type {
|
|
Credential,
|
|
CustomConfigurationModelFixedFields,
|
|
CustomModelCredential,
|
|
ModelCredential,
|
|
ModelLoadBalancingConfig,
|
|
ModelLoadBalancingConfigEntry,
|
|
ModelProvider,
|
|
} from '../declarations'
|
|
import { ConfigurationMethodEnum } from '../declarations'
|
|
import Indicator from '../../../indicator'
|
|
import CooldownTimer from './cooldown-timer'
|
|
import classNames from '@/utils/classnames'
|
|
import Tooltip from '@/app/components/base/tooltip'
|
|
import Switch from '@/app/components/base/switch'
|
|
import { Balance } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
|
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
|
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
|
import s from '@/app/components/custom/style.module.css'
|
|
import GridMask from '@/app/components/base/grid-mask'
|
|
import { useProviderContextSelector } from '@/context/provider-context'
|
|
import { IS_CE_EDITION } from '@/config'
|
|
import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
|
import Badge from '@/app/components/base/badge/index'
|
|
|
|
export type ModelLoadBalancingConfigsProps = {
|
|
draftConfig?: ModelLoadBalancingConfig
|
|
setDraftConfig: Dispatch<SetStateAction<ModelLoadBalancingConfig | undefined>>
|
|
provider: ModelProvider
|
|
configurationMethod: ConfigurationMethodEnum
|
|
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
|
|
withSwitch?: boolean
|
|
className?: string
|
|
modelCredential: ModelCredential
|
|
onUpdate?: (payload?: any, formValues?: Record<string, any>) => void
|
|
onRemove?: (credentialId: string) => void
|
|
model: CustomModelCredential
|
|
}
|
|
|
|
const ModelLoadBalancingConfigs = ({
|
|
draftConfig,
|
|
setDraftConfig,
|
|
provider,
|
|
model,
|
|
configurationMethod,
|
|
currentCustomConfigurationModelFixedFields,
|
|
withSwitch = false,
|
|
className,
|
|
modelCredential,
|
|
onUpdate,
|
|
onRemove,
|
|
}: ModelLoadBalancingConfigsProps) => {
|
|
const { t } = useTranslation()
|
|
const providerFormSchemaPredefined = configurationMethod === ConfigurationMethodEnum.predefinedModel
|
|
const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled)
|
|
|
|
const updateConfigEntry = useCallback(
|
|
(
|
|
index: number,
|
|
modifier: (entry: ModelLoadBalancingConfigEntry) => ModelLoadBalancingConfigEntry | undefined,
|
|
) => {
|
|
setDraftConfig((prev) => {
|
|
if (!prev)
|
|
return prev
|
|
const newConfigs = [...prev.configs]
|
|
const modifiedConfig = modifier(newConfigs[index])
|
|
if (modifiedConfig)
|
|
newConfigs[index] = modifiedConfig
|
|
else
|
|
newConfigs.splice(index, 1)
|
|
return {
|
|
...prev,
|
|
configs: newConfigs,
|
|
}
|
|
})
|
|
},
|
|
[setDraftConfig],
|
|
)
|
|
|
|
const addConfigEntry = useCallback((credential: Credential) => {
|
|
setDraftConfig((prev: any) => {
|
|
if (!prev)
|
|
return prev
|
|
return {
|
|
...prev,
|
|
configs: [...prev.configs, {
|
|
credential_id: credential.credential_id,
|
|
enabled: true,
|
|
name: credential.credential_name,
|
|
}],
|
|
}
|
|
})
|
|
}, [setDraftConfig])
|
|
|
|
const toggleModalBalancing = useCallback((enabled: boolean) => {
|
|
if ((modelLoadBalancingEnabled || !enabled) && draftConfig) {
|
|
setDraftConfig({
|
|
...draftConfig,
|
|
enabled,
|
|
})
|
|
}
|
|
}, [draftConfig, modelLoadBalancingEnabled, setDraftConfig])
|
|
|
|
const toggleConfigEntryEnabled = useCallback((index: number, state?: boolean) => {
|
|
updateConfigEntry(index, entry => ({
|
|
...entry,
|
|
enabled: typeof state === 'boolean' ? state : !entry.enabled,
|
|
}))
|
|
}, [updateConfigEntry])
|
|
|
|
const clearCountdown = useCallback((index: number) => {
|
|
updateConfigEntry(index, ({ ttl: _, ...entry }) => {
|
|
return {
|
|
...entry,
|
|
in_cooldown: false,
|
|
}
|
|
})
|
|
}, [updateConfigEntry])
|
|
|
|
const validDraftConfigList = useMemo(() => {
|
|
if (!draftConfig)
|
|
return []
|
|
return draftConfig.configs
|
|
}, [draftConfig])
|
|
|
|
const handleUpdate = useCallback((payload?: any, formValues?: Record<string, any>) => {
|
|
onUpdate?.(payload, formValues)
|
|
}, [onUpdate])
|
|
|
|
const handleRemove = useCallback((credentialId: string) => {
|
|
const index = draftConfig?.configs.findIndex(item => item.credential_id === credentialId && item.name !== '__inherit__')
|
|
if (index && index > -1)
|
|
updateConfigEntry(index, () => undefined)
|
|
onRemove?.(credentialId)
|
|
}, [draftConfig?.configs, updateConfigEntry, onRemove])
|
|
|
|
if (!draftConfig)
|
|
return null
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={classNames(
|
|
'min-h-16 rounded-xl border bg-components-panel-bg transition-colors',
|
|
(withSwitch || !draftConfig.enabled) ? 'border-components-panel-border' : 'border-util-colors-blue-blue-600',
|
|
(withSwitch || draftConfig.enabled) ? 'cursor-default' : 'cursor-pointer',
|
|
className,
|
|
)}
|
|
onClick={(!withSwitch && !draftConfig.enabled) ? () => toggleModalBalancing(true) : undefined}
|
|
>
|
|
<div className='flex select-none items-center gap-2 px-[15px] py-3'>
|
|
<div className='flex h-8 w-8 shrink-0 grow-0 items-center justify-center rounded-lg border border-util-colors-indigo-indigo-100 bg-util-colors-indigo-indigo-50 text-util-colors-blue-blue-600'>
|
|
<Balance className='h-4 w-4' />
|
|
</div>
|
|
<div className='grow'>
|
|
<div className='flex items-center gap-1 text-sm text-text-primary'>
|
|
{t('common.modelProvider.loadBalancing')}
|
|
<Tooltip
|
|
popupContent={t('common.modelProvider.loadBalancingInfo')}
|
|
popupClassName='max-w-[300px]'
|
|
triggerClassName='w-3 h-3'
|
|
/>
|
|
</div>
|
|
<div className='text-xs text-text-tertiary'>{t('common.modelProvider.loadBalancingDescription')}</div>
|
|
</div>
|
|
{
|
|
withSwitch && (
|
|
<Switch
|
|
defaultValue={Boolean(draftConfig.enabled)}
|
|
size='l'
|
|
className='ml-3 justify-self-end'
|
|
disabled={!modelLoadBalancingEnabled && !draftConfig.enabled}
|
|
onChange={value => toggleModalBalancing(value)}
|
|
/>
|
|
)
|
|
}
|
|
</div>
|
|
{draftConfig.enabled && (
|
|
<div className='flex flex-col gap-1 px-3 pb-3'>
|
|
{validDraftConfigList.map((config, index) => {
|
|
const isProviderManaged = config.name === '__inherit__'
|
|
const credential = modelCredential.available_credentials.find(c => c.credential_id === config.credential_id)
|
|
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'>
|
|
<div className='flex grow items-center'>
|
|
<div className='mr-2 flex h-3 w-3 items-center justify-center'>
|
|
{(config.in_cooldown && Boolean(config.ttl))
|
|
? (
|
|
<CooldownTimer secondsRemaining={config.ttl} onFinish={() => clearCountdown(index)} />
|
|
)
|
|
: (
|
|
<Tooltip popupContent={t('common.modelProvider.apiKeyStatusNormal')}>
|
|
<Indicator color='green' />
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
<div className='mr-1 text-[13px] text-text-secondary'>
|
|
{isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
|
|
</div>
|
|
{isProviderManaged && providerFormSchemaPredefined && (
|
|
<Badge className='ml-2'>{t('common.modelProvider.providerManaged')}</Badge>
|
|
)}
|
|
{
|
|
credential?.from_enterprise && (
|
|
<Badge className='ml-2'>Enterprise</Badge>
|
|
)
|
|
}
|
|
</div>
|
|
<div className='flex items-center gap-1'>
|
|
{!isProviderManaged && (
|
|
<>
|
|
<div className='flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>
|
|
<Tooltip popupContent={t('common.operation.remove')}>
|
|
<span
|
|
className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-secondary-bg text-text-tertiary transition-colors hover:bg-components-button-secondary-bg-hover'
|
|
onClick={() => updateConfigEntry(index, () => undefined)}
|
|
>
|
|
<RiIndeterminateCircleLine className='h-4 w-4' />
|
|
</span>
|
|
</Tooltip>
|
|
</div>
|
|
</>
|
|
)}
|
|
{
|
|
(config.credential_id || config.name === '__inherit__') && (
|
|
<>
|
|
<span className='mr-2 h-3 border-r border-r-divider-subtle' />
|
|
<Switch
|
|
defaultValue={Boolean(config.enabled)}
|
|
size='md'
|
|
className='justify-self-end'
|
|
onChange={value => toggleConfigEntryEnabled(index, value)}
|
|
disabled={credential?.not_allowed_to_use}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
<AddCredentialInLoadBalancing
|
|
provider={provider}
|
|
model={model}
|
|
configurationMethod={configurationMethod}
|
|
modelCredential={modelCredential}
|
|
onSelectCredential={addConfigEntry}
|
|
onUpdate={handleUpdate}
|
|
onRemove={handleRemove}
|
|
/>
|
|
</div>
|
|
)}
|
|
{
|
|
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')}
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
|
|
{!modelLoadBalancingEnabled && !IS_CE_EDITION && (
|
|
<GridMask canvasClassName='!rounded-xl'>
|
|
<div className='mt-2 flex h-14 items-center justify-between rounded-xl border-[0.5px] border-components-panel-border px-4 shadow-md'>
|
|
<div
|
|
className={classNames('text-gradient text-sm font-semibold leading-tight', s.textGradient)}
|
|
>
|
|
{t('common.modelProvider.upgradeForLoadBalancing')}
|
|
</div>
|
|
<UpgradeBtn />
|
|
</div>
|
|
</GridMask>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default ModelLoadBalancingConfigs
|