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:
非法操作
2025-08-25 16:12:29 +08:00
committed by GitHub
parent b08bfa203a
commit 6010d5f24c
65 changed files with 5202 additions and 1814 deletions

View File

@@ -2,43 +2,22 @@ import type { FC } from 'react'
import {
memo,
useCallback,
useEffect,
useMemo,
useState,
useRef,
} from 'react'
import { RiCloseLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import {
RiErrorWarningFill,
} from '@remixicon/react'
import type {
CredentialFormSchema,
CredentialFormSchemaRadio,
CredentialFormSchemaSelect,
CustomConfigurationModelFixedFields,
FormValue,
ModelLoadBalancingConfig,
ModelLoadBalancingConfigEntry,
ModelProvider,
} from '../declarations'
import {
ConfigurationMethodEnum,
CustomConfigurationStatusEnum,
FormTypeEnum,
} from '../declarations'
import {
genModelNameFormSchema,
genModelTypeFormSchema,
removeCredentials,
saveCredentials,
} from '../utils'
import {
useLanguage,
useProviderCredentialsAndLoadBalancing,
} from '../hooks'
import { useValidate } from '../../key-validator/hooks'
import { ValidatedStatus } from '../../key-validator/declarations'
import ModelLoadBalancingConfigs from '../provider-added-card/model-load-balancing-configs'
import Form from './Form'
import Button from '@/app/components/base/button'
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
@@ -46,9 +25,26 @@ import {
PortalToFollowElem,
PortalToFollowElemContent,
} from '@/app/components/base/portal-to-follow-elem'
import { useToastContext } from '@/app/components/base/toast'
import Confirm from '@/app/components/base/confirm'
import { useAppContext } from '@/context/app-context'
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
import type {
FormRefObject,
FormSchema,
} from '@/app/components/base/form/types'
import { useModelFormSchemas } from '../model-auth/hooks'
import type {
Credential,
CustomModel,
} from '../declarations'
import Loading from '@/app/components/base/loading'
import {
useAuth,
useCredentialData,
} from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks'
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
import Badge from '@/app/components/base/badge'
import { useRenderI18nObject } from '@/hooks/use-i18n'
type ModelModalProps = {
provider: ModelProvider
@@ -56,6 +52,9 @@ type ModelModalProps = {
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
onCancel: () => void
onSave: () => void
model?: CustomModel
credential?: Credential
isModelCredential?: boolean
}
const ModelModal: FC<ModelModalProps> = ({
@@ -64,244 +63,173 @@ const ModelModal: FC<ModelModalProps> = ({
currentCustomConfigurationModelFixedFields,
onCancel,
onSave,
model,
credential,
isModelCredential,
}) => {
const renderI18nObject = useRenderI18nObject()
const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel
const {
isLoading,
credentialData,
} = useCredentialData(provider, providerFormSchemaPredefined, isModelCredential, credential, model)
const {
handleSaveCredential,
handleConfirmDelete,
deleteCredentialId,
closeConfirmDelete,
openConfirmDelete,
doingAction,
} = useAuth(provider, configurateMethod, currentCustomConfigurationModelFixedFields, isModelCredential, onSave)
const {
credentials: formSchemasValue,
loadBalancing: originalConfig,
mutate,
} = useProviderCredentialsAndLoadBalancing(
provider.provider,
configurateMethod,
providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active,
currentCustomConfigurationModelFixedFields,
)
} = credentialData as any
const { isCurrentWorkspaceManager } = useAppContext()
const isEditMode = !!formSchemasValue && isCurrentWorkspaceManager
const { t } = useTranslation()
const { notify } = useToastContext()
const language = useLanguage()
const [loading, setLoading] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
const {
formSchemas,
formValues,
} = useModelFormSchemas(provider, providerFormSchemaPredefined, formSchemasValue, credential, model)
const formRef = useRef<FormRefObject>(null)
const [draftConfig, setDraftConfig] = useState<ModelLoadBalancingConfig>()
const originalConfigMap = useMemo(() => {
if (!originalConfig)
return {}
return originalConfig?.configs.reduce((prev, config) => {
if (config.id)
prev[config.id] = config
return prev
}, {} as Record<string, ModelLoadBalancingConfigEntry>)
}, [originalConfig])
useEffect(() => {
if (originalConfig && !draftConfig)
setDraftConfig(originalConfig)
}, [draftConfig, originalConfig])
const handleSave = useCallback(async () => {
const {
isCheckValidated,
values,
} = formRef.current?.getFormValues({
needCheckValidatedValues: true,
needTransformWhenSecretFieldIsPristine: true,
}) || { isCheckValidated: false, values: {} }
if (!isCheckValidated)
return
const formSchemas = useMemo(() => {
return providerFormSchemaPredefined
? provider.provider_credential_schema.credential_form_schemas
: [
genModelTypeFormSchema(provider.supported_model_types),
genModelNameFormSchema(provider.model_credential_schema?.model),
...(draftConfig?.enabled ? [] : provider.model_credential_schema.credential_form_schemas),
]
}, [
providerFormSchemaPredefined,
provider.provider_credential_schema?.credential_form_schemas,
provider.supported_model_types,
provider.model_credential_schema?.credential_form_schemas,
provider.model_credential_schema?.model,
draftConfig?.enabled,
])
const [
requiredFormSchemas,
defaultFormSchemaValue,
showOnVariableMap,
] = useMemo(() => {
const requiredFormSchemas: CredentialFormSchema[] = []
const defaultFormSchemaValue: Record<string, string | number> = {}
const showOnVariableMap: Record<string, string[]> = {}
const {
__authorization_name__,
__model_name,
__model_type,
...rest
} = values
if (__model_name && __model_type) {
handleSaveCredential({
credential_id: credential?.credential_id,
credentials: rest,
name: __authorization_name__,
model: __model_name,
model_type: __model_type,
})
}
else {
handleSaveCredential({
credential_id: credential?.credential_id,
credentials: rest,
name: __authorization_name__,
})
}
}, [handleSaveCredential, credential?.credential_id, model])
formSchemas.forEach((formSchema) => {
if (formSchema.required)
requiredFormSchemas.push(formSchema)
if (formSchema.default)
defaultFormSchemaValue[formSchema.variable] = formSchema.default
if (formSchema.show_on.length) {
formSchema.show_on.forEach((showOnItem) => {
if (!showOnVariableMap[showOnItem.variable])
showOnVariableMap[showOnItem.variable] = []
if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable))
showOnVariableMap[showOnItem.variable].push(formSchema.variable)
})
}
if (formSchema.type === FormTypeEnum.select || formSchema.type === FormTypeEnum.radio) {
(formSchema as (CredentialFormSchemaRadio | CredentialFormSchemaSelect)).options.forEach((option) => {
if (option.show_on.length) {
option.show_on.forEach((showOnItem) => {
if (!showOnVariableMap[showOnItem.variable])
showOnVariableMap[showOnItem.variable] = []
if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable))
showOnVariableMap[showOnItem.variable].push(formSchema.variable)
})
}
})
}
})
return [
requiredFormSchemas,
defaultFormSchemaValue,
showOnVariableMap,
]
}, [formSchemas])
const initialFormSchemasValue: Record<string, string | number> = useMemo(() => {
return {
...defaultFormSchemaValue,
...formSchemasValue,
} as unknown as Record<string, string | number>
}, [formSchemasValue, defaultFormSchemaValue])
const [value, setValue] = useState(initialFormSchemasValue)
useEffect(() => {
setValue(initialFormSchemasValue)
}, [initialFormSchemasValue])
const [_, validating, validatedStatusState] = useValidate(value)
const filteredRequiredFormSchemas = requiredFormSchemas.filter((requiredFormSchema) => {
if (requiredFormSchema.show_on.length && requiredFormSchema.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return true
if (!requiredFormSchema.show_on.length)
return true
return false
})
const handleValueChange = (v: FormValue) => {
setValue(v)
}
const extendedSecretFormSchemas = useMemo(
() =>
(providerFormSchemaPredefined
? provider.provider_credential_schema.credential_form_schemas
: [
genModelTypeFormSchema(provider.supported_model_types),
genModelNameFormSchema(provider.model_credential_schema?.model),
...provider.model_credential_schema.credential_form_schemas,
]).filter(({ type }) => type === FormTypeEnum.secretInput),
[
provider.model_credential_schema?.credential_form_schemas,
provider.model_credential_schema?.model,
provider.provider_credential_schema?.credential_form_schemas,
provider.supported_model_types,
providerFormSchemaPredefined,
],
)
const encodeSecretValues = useCallback((v: FormValue) => {
const result = { ...v }
extendedSecretFormSchemas.forEach(({ variable }) => {
if (result[variable] === formSchemasValue?.[variable] && result[variable] !== undefined)
result[variable] = '[__HIDDEN__]'
})
return result
}, [extendedSecretFormSchemas, formSchemasValue])
const encodeConfigEntrySecretValues = useCallback((entry: ModelLoadBalancingConfigEntry) => {
const result = { ...entry }
extendedSecretFormSchemas.forEach(({ variable }) => {
if (entry.id && result.credentials[variable] === originalConfigMap[entry.id]?.credentials?.[variable])
result.credentials[variable] = '[__HIDDEN__]'
})
return result
}, [extendedSecretFormSchemas, originalConfigMap])
const handleSave = async () => {
try {
setLoading(true)
const res = await saveCredentials(
providerFormSchemaPredefined,
provider.provider,
encodeSecretValues(value),
{
...draftConfig,
enabled: Boolean(draftConfig?.enabled),
configs: draftConfig?.configs.map(encodeConfigEntrySecretValues) || [],
},
const modalTitle = useMemo(() => {
if (!providerFormSchemaPredefined && !model) {
return (
<div className='flex items-center'>
<ModelIcon
className='mr-2 h-10 w-10 shrink-0'
iconClassName='h-10 w-10'
provider={provider}
/>
<div>
<div className='system-xs-medium-uppercase text-text-tertiary'>{t('common.modelProvider.auth.apiKeyModal.addModel')}</div>
<div className='system-md-semibold text-text-primary'>{renderI18nObject(provider.label)}</div>
</div>
</div>
)
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
mutate()
onSave()
onCancel()
}
}
finally {
setLoading(false)
}
}
let label = t('common.modelProvider.auth.apiKeyModal.title')
const handleRemove = async () => {
try {
setLoading(true)
if (model)
label = t('common.modelProvider.auth.addModelCredential')
const res = await removeCredentials(
providerFormSchemaPredefined,
provider.provider,
value,
return (
<div className='title-2xl-semi-bold text-text-primary'>
{label}
</div>
)
}, [providerFormSchemaPredefined, t, model, renderI18nObject])
const modalDesc = useMemo(() => {
if (providerFormSchemaPredefined) {
return (
<div className='system-xs-regular mt-1 text-text-tertiary'>
{t('common.modelProvider.auth.apiKeyModal.desc')}
</div>
)
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
mutate()
onSave()
onCancel()
}
}
finally {
setLoading(false)
}
}
const renderTitlePrefix = () => {
const prefix = isEditMode ? t('common.operation.setup') : t('common.operation.add')
return `${prefix} ${provider.label[language] || provider.label.en_US}`
}
return null
}, [providerFormSchemaPredefined, t])
const modalModel = useMemo(() => {
if (model) {
return (
<div className='mt-2 flex items-center'>
<ModelIcon
className='mr-2 h-4 w-4 shrink-0'
provider={provider}
modelName={model.model}
/>
<div className='system-md-regular mr-1 text-text-secondary'>{model.model}</div>
<Badge>{model.model_type}</Badge>
</div>
)
}
return null
}, [model, provider])
return (
<PortalToFollowElem open>
<PortalToFollowElemContent className='z-[60] h-full w-full'>
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
<div className='mx-2 w-[640px] overflow-auto rounded-2xl bg-components-panel-bg shadow-xl'>
<div className='px-8 pt-8'>
<div className='mb-2 flex items-center'>
<div className='text-xl font-semibold text-text-primary'>{renderTitlePrefix()}</div>
<div className='relative w-[640px] rounded-2xl bg-components-panel-bg shadow-xl'>
<div
className='absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center'
onClick={onCancel}
>
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
</div>
<div className='px-6 pt-6'>
<div className='pb-3'>
{modalTitle}
{modalDesc}
{modalModel}
</div>
<div className='max-h-[calc(100vh-320px)] overflow-y-auto'>
<Form
value={value}
onChange={handleValueChange}
formSchemas={formSchemas}
validating={validating}
validatedSuccess={validatedStatusState.status === ValidatedStatus.Success}
showOnVariableMap={showOnVariableMap}
isEditMode={isEditMode}
/>
<div className='mb-4 mt-1 border-t-[0.5px] border-t-divider-regular' />
<ModelLoadBalancingConfigs withSwitch {...{
draftConfig,
setDraftConfig,
provider,
currentCustomConfigurationModelFixedFields,
configurationMethod: configurateMethod,
}} />
{
isLoading && (
<div className='flex items-center justify-center'>
<Loading />
</div>
)
}
{
!isLoading && (
<AuthForm
formSchemas={formSchemas.map((formSchema) => {
return {
...formSchema,
name: formSchema.variable,
showRadioUI: formSchema.type === FormTypeEnum.radio,
}
}) as FormSchema[]}
defaultValues={formValues}
inputClassName='justify-start'
ref={formRef}
/>
)
}
</div>
<div className='sticky bottom-0 -mx-2 mt-2 flex flex-wrap items-center justify-between gap-y-2 bg-components-panel-bg px-2 pb-6 pt-4'>
@@ -327,7 +255,7 @@ const ModelModal: FC<ModelModalProps> = ({
variant='warning'
size='large'
className='mr-2'
onClick={() => setShowConfirm(true)}
onClick={() => openConfirmDelete(credential, model)}
>
{t('common.operation.remove')}
</Button>
@@ -344,12 +272,7 @@ const ModelModal: FC<ModelModalProps> = ({
size='large'
variant='primary'
onClick={handleSave}
disabled={
loading
|| filteredRequiredFormSchemas.some(item => value[item.variable] === undefined)
|| (draftConfig?.enabled && (draftConfig?.configs.filter(config => config.enabled).length ?? 0) < 2)
}
disabled={isLoading || doingAction}
>
{t('common.operation.save')}
</Button>
@@ -357,38 +280,28 @@ const ModelModal: FC<ModelModalProps> = ({
</div>
</div>
<div className='border-t-[0.5px] border-t-divider-regular'>
{
(validatedStatusState.status === ValidatedStatus.Error && validatedStatusState.message)
? (
<div className='flex bg-background-section-burn px-[10px] py-3 text-xs text-[#D92D20]'>
<RiErrorWarningFill className='mr-2 mt-[1px] h-[14px] w-[14px]' />
{validatedStatusState.message}
</div>
)
: (
<div className='flex items-center justify-center bg-background-section-burn py-3 text-xs text-text-tertiary'>
<Lock01 className='mr-1 h-3 w-3 text-text-tertiary' />
{t('common.modelProvider.encrypted.front')}
<a
className='mx-1 text-text-accent'
target='_blank' rel='noopener noreferrer'
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
>
PKCS1_OAEP
</a>
{t('common.modelProvider.encrypted.back')}
</div>
)
}
<div className='flex items-center justify-center rounded-b-2xl bg-background-section-burn py-3 text-xs text-text-tertiary'>
<Lock01 className='mr-1 h-3 w-3 text-text-tertiary' />
{t('common.modelProvider.encrypted.front')}
<a
className='mx-1 text-text-accent'
target='_blank' rel='noopener noreferrer'
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
>
PKCS1_OAEP
</a>
{t('common.modelProvider.encrypted.back')}
</div>
</div>
</div>
{
showConfirm && (
deleteCredentialId && (
<Confirm
isShow
title={t('common.modelProvider.confirmDelete')}
isShow={showConfirm}
onCancel={() => setShowConfirm(false)}
onConfirm={handleRemove}
isDisabled={doingAction}
onCancel={closeConfirmDelete}
onConfirm={handleConfirmDelete}
/>
)
}

View File

@@ -1,348 +0,0 @@
import type { FC } from 'react'
import {
memo,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
RiErrorWarningFill,
} from '@remixicon/react'
import type {
CredentialFormSchema,
CredentialFormSchemaRadio,
CredentialFormSchemaSelect,
CredentialFormSchemaTextInput,
CustomConfigurationModelFixedFields,
FormValue,
ModelLoadBalancingConfigEntry,
ModelProvider,
} from '../declarations'
import {
ConfigurationMethodEnum,
FormTypeEnum,
} from '../declarations'
import {
useLanguage,
} from '../hooks'
import { useValidate } from '../../key-validator/hooks'
import { ValidatedStatus } from '../../key-validator/declarations'
import { validateLoadBalancingCredentials } from '../utils'
import Form from './Form'
import Button from '@/app/components/base/button'
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
import {
PortalToFollowElem,
PortalToFollowElemContent,
} from '@/app/components/base/portal-to-follow-elem'
import { useToastContext } from '@/app/components/base/toast'
import Confirm from '@/app/components/base/confirm'
type ModelModalProps = {
provider: ModelProvider
configurationMethod: ConfigurationMethodEnum
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
entry?: ModelLoadBalancingConfigEntry
onCancel: () => void
onSave: (entry: ModelLoadBalancingConfigEntry) => void
onRemove: () => void
}
const ModelLoadBalancingEntryModal: FC<ModelModalProps> = ({
provider,
configurationMethod,
currentCustomConfigurationModelFixedFields,
entry,
onCancel,
onSave,
onRemove,
}) => {
const providerFormSchemaPredefined = configurationMethod === ConfigurationMethodEnum.predefinedModel
// const { credentials: formSchemasValue } = useProviderCredentialsAndLoadBalancing(
// provider.provider,
// configurationMethod,
// providerFormSchemaPredefined && provider.custom_configuration.status === CustomConfigurationStatusEnum.active,
// currentCustomConfigurationModelFixedFields,
// )
const isEditMode = !!entry
const { t } = useTranslation()
const { notify } = useToastContext()
const language = useLanguage()
const [loading, setLoading] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
const formSchemas = useMemo(() => {
return [
{
type: FormTypeEnum.textInput,
label: {
en_US: 'Config Name',
zh_Hans: '配置名称',
},
variable: 'name',
required: true,
show_on: [],
placeholder: {
en_US: 'Enter your Config Name here',
zh_Hans: '输入配置名称',
},
} as CredentialFormSchemaTextInput,
...(
providerFormSchemaPredefined
? provider.provider_credential_schema.credential_form_schemas
: provider.model_credential_schema.credential_form_schemas
),
]
}, [
providerFormSchemaPredefined,
provider.provider_credential_schema?.credential_form_schemas,
provider.model_credential_schema?.credential_form_schemas,
])
const [
requiredFormSchemas,
secretFormSchemas,
defaultFormSchemaValue,
showOnVariableMap,
] = useMemo(() => {
const requiredFormSchemas: CredentialFormSchema[] = []
const secretFormSchemas: CredentialFormSchema[] = []
const defaultFormSchemaValue: Record<string, string | number> = {}
const showOnVariableMap: Record<string, string[]> = {}
formSchemas.forEach((formSchema) => {
if (formSchema.required)
requiredFormSchemas.push(formSchema)
if (formSchema.type === FormTypeEnum.secretInput)
secretFormSchemas.push(formSchema)
if (formSchema.default)
defaultFormSchemaValue[formSchema.variable] = formSchema.default
if (formSchema.show_on.length) {
formSchema.show_on.forEach((showOnItem) => {
if (!showOnVariableMap[showOnItem.variable])
showOnVariableMap[showOnItem.variable] = []
if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable))
showOnVariableMap[showOnItem.variable].push(formSchema.variable)
})
}
if (formSchema.type === FormTypeEnum.select || formSchema.type === FormTypeEnum.radio) {
(formSchema as (CredentialFormSchemaRadio | CredentialFormSchemaSelect)).options.forEach((option) => {
if (option.show_on.length) {
option.show_on.forEach((showOnItem) => {
if (!showOnVariableMap[showOnItem.variable])
showOnVariableMap[showOnItem.variable] = []
if (!showOnVariableMap[showOnItem.variable].includes(formSchema.variable))
showOnVariableMap[showOnItem.variable].push(formSchema.variable)
})
}
})
}
})
return [
requiredFormSchemas,
secretFormSchemas,
defaultFormSchemaValue,
showOnVariableMap,
]
}, [formSchemas])
const [initialValue, setInitialValue] = useState<ModelLoadBalancingConfigEntry['credentials']>()
useEffect(() => {
if (entry && !initialValue) {
setInitialValue({
...defaultFormSchemaValue,
...entry.credentials,
id: entry.id,
name: entry.name,
} as Record<string, string | undefined | boolean>)
}
}, [entry, defaultFormSchemaValue, initialValue])
const formSchemasValue = useMemo(() => ({
...currentCustomConfigurationModelFixedFields,
...initialValue,
}), [currentCustomConfigurationModelFixedFields, initialValue])
const initialFormSchemasValue: Record<string, string | number> = useMemo(() => {
return {
...defaultFormSchemaValue,
...formSchemasValue,
} as Record<string, string | number>
}, [formSchemasValue, defaultFormSchemaValue])
const [value, setValue] = useState(initialFormSchemasValue)
useEffect(() => {
setValue(initialFormSchemasValue)
}, [initialFormSchemasValue])
const [_, validating, validatedStatusState] = useValidate(value)
const filteredRequiredFormSchemas = requiredFormSchemas.filter((requiredFormSchema) => {
if (requiredFormSchema.show_on.length && requiredFormSchema.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return true
if (!requiredFormSchema.show_on.length)
return true
return false
})
const getSecretValues = useCallback((v: FormValue) => {
return secretFormSchemas.reduce((prev, next) => {
if (isEditMode && v[next.variable] && v[next.variable] === initialFormSchemasValue[next.variable])
prev[next.variable] = '[__HIDDEN__]'
return prev
}, {} as Record<string, string>)
}, [initialFormSchemasValue, isEditMode, secretFormSchemas])
// const handleValueChange = ({ __model_type, __model_name, ...v }: FormValue) => {
const handleValueChange = (v: FormValue) => {
setValue(v)
}
const handleSave = async () => {
try {
setLoading(true)
const res = await validateLoadBalancingCredentials(
providerFormSchemaPredefined,
provider.provider,
{
...value,
...getSecretValues(value),
},
entry?.id,
)
if (res.status === ValidatedStatus.Success) {
// notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
const { __model_type, __model_name, name, ...credentials } = value
onSave({
...(entry || {}),
name: name as string,
credentials: credentials as Record<string, string | boolean | undefined>,
})
// onCancel()
}
else {
notify({ type: 'error', message: res.message || '' })
}
}
finally {
setLoading(false)
}
}
const handleRemove = () => {
onRemove?.()
}
return (
<PortalToFollowElem open>
<PortalToFollowElemContent className='z-[60] h-full w-full'>
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
<div className='mx-2 max-h-[calc(100vh-120px)] w-[640px] overflow-y-auto rounded-2xl bg-white shadow-xl'>
<div className='px-8 pt-8'>
<div className='mb-2 flex items-center justify-between'>
<div className='text-xl font-semibold text-gray-900'>{t(isEditMode ? 'common.modelProvider.editConfig' : 'common.modelProvider.addConfig')}</div>
</div>
<Form
value={value}
onChange={handleValueChange}
formSchemas={formSchemas}
validating={validating}
validatedSuccess={validatedStatusState.status === ValidatedStatus.Success}
showOnVariableMap={showOnVariableMap}
isEditMode={isEditMode}
/>
<div className='sticky bottom-0 flex flex-wrap items-center justify-between gap-y-2 bg-white py-6'>
{
(provider.help && (provider.help.title || provider.help.url))
? (
<a
href={provider.help?.url[language] || provider.help?.url.en_US}
target='_blank' rel='noopener noreferrer'
className='inline-flex items-center text-xs text-primary-600'
onClick={e => !provider.help.url && e.preventDefault()}
>
{provider.help.title?.[language] || provider.help.url[language] || provider.help.title?.en_US || provider.help.url.en_US}
<LinkExternal02 className='ml-1 h-3 w-3' />
</a>
)
: <div />
}
<div>
{
isEditMode && (
<Button
size='large'
className='mr-2 text-[#D92D20]'
onClick={() => setShowConfirm(true)}
>
{t('common.operation.remove')}
</Button>
)
}
<Button
size='large'
className='mr-2'
onClick={onCancel}
>
{t('common.operation.cancel')}
</Button>
<Button
size='large'
variant='primary'
onClick={handleSave}
disabled={loading || filteredRequiredFormSchemas.some(item => value[item.variable] === undefined)}
>
{t('common.operation.save')}
</Button>
</div>
</div>
</div>
<div className='border-t-[0.5px] border-t-black/5'>
{
(validatedStatusState.status === ValidatedStatus.Error && validatedStatusState.message)
? (
<div className='flex bg-[#FEF3F2] px-[10px] py-3 text-xs text-[#D92D20]'>
<RiErrorWarningFill className='mr-2 mt-[1px] h-[14px] w-[14px]' />
{validatedStatusState.message}
</div>
)
: (
<div className='flex items-center justify-center bg-gray-50 py-3 text-xs text-gray-500'>
<Lock01 className='mr-1 h-3 w-3 text-gray-500' />
{t('common.modelProvider.encrypted.front')}
<a
className='mx-1 text-primary-600'
target='_blank' rel='noopener noreferrer'
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
>
PKCS1_OAEP
</a>
{t('common.modelProvider.encrypted.back')}
</div>
)
}
</div>
</div>
{
showConfirm && (
<Confirm
title={t('common.modelProvider.confirmDelete')}
isShow={showConfirm}
onCancel={() => setShowConfirm(false)}
onConfirm={handleRemove}
/>
)
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(ModelLoadBalancingEntryModal)