feat: add multi model credentials (#24451)
Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
b08bfa203a
commit
6010d5f24c
65 changed files with 5202 additions and 1814 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import type { FC } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import type {
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
|
|
@ -15,19 +16,19 @@ import PrioritySelector from './priority-selector'
|
|||
import PriorityUseTip from './priority-use-tip'
|
||||
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { changeModelProviderPriority } from '@/service/common'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useCredentialStatus } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks'
|
||||
import { ConfigProvider } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
|
||||
type CredentialPanelProps = {
|
||||
provider: ModelProvider
|
||||
onSetup: () => void
|
||||
}
|
||||
const CredentialPanel: FC<CredentialPanelProps> = ({
|
||||
const CredentialPanel = ({
|
||||
provider,
|
||||
onSetup,
|
||||
}) => {
|
||||
}: CredentialPanelProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
|
@ -38,6 +39,13 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
|||
const priorityUseType = provider.preferred_provider_type
|
||||
const isCustomConfigured = customConfig.status === CustomConfigurationStatusEnum.active
|
||||
const configurateMethods = provider.configurate_methods
|
||||
const {
|
||||
hasCredential,
|
||||
authorized,
|
||||
authRemoved,
|
||||
current_credential_name,
|
||||
notAllowedToUse,
|
||||
} = useCredentialStatus(provider)
|
||||
|
||||
const handleChangePriority = async (key: PreferredProviderTypeEnum) => {
|
||||
const res = await changeModelProviderPriority({
|
||||
|
|
@ -61,25 +69,50 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
|||
} as any)
|
||||
}
|
||||
}
|
||||
const credentialLabel = useMemo(() => {
|
||||
if (!hasCredential)
|
||||
return t('common.modelProvider.auth.unAuthorized')
|
||||
if (authorized)
|
||||
return current_credential_name
|
||||
if (authRemoved)
|
||||
return t('common.modelProvider.auth.authRemoved')
|
||||
|
||||
return ''
|
||||
}, [authorized, authRemoved, current_credential_name, hasCredential])
|
||||
|
||||
const color = useMemo(() => {
|
||||
if (authRemoved)
|
||||
return 'red'
|
||||
if (notAllowedToUse)
|
||||
return 'gray'
|
||||
return 'green'
|
||||
}, [authRemoved, notAllowedToUse])
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
provider.provider_credential_schema && (
|
||||
<div className='relative ml-1 w-[112px] shrink-0 rounded-lg border-[0.5px] border-components-panel-border bg-white/[0.18] p-1'>
|
||||
<div className='system-xs-medium-uppercase mb-1 flex h-5 items-center justify-between pl-2 pr-[7px] pt-1 text-text-tertiary'>
|
||||
API-KEY
|
||||
<Indicator color={isCustomConfigured ? 'green' : 'red'} />
|
||||
<div className={cn(
|
||||
'relative ml-1 w-[120px] shrink-0 rounded-lg border-[0.5px] border-components-panel-border bg-white/[0.18] p-1',
|
||||
authRemoved && 'border-state-destructive-border bg-state-destructive-hover',
|
||||
)}>
|
||||
<div className='system-xs-medium mb-1 flex h-5 items-center justify-between pl-2 pr-[7px] pt-1 text-text-tertiary'>
|
||||
<div
|
||||
className={cn(
|
||||
'grow truncate',
|
||||
authRemoved && 'text-text-destructive',
|
||||
)}
|
||||
title={credentialLabel}
|
||||
>
|
||||
{credentialLabel}
|
||||
</div>
|
||||
<Indicator className='shrink-0' color={color} />
|
||||
</div>
|
||||
<div className='flex items-center gap-0.5'>
|
||||
<Button
|
||||
className='grow'
|
||||
size='small'
|
||||
onClick={onSetup}
|
||||
>
|
||||
<RiEqualizer2Line className='mr-1 h-3.5 w-3.5' />
|
||||
{t('common.operation.setup')}
|
||||
</Button>
|
||||
<ConfigProvider
|
||||
provider={provider}
|
||||
configurationMethod={ConfigurationMethodEnum.predefinedModel}
|
||||
/>
|
||||
{
|
||||
systemConfig.enabled && isCustomConfigured && (
|
||||
<PrioritySelector
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type {
|
||||
CustomConfigurationModelFixedFields,
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
|
|
@ -21,23 +20,21 @@ import ModelBadge from '../model-badge'
|
|||
import CredentialPanel from './credential-panel'
|
||||
import QuotaPanel from './quota-panel'
|
||||
import ModelList from './model-list'
|
||||
import AddModelButton from './add-model-button'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import cn from '@/utils/classnames'
|
||||
import { AddCustomModel } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
|
||||
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
||||
type ProviderAddedCardProps = {
|
||||
notConfigured?: boolean
|
||||
provider: ModelProvider
|
||||
onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
|
||||
}
|
||||
const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
notConfigured,
|
||||
provider,
|
||||
onOpenModal,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
|
@ -114,7 +111,6 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
{
|
||||
showCredential && (
|
||||
<CredentialPanel
|
||||
onSetup={() => onOpenModal(ConfigurationMethodEnum.predefinedModel)}
|
||||
provider={provider}
|
||||
/>
|
||||
)
|
||||
|
|
@ -159,9 +155,9 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
)}
|
||||
{
|
||||
configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && (
|
||||
<AddModelButton
|
||||
onClick={() => onOpenModal(ConfigurationMethodEnum.customizableModel)}
|
||||
className='flex'
|
||||
<AddCustomModel
|
||||
provider={provider}
|
||||
configurationMethod={ConfigurationMethodEnum.customizableModel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -174,7 +170,6 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
provider={provider}
|
||||
models={modelList}
|
||||
onCollapse={() => setCollapsed(true)}
|
||||
onConfig={currentCustomConfigurationModelFixedFields => onOpenModal(ConfigurationMethodEnum.customizableModel, currentCustomConfigurationModelFixedFields)}
|
||||
onChange={(provider: string) => getModelList(provider)}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,29 @@
|
|||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import type { CustomConfigurationModelFixedFields, ModelItem, ModelProvider } from '../declarations'
|
||||
import { ConfigurationMethodEnum, ModelStatusEnum } from '../declarations'
|
||||
import ModelBadge from '../model-badge'
|
||||
import type { ModelItem, ModelProvider } from '../declarations'
|
||||
import { ModelStatusEnum } from '../declarations'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
import classNames from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { Balance } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useProviderContext, useProviderContextSelector } from '@/context/provider-context'
|
||||
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
|
||||
provider: ModelProvider
|
||||
isConfigurable: boolean
|
||||
onConfig: (currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
|
||||
onModifyLoadBalancing?: (model: ModelItem) => void
|
||||
}
|
||||
|
||||
const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoadBalancing }: ModelListItemProps) => {
|
||||
const ModelListItem = ({ model, provider, isConfigurable, onModifyLoadBalancing }: ModelListItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled)
|
||||
|
|
@ -46,7 +44,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
|
|||
|
||||
return (
|
||||
<div
|
||||
key={model.model}
|
||||
key={`${model.model}-${model.fetch_from}`}
|
||||
className={classNames(
|
||||
'group flex h-8 items-center rounded-lg pl-2 pr-2.5',
|
||||
isConfigurable && 'hover:bg-components-panel-on-panel-item-bg-hover',
|
||||
|
|
@ -65,38 +63,22 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
|
|||
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 && !model.has_invalid_load_balancing_configs && (
|
||||
<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>
|
||||
)}
|
||||
{
|
||||
model.fetch_from === ConfigurationMethodEnum.customizableModel
|
||||
? (isCurrentWorkspaceManager && (
|
||||
<Button
|
||||
size='small'
|
||||
className='hidden group-hover:flex'
|
||||
onClick={() => onConfig({ __model_name: model.model, __model_type: model.model_type })}
|
||||
>
|
||||
<Settings01 className='mr-1 h-3.5 w-3.5' />
|
||||
{t('common.modelProvider.config')}
|
||||
</Button>
|
||||
))
|
||||
: (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status))
|
||||
? (
|
||||
<Button
|
||||
size='small'
|
||||
className='opacity-0 transition-opacity group-hover:opacity-100'
|
||||
onClick={() => onModifyLoadBalancing?.(model)}
|
||||
>
|
||||
<Balance className='mr-1 h-3.5 w-3.5' />
|
||||
{t('common.modelProvider.configLoadBalancing')}
|
||||
</Button>
|
||||
)
|
||||
: null
|
||||
(isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) && (
|
||||
<ConfigModel
|
||||
onClick={() => onModifyLoadBalancing?.(model)}
|
||||
loadBalancingEnabled={model.load_balancing_enabled}
|
||||
loadBalancingInvalid={model.has_invalid_load_balancing_configs}
|
||||
credentialRemoved={model.status === ModelStatusEnum.credentialRemoved}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
model.deprecated
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
RiArrowRightSLine,
|
||||
} from '@remixicon/react'
|
||||
import type {
|
||||
CustomConfigurationModelFixedFields,
|
||||
Credential,
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
|
|
@ -13,34 +13,33 @@ import {
|
|||
ConfigurationMethodEnum,
|
||||
} from '../declarations'
|
||||
// import Tab from './tab'
|
||||
import AddModelButton from './add-model-button'
|
||||
import ModelListItem from './model-list-item'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { AddCustomModel } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
|
||||
type ModelListProps = {
|
||||
provider: ModelProvider
|
||||
models: ModelItem[]
|
||||
onCollapse: () => void
|
||||
onConfig: (currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
|
||||
onChange?: (provider: string) => void
|
||||
}
|
||||
const ModelList: FC<ModelListProps> = ({
|
||||
provider,
|
||||
models,
|
||||
onCollapse,
|
||||
onConfig,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const configurativeMethods = provider.configurate_methods.filter(method => method !== ConfigurationMethodEnum.fetchFromRemote)
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const isConfigurable = configurativeMethods.includes(ConfigurationMethodEnum.customizableModel)
|
||||
|
||||
const setShowModelLoadBalancingModal = useModalContextSelector(state => state.setShowModelLoadBalancingModal)
|
||||
const onModifyLoadBalancing = useCallback((model: ModelItem) => {
|
||||
const onModifyLoadBalancing = useCallback((model: ModelItem, credential?: Credential) => {
|
||||
setShowModelLoadBalancingModal({
|
||||
provider,
|
||||
credential,
|
||||
configurateMethod: model.fetch_from,
|
||||
model: model!,
|
||||
open: !!model,
|
||||
onClose: () => setShowModelLoadBalancingModal(null),
|
||||
|
|
@ -65,17 +64,14 @@ const ModelList: FC<ModelListProps> = ({
|
|||
<RiArrowRightSLine className='mr-0.5 h-4 w-4 rotate-90' />
|
||||
</span>
|
||||
</span>
|
||||
{/* {
|
||||
isConfigurable && canSystemConfig && (
|
||||
<span className='flex items-center'>
|
||||
<Tab active='all' onSelect={() => {}} />
|
||||
</span>
|
||||
)
|
||||
} */}
|
||||
{
|
||||
isConfigurable && isCurrentWorkspaceManager && (
|
||||
<div className='flex grow justify-end'>
|
||||
<AddModelButton onClick={() => onConfig()} />
|
||||
<AddCustomModel
|
||||
provider={provider}
|
||||
configurationMethod={ConfigurationMethodEnum.customizableModel}
|
||||
currentCustomConfigurationModelFixedFields={undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -83,12 +79,11 @@ const ModelList: FC<ModelListProps> = ({
|
|||
{
|
||||
models.map(model => (
|
||||
<ModelListItem
|
||||
key={model.model}
|
||||
key={`${model.model}-${model.fetch_from}`}
|
||||
{...{
|
||||
model,
|
||||
provider,
|
||||
isConfigurable,
|
||||
onConfig,
|
||||
onModifyLoadBalancing,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,35 @@
|
|||
import type { Dispatch, SetStateAction } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEqualizer2Line,
|
||||
} from '@remixicon/react'
|
||||
import type { ConfigurationMethodEnum, CustomConfigurationModelFixedFields, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations'
|
||||
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 { 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'
|
||||
import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
import { useModelModalHandler } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import Badge from '@/app/components/base/badge/index'
|
||||
|
||||
export type ModelLoadBalancingConfigsProps = {
|
||||
draftConfig?: ModelLoadBalancingConfig
|
||||
|
|
@ -28,19 +39,27 @@ export type ModelLoadBalancingConfigsProps = {
|
|||
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
|
||||
withSwitch?: boolean
|
||||
className?: string
|
||||
modelCredential: ModelCredential
|
||||
onUpdate?: () => void
|
||||
model: CustomModelCredential
|
||||
}
|
||||
|
||||
const ModelLoadBalancingConfigs = ({
|
||||
draftConfig,
|
||||
setDraftConfig,
|
||||
provider,
|
||||
model,
|
||||
configurationMethod,
|
||||
currentCustomConfigurationModelFixedFields,
|
||||
withSwitch = false,
|
||||
className,
|
||||
modelCredential,
|
||||
onUpdate,
|
||||
}: ModelLoadBalancingConfigsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const providerFormSchemaPredefined = configurationMethod === ConfigurationMethodEnum.predefinedModel
|
||||
const modelLoadBalancingEnabled = useProviderContextSelector(state => state.modelLoadBalancingEnabled)
|
||||
const handleOpenModal = useModelModalHandler()
|
||||
|
||||
const updateConfigEntry = useCallback(
|
||||
(
|
||||
|
|
@ -65,6 +84,21 @@ const ModelLoadBalancingConfigs = ({
|
|||
[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({
|
||||
|
|
@ -81,54 +115,6 @@ const ModelLoadBalancingConfigs = ({
|
|||
}))
|
||||
}, [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 {
|
||||
|
|
@ -138,6 +124,12 @@ const ModelLoadBalancingConfigs = ({
|
|||
})
|
||||
}, [updateConfigEntry])
|
||||
|
||||
const validDraftConfigList = useMemo(() => {
|
||||
if (!draftConfig)
|
||||
return []
|
||||
return draftConfig.configs
|
||||
}, [draftConfig])
|
||||
|
||||
if (!draftConfig)
|
||||
return null
|
||||
|
||||
|
|
@ -181,8 +173,9 @@ const ModelLoadBalancingConfigs = ({
|
|||
</div>
|
||||
{draftConfig.enabled && (
|
||||
<div className='flex flex-col gap-1 px-3 pb-3'>
|
||||
{draftConfig.configs.map((config, index) => {
|
||||
{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'>
|
||||
|
|
@ -200,54 +193,81 @@ const ModelLoadBalancingConfigs = ({
|
|||
<div className='mr-1 text-[13px]'>
|
||||
{isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
|
||||
</div>
|
||||
{isProviderManaged && (
|
||||
<span className='rounded-[5px] border border-divider-regular px-1 text-2xs uppercase text-text-tertiary'>{t('common.modelProvider.providerManaged')}</span>
|
||||
{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'>
|
||||
<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={() => toggleEntryModal(index, config)}
|
||||
>
|
||||
<Edit02 className='h-4 w-4' />
|
||||
</span>
|
||||
{
|
||||
config.credential_id && !credential?.not_allowed_to_use && !credential?.from_enterprise && (
|
||||
<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={() => {
|
||||
handleOpenModal(
|
||||
provider,
|
||||
configurationMethod,
|
||||
currentCustomConfigurationModelFixedFields,
|
||||
configurationMethod === ConfigurationMethodEnum.customizableModel,
|
||||
(config.credential_id && config.name) ? {
|
||||
credential_id: config.credential_id,
|
||||
credential_name: config.name,
|
||||
} : undefined,
|
||||
model,
|
||||
)
|
||||
}}
|
||||
>
|
||||
<RiEqualizer2Line className='h-4 w-4' />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
<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)}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
</span>
|
||||
<span className='mr-2 h-3 border-r border-r-divider-subtle' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Switch
|
||||
defaultValue={Boolean(config.enabled)}
|
||||
size='md'
|
||||
className='justify-self-end'
|
||||
onChange={value => toggleConfigEntryEnabled(index, value)}
|
||||
/>
|
||||
{
|
||||
(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>
|
||||
)
|
||||
})}
|
||||
|
||||
<div
|
||||
className='mt-1 flex h-8 items-center px-3 text-[13px] font-medium text-primary-600'
|
||||
onClick={() => toggleEntryModal()}
|
||||
>
|
||||
<div className='flex cursor-pointer items-center'>
|
||||
<Plus02 className='mr-2 h-3 w-3' />{t('common.modelProvider.addConfig')}
|
||||
</div>
|
||||
</div>
|
||||
<AddCredentialInLoadBalancing
|
||||
provider={provider}
|
||||
model={model}
|
||||
configurationMethod={configurationMethod}
|
||||
modelCredential={modelCredential}
|
||||
onSelectCredential={addConfigEntry}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
draftConfig.enabled && draftConfig.configs.length < 2 && (
|
||||
<div className='flex h-[34px] items-center border-t border-t-divider-subtle bg-components-panel-bg px-6 text-xs text-text-secondary'>
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -1,40 +1,69 @@
|
|||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import type { ModelItem, ModelLoadBalancingConfig, ModelLoadBalancingConfigEntry, ModelProvider } from '../declarations'
|
||||
import { FormTypeEnum } from '../declarations'
|
||||
import type {
|
||||
Credential,
|
||||
ModelItem,
|
||||
ModelLoadBalancingConfig,
|
||||
ModelLoadBalancingConfigEntry,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
FormTypeEnum,
|
||||
} from '../declarations'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
import { savePredefinedLoadBalancingConfig } from '../utils'
|
||||
import ModelLoadBalancingConfigs from './model-load-balancing-configs'
|
||||
import classNames from '@/utils/classnames'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { fetchModelLoadBalancingConfig } from '@/service/common'
|
||||
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 {
|
||||
useGetModelCredential,
|
||||
useUpdateModelLoadBalancingConfig,
|
||||
} from '@/service/use-models'
|
||||
|
||||
export type ModelLoadBalancingModalProps = {
|
||||
provider: ModelProvider
|
||||
configurateMethod: ConfigurationMethodEnum
|
||||
model: ModelItem
|
||||
credential?: Credential
|
||||
open?: boolean
|
||||
onClose?: () => void
|
||||
onSave?: (provider: string) => void
|
||||
}
|
||||
|
||||
// model balancing config modal
|
||||
const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSave }: ModelLoadBalancingModalProps) => {
|
||||
const ModelLoadBalancingModal = ({
|
||||
provider,
|
||||
configurateMethod,
|
||||
model,
|
||||
credential,
|
||||
open = false,
|
||||
onClose,
|
||||
onSave,
|
||||
}: ModelLoadBalancingModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const { data, mutate } = useSWR(
|
||||
`/workspaces/current/model-providers/${provider.provider}/models/credentials?model=${model.model}&model_type=${model.model_type}`,
|
||||
fetchModelLoadBalancingConfig,
|
||||
)
|
||||
|
||||
const originalConfig = data?.load_balancing
|
||||
const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel
|
||||
const configFrom = providerFormSchemaPredefined ? 'predefined-model' : 'custom-model'
|
||||
const {
|
||||
isLoading,
|
||||
data,
|
||||
refetch,
|
||||
} = useGetModelCredential(true, provider.provider, credential?.credential_id, model.model, model.model_type, configFrom)
|
||||
const modelCredential = data
|
||||
const {
|
||||
load_balancing,
|
||||
current_credential_id,
|
||||
available_credentials,
|
||||
current_credential_name,
|
||||
} = modelCredential ?? {}
|
||||
const originalConfig = load_balancing
|
||||
const [draftConfig, setDraftConfig] = useState<ModelLoadBalancingConfig>()
|
||||
const originalConfigMap = useMemo(() => {
|
||||
if (!originalConfig)
|
||||
|
|
@ -60,10 +89,17 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
}, [draftConfig])
|
||||
|
||||
const extendedSecretFormSchemas = useMemo(
|
||||
() => provider.provider_credential_schema.credential_form_schemas.filter(
|
||||
({ type }) => type === FormTypeEnum.secretInput,
|
||||
),
|
||||
[provider.provider_credential_schema.credential_form_schemas],
|
||||
() => {
|
||||
if (providerFormSchemaPredefined) {
|
||||
return provider?.provider_credential_schema?.credential_form_schemas?.filter(
|
||||
({ type }) => type === FormTypeEnum.secretInput,
|
||||
) ?? []
|
||||
}
|
||||
return provider?.model_credential_schema?.credential_form_schemas?.filter(
|
||||
({ type }) => type === FormTypeEnum.secretInput,
|
||||
) ?? []
|
||||
},
|
||||
[provider?.model_credential_schema?.credential_form_schemas, provider?.provider_credential_schema?.credential_form_schemas, providerFormSchemaPredefined],
|
||||
)
|
||||
|
||||
const encodeConfigEntrySecretValues = useCallback((entry: ModelLoadBalancingConfigEntry) => {
|
||||
|
|
@ -75,25 +111,34 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
return result
|
||||
}, [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 savePredefinedLoadBalancingConfig(
|
||||
provider.provider,
|
||||
({
|
||||
...(data?.credentials ?? {}),
|
||||
__model_type: model.model_type,
|
||||
__model_name: model.model,
|
||||
}),
|
||||
const res = await updateModelLoadBalancingConfig(
|
||||
{
|
||||
...draftConfig,
|
||||
enabled: Boolean(draftConfig?.enabled),
|
||||
configs: draftConfig!.configs.map(encodeConfigEntrySecretValues),
|
||||
credential_id: customModelCredential?.credential_id || current_credential_id,
|
||||
config_from: configFrom,
|
||||
model: model.model,
|
||||
model_type: model.model_type,
|
||||
load_balancing: {
|
||||
...draftConfig,
|
||||
configs: draftConfig!.configs.map(encodeConfigEntrySecretValues),
|
||||
enabled: Boolean(draftConfig?.enabled),
|
||||
},
|
||||
},
|
||||
)
|
||||
if (res.result === 'success') {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
mutate()
|
||||
onSave?.(provider.provider)
|
||||
onClose?.()
|
||||
}
|
||||
|
|
@ -110,7 +155,11 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
className='w-[640px] max-w-none px-8 pt-8'
|
||||
title={
|
||||
<div className='pb-3 font-semibold'>
|
||||
<div className='h-[30px]'>{t('common.modelProvider.configLoadBalancing')}</div>
|
||||
<div className='h-[30px]'>{
|
||||
draftConfig?.enabled
|
||||
? t('common.modelProvider.auth.configLoadBalancing')
|
||||
: t('common.modelProvider.auth.configModel')
|
||||
}</div>
|
||||
{Boolean(model) && (
|
||||
<div className='flex h-5 items-center'>
|
||||
<ModelIcon
|
||||
|
|
@ -149,23 +198,51 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
)}
|
||||
</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>
|
||||
{
|
||||
!providerFormSchemaPredefined && (
|
||||
<SwitchCredentialInLoadBalancing
|
||||
provider={provider}
|
||||
customModelCredential={initialCustomModelCredential ?? customModelCredential}
|
||||
setCustomModelCredential={setCustomModelCredential}
|
||||
model={model}
|
||||
credentials={available_credentials}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModelLoadBalancingConfigs {...{
|
||||
draftConfig,
|
||||
setDraftConfig,
|
||||
provider,
|
||||
currentCustomConfigurationModelFixedFields: {
|
||||
__model_name: model.model,
|
||||
__model_type: model.model_type,
|
||||
},
|
||||
configurationMethod: model.fetch_from,
|
||||
className: 'mt-2',
|
||||
}} />
|
||||
{
|
||||
modelCredential && (
|
||||
<ModelLoadBalancingConfigs {...{
|
||||
draftConfig,
|
||||
setDraftConfig,
|
||||
provider,
|
||||
currentCustomConfigurationModelFixedFields: {
|
||||
__model_name: model.model,
|
||||
__model_type: model.model_type,
|
||||
},
|
||||
configurationMethod: model.fetch_from,
|
||||
className: 'mt-2',
|
||||
modelCredential,
|
||||
onUpdate: refetch,
|
||||
model: {
|
||||
model: model.model,
|
||||
model_type: model.model_type,
|
||||
},
|
||||
}} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='mt-6 flex items-center justify-end gap-2'>
|
||||
|
|
@ -176,6 +253,7 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
disabled={
|
||||
loading
|
||||
|| (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
|
||||
|| isLoading
|
||||
}
|
||||
>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue