refactor: deployments dir tree

This commit is contained in:
jialin
2026-03-19 12:28:18 +08:00
committed by jialin
parent 9840a48301
commit fd1a079271
44 changed files with 118 additions and 306 deletions
@@ -0,0 +1,453 @@
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 ColumnWrapper from '@/pages/_components/column-wrapper';
import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
import { useIntl } from '@umijs/max';
import { Button, message } from 'antd';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import styled from 'styled-components';
import { queryCatalogItemSpec } from '../../apis';
import { DeployFormKeyMap, sourceOptions } from '../../config';
import { CatalogFormContext } from '../../config/form-context';
import {
CatalogSpec,
FormData,
ListItem,
SourceType
} from '../../config/types';
import { backendOptionsMap } from '../../constants/backend-parameters';
import DataForm from '../../forms';
import { useCheckCompatibility } from '../../hooks';
import useFormInitialValues from '../../hooks/use-form-initial-values';
import { generateGPUIds } from '../../utils';
import CompatibilityAlert from '../compatible-alert';
const ModesMap: Record<string, string> = {
latency: 'models.form.mode.latency',
standard: 'models.form.mode.baseline',
throughput: 'models.form.mode.throughput'
};
const ModesTipsMap: Record<string, string> = {
latency: 'models.form.mode.latency.tips',
standard: 'models.form.mode.baseline.tips',
throughput: 'models.form.mode.throughput.tips'
};
const pickFieldsFromSpec = [
'env',
'size',
'source',
'quantization',
'backend_version',
'backend_parameters',
'backend',
'extended_kv_cache',
'speculative_config'
];
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 AddModal: React.FC<AddModalProps> = (props) => {
const {
title,
open,
onOk,
onCancel,
source,
action,
current,
width = 600
} = props || {};
const {
setWarningStatus,
handleDoEvalute,
cancelEvaluate,
clearCacheFormValues,
submitAnyway,
handleOnValuesChange,
warningStatus
} = useCheckCompatibility();
const { getClusterList, getWorkerList, clusterList } = useFormInitialValues();
const intl = useIntl();
const form = useRef<any>({});
const [isGGUF, setIsGGUF] = useState<boolean>(false);
const [sourceList, setSourceList] = useState<any[]>([]);
const [modeList, setModeList] = useState<
Global.BaseOption<string, { isBuiltIn: boolean; tips: string }>[]
>([]);
const sourceGroupMap = useRef<any>({});
const axiosToken = useRef<any>(null);
const selectSpecRef = useRef<CatalogSpec>({} as CatalogSpec);
const specListRef = useRef<any[]>([]);
const noCompatibleGPUsRef = useRef<boolean>(false);
const handleSumit = () => {
form.current?.submit?.();
};
const handleSubmitAnyway = async () => {
if (noCompatibleGPUsRef.current) {
message.error(intl.formatMessage({ id: 'models.catalog.nogpus.tips' }));
return;
}
submitAnyway.current = true;
form.current?.submit?.();
};
const generateSubmitData = (formData: FormData) => {
const gpuSelector = generateGPUIds(formData);
const data = {
..._.omit(selectSpecRef.current, ['name']),
...formData,
...gpuSelector
};
return data;
};
const getModelSpec = (data: {
mode?: string;
backend: string;
size: number;
quantization: string;
}) => {
const defaultSpec = _.find(
specListRef.current,
(item: CatalogSpec) => item.mode === data.mode
);
selectSpecRef.current = defaultSpec;
return {
..._.pick(defaultSpec, 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 handleCheckCompatibility = async (formData: FormData) => {
// no compatible gpus, do nothing
if (noCompatibleGPUsRef.current) {
return;
}
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);
// set form value
initFormDataBySource(defaultSpec);
handleCheckFormData();
};
const onValuesChange = async (changedValues: any, allValues: any) => {
const data = {
..._.omit(selectSpecRef.current, ['name']),
...allValues
};
// no compatible gpus, do nothing
if (noCompatibleGPUsRef.current) {
return;
}
handleOnValuesChange?.({
changedValues,
allValues: data,
source: props.source
});
};
const handleBackendChange = (backend: string) => {
handleCheckFormData();
};
const initClusterId = (): number => {
const defaultCluster = clusterList?.find((item) => item.is_default);
if (defaultCluster) {
return defaultCluster.value;
}
const cluster_id =
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
?.value || clusterList?.[0]?.value;
return cluster_id as number;
};
const fetchSpecData = async (clusterId: number) => {
try {
axiosToken.current?.cancel?.();
axiosToken.current = createAxiosToken();
const res: any = await queryCatalogItemSpec(
{
id: current.id,
cluster_id: clusterId
},
{
token: axiosToken.current.token
}
);
const groupList = _.groupBy(res.items, 'source');
const modes: string[] = res.items?.map((item: CatalogSpec) => {
return item.mode;
});
const modeDataList = [...new Set(modes)].map((key: string) => {
return {
label: _.get(ModesMap, key, key || ''),
isBuiltIn: ModesMap[key] ? true : false,
value: key,
tips: _.get(ModesTipsMap, key, '')
};
});
sourceGroupMap.current = groupList;
specListRef.current = res.items;
const sources = _.filter(sourceOptions, (item: any) => {
return groupList[item.value];
});
const list = _.sortBy(res.items, 'size');
const defaultSpec =
_.find(
list,
(item: CatalogSpec) => item.mode === modeDataList[0]?.value
) || {};
selectSpecRef.current = defaultSpec;
setModeList(modeDataList);
setSourceList(sources);
initFormDataBySource({
...defaultSpec,
cluster_id: clusterId
});
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: clusterId,
name
});
// If no avaliable gpus for the model, show warning message
if (!res.items.length) {
noCompatibleGPUsRef.current = true;
setWarningStatus({
show: true,
type: 'warning',
message: intl.formatMessage({ id: 'models.catalog.nogpus.tips' })
});
return;
}
noCompatibleGPUsRef.current = false;
handleCheckCompatibility(allValues);
} catch (error) {
// ignore
}
};
const handleOnModeChange = (val: string) => {
const data = getModelSpec({
mode: val,
backend: form.current.getFieldValue('backend'),
size: 0,
quantization: ''
});
console.log('mode change data:', data);
form.current.setFieldsValue({
...data
});
handleCheckFormData();
};
const handleOnClusterChange = async (clusterId: number) => {
await fetchSpecData(clusterId);
};
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(() => {
getClusterList();
getWorkerList();
}, []);
useEffect(() => {
if (open) {
setTimeout(() => {
const clusterId = initClusterId();
fetchSpecData(clusterId);
form.current?.getGPUOptionList?.({
clusterId: clusterId
});
form.current?.getBackendOptions?.({
cluster_id: clusterId
});
}, 100);
}
return () => {
axiosToken.current?.cancel?.();
cancelEvaluate();
setWarningStatus({
show: false,
title: '',
message: []
});
};
}, [open, current]);
return (
<GSDrawer
title={title}
open={open}
onClose={handleCancel}
destroyOnHidden={true}
closeIcon={false}
mask={{
closable: false
}}
keyboard={false}
styles={{
wrapper: { width: width }
}}
footer={false}
>
<CatalogFormContext.Provider
value={{
sizeOptions: [],
quantizationOptions: [],
modeList: modeList,
onModeChange: handleOnModeChange
}}
>
<FormWrapper>
<ColumnWrapper
styles={{
container: {
paddingBlock: 0
}
}}
footer={
<>
<CompatibilityAlert
showClose={true}
onClose={() => {
setWarningStatus({
show: false,
message: ''
});
}}
warningStatus={warningStatus}
contentStyle={{ paddingInline: '0 6px' }}
></CompatibilityAlert>
<ModalFooter
onCancel={handleCancel}
onOk={handleSumit}
showOkBtn={!showExtraButton}
extra={
showExtraButton && (
<Button type="primary" onClick={handleSubmitAnyway}>
{intl.formatMessage({
id: 'models.form.submit.anyway'
})}
</Button>
)
}
style={{
padding: '16px 24px 8px',
display: 'flex',
justifyContent: 'flex-end'
}}
></ModalFooter>
</>
}
>
<>
<DataForm
fields={[]}
source={source}
action={action}
onOk={handleOk}
ref={form}
isGGUF={isGGUF}
formKey={DeployFormKeyMap.CATALOG}
sourceDisable={false}
sourceList={sourceList}
clusterList={clusterList}
onClusterChange={handleOnClusterChange}
onBackendChange={handleBackendChange}
onSourceChange={handleSourceChange}
onValuesChange={onValuesChange}
clearCacheFormValues={clearCacheFormValues}
></DataForm>
</>
</ColumnWrapper>
</FormWrapper>
</CatalogFormContext.Provider>
</GSDrawer>
);
};
export default AddModal;
@@ -0,0 +1,682 @@
import { getRequestId } from '@/atoms/models';
import ModalFooter from '@/components/modal-footer';
import GSDrawer from '@/components/scroller-modal/gs-drawer';
import { PageActionType } from '@/config/types';
import useDeferredRequest from '@/hooks/use-deferred-request';
import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Button } from 'antd';
import _ from 'lodash';
import { FC, useEffect, useMemo, useRef, useState } from 'react';
import styled from 'styled-components';
import ColumnWrapper from '../../../_components/column-wrapper';
import {
defaultFormValues,
DeployFormKeyMap,
modelSourceMap
} from '../../config';
import { FormData, SourceType } from '../../config/types';
import { backendOptionsMap } from '../../constants/backend-parameters';
import DataForm from '../../forms';
import {
MessageStatus,
useCheckCompatibility,
useSelectModel,
WarningStausOptions
} from '../../hooks';
import useCheckBackend from '../../hooks/use-check-backend';
import CompatibilityAlert from '../compatible-alert';
import HFModelFile from '../model-source/hf-model-file';
import ModelCard from '../model-source/model-card';
import SearchModel from '../model-source/search-model';
import Separator from '../separator';
import TitleWrapper from '../title-wrapper';
const pickFieldsFromSpec = ['backend_version', 'backend_parameters', 'env'];
const dropFieldsFromForm = [
'name',
'huggingface_filename',
'model_scope_file_path',
'model_scope_model_id',
'huggingface_repo_id',
'backend'
];
const resetFields = ['worker_selector', 'env'];
const ModalFooterStyle = {
padding: '16px 24px 8px',
display: 'flex',
justifyContent: 'flex-end'
};
const Container = styled.div`
display: flex;
height: 100%;
`;
const ColWrapper = styled.div`
display: flex;
flex: 1;
max-width: 33.33%;
`;
const FormWrapper = styled.div`
display: flex;
flex: 1;
maxwidth: 100%;
`;
type AddModalProps = {
title: string;
hasLinuxWorker?: boolean;
action: PageActionType;
open: boolean;
source: SourceType;
isGGUF?: boolean;
width?: string | number;
initialValues?: any;
deploymentType?: 'modelList' | 'modelFiles';
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number; is_default: boolean }
>[];
onOk: (values: FormData) => void;
onCancel: () => void;
};
type EvaluateProccessType = 'model' | 'file' | 'form';
const EvaluateProccess: Record<string, EvaluateProccessType> = {
model: 'model',
file: 'file',
form: 'form'
};
const AddModal: FC<AddModalProps> = (props) => {
const {
title,
open,
onOk,
onCancel,
hasLinuxWorker,
source,
action,
width = 600,
deploymentType = 'modelList',
initialValues,
clusterList
} = props || {};
const SEARCH_SOURCE = [
modelSourceMap.huggingface_value,
modelSourceMap.modelscope_value
];
const { checkOnlyAscendNPU } = useCheckBackend();
const {
setWarningStatus,
handleBackendChangeBefore,
cancelEvaluate,
unlockWarningStatus,
handleOnValuesChange: handleOnValuesChangeBefore,
clearCacheFormValues,
warningStatus,
submitAnyway
} = useCheckCompatibility();
const { onSelectModel } = useSelectModel({ gpuOptions: [] });
const form = useRef<any>({});
const intl = useIntl();
const [selectedModel, setSelectedModel] = useState<any>({});
const [collapsed, setCollapsed] = useState<boolean>(false);
const [isGGUF, setIsGGUF] = useState<boolean>(false);
const modelFileRef = useRef<any>(null);
const evaluateStateRef = useRef<{
state: EvaluateProccessType;
requestModelId: number;
}>({
state: 'form',
requestModelId: 0
});
const requestModelIdRef = useRef<number>(0);
const currentSelectedModel = useRef<any>({});
const flatBackendOptionsRef = useRef<any[]>([]);
const { run: fetchModelFiles } = useDeferredRequest(
() => modelFileRef.current?.fetchModelFiles?.(),
100
);
const updateSelectedModel = (model: any) => {
currentSelectedModel.current = model;
setSelectedModel(model);
};
/**
* Update the request model id to distinguish
* the evaluate request.
*/
const updateRequestModelId = () => {
requestModelIdRef.current += 1;
return requestModelIdRef.current;
};
/**
*
* @param state target to distinguish the evaluate state, current evaluate state
* can be 'model', 'file' or 'form'.
*/
const setEvaluteState = (state: {
state: EvaluateProccessType;
requestModelId: number;
}) => {
evaluateStateRef.current = state;
};
const handleOnValuesChange = (data: {
changedValues: any;
allValues: any;
source: SourceType;
}) => {
setEvaluteState({
state: EvaluateProccess.form,
requestModelId: updateRequestModelId()
});
handleOnValuesChangeBefore(data);
};
const getDefaultSpec = (item: any) => {
const defaultSpec = _.pick(
item.evaluateResult?.default_spec,
pickFieldsFromSpec
);
return defaultSpec;
};
const getCategory = (item: any) => {
const categories = item.evaluateResult?.default_spec?.categories || [];
if (Array.isArray(categories)) {
return categories?.[0] || null;
}
return categories || null;
};
const { run: onClickModel } = useDeferredRequest(async () => {
const allValues = form.current?.form?.getFieldsValue?.();
handleOnValuesChangeBefore({
changedValues: {},
allValues: allValues,
source: props.source
});
}, 100);
const { run: onSelectFile } = useDeferredRequest(
async (item: any, modelInfo: any, manual?: boolean) => {
unlockWarningStatus();
const evaluateRes = await handleOnValuesChangeBefore?.({
changedValues: {},
allValues: form.current?.form?.getFieldsValue?.(),
source: props.source
});
console.log('onSelectFile:', item, modelInfo, evaluateRes);
// for cancel evaluate request case
if (!evaluateRes) {
return;
}
const defaultSpec = getDefaultSpec({
evaluateResult: evaluateRes
});
/**
* do not reset backend_parameters when select a model file
*/
const formValues = form.current?.getFieldsValue?.(pickFieldsFromSpec);
form.current?.setFieldsValue?.({
..._.omit(modelInfo, ['name']),
huggingface_filename: item.fakeName,
model_scope_file_path: item.fakeName,
backend_parameters:
formValues.backend_parameters?.length > 0
? formValues.backend_parameters
: defaultSpec.backend_parameters || [],
backend_version:
formValues.backend_version || defaultSpec.backend_version,
env: formValues.env || defaultSpec.env,
categories: getCategory(item)
});
},
100
);
const handleSelectModelFile = async (
item: any,
options: { requestModelId: number; manual?: boolean }
) => {
const { requestModelId, manual } = options || {};
if (requestModelId !== getRequestId()) {
return;
}
const modelInfo = onSelectModel(selectedModel, {
source: props.source,
defaultBackend: form.current?.getFieldValue?.('backend'),
flatBackendOptions: flatBackendOptionsRef.current
});
form.current?.setFieldsValue?.({
..._.omit(modelInfo, ['name']),
huggingface_filename: item.fakeName,
model_scope_file_path: item.fakeName,
backend_parameters: [],
backend_version: null,
backend: modelInfo.backend,
env: {
...modelInfo.env
},
categories: getCategory(item)
});
// evaluate the form data when select a model file
// TODO: reset backend related fields when select a GGUF file
if (item.fakeName) {
onSelectFile(item, modelInfo, manual);
}
};
const handleCancelFiles = () => {
cancelEvaluate();
modelFileRef.current?.cancelRequest();
};
const generateNameValue = (
item: any,
modelName: string,
manual?: boolean
) => {
if (item.name === currentSelectedModel.current.name) {
return manual
? modelName
: form.current?.getFieldValue?.('name') || modelName;
}
return modelName;
};
const currentModelDuringEvaluate = (item: any) => {
return (
evaluateStateRef.current.state === EvaluateProccess.form &&
item.name === currentSelectedModel.current.name
);
};
const handleOnSelectModel = async (item: any, manual?: boolean) => {
// If the item is empty or the same as the selected model, do nothing
handleCancelFiles();
if (
_.isEmpty(item) ||
(item.isGGUF === selectedModel.isGGUF && item.name === selectedModel.name) // --- because sometimes has the same model name with different isGGUF value
) {
return;
}
setIsGGUF(item.isGGUF);
clearCacheFormValues();
unlockWarningStatus();
setEvaluteState({
state: EvaluateProccess.model,
requestModelId: updateRequestModelId()
});
// TODO
form.current?.resetFields(resetFields);
const modelInfo = onSelectModel(item, {
source: props.source,
flatBackendOptions: flatBackendOptionsRef.current
});
form.current?.setFieldsValue?.({
...defaultFormValues,
...modelInfo,
env: {
...modelInfo.env
},
name: generateNameValue(item, modelInfo.name, manual),
categories: getCategory(item)
});
updateSelectedModel(item);
let warningStatus: MessageStatus = {
show: true,
title: '',
type: 'transition',
message: intl.formatMessage({ id: 'models.form.evaluating' })
};
if (item.isGGUF) {
fetchModelFiles();
}
setWarningStatus(warningStatus, { override: true });
};
const handleOnSelectModelAfterEvaluate = (item: any, manual?: boolean) => {
if (currentModelDuringEvaluate(item)) {
return;
}
if (manual) {
form.current?.resetFields(resetFields);
}
// If the item is empty
setIsGGUF(item.isGGUF);
updateSelectedModel(item);
setEvaluteState({
state: EvaluateProccess.model,
requestModelId: updateRequestModelId()
});
handleCancelFiles();
const modelInfo = onSelectModel(item, {
source: props.source,
flatBackendOptions: flatBackendOptionsRef.current
});
if (
evaluateStateRef.current.state === EvaluateProccess.model &&
item.evaluated
) {
const defaultSpec = getDefaultSpec(item);
const newFormValues = {
...(manual
? { ...defaultFormValues }
: _.omit(form.current?.form?.getFieldsValue?.(), [
...dropFieldsFromForm
])),
...defaultSpec,
...modelInfo,
env: {
...modelInfo.env,
...defaultSpec.env
},
name: generateNameValue(item, modelInfo.name, manual),
categories: getCategory(item)
};
form.current?.form?.setFieldsValue?.(newFormValues);
onClickModel();
}
};
const handleOnOk = async (allValues: FormData) => {
onOk(allValues);
};
const handleSubmitAnyway = async () => {
submitAnyway.current = true;
form.current?.submit?.();
};
const handleSumit = () => {
form.current?.submit?.();
};
const handleSetIsGGUF = async (flag: boolean) => {
setIsGGUF(flag);
};
const handleBackendChange = async (backend: string) => {
const data = form.current.form.getFieldsValue?.();
const res = handleBackendChangeBefore(data);
if (res.show) {
return;
}
// TODO: confirm gguf change backend behavior
handleOnValuesChange?.({
changedValues: {},
allValues: data,
source: props.source
});
};
const onValuesChange = async (changedValues: any, allValues: any) => {
handleOnValuesChange?.({
changedValues,
allValues,
source: props.source
});
};
const handleCancel = useMemoizedFn(() => {
onCancel?.();
});
const initClusterId = () => {
if (initialValues?.cluster_id) {
return initialValues.cluster_id;
}
// Find default cluster
const defaultCluster = clusterList?.find((item) => item.is_default);
if (defaultCluster) {
return defaultCluster.value;
}
const cluster_id =
clusterList?.find((item) => item.state === ClusterStatusValueMap.Ready)
?.value || clusterList?.[0]?.value;
return cluster_id;
};
const handleOnOpen = async () => {
const [backendOptions, gpuOptions] = await Promise.all([
form.current?.getBackendOptions?.({
cluster_id: initClusterId()
}),
form.current?.getGPUOptionList?.({
clusterId: initClusterId()
})
]);
flatBackendOptionsRef.current = backendOptions;
if (props.deploymentType === 'modelFiles') {
form.current?.form?.setFieldsValue({
...props.initialValues
});
handleOnValuesChange?.({
changedValues: {},
allValues: form.current?.form?.getFieldsValue(),
source: source
});
} else {
let backend = checkOnlyAscendNPU(gpuOptions)
? backendOptionsMap.ascendMindie
: backendOptionsMap.vllm;
const currentDefaultBackend = backendOptions?.find(
(item: {
value: string;
label: string;
default_backend_param: string[];
default_version: string;
versions: { label: string; value: string }[];
}) => item.value === backend
);
form.current?.setFieldsValue?.({
backend,
env: {
...currentDefaultBackend?.default_env
},
default_version: currentDefaultBackend?.default_version,
backend_parameters: currentDefaultBackend?.default_backend_param || [],
cluster_id: initClusterId()
});
}
};
const showExtraButton = useMemo(() => {
return warningStatus.show && warningStatus.type !== 'success';
}, [warningStatus.show, warningStatus.type]);
// This is only a placeholder for querying the model or file during the transition period.
const displayEvaluateStatus = (
params: MessageStatus,
options?: WarningStausOptions
) => {
setWarningStatus(
{
show: params.show,
title: '',
type: 'transition',
message: intl.formatMessage({ id: 'models.form.evaluating' })
},
options
);
};
useEffect(() => {
if (open) {
handleOnOpen();
} else {
cancelEvaluate();
clearCacheFormValues();
}
return () => {
setSelectedModel({});
setWarningStatus({
show: false,
title: '',
message: []
});
};
}, [open, clusterList, initialValues?.cluster_id]);
return (
<GSDrawer
title={title}
open={open}
onClose={handleCancel}
destroyOnHidden={true}
closeIcon={false}
mask={{
closable: false
}}
keyboard={false}
styles={{
wrapper: { width: width }
}}
footer={false}
>
<Container>
{SEARCH_SOURCE.includes(props.source) &&
deploymentType === 'modelList' && (
<>
<ColWrapper>
<SearchModel
hasLinuxWorker={hasLinuxWorker}
modelSource={props.source}
onSelectModel={handleOnSelectModel}
onSelectModelAfterEvaluate={handleOnSelectModelAfterEvaluate}
clusterId={
form.current?.getFieldValue?.('cluster_id') ||
initClusterId()
}
displayEvaluateStatus={displayEvaluateStatus}
gpuOptions={[]}
></SearchModel>
<Separator></Separator>
</ColWrapper>
<ColWrapper>
<ColumnWrapper styles={{ container: { padding: 0 } }}>
<ModelCard
selectedModel={selectedModel}
onCollapse={setCollapsed}
collapsed={collapsed}
modelSource={props.source}
isGGUF={isGGUF}
setIsGGUF={handleSetIsGGUF}
></ModelCard>
{isGGUF && (
<HFModelFile
ref={modelFileRef}
selectedModel={selectedModel}
modelSource={props.source}
onSelectFile={handleSelectModelFile}
collapsed={collapsed}
></HFModelFile>
)}
</ColumnWrapper>
<Separator></Separator>
</ColWrapper>
</>
)}
<FormWrapper>
<ColumnWrapper
styles={{
container: { paddingBlock: 0 }
}}
footer={
<>
<CompatibilityAlert
showClose={true}
onClose={() => {
setWarningStatus({
show: false,
message: ''
});
}}
warningStatus={warningStatus}
contentStyle={{ paddingInline: '0 6px' }}
></CompatibilityAlert>
<ModalFooter
onCancel={handleCancel}
onOk={handleSumit}
showOkBtn={!showExtraButton}
extra={
showExtraButton && (
<Button type="primary" onClick={handleSubmitAnyway}>
{intl.formatMessage({
id: 'models.form.submit.anyway'
})}
</Button>
)
}
style={ModalFooterStyle}
></ModalFooter>
</>
}
>
<>
{SEARCH_SOURCE.includes(source) &&
deploymentType === 'modelList' && (
<TitleWrapper>
{intl.formatMessage({ id: 'models.form.configurations' })}
</TitleWrapper>
)}
<DataForm
formKey={DeployFormKeyMap.DEPLOYMENT}
initialValues={initialValues}
source={source}
action={action}
clusterList={clusterList}
onOk={handleOnOk}
ref={form}
isGGUF={isGGUF}
onBackendChange={handleBackendChange}
onValuesChange={onValuesChange}
clearCacheFormValues={clearCacheFormValues}
></DataForm>
</>
</ColumnWrapper>
</FormWrapper>
</Container>
</GSDrawer>
);
};
export default AddModal;
@@ -0,0 +1,293 @@
import ModalFooter from '@/components/modal-footer';
import GSDrawer from '@/components/scroller-modal/gs-drawer';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import ColumnWrapper from '@/pages/_components/column-wrapper';
import { useIntl } from '@umijs/max';
import _ from 'lodash';
import React, { useEffect, useRef } from 'react';
import {
DeployFormKeyMap,
DO_NOT_NOTIFY_RECREATE,
ScheduleValueMap
} from '../../config';
import { FormData } from '../../config/types';
import { backendOptionsMap } from '../../constants/backend-parameters';
import DataForm from '../../forms';
import { useCheckCompatibility } from '../../hooks';
import { generateGPUSelector } from '../../utils';
import CompatibilityAlert from '../compatible-alert';
type AddModalProps = {
title: string;
action: PageActionType;
open: boolean;
currentData: {
data: FormData;
isGGUF: boolean;
realAction?: PageActionType;
};
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
onOk: (values: FormData) => void;
onCancel: () => void;
};
const ModalFooterStyle = {
padding: '16px 24px 8px',
display: 'flex',
justifyContent: 'flex-end'
};
const UpdateModal: React.FC<AddModalProps> = (props) => {
const {
title,
action,
open,
onOk,
onCancel,
clusterList,
currentData: { isGGUF, data: formData, realAction }
} = props || {};
const intl = useIntl();
const {
setWarningStatus,
handleBackendChangeBefore,
checkTokenRef,
warningStatus
} = useCheckCompatibility();
const formRef = useRef<any>(null);
const submitAnyway = useRef<boolean>(false);
const originFormData = useRef<any>(null);
const setOriginalFormData = () => {
if (!originFormData.current) {
originFormData.current = _.cloneDeep(formData);
if (!originFormData.current.extended_kv_cache?.enabled) {
originFormData.current.extended_kv_cache = {
enabled: false
};
}
if (!originFormData.current.speculative_config?.enabled) {
originFormData.current.speculative_config = {
enabled: false
};
}
}
};
const customizer = (val1: any, val2: any) => {
if (
(val1 === null && val2 === '') ||
(val1 === '' && val2 === null) ||
(_.isEmpty(val1) && val2 === null) ||
(_.isEmpty(val2) && val1 === null)
) {
return true;
}
return undefined;
};
// this function is used compare form data changes in updating model, and show warning if needed
const handleOnValuesChange = _.debounce((data: any) => {
const formdata = formRef.current?.getFieldsValue?.();
console.log('handleOnValuesChange:', formdata);
let alldata = {};
if (formdata.scheduleType === ScheduleValueMap.Manual) {
alldata = {
..._.omit(formdata, ['worker_selector']),
env: formdata.env || originFormData.current?.env || null,
gpu_selector: formdata.gpu_selector
};
} else {
alldata = {
..._.omit(formdata, ['gpu_selector']),
env: formdata.env || originFormData.current?.env || null,
worker_selector:
formdata.worker_selector ||
originFormData.current?.worker_selector ||
null
};
}
const originalData = _.pick(originFormData.current, Object.keys(alldata));
const isEqual = _.isEqualWith(
_.omit(alldata, DO_NOT_NOTIFY_RECREATE),
_.omit(originalData, DO_NOT_NOTIFY_RECREATE),
customizer
);
if (isEqual) {
setWarningStatus({
show: false,
message: ''
});
} else {
setWarningStatus({
show: true,
isDefault: true,
message: intl.formatMessage({
id: 'models.form.update.tips'
})
});
}
}, 100);
const handleBackendChange = (backend: string) => {
const data = formRef.current?.getFieldsValue?.();
const res = handleBackendChangeBefore(data);
if (res.show) {
return;
}
handleOnValuesChange?.({
changedValues: {},
allValues: data,
source: data.source
});
};
const handleAsyncBackendChange = (backend: string) => {
setTimeout(() => {
handleBackendChange(backend);
}, 100);
};
const handleSumit = () => {
formRef.current?.submit();
};
const handleSubmitAnyway = async () => {
submitAnyway.current = true;
formRef.current?.submit?.();
};
const handleOk = async (formdata: FormData) => {
let submitData = {} as FormData;
const isVoxBox = [backendOptionsMap.voxBox].includes(formdata.backend);
submitData = {
..._.omit(formdata, ['scheduleType']),
worker_selector:
formdata.scheduleType === ScheduleValueMap.Manual
? null
: formdata.worker_selector,
...(isVoxBox
? {
distributed_inference_across_workers: false,
cpu_offloading: false
}
: {})
};
onOk(submitData);
};
const handleManulOnValuesChange = (changedValues: any, allValues: any) => {
handleOnValuesChange({
changedValues,
allValues,
source: formData?.source as string
});
};
const handleOnClose = () => {
onCancel?.();
};
useEffect(() => {
const initGPUSelector = async () => {
const gpuOptions = await formRef.current?.getGPUOptionList({
clusterId: formData.cluster_id
});
const gpuSelector = generateGPUSelector(formData, gpuOptions);
formRef.current?.setFieldsValue(gpuSelector);
};
if (open && formData) {
setTimeout(() => {
setOriginalFormData();
initGPUSelector();
formRef.current?.getBackendOptions?.({
cluster_id: formData.cluster_id
});
}, 100);
}
if (!open) {
checkTokenRef.current?.cancel?.();
originFormData.current = null;
setWarningStatus({
show: false,
message: ''
});
}
}, [open, formData]);
return (
<GSDrawer
title={title}
open={open}
onClose={handleOnClose}
destroyOnHidden={true}
closeIcon={true}
mask={{
closable: false
}}
keyboard={false}
styles={{
wrapper: { width: 600 }
}}
footer={false}
>
<ColumnWrapper
styles={{
container: {
paddingBlock: 0
}
}}
footer={
<>
{realAction === PageAction.EDIT && (
<CompatibilityAlert
showClose={false}
onClose={() => {
setWarningStatus({
show: false,
message: ''
});
}}
warningStatus={warningStatus}
contentStyle={{ paddingInline: 0 }}
></CompatibilityAlert>
)}
<ModalFooter
style={ModalFooterStyle}
onCancel={onCancel}
onOk={handleSumit}
></ModalFooter>
</>
}
>
<DataForm
formKey={DeployFormKeyMap.DEPLOYMENT}
initialValues={formData}
source={formData.source || ''}
action={action}
realAction={realAction}
clusterList={clusterList}
onOk={handleOk}
ref={formRef}
isGGUF={isGGUF}
onBackendChange={handleAsyncBackendChange}
onValuesChange={handleManulOnValuesChange}
></DataForm>
</ColumnWrapper>
</GSDrawer>
);
};
export default UpdateModal;