mirror of
https://github.com/langgenius/dify.git
synced 2026-08-28 19:38:30 +08:00
265 lines
8.8 KiB
TypeScript
265 lines
8.8 KiB
TypeScript
import type { FC } from 'react'
|
|
import {
|
|
memo,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import type {
|
|
CustomConfigurationModelFixedFields,
|
|
ModelLoadBalancingConfigEntry,
|
|
ModelProvider,
|
|
} from '../declarations'
|
|
import {
|
|
ConfigurationMethodEnum,
|
|
FormTypeEnum,
|
|
} from '../declarations'
|
|
|
|
import {
|
|
useLanguage,
|
|
} from '../hooks'
|
|
import { ValidatedStatus } from '../../key-validator/declarations'
|
|
import { validateLoadBalancingCredentials } from '../utils'
|
|
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'
|
|
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
|
import type {
|
|
FormRefObject,
|
|
FormSchema,
|
|
} from '@/app/components/base/form/types'
|
|
|
|
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 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 any,
|
|
...(
|
|
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 formRef = useRef<FormRefObject>(null)
|
|
|
|
const [
|
|
defaultFormSchemaValue,
|
|
] = useMemo(() => {
|
|
const defaultFormSchemaValue: Record<string, string | number> = {}
|
|
|
|
formSchemas.forEach((formSchema) => {
|
|
if (formSchema.default)
|
|
defaultFormSchemaValue[formSchema.variable] = formSchema.default
|
|
})
|
|
|
|
return [
|
|
defaultFormSchemaValue,
|
|
]
|
|
}, [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 handleSave = async () => {
|
|
try {
|
|
setLoading(true)
|
|
const {
|
|
isCheckValidated,
|
|
values,
|
|
} = formRef.current?.getFormValues({
|
|
needCheckValidatedValues: true,
|
|
needTransformWhenSecretFieldIsPristine: true,
|
|
}) || { isCheckValidated: false, values: {} }
|
|
if (!isCheckValidated)
|
|
return
|
|
const res = await validateLoadBalancingCredentials(
|
|
providerFormSchemaPredefined,
|
|
provider.provider,
|
|
values,
|
|
entry?.id,
|
|
)
|
|
if (res.status === ValidatedStatus.Success) {
|
|
// notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
|
const { __model_type, __model_name, name, ...credentials } = values
|
|
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>
|
|
<AuthForm
|
|
formSchemas={formSchemas.map((formSchema) => {
|
|
return {
|
|
...formSchema,
|
|
name: formSchema.variable,
|
|
showRadioUI: formSchema.type === FormTypeEnum.radio,
|
|
}
|
|
}) as FormSchema[]}
|
|
defaultValues={formSchemasValue}
|
|
inputClassName='justify-start'
|
|
ref={formRef}
|
|
/>
|
|
<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}
|
|
>
|
|
{t('common.operation.save')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className='border-t-[0.5px] border-t-black/5'>
|
|
<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)
|