chore: check compatibility in edit

This commit is contained in:
jialin
2025-04-01 20:47:52 +08:00
parent a3ee010286
commit f5c4749954
8 changed files with 212 additions and 188 deletions
+6 -12
View File
@@ -5,6 +5,7 @@ import qs from 'query-string';
import {
CatalogItem,
CatalogSpec,
EvaluateResult,
EvaluateSpec,
FormData,
GPUListItem,
@@ -373,18 +374,11 @@ export async function evaluationsModelSpec(
},
options: { token: any }
) {
return {
results: [
{
compatible: true
}
]
};
// return request<{ results: EvaluateResult[] }>(`${MODEL_EVALUATIONS}`, {
// method: 'POST',
// data,
// cancelToken: options?.token
// });
return request<{ results: EvaluateResult[] }>(`${MODEL_EVALUATIONS}`, {
method: 'POST',
data,
cancelToken: options?.token
});
}
// export const evaluationsModelSpec = async (
@@ -26,6 +26,7 @@ const CloseWrapper = styled.div`
top: 6px;
right: 18px;
cursor: pointer;
background-color: var(--ant-color-warning-bg);
`;
const MessageWrapper = styled.div`
+10 -35
View File
@@ -8,7 +8,6 @@ import { FC, useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import {
backendOptionsMap,
excludeFields,
getSourceRepoConfigValue,
modelSourceMap
} from '../config';
@@ -44,6 +43,7 @@ const FormWrapper = styled.div`
type AddModalProps = {
title: string;
hasLinuxWorker?: boolean;
action: PageActionType;
open: boolean;
source: SourceType;
@@ -63,6 +63,7 @@ const AddModal: FC<AddModalProps> = (props) => {
open,
onOk,
onCancel,
hasLinuxWorker,
source,
action,
width = 600,
@@ -79,6 +80,7 @@ const AddModal: FC<AddModalProps> = (props) => {
handleUpdateWarning,
setWarningStatus,
handleEvaluate,
handleOnValuesChange,
checkTokenRef,
warningStatus
} = useCheckCompatibility();
@@ -103,14 +105,12 @@ const AddModal: FC<AddModalProps> = (props) => {
const handleOnSelectModel = (item: any, isgguf?: boolean) => {
setSelectedModel(item);
form.current?.handleOnSelectModel?.(item);
console.log('isgguf+++++++', isgguf, item);
if (!isgguf) {
handleShowCompatibleAlert(item.evaluateResult);
}
};
const handleOnOk = async (allValues: FormData) => {
console.log('allValues---------', allValues);
if (submitAnyway.current) {
onOk(allValues);
return;
@@ -181,38 +181,12 @@ const AddModal: FC<AddModalProps> = (props) => {
}
};
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
const keys = Object.keys(changedValues);
const isExcludeField = keys.some((key) => excludeFields.includes(key));
const hasValue = keys.every((key) => {
return !!changedValues[key];
const onValuesChange = async (changedValues: any, allValues: any) => {
handleOnValuesChange?.({
changedValues,
allValues,
source: props.source
});
// let hasExcludeField = false;
// let allFieldsHaveValue = true;
// for (const key of keys) {
// if (excludeFields.includes(key)) {
// hasExcludeField = true;
// break;
// }
// if (!changedValues[key]) {
// allFieldsHaveValue = false;
// }
// }
if (
!isExcludeField &&
hasValue &&
!_.has(changedValues, 'backend') &&
!_.has(changedValues, 'local_path')
) {
const values = form.current?.form.getFieldsValue?.();
const data = getSourceRepoConfigValue(props.source, values);
const evalutionData = await handleEvaluate(data.values);
handleShowCompatibleAlert?.(evalutionData);
}
};
const handleCancel = useCallback(() => {
@@ -285,6 +259,7 @@ const AddModal: FC<AddModalProps> = (props) => {
<ColWrapper>
<ColumnWrapper>
<SearchModel
hasLinuxWorker={hasLinuxWorker}
modelSource={props.source}
onSelectModel={handleOnSelectModel}
></SearchModel>
@@ -382,7 +357,7 @@ const AddModal: FC<AddModalProps> = (props) => {
gpuOptions={props.gpuOptions}
modelFileOptions={props.modelFileOptions}
onBackendChange={handleBackendChange}
onValuesChange={handleOnValuesChange}
onValuesChange={onValuesChange}
></DataForm>
</>
</ColumnWrapper>
+9 -39
View File
@@ -27,6 +27,7 @@ import SearchInput from './search-input';
import SearchResult from './search-result';
interface SearchInputProps {
hasLinuxWorker?: boolean;
modelSource: string;
isDownload?: boolean;
setLoadingModel?: (flag: boolean) => void;
@@ -36,7 +37,13 @@ interface SearchInputProps {
const SearchModel: React.FC<SearchInputProps> = (props) => {
const intl = useIntl();
const { modelSource, isDownload, setLoadingModel, onSelectModel } = props;
const {
modelSource,
isDownload,
hasLinuxWorker,
setLoadingModel,
onSelectModel
} = props;
const [dataSource, setDataSource] = useState<{
repoOptions: any[];
loading: boolean;
@@ -57,12 +64,10 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
const cacheRepoOptions = useRef<any[]>([]);
const axiosTokenRef = useRef<any>(null);
const checkTokenRef = useRef<any>(null);
const evaluateTokenRef = useRef<any>(null);
const searchInputRef = useRef<any>('');
const filterGGUFRef = useRef<boolean | undefined>();
const filterGGUFRef = useRef<boolean | undefined>(!hasLinuxWorker);
const filterTaskRef = useRef<string>('');
const timer = useRef<any>(null);
const workerRef = useRef<any>(null);
const modelFilesSortOptions = useRef<any[]>([
{
label: intl.formatMessage({ id: 'models.sort.trending' }),
@@ -219,41 +224,6 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
}
};
const handleEvaluateWorker = (params: {
list: any[];
modelSource: string;
modelSourceMap: any;
}) => {
console.log('handleEvaluateWorker=======');
const { list, modelSource, modelSourceMap } = params;
workerRef.current?.terminate();
setIsEvaluating(true);
workerRef.current = new Worker(
// @ts-ignore
new URL('../apis/evaluateWorker.ts', import.meta.url)
);
workerRef.current.postMessage({
list,
modelSource,
modelSourceMap
});
workerRef.current.onmessage = function (event: any) {
const { success, resultList } = event.data;
if (success) {
setDataSource((pre) => {
return {
...pre,
repoOptions: resultList
};
});
}
setIsEvaluating(false);
handleOnSelectModel(resultList[0]);
workerRef.current.terminate();
workerRef.current = null;
};
};
const handleOnSearchRepo = async (sortType?: string) => {
if (!SUPPORTEDSOURCE.includes(modelSource)) {
return;
+8 -1
View File
@@ -180,11 +180,13 @@ const Models: React.FC<ModelsProps> = ({
const [openDeployModal, setOpenDeployModal] = useState<{
show: boolean;
width: number | string;
hasLinuxWorker?: boolean;
source: SourceType;
gpuOptions: any[];
modelFileOptions?: any[];
}>({
show: false,
hasLinuxWorker: false,
width: 600,
source: modelSourceMap.huggingface_value as SourceType,
gpuOptions: [],
@@ -516,10 +518,14 @@ const Models: React.FC<ModelsProps> = ({
}
const config = modalConfig[item.key];
console.log('modelFileOptions:', modelFileOptions);
const hasLinuxWorker = workerList.some(
(worker) => worker.labels?.os === 'linux'
);
if (config) {
setOpenDeployModal({
...config,
hasLinuxWorker: hasLinuxWorker,
gpuOptions: gpuDeviceList.current,
modelFileOptions: modelFileOptions
});
@@ -835,6 +841,7 @@ const Models: React.FC<ModelsProps> = ({
title={intl.formatMessage({ id: 'models.button.deploy' })}
source={openDeployModal.source}
width={openDeployModal.width}
hasLinuxWorker={openDeployModal.hasLinuxWorker}
gpuOptions={openDeployModal.gpuOptions}
modelFileOptions={openDeployModal.modelFileOptions || []}
onCancel={handleDeployModalCancel}
+104 -98
View File
@@ -1,4 +1,3 @@
import AlertBlockInfo from '@/components/alert-info/block';
import IconFont from '@/components/icon-font';
import ModalFooter from '@/components/modal-footer';
import SealAutoComplete from '@/components/seal-form/auto-complete';
@@ -9,20 +8,23 @@ import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { Form, Modal, Typography } from 'antd';
import { Button, Form, Modal, Typography } from 'antd';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef } from 'react';
import {
backendOptionsMap,
backendTipsList,
getSourceRepoConfigValue,
localPathTipsList,
modelSourceMap,
ollamaModelOptions,
sourceOptions
} from '../config';
import { FormData, ListItem } from '../config/types';
import { useCheckCompatibility } from '../hooks';
import AdvanceConfig from './advance-config';
import ColumnWrapper from './column-wrapper';
import CompatibilityAlert from './compatible-alert';
type AddModalProps = {
title: string;
@@ -51,17 +53,21 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
onCancel,
updateFormInitials: { gpuOptions, isGGUF, data: formData }
} = props || {};
const {
handleShowCompatibleAlert,
handleUpdateWarning,
setWarningStatus,
handleEvaluate,
generateGPUIds,
handleOnValuesChange,
checkTokenRef,
warningStatus
} = useCheckCompatibility();
const { getRuleMessage } = useAppUtils();
const [form] = Form.useForm();
const intl = useIntl();
const localPathCache = useRef<string>('');
const [warningStatus, setWarningStatus] = useState<{
show: boolean;
message: string;
}>({
show: false,
message: ''
});
const submitAnyway = useRef<boolean>(false);
const handleSetGPUIds = (backend: string) => {
if (backend === backendOptionsMap.llamaBox) {
@@ -77,37 +83,30 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}
};
const updateShowWarning = (backend: string) => {
// trigger from local_path change or backend change
const handleBackendChangeHook = async () => {
const localPath = form.getFieldValue?.('local_path');
const backend = form.getFieldValue?.('backend');
if (formData?.source !== modelSourceMap.local_path_value || !localPath) {
return;
}
const isBlobFile = localPath?.split('/').pop()?.includes('sha256');
const isOllamaModel = localPath?.includes('ollama');
const isGGUFFile = localPath.endsWith('.gguf');
let warningMessage = '';
if (isBlobFile && isOllamaModel && backend === backendOptionsMap.llamaBox) {
warningMessage = '';
} else if (
isBlobFile &&
isOllamaModel &&
backend !== backendOptionsMap.llamaBox
) {
warningMessage = 'models.form.ollama.warning';
} else if (isGGUFFile && backend !== backendOptionsMap.llamaBox) {
warningMessage = 'models.form.backend.warning';
} else if (!isGGUFFile && backend === backendOptionsMap.llamaBox) {
warningMessage = 'models.form.backend.warning.llamabox';
}
setWarningStatus({
show: !!warningMessage,
message: warningMessage
const res = handleUpdateWarning?.({
backend,
localPath: localPath,
source: formData?.source as string
});
if (!res.show) {
const values = form.getFieldsValue?.();
const data = getSourceRepoConfigValue(formData?.source as string, values);
const evalutionData = await handleEvaluate(
_.omit(data.values, [
'cpu_offloading',
'distributed_inference_across_workers'
])
);
handleShowCompatibleAlert?.(evalutionData);
} else {
setWarningStatus?.(res);
}
};
const handleBackendChange = (val: string) => {
@@ -119,7 +118,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}
form.setFieldValue('backend_version', '');
handleSetGPUIds(val);
updateShowWarning(val);
handleBackendChangeHook();
};
const handleOnFocus = () => {
@@ -289,37 +288,14 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
form.submit();
};
const generateGPUIds = (data: FormData) => {
const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []);
if (!gpu_ids.length) {
return {};
}
const result = _.reduce(
gpu_ids,
(acc: string[], item: string | string[], index: number) => {
if (Array.isArray(item)) {
acc.push(item[1]);
} else if (index === 1) {
acc.push(item);
}
return acc;
},
[]
);
if (result.length) {
return {
gpu_selector: {
gpu_ids: result
}
};
}
return {};
const handleSubmitAnyway = async () => {
submitAnyway.current = true;
form.submit?.();
};
const handleOk = (formdata: FormData) => {
const handleOk = async (formdata: FormData) => {
let obj = {};
let submitData = {} as FormData;
if (
[backendOptionsMap.vllm, backendOptionsMap.voxBox].includes(
formdata.backend
@@ -332,26 +308,40 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}
if (formdata.scheduleType === 'manual') {
const gpuSelector = generateGPUIds(formdata);
onOk({
submitData = {
..._.omit(formdata, ['scheduleType']),
categories: formdata.categories ? [formdata.categories] : [],
worker_selector: null,
gpu_selector: formdata.gpu_selector?.gpu_ids?.length
? {
gpu_ids: formdata.gpu_selector.gpu_ids
}
: null,
...obj,
...gpuSelector
});
};
} else {
onOk({
submitData = {
..._.omit(formdata, ['scheduleType']),
categories: formdata.categories ? [formdata.categories] : [],
gpu_selector: null,
...obj
});
};
}
if (submitAnyway.current) {
onOk(submitData);
return;
}
const evalutionData = await handleEvaluate(submitData);
handleShowCompatibleAlert?.(evalutionData);
if (evalutionData?.compatible) {
onOk(submitData);
}
};
const onValuesChange = (changedValues: any, allValues: any) => {
handleOnValuesChange({
changedValues,
allValues,
source: formData?.source as string
});
};
const handleOnClose = () => {
@@ -363,6 +353,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
form.setFieldsValue(formData);
}
if (!open) {
checkTokenRef.current?.cancel?.();
setWarningStatus({
show: false,
message: ''
@@ -401,40 +392,55 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}}
footer={
<>
<ModalFooter onCancel={onCancel} onOk={handleSumit}></ModalFooter>
<ModalFooter
onCancel={onCancel}
onOk={handleSumit}
showOkBtn={!warningStatus.show}
extra={
warningStatus.show && (
<Button
type="primary"
onClick={handleSubmitAnyway}
style={{ width: '130px' }}
>
{intl.formatMessage({
id: 'models.form.submit.anyway'
})}
</Button>
)
}
></ModalFooter>
</>
}
>
<ColumnWrapper
maxHeight={550}
paddingBottom={warningStatus.show ? 70 : 0}
paddingBottom={
warningStatus.show
? Array.isArray(warningStatus.message)
? 100
: 70
: 0
}
footer={
<div style={{ paddingInline: 12 }}>
{warningStatus.show && (
<AlertBlockInfo
ellipsis={false}
message={
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: warningStatus.message
})
}}
></span>
}
title={intl.formatMessage({
id: 'common.text.tips'
})}
type="warning"
></AlertBlockInfo>
)}
</div>
<CompatibilityAlert
showClose={true}
onClose={() => {
setWarningStatus({
show: false,
message: ''
});
}}
warningStatus={warningStatus}
contentStyle={{ paddingInline: 0 }}
></CompatibilityAlert>
}
>
<Form
name="addModalForm"
form={form}
onFinish={handleOk}
onValuesChange={onValuesChange}
preserve={false}
clearOnDestroy={true}
initialValues={{
+73 -2
View File
@@ -8,13 +8,21 @@ import { useEffect, useRef, useState } from 'react';
import { evaluationsModelSpec, queryGPUList } from '../apis';
import {
backendOptionsMap,
excludeFields,
getSourceRepoConfigValue,
modelSourceMap,
setSourceRepoConfigValue
} from '../config';
import { EvaluateResult, GPUListItem, ListItem } from '../config/types';
import {
EvaluateResult,
FormData,
GPUListItem,
ListItem
} from '../config/types';
export const useGenerateFormEditInitialValues = () => {
const gpuDeviceList = useRef<any[]>([]);
const workerList = useRef<any[]>([]);
const generateCascaderOptions = (
list: GPUListItem[],
@@ -75,6 +83,7 @@ export const useGenerateFormEditInitialValues = () => {
]);
const gpuList = generateCascaderOptions(gpuData.items, workerData.items);
gpuDeviceList.current = gpuList;
workerList.current = workerData.items;
return gpuList;
};
@@ -115,7 +124,8 @@ export const useGenerateFormEditInitialValues = () => {
return {
getGPUList,
generateFormValues,
gpuDeviceList
gpuDeviceList,
workerList
};
};
@@ -331,6 +341,65 @@ export const useCheckCompatibility = () => {
return warningMessage;
};
const generateGPUIds = (data: FormData) => {
const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []);
if (!gpu_ids.length) {
return {
gpu_selector: null
};
}
const result = _.reduce(
gpu_ids,
(acc: string[], item: string | string[], index: number) => {
if (Array.isArray(item)) {
acc.push(item[1]);
} else if (index === 1) {
acc.push(item);
}
return acc;
},
[]
);
return {
gpu_selector: {
gpu_ids: result
}
};
};
const handleOnValuesChange = async (params: {
changedValues: any;
allValues: any;
source: string;
}) => {
const { changedValues, allValues, source } = params;
const keys = Object.keys(changedValues);
const isExcludeField = keys.some((key) => excludeFields.includes(key));
const hasValue = keys.every((key) => {
return !!changedValues[key];
});
if (
!isExcludeField &&
hasValue &&
!_.has(changedValues, 'backend') &&
!_.has(changedValues, 'local_path')
) {
const data = getSourceRepoConfigValue(source, allValues);
const gpuSelector = generateGPUIds(data.values);
const evalutionData = await handleEvaluate({
...data.values,
...gpuSelector
});
handleShowCompatibleAlert?.(evalutionData);
}
};
const debounceHandleValuesChange = _.debounce(handleOnValuesChange, 300);
useEffect(() => {
return () => {
checkTokenRef.current?.cancel();
@@ -341,8 +410,10 @@ export const useCheckCompatibility = () => {
return {
handleShowCompatibleAlert,
handleUpdateWarning,
handleOnValuesChange: debounceHandleValuesChange,
warningStatus,
checkTokenRef,
generateGPUIds,
handleEvaluate,
setWarningStatus
};
+1 -1
View File
@@ -61,7 +61,7 @@ export interface ListItem {
name: string;
hostname: string;
address: string;
labels: object;
labels: Record<string, string>;
state: string;
ip: string;
state_message: string;