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> provider: ModelProvider configurationMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields withSwitch?: boolean className?: string modelCredential: ModelCredential onUpdate?: (payload?: any, formValues?: Record) => void onRemove?: (credentialId: string) => void model: CustomModelCredential } const ModelLoadBalancingConfigs = ({ draftConfig, setDraftConfig, provider, model, configurationMethod, currentCustomConfigurationModelFixedFields: _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) => { 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 ( <>
toggleModalBalancing(true) : undefined} >
{t('common.modelProvider.loadBalancing')}
{t('common.modelProvider.loadBalancingDescription')}
{ withSwitch && ( toggleModalBalancing(value)} /> ) }
{draftConfig.enabled && (
{validDraftConfigList.map((config, index) => { const isProviderManaged = config.name === '__inherit__' const credential = modelCredential.available_credentials.find(c => c.credential_id === config.credential_id) return (
{(config.in_cooldown && Boolean(config.ttl)) ? ( clearCountdown(index)} /> ) : ( )}
{isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
{isProviderManaged && providerFormSchemaPredefined && ( {t('common.modelProvider.providerManaged')} )} { credential?.from_enterprise && ( Enterprise ) }
{!isProviderManaged && ( <>
updateConfigEntry(index, () => undefined)} >
)} { (config.credential_id || config.name === '__inherit__') && ( <> toggleConfigEntryEnabled(index, value)} disabled={credential?.not_allowed_to_use} /> ) }
) })}
)} { draftConfig.enabled && validDraftConfigList.length < 2 && (
{t('common.modelProvider.loadBalancingLeastKeyWarning')}
) }
{!modelLoadBalancingEnabled && !IS_CE_EDITION && (
{t('common.modelProvider.upgradeForLoadBalancing')}
)} ) } export default ModelLoadBalancingConfigs