This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,460 @@
import { useCallback, useState } from 'react'
import type { ReactNode } from 'react'
import { ValidatingTip } from '../../key-validator/ValidateStatus'
import type {
CredentialFormSchema,
CredentialFormSchemaNumberInput,
CredentialFormSchemaRadio,
CredentialFormSchemaSecretInput,
CredentialFormSchemaSelect,
CredentialFormSchemaTextInput,
FormValue,
} from '../declarations'
import { FormTypeEnum } from '../declarations'
import { useLanguage } from '../hooks'
import Input from './Input'
import cn from '@/utils/classnames'
import { SimpleSelect } from '@/app/components/base/select'
import Tooltip from '@/app/components/base/tooltip'
import Radio from '@/app/components/base/radio'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import RadioE from '@/app/components/base/radio/ui'
import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import type { Node } from 'reactflow'
type FormProps<
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
> = {
className?: string
itemClassName?: string
fieldLabelClassName?: string
value: FormValue
onChange: (val: FormValue) => void
formSchemas: Array<CredentialFormSchema | CustomFormSchema>
validating: boolean
validatedSuccess?: boolean
showOnVariableMap: Record<string, string[]>
isEditMode: boolean
isAgentStrategy?: boolean
readonly?: boolean
inputClassName?: string
isShowDefaultValue?: boolean
fieldMoreInfo?: (payload: CredentialFormSchema | CustomFormSchema) => ReactNode
customRenderField?: (
formSchema: CustomFormSchema,
props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'>,
) => ReactNode,
// If return falsy value, this field will fallback to default render
override?: [Array<FormTypeEnum>, (formSchema: CredentialFormSchema, props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'>) => ReactNode]
nodeId?: string
nodeOutputVars?: NodeOutPutVar[],
availableNodes?: Node[],
canChooseMCPTool?: boolean
}
function Form<
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
>({
className,
itemClassName,
fieldLabelClassName,
value,
onChange,
formSchemas,
validating,
validatedSuccess,
showOnVariableMap,
isEditMode,
isAgentStrategy = false,
readonly,
inputClassName,
isShowDefaultValue = false,
fieldMoreInfo,
customRenderField,
override,
nodeId,
nodeOutputVars,
availableNodes,
canChooseMCPTool,
}: FormProps<CustomFormSchema>) {
const language = useLanguage()
const [changeKey, setChangeKey] = useState('')
const filteredProps: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'> = {
className,
itemClassName,
fieldLabelClassName,
value,
onChange,
formSchemas,
validating,
validatedSuccess,
showOnVariableMap,
isEditMode,
readonly,
inputClassName,
isShowDefaultValue,
fieldMoreInfo,
}
const handleFormChange = (key: string, val: string | boolean) => {
if (isEditMode && (key === '__model_type' || key === '__model_name'))
return
setChangeKey(key)
const shouldClearVariable: Record<string, string | undefined> = {}
if (showOnVariableMap[key]?.length) {
showOnVariableMap[key].forEach((clearVariable) => {
const schema = formSchemas.find(it => it.variable === clearVariable)
shouldClearVariable[clearVariable] = schema ? schema.default : undefined
})
}
onChange({ ...value, [key]: val, ...shouldClearVariable })
}
const handleModelChanged = useCallback((key: string, model: any) => {
const newValue = {
...value[key],
...model,
type: FormTypeEnum.modelSelector,
}
onChange({ ...value, [key]: newValue })
}, [onChange, value])
const renderField = (formSchema: CredentialFormSchema | CustomFormSchema) => {
const tooltip = formSchema.tooltip
const tooltipContent = (tooltip && (
<Tooltip
popupContent={<div className='w-[200px]'>
{tooltip[language] || tooltip.en_US}
</div>}
triggerClassName='ml-1 w-4 h-4'
asChild={false} />
))
if (override) {
const [overrideTypes, overrideRender] = override
if (overrideTypes.includes(formSchema.type as FormTypeEnum)) {
const node = overrideRender(formSchema as CredentialFormSchema, filteredProps)
if (node)
return node
}
}
if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput || formSchema.type === FormTypeEnum.textNumber) {
const {
variable, label, placeholder, required, show_on,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null
const disabled = readonly || (isEditMode && (variable === '__model_type' || variable === '__model_name'))
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<Input
className={cn(inputClassName, `${disabled && 'cursor-not-allowed opacity-60'}`)}
value={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
onChange={val => handleFormChange(variable, val)}
validated={validatedSuccess}
placeholder={placeholder?.[language] || placeholder?.en_US}
disabled={disabled}
type={formSchema.type === FormTypeEnum.secretInput ? 'password'
: formSchema.type === FormTypeEnum.textNumber ? 'number'
: 'text'}
{...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})} />
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.radio) {
const {
options, variable, label, show_on, required,
} = formSchema as CredentialFormSchemaRadio
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null
const disabled = isEditMode && (variable === '__model_type' || variable === '__model_name')
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<div className={cn('grid gap-3', `grid-cols-${options?.length}`)}>
{options.filter((option) => {
if (option.show_on.length)
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true
}).map(option => (
<div
className={`
flex cursor-pointer items-center gap-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg px-3 py-2
${value[variable] === option.value && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-sm'}
${disabled && '!cursor-not-allowed opacity-60'}
`}
onClick={() => handleFormChange(variable, option.value)}
key={`${variable}-${option.value}`}
>
<RadioE isChecked={value[variable] === option.value} />
<div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div>
</div>
))}
</div>
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.select) {
const {
options, variable, label, show_on, required, placeholder,
} = formSchema as CredentialFormSchemaSelect
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<SimpleSelect
wrapperClassName='h-8'
className={cn(inputClassName)}
disabled={readonly}
defaultValue={(isShowDefaultValue && ((value[variable] as string) === '' || value[variable] === undefined || value[variable] === null)) ? formSchema.default : value[variable]}
items={options.filter((option) => {
if (option.show_on.length)
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true
}).map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))}
onSelect={item => handleFormChange(variable, item.value as string)}
placeholder={placeholder?.[language] || placeholder?.en_US} />
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.checkbox) {
const {
variable, label, show_on, required,
} = formSchema as CredentialFormSchemaRadio
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
return null
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className='system-sm-semibold flex items-center justify-between py-2 text-text-secondary'>
<div className='flex items-center space-x-2'>
<span className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>{label[language] || label.en_US}</span>
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<Radio.Group
className='flex items-center'
value={value[variable]}
onChange={val => handleFormChange(variable, val)}
>
<Radio value={true} className='!mr-1'>True</Radio>
<Radio value={false}>False</Radio>
</Radio.Group>
</div>
{fieldMoreInfo?.(formSchema)}
</div>
)
}
if (formSchema.type === FormTypeEnum.modelSelector) {
const {
variable, label, required, scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<ModelParameterModal
popupClassName='!w-[387px]'
isAdvancedMode
isInWorkflow
isAgentStrategy={isAgentStrategy}
value={value[variable]}
setModel={model => handleModelChanged(variable, model)}
readonly={readonly}
scope={scope} />
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.toolSelector) {
const {
variable,
label,
required,
scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<ToolSelector
scope={scope}
nodeId={nodeId}
nodeOutputVars={nodeOutputVars || []}
availableNodes={availableNodes || []}
disabled={readonly}
value={value[variable]}
// selectedTools={value[variable] ? [value[variable]] : []}
onSelect={item => handleFormChange(variable, item as any)}
onDelete={() => handleFormChange(variable, null as any)}
/>
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.multiToolSelector) {
const {
variable,
label,
tooltip,
required,
scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<MultipleToolSelector
disabled={readonly}
nodeId={nodeId}
nodeOutputVars={nodeOutputVars || []}
availableNodes={availableNodes || []}
scope={scope}
label={label[language] || label.en_US}
required={required}
tooltip={tooltip?.[language] || tooltip?.en_US}
value={value[variable] || []}
onChange={item => handleFormChange(variable, item as any)}
supportCollapse
canChooseMCPTool={canChooseMCPTool}
/>
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.appSelector) {
const {
variable, label, required, scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<AppSelector
disabled={readonly}
scope={scope}
value={value[variable]}
onSelect={item => handleFormChange(variable, { ...item, type: FormTypeEnum.appSelector } as any)} />
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.any) {
const {
variable, label, required, scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'system-sm-semibold flex items-center py-2 text-text-secondary')}>
{label[language] || label.en_US}
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<VarReferencePicker
zIndex={1001}
readonly={false}
isShowNodeName
nodeId={nodeId || ''}
value={value[variable] || []}
onChange={item => handleFormChange(variable, item as any)}
filterVar={(varPayload) => {
if (!scope) return true
return scope.split('&').includes(varPayload.type)
}}
/>
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
// @ts-expect-error it work
if (!Object.values(FormTypeEnum).includes(formSchema.type))
return customRenderField?.(formSchema as CustomFormSchema, filteredProps)
}
return (
<div className={className}>
{formSchemas.map(formSchema => renderField(formSchema))}
</div>
)
}
export default Form

View File

@@ -0,0 +1,16 @@
import { render } from '@testing-library/react'
import Input from './Input'
test('Input renders correctly as password type with no autocomplete', () => {
const { asFragment, getByPlaceholderText } = render(
<Input
type="password"
placeholder="API Key"
onChange={jest.fn()}
/>,
)
const input = getByPlaceholderText('API Key')
expect(input).toHaveAttribute('type', 'password')
expect(input).not.toHaveAttribute('autocomplete')
expect(asFragment()).toMatchSnapshot()
})

View File

@@ -0,0 +1,73 @@
import type { FC } from 'react'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
type InputProps = {
value?: string
onChange: (v: string) => void
onFocus?: () => void
placeholder?: string
validated?: boolean
className?: string
disabled?: boolean
type?: string
min?: number
max?: number
}
const Input: FC<InputProps> = ({
value,
onChange,
onFocus,
placeholder,
validated,
className,
disabled,
type = 'text',
min,
max,
}) => {
const toLimit = (v: string) => {
const minNum = Number.parseFloat(`${min}`)
const maxNum = Number.parseFloat(`${max}`)
if (!isNaN(minNum) && Number.parseFloat(v) < minNum) {
onChange(`${min}`)
return
}
if (!isNaN(maxNum) && Number.parseFloat(v) > maxNum)
onChange(`${max}`)
}
return (
<div className='relative'>
<input
tabIndex={0}
// Do not set autoComplete for security - prevents browser from storing sensitive API keys
className={`
block h-8 w-full appearance-none rounded-lg border border-transparent bg-components-input-bg-normal px-3 text-sm
text-components-input-text-filled caret-primary-600 outline-none
placeholder:text-sm placeholder:text-text-tertiary
hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active
focus:bg-components-input-bg-active focus:shadow-xs
${validated ? 'pr-[30px]' : ''}
${className || ''}
`}
placeholder={placeholder || ''}
onChange={e => onChange(e.target.value)}
onBlur={e => toLimit(e.target.value)}
onFocus={onFocus}
value={value}
disabled={disabled}
type={type}
min={min}
max={max}
/>
{validated && (
<div className='absolute right-2.5 top-2.5'>
<CheckCircle className='h-4 w-4 text-[#039855]' />
</div>
)}
</div>
)
}
export default Input

View File

@@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Input renders correctly as password type with no autocomplete 1`] = `
<DocumentFragment>
<div
class="relative"
>
<input
class="
block h-8 w-full appearance-none rounded-lg border border-transparent bg-components-input-bg-normal px-3 text-sm
text-components-input-text-filled caret-primary-600 outline-none
placeholder:text-sm placeholder:text-text-tertiary
hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active
focus:bg-components-input-bg-active focus:shadow-xs
"
placeholder="API Key"
tabindex="0"
type="password"
/>
</div>
</DocumentFragment>
`;

View File

@@ -0,0 +1,449 @@
import type { FC } from 'react'
import {
memo,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { RiCloseLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import type {
CustomConfigurationModelFixedFields,
ModelProvider,
} from '../declarations'
import {
ConfigurationMethodEnum,
FormTypeEnum,
ModelModalModeEnum,
} from '../declarations'
import {
useLanguage,
} from '../hooks'
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 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'
import { CredentialSelector } from '../model-auth'
type ModelModalProps = {
provider: ModelProvider
configurateMethod: ConfigurationMethodEnum
currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields
onCancel: () => void
onSave: (formValues?: Record<string, any>) => void
onRemove: (formValues?: Record<string, any>) => void
model?: CustomModel
credential?: Credential
isModelCredential?: boolean
mode?: ModelModalModeEnum
}
const ModelModal: FC<ModelModalProps> = ({
provider,
configurateMethod,
currentCustomConfigurationModelFixedFields,
onCancel,
onSave,
model,
credential,
isModelCredential,
mode = ModelModalModeEnum.configProviderCredential,
}) => {
const renderI18nObject = useRenderI18nObject()
const providerFormSchemaPredefined = configurateMethod === ConfigurationMethodEnum.predefinedModel
const {
isLoading,
credentialData,
} = useCredentialData(provider, providerFormSchemaPredefined, isModelCredential, credential, model)
const {
handleSaveCredential,
handleConfirmDelete,
deleteCredentialId,
closeConfirmDelete,
openConfirmDelete,
doingAction,
handleActiveCredential,
} = useAuth(
provider,
configurateMethod,
currentCustomConfigurationModelFixedFields,
{
isModelCredential,
mode,
},
)
const {
credentials: formSchemasValue,
available_credentials,
} = credentialData as any
const { isCurrentWorkspaceManager } = useAppContext()
const { t } = useTranslation()
const language = useLanguage()
const {
formSchemas,
formValues,
modelNameAndTypeFormSchemas,
modelNameAndTypeFormValues,
} = useModelFormSchemas(provider, providerFormSchemaPredefined, formSchemasValue, credential, model)
const formRef1 = useRef<FormRefObject>(null)
const [selectedCredential, setSelectedCredential] = useState<Credential & { addNewCredential?: boolean } | undefined>()
const formRef2 = useRef<FormRefObject>(null)
const isEditMode = !!credential && !!Object.keys(formSchemasValue || {}).filter((key) => {
return key !== '__model_name' && key !== '__model_type' && !!formValues[key]
}).length && isCurrentWorkspaceManager
const handleSave = useCallback(async () => {
if (mode === ModelModalModeEnum.addCustomModelToModelList && selectedCredential && !selectedCredential?.addNewCredential) {
handleActiveCredential(selectedCredential, model)
onCancel()
return
}
let modelNameAndTypeIsCheckValidated = true
let modelNameAndTypeValues: Record<string, any> = {}
if (mode === ModelModalModeEnum.configCustomModel) {
const formResult = formRef1.current?.getFormValues({
needCheckValidatedValues: true,
}) || { isCheckValidated: false, values: {} }
modelNameAndTypeIsCheckValidated = formResult.isCheckValidated
modelNameAndTypeValues = formResult.values
}
if (mode === ModelModalModeEnum.configModelCredential && model) {
modelNameAndTypeValues = {
__model_name: model.model,
__model_type: model.model_type,
}
}
if (mode === ModelModalModeEnum.addCustomModelToModelList && selectedCredential?.addNewCredential && model) {
modelNameAndTypeValues = {
__model_name: model.model,
__model_type: model.model_type,
}
}
const {
isCheckValidated,
values,
} = formRef2.current?.getFormValues({
needCheckValidatedValues: true,
needTransformWhenSecretFieldIsPristine: true,
}) || { isCheckValidated: false, values: {} }
if (!isCheckValidated || !modelNameAndTypeIsCheckValidated)
return
const {
__model_name,
__model_type,
} = modelNameAndTypeValues
const {
__authorization_name__,
...rest
} = values
if (__model_name && __model_type) {
await handleSaveCredential({
credential_id: credential?.credential_id,
credentials: rest,
name: __authorization_name__,
model: __model_name,
model_type: __model_type,
})
}
else {
await handleSaveCredential({
credential_id: credential?.credential_id,
credentials: rest,
name: __authorization_name__,
})
}
onSave(values)
}, [handleSaveCredential, credential?.credential_id, model, onSave, mode, selectedCredential, handleActiveCredential])
const modalTitle = useMemo(() => {
let label = t('common.modelProvider.auth.apiKeyModal.title')
if (mode === ModelModalModeEnum.configCustomModel || mode === ModelModalModeEnum.addCustomModelToModelList)
label = t('common.modelProvider.auth.addModel')
if (mode === ModelModalModeEnum.configModelCredential) {
if (credential)
label = t('common.modelProvider.auth.editModelCredential')
else
label = t('common.modelProvider.auth.addModelCredential')
}
return (
<div className='title-2xl-semi-bold text-text-primary'>
{label}
</div>
)
}, [t, mode, credential])
const modalDesc = useMemo(() => {
if (providerFormSchemaPredefined) {
return (
<div className='system-xs-regular mt-1 text-text-tertiary'>
{t('common.modelProvider.auth.apiKeyModal.desc')}
</div>
)
}
return null
}, [providerFormSchemaPredefined, t])
const modalModel = useMemo(() => {
if (mode === ModelModalModeEnum.configCustomModel) {
return (
<div className='mt-2 flex items-center'>
<ModelIcon
className='mr-2 h-4 w-4 shrink-0'
provider={provider}
/>
<div className='system-md-regular mr-1 text-text-secondary'>{renderI18nObject(provider.label)}</div>
</div>
)
}
if (model && (mode === ModelModalModeEnum.configModelCredential || mode === ModelModalModeEnum.addCustomModelToModelList)) {
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, mode, renderI18nObject])
const showCredentialLabel = useMemo(() => {
if (mode === ModelModalModeEnum.configCustomModel)
return true
if (mode === ModelModalModeEnum.addCustomModelToModelList)
return selectedCredential?.addNewCredential
}, [mode, selectedCredential])
const showCredentialForm = useMemo(() => {
if (mode !== ModelModalModeEnum.addCustomModelToModelList)
return true
return selectedCredential?.addNewCredential
}, [mode, selectedCredential])
const saveButtonText = useMemo(() => {
if (mode === ModelModalModeEnum.addCustomModelToModelList || mode === ModelModalModeEnum.configCustomModel)
return t('common.operation.add')
return t('common.operation.save')
}, [mode, t])
const handleDeleteCredential = useCallback(() => {
handleConfirmDelete()
onCancel()
}, [handleConfirmDelete])
const handleModelNameAndTypeChange = useCallback((field: string, value: any) => {
const {
getForm,
} = formRef2.current as FormRefObject || {}
if (getForm())
getForm()?.setFieldValue(field, value)
}, [])
const notAllowCustomCredential = provider.allow_custom_token === false
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.stopPropagation()
onCancel()
}
}
document.addEventListener('keydown', handleKeyDown, true)
return () => {
document.removeEventListener('keydown', handleKeyDown, true)
}
}, [onCancel])
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='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='p-6 pb-3'>
{modalTitle}
{modalDesc}
{modalModel}
</div>
<div className='max-h-[calc(100vh-320px)] overflow-y-auto px-6 py-3'>
{
mode === ModelModalModeEnum.configCustomModel && (
<AuthForm
formSchemas={modelNameAndTypeFormSchemas.map((formSchema) => {
return {
...formSchema,
name: formSchema.variable,
}
}) as FormSchema[]}
defaultValues={modelNameAndTypeFormValues}
inputClassName='justify-start'
ref={formRef1}
onChange={handleModelNameAndTypeChange}
/>
)
}
{
mode === ModelModalModeEnum.addCustomModelToModelList && (
<CredentialSelector
credentials={available_credentials || []}
onSelect={setSelectedCredential}
selectedCredential={selectedCredential}
disabled={isLoading}
notAllowAddNewCredential={notAllowCustomCredential}
/>
)
}
{
showCredentialLabel && (
<div className='system-xs-medium-uppercase mb-3 mt-6 flex items-center text-text-tertiary'>
{t('common.modelProvider.auth.modelCredential')}
<div className='ml-2 h-px grow bg-gradient-to-r from-divider-regular to-background-gradient-mask-transparent' />
</div>
)
}
{
isLoading && (
<div className='mt-3 flex items-center justify-center'>
<Loading />
</div>
)
}
{
!isLoading
&& showCredentialForm
&& (
<AuthForm
formSchemas={formSchemas.map((formSchema) => {
return {
...formSchema,
name: formSchema.variable,
showRadioUI: formSchema.type === FormTypeEnum.radio,
}
}) as FormSchema[]}
defaultValues={formValues}
inputClassName='justify-start'
ref={formRef2}
/>
)
}
</div>
<div className='flex justify-between p-6 pt-5'>
{
(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='system-xs-regular mt-2 inline-block align-middle text-text-accent'
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 mt-[-2px] inline-block h-3 w-3' />
</a>
)
: <div />
}
<div className='ml-2 flex items-center justify-end space-x-2'>
{
isEditMode && (
<Button
variant='warning'
onClick={() => openConfirmDelete(credential, model)}
>
{t('common.operation.remove')}
</Button>
)
}
<Button
onClick={onCancel}
>
{t('common.operation.cancel')}
</Button>
<Button
variant='primary'
onClick={handleSave}
disabled={isLoading || doingAction}
>
{saveButtonText}
</Button>
</div>
</div>
{
(mode === ModelModalModeEnum.configCustomModel || mode === ModelModalModeEnum.configProviderCredential) && (
<div className='border-t-[0.5px] border-t-divider-regular'>
<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>
{
deleteCredentialId && (
<Confirm
isShow
title={t('common.modelProvider.confirmDelete')}
isDisabled={doingAction}
onCancel={closeConfirmDelete}
onConfirm={handleDeleteCredential}
/>
)
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default memo(ModelModal)