import IconFont from '@/components/icon-font'; import SealAutoComplete from '@/components/seal-form/auto-complete'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { useIntl } from '@umijs/max'; import { Form, Tooltip, Typography } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { queryGPUList } from '../apis'; import { HuggingFaceTaskMap, ModelscopeTaskMap, backendOptionsMap, modelSourceMap, modelTaskMap, ollamaModelOptions } from '../config'; import { HuggingFaceModels, ModelScopeModels } from '../config/audio-catalog'; import { FormData, GPUListItem } from '../config/types'; import AdvanceConfig from './advance-config'; interface DataFormProps { ref?: any; source: string; action: PageActionType; selectedModel: any; isGGUF: boolean; sizeOptions?: Global.BaseOption[]; quantizationOptions?: Global.BaseOption[]; sourceDisable?: boolean; byBuiltIn?: boolean; backendOptions?: Global.BaseOption[]; onOk: (values: FormData) => void; onBackendChange?: (value: string) => void; } const SEARCH_SOURCE = [ modelSourceMap.huggingface_value, modelSourceMap.modelscope_value ]; const sourceOptions = [ { label: 'Hugging Face', value: modelSourceMap.huggingface_value, key: 'huggingface' }, { label: 'Ollama Library', value: modelSourceMap.ollama_library_value, key: 'ollama_library' }, { label: 'ModelScope', value: modelSourceMap.modelscope_value, key: 'model_scope' }, { label: 'models.form.localPath', locale: true, value: modelSourceMap.local_path_value, key: 'local_path' } ]; const DataForm: React.FC = forwardRef((props, ref) => { const { action, isGGUF, sourceDisable = true, backendOptions, byBuiltIn, onOk } = props; const [form] = Form.useForm(); const intl = useIntl(); const [gpuOptions, setGpuOptions] = useState< Array >([]); const [modelTask, setModelTask] = useState>({ type: '', value: '', text2speech: false, speech2text: false }); const localPathCache = useRef(''); const getGPUList = async () => { const data = await queryGPUList(); const list = _.map(data.items, (item: GPUListItem) => { return { ...item, title: '', label: ` ${item.name}(${item.worker_name})[ ${intl.formatMessage({ id: 'resources.table.index' })}:${item.index}]`, value: item.id }; }); setGpuOptions(list); }; useEffect(() => { getGPUList(); }, []); const handleSumit = () => { form.submit(); }; useImperativeHandle( ref, () => { return { submit: handleSumit, setFieldsValue: (values: FormData) => { form.setFieldsValue(values); }, setFieldValue: (name: string, value: any) => { form.setFieldValue(name, value); }, getFieldValue: (name: string) => { return form.getFieldValue(name); }, resetFields() { form.resetFields(); } }; }, [] ); const identifyModelTask = () => { let data = null; if (props.source === modelSourceMap.huggingface_value) { data = HuggingFaceModels.find( (item) => `${item.org}/${item.name}`.indexOf(props.selectedModel.name) > -1 || props.selectedModel.name?.indexOf(`${item.org}/${item.name}`) > -1 ); } if (props.source === modelSourceMap.modelscope_value) { data = ModelScopeModels.find( (item) => `${item.org}/${item.name}`.indexOf(props.selectedModel.name) > -1 || props.selectedModel.name?.indexOf(`${item.org}/${item.name}`) > -1 ); } if (data) { return modelTaskMap.audio; } return ''; }; const handleOnSelectModel = () => { let name = _.split(props.selectedModel.name, '/').slice(-1)[0]; const reg = /(-gguf)$/i; name = _.toLower(name).replace(reg, ''); const modelTaskType = identifyModelTask(); const modelTask = HuggingFaceTaskMap.audio.includes(props.selectedModel.task) || ModelscopeTaskMap.audio.includes(props.selectedModel.task) ? modelTaskMap.audio : ''; setModelTask({ value: props.selectedModel.task, type: modelTaskType || modelTask, text2speech: HuggingFaceTaskMap[modelTaskMap.textToSpeech] === props.selectedModel.task || ModelscopeTaskMap[modelTaskMap.textToSpeech] === props.selectedModel.task, speech2text: HuggingFaceTaskMap[modelTaskMap.speechToText] === props.selectedModel.task || ModelscopeTaskMap[modelTaskMap.speechToText] === props.selectedModel.task }); if (SEARCH_SOURCE.includes(props.source)) { form.setFieldsValue({ repo_id: props.selectedModel.name, name: name }); } else { form.setFieldsValue({ ollama_library_model_name: props.selectedModel.name, name: name }); } }; const handleOnFocus = () => { localPathCache.current = form.getFieldValue('local_path'); }; const handleLocalPathBlur = (e: any) => { const value = e.target.value; if (value === localPathCache.current && value) { return; } const isEndwithGGUF = _.endsWith(value, '.gguf'); let backend = backendOptionsMap.llamaBox; if (!isEndwithGGUF) { backend = backendOptionsMap.vllm; } props.onBackendChange?.(backend); form.setFieldValue('backend', backend); }; const renderHuggingfaceFields = () => { return ( <> name="repo_id" key="repo_id" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.input' }, { name: intl.formatMessage({ id: 'models.form.repoid' }) } ) } ]} > {isGGUF && ( name="file_name" key="file_name" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.input' }, { name: intl.formatMessage({ id: 'models.form.filename' }) } ) } ]} > )} ); }; const renderOllamaModelFields = () => { return ( <> name="ollama_library_model_name" key="ollama_library_model_name" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.input' }, { name: intl.formatMessage({ id: 'models.table.name' }) } ) } ]} > } required > ); }; const renderLocalPathFields = () => { return ( <> name="local_path" key="local_path" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.input' }, { name: intl.formatMessage({ id: 'models.form.filePath' }) } ) } ]} > ); }; const handleSizeChange = (val: any) => { form.setFieldsValue({ quantization: '' }); }; const renderFieldsFromCatalog = useMemo(() => { if (!byBuiltIn) { return null; } return ( <> name="size" key="size" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.select' }, { name: 'size' } ) } ]} > name="quantization" key="quantization" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.select' }, { name: 'quantization' } ) } ]} > ); }, [props.sizeOptions, props.quantizationOptions, byBuiltIn]); const renderFieldsBySource = useMemo(() => { if (SEARCH_SOURCE.includes(props.source)) { return renderHuggingfaceFields(); } if (props.source === modelSourceMap.ollama_library_value) { return renderOllamaModelFields(); } if (props.source === modelSourceMap.local_path_value) { return renderLocalPathFields(); } return null; }, [props.source, isGGUF, intl]); const handleBackendChange = useCallback((val: string) => { if (val === backendOptionsMap.llamaBox) { form.setFieldsValue({ distributed_inference_across_workers: true, cpu_offloading: true }); } form.setFieldValue('backend_version', ''); }, []); const handleOk = (formdata: FormData) => { let data = _.cloneDeep(formdata); if (data.categories) { data.categories = [data.categories]; } else { data.categories = []; } onOk({ ..._.omit(data, ['scheduleType']) }); }; useEffect(() => { if (action === PageAction.EDIT) return; if (modelTask.type === modelTaskMap.audio) { form.setFieldValue('backend', backendOptionsMap.voxBox); } else { form.setFieldValue( 'backend', isGGUF ? backendOptionsMap.llamaBox : backendOptionsMap.vllm ); } }, [isGGUF, modelTask]); useEffect(() => { handleOnSelectModel(); }, [props.selectedModel.name]); return (
name="name" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.input' }, { name: intl.formatMessage({ id: 'common.table.name' }) } ) } ]} > { name="source" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.select' }, { name: intl.formatMessage({ id: 'models.form.source' }) } ) } ]} > { } } {renderFieldsBySource} name="replicas" rules={[ { required: true, message: intl.formatMessage( { id: 'common.form.rule.input' }, { name: intl.formatMessage({ id: 'models.form.replicas' }) } ) } ]} >
1. {intl.formatMessage({ id: 'models.form.backend.llamabox' })}
2. {intl.formatMessage({ id: 'models.form.backend.vllm' })}
3. {intl.formatMessage({ id: 'models.form.backend.voxbox' })}
} options={ backendOptions ?? [ { label: `llama-box`, value: backendOptionsMap.llamaBox, disabled: props.source === modelSourceMap.local_path_value ? false : !isGGUF }, { label: 'vLLM', value: backendOptionsMap.vllm, disabled: props.source === modelSourceMap.local_path_value ? false : isGGUF }, { label: 'vox-box', value: backendOptionsMap.voxBox, disabled: props.source === modelSourceMap.ollama_library_value } ] } disabled={ action === PageAction.EDIT && props.source !== modelSourceMap.local_path_value } >
{renderFieldsFromCatalog} name="description"> ); }); export default React.memo(DataForm);