Spaces:
Build error
Build error
import type { FC } from 'react' | |
import { | |
memo, | |
useEffect, | |
useState, | |
} from 'react' | |
import { useTranslation } from 'react-i18next' | |
import { | |
RiBook2Line, | |
RiCloseLine, | |
RiInformation2Line, | |
RiLock2Fill, | |
} from '@remixicon/react' | |
import type { CreateExternalAPIReq, FormSchema } from '../declarations' | |
import Form from './Form' | |
import ActionButton from '@/app/components/base/action-button' | |
import Confirm from '@/app/components/base/confirm' | |
import { | |
PortalToFollowElem, | |
PortalToFollowElemContent, | |
} from '@/app/components/base/portal-to-follow-elem' | |
import { createExternalAPI } from '@/service/datasets' | |
import { useToastContext } from '@/app/components/base/toast' | |
import Button from '@/app/components/base/button' | |
import Tooltip from '@/app/components/base/tooltip' | |
type AddExternalAPIModalProps = { | |
data?: CreateExternalAPIReq | |
onSave: (formValue: CreateExternalAPIReq) => void | |
onCancel: () => void | |
onEdit?: (formValue: CreateExternalAPIReq) => Promise<void> | |
datasetBindings?: { id: string; name: string }[] | |
isEditMode: boolean | |
} | |
const formSchemas: FormSchema[] = [ | |
{ | |
variable: 'name', | |
type: 'text', | |
label: { | |
en_US: 'Name', | |
}, | |
required: true, | |
}, | |
{ | |
variable: 'endpoint', | |
type: 'text', | |
label: { | |
en_US: 'API Endpoint', | |
}, | |
required: true, | |
}, | |
{ | |
variable: 'api_key', | |
type: 'secret', | |
label: { | |
en_US: 'API Key', | |
}, | |
required: true, | |
}, | |
] | |
const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({ data, onSave, onCancel, datasetBindings, isEditMode, onEdit }) => { | |
const { t } = useTranslation() | |
const { notify } = useToastContext() | |
const [loading, setLoading] = useState(false) | |
const [showConfirm, setShowConfirm] = useState(false) | |
const [formData, setFormData] = useState<CreateExternalAPIReq>({ name: '', settings: { endpoint: '', api_key: '' } }) | |
useEffect(() => { | |
if (isEditMode && data) | |
setFormData(data) | |
}, [isEditMode, data]) | |
const hasEmptyInputs = Object.values(formData).some(value => | |
typeof value === 'string' ? value.trim() === '' : Object.values(value).some(v => v.trim() === ''), | |
) | |
const handleDataChange = (val: CreateExternalAPIReq) => { | |
setFormData(val) | |
} | |
const handleSave = async () => { | |
if (formData && formData.settings.api_key && formData.settings.api_key?.length < 5) { | |
notify({ type: 'error', message: t('common.apiBasedExtension.modal.apiKey.lengthError') }) | |
setLoading(false) | |
return | |
} | |
try { | |
setLoading(true) | |
if (isEditMode && onEdit) { | |
await onEdit( | |
{ | |
...formData, | |
settings: { ...formData.settings, api_key: formData.settings.api_key ? '[__HIDDEN__]' : formData.settings.api_key }, | |
}, | |
) | |
notify({ type: 'success', message: 'External API updated successfully' }) | |
} | |
else { | |
const res = await createExternalAPI({ body: formData }) | |
if (res && res.id) { | |
notify({ type: 'success', message: 'External API saved successfully' }) | |
onSave(res) | |
} | |
} | |
onCancel() | |
} | |
catch (error) { | |
console.error('Error saving/updating external API:', error) | |
notify({ type: 'error', message: 'Failed to save/update External API' }) | |
} | |
finally { | |
setLoading(false) | |
} | |
} | |
return ( | |
<PortalToFollowElem open> | |
<PortalToFollowElemContent className='w-full h-full z-[60]'> | |
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'> | |
<div className='flex relative w-[480px] flex-col items-start bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadows-shadow-xl'> | |
<div className='flex flex-col pt-6 pl-6 pb-3 pr-14 items-start gap-2 self-stretch'> | |
<div className='self-stretch text-text-primary title-2xl-semi-bold flex-grow'> | |
{ | |
isEditMode ? t('dataset.editExternalAPIFormTitle') : t('dataset.createExternalAPI') | |
} | |
</div> | |
{isEditMode && (datasetBindings?.length ?? 0) > 0 && ( | |
<div className='text-text-tertiary system-xs-regular flex items-center'> | |
{t('dataset.editExternalAPIFormWarning.front')} | |
<span className='text-text-accent cursor-pointer flex items-center'> | |
{datasetBindings?.length} {t('dataset.editExternalAPIFormWarning.end')} | |
<Tooltip | |
popupClassName='flex items-center self-stretch w-[320px]' | |
popupContent={ | |
<div className='p-1'> | |
<div className='flex pt-1 pb-0.5 pl-2 pr-3 items-start self-stretch'> | |
<div className='text-text-tertiary system-xs-medium-uppercase'>{`${datasetBindings?.length} ${t('dataset.editExternalAPITooltipTitle')}`}</div> | |
</div> | |
{datasetBindings?.map(binding => ( | |
<div key={binding.id} className='flex px-2 py-1 items-center gap-1 self-stretch'> | |
<RiBook2Line className='w-4 h-4 text-text-secondary' /> | |
<div className='text-text-secondary system-sm-medium'>{binding.name}</div> | |
</div> | |
))} | |
</div> | |
} | |
asChild={false} | |
position='bottom' | |
> | |
<RiInformation2Line className='w-3.5 h-3.5' /> | |
</Tooltip> | |
</span> | |
</div> | |
)} | |
</div> | |
<ActionButton className='absolute top-5 right-5' onClick={onCancel}> | |
<RiCloseLine className='w-[18px] h-[18px] text-text-tertiary flex-shrink-0' /> | |
</ActionButton> | |
<Form | |
value={formData} | |
onChange={handleDataChange} | |
formSchemas={formSchemas} | |
className='flex px-6 py-3 flex-col justify-center items-start gap-4 self-stretch' | |
/> | |
<div className='flex p-6 pt-5 justify-end items-center gap-2 self-stretch'> | |
<Button type='button' variant='secondary' onClick={onCancel}> | |
{t('dataset.externalAPIForm.cancel')} | |
</Button> | |
<Button | |
type='submit' | |
variant='primary' | |
onClick={() => { | |
if (isEditMode && (datasetBindings?.length ?? 0) > 0) | |
setShowConfirm(true) | |
else if (isEditMode && onEdit) | |
onEdit(formData) | |
else | |
handleSave() | |
}} | |
disabled={hasEmptyInputs || loading} | |
> | |
{t('dataset.externalAPIForm.save')} | |
</Button> | |
</div> | |
<div className='flex px-2 py-3 justify-center items-center gap-1 self-stretch rounded-b-2xl | |
border-t-[0.5px] border-divider-subtle bg-background-soft text-text-tertiary system-xs-regular' | |
> | |
<RiLock2Fill className='w-3 h-3 text-text-quaternary' /> | |
{t('dataset.externalAPIForm.encrypted.front')} | |
<a | |
className='text-text-accent' | |
target='_blank' rel='noopener noreferrer' | |
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html' | |
> | |
PKCS1_OAEP | |
</a> | |
{t('dataset.externalAPIForm.encrypted.end')} | |
</div> | |
</div> | |
{showConfirm && (datasetBindings?.length ?? 0) > 0 && ( | |
<Confirm | |
isShow={showConfirm} | |
type='warning' | |
title='Warning' | |
content={`${t('dataset.editExternalAPIConfirmWarningContent.front')} ${datasetBindings?.length} ${t('dataset.editExternalAPIConfirmWarningContent.end')}`} | |
onCancel={() => setShowConfirm(false)} | |
onConfirm={handleSave} | |
/> | |
)} | |
</div> | |
</PortalToFollowElemContent> | |
</PortalToFollowElem> | |
) | |
} | |
export default memo(AddExternalAPIModal) | |