/* * @Author: ZhaoYing * @Date: 2026-02-03 16:49:28 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-04-16 18:03:53 */ /** * Custom Model Modal * Modal for creating and editing custom models in the model square * Supports logo upload, type/provider selection, and tagging */ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { Form, Input, App, Checkbox, Button, Row, Col } from 'antd'; import { useTranslation } from 'react-i18next'; import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps, Capability } from '../types'; import RbModal from '@/components/RbModal' import CustomSelect from '@/components/CustomSelect' import UploadImages from '@/components/Upload/UploadImages' import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models' import { getFileLink } from '@/api/fileStorage' import { validateSquareImage, stringRegExp } from '@/utils/validator' /** * Custom model modal component */ const CustomModelModal = forwardRef(({ refresh }, ref) => { const { t } = useTranslation(); const { message } = App.useApp(); const [visible, setVisible] = useState(false); const [model, setModel] = useState({} as ModelListItem); const [isEdit, setIsEdit] = useState(false); const [form] = Form.useForm(); const [loading, setLoading] = useState(false) const [abortController, setAbortController] = useState(null) const modelType = Form.useWatch(['type'], form); const isOmni = Form.useWatch(['is_omni'], form); useEffect(() => { if (isOmni) { form.setFieldsValue({ is_vision: true, is_video: true, is_audio: true }) } }, [isOmni]) /** Close modal and reset state */ const handleClose = () => { abortController?.abort() setAbortController(null) setModel({} as ModelListItem); form.resetFields(); setLoading(false) setVisible(false); }; /** Open modal with optional model data for editing */ const handleOpen = (model?: ModelListItem) => { if (model) { setIsEdit(true); setModel(model); const { capability, is_omni, ...rest} = model form.setFieldsValue({ ...rest, logo: model.logo && model.logo.startsWith('http') ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined, is_omni, is_vision: capability?.includes('vision') || false, is_video: capability?.includes('video') || false, is_audio: capability?.includes('audio') || false, is_thinking: capability?.includes('thinking') || false, json_output: capability?.includes('json_output') || false, }); } else { setIsEdit(false); form.resetFields(); } setVisible(true); }; /** Update or create custom model */ const handleUpdate = (data: CustomModelForm) => { setLoading(true) const controller = new AbortController() setAbortController(controller) const { type, provider, ...rest} = data const res = isEdit ? updateCustomModel(model.id, rest, controller.signal) : addCustomModel(data, controller.signal) res.then(() => { refresh?.(isEdit) handleClose() message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess')) }) .catch(() => { setLoading(false) }); } /** Validate and save custom model */ const handleSave = () => { form .validateFields() .then((values) => { const { logo, type, is_vision, is_video, is_audio, is_omni, is_thinking, json_output, ...rest } = values; const formData: CustomModelForm = { ...rest, type, } if (!['embedding', 'rerank'].includes(type as string)) { let capability: Capability[] = is_omni ? ["vision", "audio", 'video'] : [] if (!is_omni) { if (is_vision) { capability.push('vision') } if (is_audio) { capability.push('audio') } if (is_video) { capability.push('video') } } if (is_thinking) { capability.push('thinking') } if (json_output) { capability.push('json_output') } formData.capability = capability formData.is_omni = is_omni } if (typeof logo === 'object' && logo?.response?.data.file_id) { getFileLink(logo?.response?.data.file_id) .then(res => { const logoRes = res as { url: string } formData.logo = logoRes.url handleUpdate(formData) }) .catch(() => { handleUpdate(formData) }) } else { formData.logo = typeof logo === 'string' ? logo : logo.url handleUpdate(formData) } }) .catch((err) => { console.log('err', err) }); } /** Expose methods to parent component */ useImperativeHandle(ref, () => ({ handleOpen, handleClose })); return ( {t('common.cancel')}, , ]} >
{vo}
)} >
items.map((item) => ({ label: t(`modelNew.${item}`), value: String(item) }))} /> items.map((item) => ({ label: String(item).charAt(0).toUpperCase() + String(item).slice(1), value: String(item) }))} /> {['llm', 'chat'].includes(modelType as string) && {t('modelNew.is_omni')} {t('modelNew.is_vision')} {t('modelNew.is_video')} {t('modelNew.is_audio')} {t('modelNew.is_thinking')} {t('modelNew.json_output')} }
); }); export default CustomModelModal;