123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import type { Dispatch, SetStateAction } from 'react'
- import { useCallback } from 'react'
- import { useTranslation } from 'react-i18next'
- import {
- RiDeleteBinLine,
- } from '@remixicon/react'
- import type { ConfigurationMethodEnum, CustomConfigurationModelFixedFields, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } 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 { Edit02, Plus02 } from '@/app/components/base/icons/src/vender/line/general'
- import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
- import { useModalContextSelector } from '@/context/modal-context'
- 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'
- export type ModelLoadBalancingConfigsProps = {
- draftConfig?: ModelLoadBalancingConfig
- setDraftConfig: Dispatch<SetStateAction<ModelLoadBalancingConfig | undefined>>
- provider: ModelProvider
- configurationMethod: ConfigurationMethodEnum
- currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
- withSwitch?: boolean
- className?: string
- }
- const ModelLoadBalancingConfigs = ({
- draftConfig,
- setDraftConfig,
- provider,
- configurationMethod,
- currentCustomConfigurationModelFixedFields,
- withSwitch = false,
- className,
- }: ModelLoadBalancingConfigsProps) => {
- const { t } = useTranslation()
- 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 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 setShowModelLoadBalancingEntryModal = useModalContextSelector(state => state.setShowModelLoadBalancingEntryModal)
- const toggleEntryModal = useCallback((index?: number, entry?: ModelLoadBalancingConfigEntry) => {
- setShowModelLoadBalancingEntryModal({
- payload: {
- currentProvider: provider,
- currentConfigurationMethod: configurationMethod,
- currentCustomConfigurationModelFixedFields,
- entry,
- index,
- },
- onSaveCallback: ({ entry: result }) => {
- if (entry) {
- // edit
- setDraftConfig(prev => ({
- ...prev,
- enabled: !!prev?.enabled,
- configs: prev?.configs.map((config, i) => i === index ? result! : config) || [],
- }))
- }
- else {
- // add
- setDraftConfig(prev => ({
- ...prev,
- enabled: !!prev?.enabled,
- configs: (prev?.configs || []).concat([{ ...result!, enabled: true }]),
- }))
- }
- },
- onRemoveCallback: ({ index }) => {
- if (index !== undefined && (draftConfig?.configs?.length ?? 0) > index) {
- setDraftConfig(prev => ({
- ...prev,
- enabled: !!prev?.enabled,
- configs: prev?.configs.filter((_, i) => i !== index) || [],
- }))
- }
- },
- })
- }, [
- configurationMethod,
- currentCustomConfigurationModelFixedFields,
- draftConfig?.configs?.length,
- provider,
- setDraftConfig,
- setShowModelLoadBalancingEntryModal,
- ])
- const clearCountdown = useCallback((index: number) => {
- updateConfigEntry(index, ({ ttl: _, ...entry }) => {
- return {
- ...entry,
- in_cooldown: false,
- }
- })
- }, [updateConfigEntry])
- if (!draftConfig)
- return null
- return (
- <>
- <div
- className={classNames(
- 'min-h-16 bg-gray-50 border rounded-xl transition-colors',
- (withSwitch || !draftConfig.enabled) ? 'border-gray-200' : 'border-primary-400',
- (withSwitch || draftConfig.enabled) ? 'cursor-default' : 'cursor-pointer',
- className,
- )}
- onClick={(!withSwitch && !draftConfig.enabled) ? () => toggleModalBalancing(true) : undefined}
- >
- <div className='flex items-center px-[15px] py-3 gap-2 select-none'>
- <div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 text-primary-600 bg-indigo-50 border border-indigo-100 rounded-lg'>
- <Balance className='w-4 h-4' />
- </div>
- <div className='grow'>
- <div className='flex items-center gap-1 text-sm'>
- {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-gray-500'>{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'>
- {draftConfig.configs.map((config, index) => {
- const isProviderManaged = config.name === '__inherit__'
- return (
- <div key={config.id || index} className='group flex items-center px-3 h-10 bg-white border border-gray-200 rounded-lg shadow-xs'>
- <div className='grow flex items-center'>
- <div className='flex items-center justify-center mr-2 w-3 h-3'>
- {(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='text-[13px] mr-1'>
- {isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
- </div>
- {isProviderManaged && (
- <span className='px-1 text-2xs uppercase text-gray-500 border border-black/8 rounded-[5px]'>{t('common.modelProvider.providerManaged')}</span>
- )}
- </div>
- <div className='flex items-center gap-1'>
- {!isProviderManaged && (
- <>
- <div className='flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>
- <span
- className='flex items-center justify-center w-8 h-8 text-gray-500 bg-white rounded-lg transition-colors cursor-pointer hover:bg-black/5'
- onClick={() => toggleEntryModal(index, config)}
- >
- <Edit02 className='w-4 h-4' />
- </span>
- <span
- className='flex items-center justify-center w-8 h-8 text-gray-500 bg-white rounded-lg transition-colors cursor-pointer hover:bg-black/5'
- onClick={() => updateConfigEntry(index, () => undefined)}
- >
- <RiDeleteBinLine className='w-4 h-4' />
- </span>
- <span className='mr-2 h-3 border-r border-r-gray-100' />
- </div>
- </>
- )}
- <Switch
- defaultValue={Boolean(config.enabled)}
- size='md'
- className='justify-self-end'
- onChange={value => toggleConfigEntryEnabled(index, value)}
- />
- </div>
- </div>
- )
- })}
- <div
- className='flex items-center px-3 mt-1 h-8 text-[13px] font-medium text-primary-600'
- onClick={() => toggleEntryModal()}
- >
- <div className='flex items-center cursor-pointer'>
- <Plus02 className='mr-2 w-3 h-3' />{t('common.modelProvider.addConfig')}
- </div>
- </div>
- </div>
- )}
- {
- draftConfig.enabled && draftConfig.configs.length < 2 && (
- <div className='flex items-center px-6 h-[34px] text-xs text-gray-700 bg-black/2 border-t border-t-black/5'>
- <AlertTriangle className='mr-1 w-3 h-3 text-[#f79009]' />
- {t('common.modelProvider.loadBalancingLeastKeyWarning')}
- </div>
- )
- }
- </div>
- {!modelLoadBalancingEnabled && !IS_CE_EDITION && (
- <GridMask canvasClassName='!rounded-xl'>
- <div className='flex items-center justify-between mt-2 px-4 h-14 border-[0.5px] border-gray-200 rounded-xl shadow-md'>
- <div
- className={classNames('text-sm font-semibold leading-tight text-gradient', s.textGradient)}
- >
- {t('common.modelProvider.upgradeForLoadBalancing')}
- </div>
- <UpgradeBtn />
- </div>
- </GridMask>
- )}
- </>
- )
- }
- export default ModelLoadBalancingConfigs
|