import ModalFooter from '@/components/modal-footer'; import GSDrawer from '@/components/scroller-modal/gs-drawer'; import { PageActionType } from '@/config/types'; import { createAxiosToken } from '@/hooks/use-chunk-request'; import { ProviderValueMap } from '@/pages/cluster-management/config'; import { useIntl } from '@umijs/max'; import { Button } from 'antd'; import _ from 'lodash'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { queryCatalogItemSpec } from '../apis'; import { backendOptionsMap, defaultFormValues, modelCategoriesMap, sourceOptions } from '../config'; import { FormContext } from '../config/form-context'; import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types'; import { useCheckCompatibility } from '../hooks'; import useFormInitialValues from '../hooks/use-form-initial-values'; import ColumnWrapper from './column-wrapper'; import CompatibilityAlert from './compatible-alert'; import DataForm from './data-form'; const pickFieldsFromSpec = [ 'backend_version', 'backend_parameters', 'env', 'size', 'quantization' ]; type AddModalProps = { title: string; action: PageActionType; open: boolean; data?: ListItem; source: SourceType; width?: string | number; current?: any; onOk: (values: FormData) => void; onCancel: () => void; }; const FormWrapper = styled.div` display: flex; flex: 1; height: 100%; maxwidth: 100%; `; const backendOptions = [ { label: `llama-box`, value: backendOptionsMap.llamaBox }, { label: 'vLLM', value: backendOptionsMap.vllm }, { label: 'Ascend Mindie', value: backendOptionsMap.ascendMindie }, { label: 'vox-box', value: backendOptionsMap.voxBox } ]; const quantiCapitMap: Record = { F16: 'FP16', f16: 'FP16', F32: 'FP32', f32: 'FP32' }; const defaultQuant = ['Q4_K_M']; const EmbeddingRerankFirstQuant = ['FP16', 'F16']; const AscendNPUQuant_F16 = ['F16', 'FP16']; const AscendNPUQuant_Q8 = ['Q8_0']; const AddModal: React.FC = (props) => { const { title, open, onOk, onCancel, source, action, current, width = 600 } = props || {}; const { setWarningStatus, handleDoEvalute, generateGPUIds, cancelEvaluate, submitAnyway, handleOnValuesChange, warningStatus } = useCheckCompatibility(); const { getClusterList, clusterList } = useFormInitialValues(); const intl = useIntl(); const form = useRef({}); const [isGGUF, setIsGGUF] = useState(false); const [sourceList, setSourceList] = useState([]); const [backendList, setBackendList] = useState([]); const [sizeOptions, setSizeOptions] = useState([]); const [quantizationOptions, setQuantizationOptions] = useState([]); const sourceGroupMap = useRef({}); const axiosToken = useRef(null); const selectSpecRef = useRef({} as CatalogSpec); const specListRef = useRef([]); const hasF16Ref = useRef(false); const handleSumit = () => { form.current?.submit?.(); }; const handleSubmitAnyway = async () => { submitAnyway.current = true; form.current?.submit?.(); }; // use for size change and quantization change const pickSomeFieldsValue = (defaultSpec: CatalogSpec) => { const formData = form.current?.getFieldsValue(); const currentData = _.pick(formData, Object.keys(defaultFormValues)); // if the backend_parameters is empty, use the defaultSpec.backend_parameters return currentData; }; const generateSubmitData = (formData: FormData) => { const gpuSelector = generateGPUIds(formData); const data = { ..._.omit(selectSpecRef.current, ['name']), ...formData, ...gpuSelector }; return data; }; const getDefaultQuant = (data: { category: string; quantOption: string; backend: string; condidateQuant?: string[]; }) => { if ( data.category === modelCategoriesMap.embedding || data.category === modelCategoriesMap.reranker ) { return EmbeddingRerankFirstQuant.includes(_.toUpper(data.quantOption)); } return defaultQuant.includes(_.toUpper(data.quantOption)); }; const getModelSpec = (data: { backend: string; size: number; quantization: string; }) => { const spec = _.find(specListRef.current, (item: CatalogSpec) => { if (data.size && data.quantization) { return ( item.size === data.size && item.backend === data.backend && item.quantization === data.quantization ); } if (data.size) { return item.size === data.size && item.backend === data.backend; } if (data.quantization) { return ( item.quantization === data.quantization && item.backend === data.backend ); } return item.backend === data.backend; }); selectSpecRef.current = spec; return { ..._.pick(spec, pickFieldsFromSpec), categories: _.get(current, 'categories.0', null) }; }; const initFormDataBySource = (data: CatalogSpec) => { selectSpecRef.current = data; form.current?.setFieldsValue({ ..._.omit(data, ['name']), categories: _.get(current, 'categories.0', null) }); }; const handleSetSizeOptions = (data: { backend: string }) => { const sizeGroup = _.groupBy( _.filter(specListRef.current, (item: CatalogSpec) => { return item.backend === data.backend; }), 'size' ); const sizeList = _.keys(sizeGroup) .map((size: string) => { return { label: `${size}B`, value: _.toNumber(size) }; }) .filter((item: any) => item.value); const result = _.sortBy(sizeList, 'value'); setSizeOptions(result); return result; }; const handleSetQuantizationOptions = (data: { size: number; backend: string; }) => { const sizeGroup = _.filter(specListRef.current, (item: CatalogSpec) => { return item.size === data.size && item.backend === data.backend; }); const quantizationList = _.map(sizeGroup, (item: CatalogSpec) => { return { label: quantiCapitMap[item.quantization] ?? _.toUpper(item.quantization), value: item.quantization }; }); const result = _.uniqBy(quantizationList, 'value'); setQuantizationOptions(result); return result; }; const handleSetBackendOptions = () => { const backendGroup = _.groupBy(specListRef.current, 'backend'); const backendList = _.filter(backendOptions, (item: any) => { return backendGroup[item.value]; }); setBackendList(backendList); return backendList; }; const handleCheckCompatibility = async (formData: FormData) => { handleDoEvalute(formData); }; const handleCheckFormData = () => { const values = form.current?.getFieldsValue(); const allValues = generateSubmitData(values); handleCheckCompatibility(allValues); }; const handleSourceChange = (source: string) => { const defaultSpec = _.get(sourceGroupMap.current, `${source}.0`, {}); initFormDataBySource(defaultSpec); handleSetSizeOptions({ backend: defaultSpec.backend }); handleSetQuantizationOptions({ size: defaultSpec.size, backend: defaultSpec.backend }); // set form value initFormDataBySource(defaultSpec); handleCheckFormData(); }; const checkSize = (list: any[]) => { return ( _.find( list, (item: { label: string; value: string }) => item.value === form.current.getFieldValue('size') )?.value || _.get(list, '0.value', 0) ); }; const checkQuantization = (list: any[]) => { return ( _.find( list, (item: { label: string; value: string }) => item.value === form.current.getFieldValue('quantization') )?.value || _.find(list, (item: { label: string; value: string }) => getDefaultQuant({ category: _.get(current, 'categories.0', ''), quantOption: item.value, backend: form.current.getFieldValue('backend') }) )?.value || _.get(list, '0.value', '') ); }; const onValuesChange = async (changedValues: any, allValues: any) => { const data = { ..._.omit(selectSpecRef.current, ['name']), ...allValues }; handleOnValuesChange?.({ changedValues, allValues: data, source: props.source }); }; const handleBackendChange = (backend: string) => { if (backend === backendOptionsMap.llamaBox) { setIsGGUF(true); } else { setIsGGUF(false); } const sizeList = handleSetSizeOptions({ backend: backend }); const size = checkSize(sizeList); const quantizaList = handleSetQuantizationOptions({ size: size, backend: backend }); const quantization = checkQuantization(quantizaList); const data = getModelSpec({ backend: backend, size: size, quantization: quantization }); form.current.setFieldsValue({ ...defaultFormValues, ...data }); handleCheckFormData(); }; const initClusterId = () => { const cluster_id = clusterList?.find((item) => item.provider === ProviderValueMap.Docker) ?.value || clusterList?.[0]?.value; console.log('cluster_id:', cluster_id); return cluster_id; }; const fetchSpecData = async () => { try { axiosToken.current?.cancel?.(); axiosToken.current = createAxiosToken(); const res: any = await queryCatalogItemSpec( { id: current.id }, { token: axiosToken.current.token } ); const groupList = _.groupBy(res.items, 'source'); sourceGroupMap.current = groupList; specListRef.current = res.items; const sources = _.filter(sourceOptions, (item: any) => { return groupList[item.value]; }); const list = _.sortBy(res.items, 'size'); hasF16Ref.current = _.some(res.items, (item: CatalogSpec) => { return AscendNPUQuant_F16.includes(_.toUpper(item.quantization)); }); const defaultSpec = _.find(list, (item: CatalogSpec) => { return getDefaultQuant({ category: _.get(current, 'categories.0', ''), quantOption: item.quantization, backend: item.backend }); }) || _.get(res.items, `0`, {}); selectSpecRef.current = defaultSpec; setSourceList(sources); handleSetBackendOptions(); handleSetSizeOptions({ backend: defaultSpec.backend }); handleSetQuantizationOptions({ size: defaultSpec.size, backend: defaultSpec.backend }); initFormDataBySource({ ...defaultSpec, cluster_id: initClusterId() }); const name = _.toLower(current.name).replace(/\s/g, '-') || ''; form.current.setFieldValue('name', name); if (defaultSpec.backend === backendOptionsMap.llamaBox) { setIsGGUF(true); } else { setIsGGUF(false); } const allValues = generateSubmitData({ ...defaultSpec, categories: _.get(current, 'categories.0', null), cluster_id: initClusterId(), name }); handleCheckCompatibility(allValues); } catch (error) { // ignore } }; const handleOnQuantizationChange = (val: string) => { const data = getModelSpec({ backend: form.current.getFieldValue('backend'), size: form.current.getFieldValue('size'), quantization: val }); form.current.setFieldsValue({ ...data, ...pickSomeFieldsValue(data) }); handleCheckFormData(); }; const handleOnSizeChange = (val: number) => { // TODO form.current.setFieldValue(defaultFormValues); const list = handleSetQuantizationOptions({ backend: form.current.getFieldValue('backend'), size: val }); const quantization = checkQuantization(list); const data = getModelSpec({ backend: form.current.getFieldValue('backend'), size: val, quantization: quantization }); // set form data form.current.setFieldsValue({ ...defaultFormValues, ...data }); handleCheckFormData(); }; const handleOk = async (values: FormData) => { const data = { ..._.omit(selectSpecRef.current, ['name']), ...values }; onOk(data); }; const handleCancel = () => { onCancel?.(); axiosToken.current?.cancel?.(); }; const showExtraButton = useMemo(() => { return warningStatus.show && warningStatus.type !== 'success'; }, [warningStatus.show, warningStatus.type]); useEffect(() => { if (open) { setTimeout(() => { fetchSpecData(); }, 100); } return () => { axiosToken.current?.cancel?.(); cancelEvaluate(); setWarningStatus({ show: false, title: '', message: [] }); }; }, [open, current]); useEffect(() => { getClusterList(); }, []); return ( { setWarningStatus({ show: false, message: '' }); }} warningStatus={warningStatus} contentStyle={{ paddingInline: 0 }} > {intl.formatMessage({ id: 'models.form.submit.anyway' })} ) } style={{ padding: '16px 24px', display: 'flex', justifyContent: 'flex-end' }} > } > <> ); }; export default AddModal;