fix: gpu selector by cluster

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent 207c90e26c
commit 553ec724c2
40 changed files with 1106 additions and 1233 deletions
+5 -3
View File
@@ -52,9 +52,11 @@ export async function queryModelsList(
);
}
export async function queryGPUList(params?: Global.SearchParams) {
export async function queryGPUList<T extends Record<string, any>>(
params?: Global.SearchParams & T
) {
return request<Global.PageResponse<GPUListItem>>(`/gpu-devices`, {
methos: 'GET',
method: 'GET',
params
});
}
@@ -172,7 +174,7 @@ export async function queryModelScopeModels(
config?: any
) {
const tagsCriterion = params.tags?.map((tag: string) => {
return { category: 'libraries', predicate: 'contains', values: [tag] };
return { category: 'tags', predicate: 'contains', values: [tag] };
});
const tasksCriterion = params.tasks?.map((task: string) => {
return { category: 'tasks', predicate: 'contains', values: [task] };
@@ -30,7 +30,6 @@ import vllmConfig from '../config/vllm-config';
import dataformStyles from '../style/data-form.less';
import GPUCard from './gpu-card';
import Performance from './performance';
import Scaling from './scaling';
interface AdvanceConfigProps {
isGGUF: boolean;
@@ -460,19 +459,19 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
forceRender: true,
children: <Performance></Performance>
},
{
key: '3',
label: (
<span
style={{ fontWeight: 'var(--font-weight-medium)' }}
className="font-size-14"
>
Scaling
</span>
),
forceRender: true,
children: <Scaling></Scaling>
},
// {
// key: '3',
// label: (
// <span
// style={{ fontWeight: 'var(--font-weight-medium)' }}
// className="font-size-14"
// >
// Scaling
// </span>
// ),
// forceRender: true,
// children: <Scaling></Scaling>
// },
{
key: '1',
label: (
+106 -90
View File
@@ -6,17 +6,13 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
import React, { forwardRef, useImperativeHandle } from 'react';
import {
backendOptionsMap,
excludeFields,
modelSourceMap,
sourceOptions
} from '../config';
import { backendOptionsMap, excludeFields, sourceOptions } from '../config';
import { FormInnerContext } from '../config/form-context';
import { FormData, SourceType } from '../config/types';
import CatalogFrom from '../forms/catalog';
import HuggingFaceForm from '../forms/hugging-face';
import LocalPathForm from '../forms/local-path';
import { useGenerateGPUOptions } from '../hooks/use-form-initial-values';
import AdvanceConfig from './advance-config';
interface DataFormProps {
@@ -29,8 +25,7 @@ interface DataFormProps {
sourceDisable?: boolean;
backendOptions?: Global.BaseOption<string>[];
sourceList?: Global.BaseOption<string>[];
gpuOptions: any[];
modelFileOptions?: any[];
clusterList: Global.BaseOption<number>[];
fields?: string[];
onValuesChange?: (changedValues: any, allValues: any) => void;
onSourceChange?: (value: string) => void;
@@ -38,11 +33,6 @@ interface DataFormProps {
onBackendChange?: (value: string) => void;
}
const SEARCH_SOURCE = [
modelSourceMap.huggingface_value,
modelSourceMap.modelscope_value
];
const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
const {
action,
@@ -51,13 +41,13 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
sourceDisable = true,
backendOptions,
sourceList,
gpuOptions = [],
modelFileOptions = [],
clusterList = [],
fields = ['source'],
onSourceChange,
onValuesChange,
onOk
} = props;
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
const { getRuleMessage } = useAppUtils();
const [form] = Form.useForm();
const intl = useIntl();
@@ -143,6 +133,10 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
onSourceChange?.(val);
};
const handleClusterChange = (value: number) => {
getGPUOptionList({ clusterId: value });
};
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
const fieldName = Object.keys(changedValues)[0];
@@ -172,6 +166,9 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
},
getFieldsValue: () => {
return form.getFieldsValue();
},
getGPUOptionList(params: { clusterId: number }) {
getGPUOptionList(params);
}
};
},
@@ -179,78 +176,96 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
);
return (
<Form
name="deployModel"
form={form}
onFinish={handleOk}
preserve={false}
style={{ padding: '16px 24px' }}
clearOnDestroy={true}
onValuesChange={handleOnValuesChange}
scrollToFirstError={true}
initialValues={{
replicas: 1,
source: props.source,
placement_strategy: 'spread',
cpu_offloading: true,
scheduleType: 'auto',
categories: null,
restart_on_error: true,
distributed_inference_across_workers: true,
...initialValues
<FormInnerContext.Provider
value={{
onBackendChange: handleBackendChange,
onValuesChange: onValuesChange,
gpuOptions: gpuOptions
}}
>
<Form.Item<FormData>
name="name"
rules={[
{
required: true,
message: getRuleMessage('input', 'common.table.name')
}
]}
<Form
name="deployModel"
form={form}
onFinish={handleOk}
preserve={false}
style={{ padding: '16px 24px' }}
clearOnDestroy={true}
onValuesChange={handleOnValuesChange}
scrollToFirstError={true}
initialValues={{
replicas: 1,
source: props.source,
placement_strategy: 'spread',
cpu_offloading: true,
scheduleType: 'auto',
categories: null,
restart_on_error: true,
distributed_inference_across_workers: true,
...initialValues
}}
>
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item>
{fields.includes('source') && (
<Form.Item<FormData>
name="source"
name="name"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.source')
message: getRuleMessage('input', 'common.table.name')
}
]}
>
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item>
{fields.includes('source') && (
<Form.Item<FormData>
name="source"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.source')
}
]}
>
{
<SealSelect
onChange={handleOnSourceChange}
disabled={sourceDisable}
label={intl.formatMessage({
id: 'models.form.source'
})}
options={sourceList ?? sourceOptions}
required
></SealSelect>
}
</Form.Item>
)}
<HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm>
<Form.Item<FormData>
name="cluster_id"
rules={[
{
required: true,
message: getRuleMessage('select', 'Cluster', false)
}
]}
>
{
<SealSelect
onChange={handleOnSourceChange}
disabled={sourceDisable}
label={intl.formatMessage({
id: 'models.form.source'
})}
options={sourceList ?? sourceOptions}
onChange={handleClusterChange}
label="Cluster"
options={clusterList}
required
></SealSelect>
}
</Form.Item>
)}
<FormInnerContext.Provider
value={{
onBackendChange: handleBackendChange,
onValuesChange: onValuesChange,
gpuOptions: gpuOptions
}}
>
<HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm>
</FormInnerContext.Provider>
{/* <Form.Item name="backend" rules={[{ required: true }]}>
{/* <Form.Item name="backend" rules={[{ required: true }]}>
<SealSelect
required
onChange={handleBackendChange}
@@ -291,25 +306,26 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
}
></SealSelect>
</Form.Item> */}
<CatalogFrom></CatalogFrom>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
<AdvanceConfig
form={form}
gpuOptions={gpuOptions}
isGGUF={isGGUF}
action={action}
source={props.source}
backendOptions={backendOptions}
handleBackendChange={handleBackendChange}
></AdvanceConfig>
</Form>
<CatalogFrom></CatalogFrom>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
<AdvanceConfig
form={form}
gpuOptions={gpuOptions}
isGGUF={isGGUF}
action={action}
source={props.source}
backendOptions={backendOptions}
handleBackendChange={handleBackendChange}
></AdvanceConfig>
</Form>
</FormInnerContext.Provider>
);
});
@@ -2,6 +2,7 @@ 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 { CloseOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
@@ -17,11 +18,8 @@ import {
} from '../config';
import { FormContext } from '../config/form-context';
import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types';
import {
checkOnlyAscendNPU,
useCheckCompatibility,
useGenerateFormEditInitialValues
} from '../hooks';
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';
@@ -104,10 +102,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
handleOnValuesChange,
warningStatus
} = useCheckCompatibility();
const { getClusterList, clusterList } = useFormInitialValues();
const intl = useIntl();
const { getGPUList } = useGenerateFormEditInitialValues();
const form = useRef<any>({});
const [gpuOptions, setGpuOptions] = useState<any[]>([]);
const [isGGUF, setIsGGUF] = useState<boolean>(false);
const [sourceList, setSourceList] = useState<any[]>([]);
const [backendList, setBackendList] = useState<any[]>([]);
@@ -161,15 +158,6 @@ const AddModal: React.FC<AddModalProps> = (props) => {
return EmbeddingRerankFirstQuant.includes(_.toUpper(data.quantOption));
}
if (
data.backend === backendOptionsMap.llamaBox &&
checkOnlyAscendNPU(gpuOptions)
) {
return hasF16Ref.current
? AscendNPUQuant_F16.includes(_.toUpper(data.quantOption))
: AscendNPUQuant_Q8.includes(_.toUpper(data.quantOption));
}
return defaultQuant.includes(_.toUpper(data.quantOption));
};
@@ -360,6 +348,15 @@ const AddModal: React.FC<AddModalProps> = (props) => {
handleCheckFormData();
};
const initClusterId = () => {
const cluster_id =
clusterList?.find((item) => item.provider === ProviderValueMap.Custom)
?.value || clusterList?.[0]?.value;
console.log('cluster_id:', cluster_id);
return cluster_id;
};
const fetchSpecData = async () => {
try {
axiosToken.current?.cancel?.();
@@ -407,7 +404,10 @@ const AddModal: React.FC<AddModalProps> = (props) => {
size: defaultSpec.size,
backend: defaultSpec.backend
});
initFormDataBySource(defaultSpec);
initFormDataBySource({
...defaultSpec,
cluster_id: initClusterId()
});
const name = _.toLower(current.name).replace(/\s/g, '-') || '';
form.current.setFieldValue('name', name);
@@ -420,6 +420,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const allValues = generateSubmitData({
...defaultSpec,
categories: _.get(current, 'categories.0', null),
cluster_id: initClusterId(),
name
});
handleCheckCompatibility(allValues);
@@ -484,7 +485,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
useEffect(() => {
if (open) {
fetchSpecData();
setTimeout(() => {
fetchSpecData();
}, 100);
}
return () => {
axiosToken.current?.cancel?.();
@@ -498,9 +501,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
}, [open, current]);
useEffect(() => {
getGPUList().then((data) => {
setGpuOptions(data);
});
getClusterList();
}, []);
return (
@@ -608,7 +609,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
sourceDisable={false}
backendOptions={backendList}
sourceList={sourceList}
gpuOptions={gpuOptions}
clusterList={clusterList}
onBackendChange={handleBackendChange}
onSourceChange={handleSourceChange}
onValuesChange={onValuesChange}
+33 -89
View File
@@ -1,8 +1,8 @@
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 { ProviderValueMap } from '@/pages/cluster-management/config';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import _ from 'lodash';
@@ -68,10 +68,12 @@ type AddModalProps = {
source: SourceType;
isGGUF?: boolean;
width?: string | number;
gpuOptions: any[];
modelFileOptions: any[];
initialValues?: any;
deploymentType?: 'modelList' | 'modelFiles';
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
onOk: (values: FormData) => void;
onCancel: () => void;
};
@@ -95,7 +97,8 @@ const AddModal: FC<AddModalProps> = (props) => {
action,
width = 600,
deploymentType = 'modelList',
initialValues
initialValues,
clusterList
} = props || {};
const SEARCH_SOURCE = [
modelSourceMap.huggingface_value,
@@ -113,7 +116,7 @@ const AddModal: FC<AddModalProps> = (props) => {
warningStatus,
submitAnyway
} = useCheckCompatibility();
const { onSelectModel } = useSelectModel({ gpuOptions: props.gpuOptions });
const { onSelectModel } = useSelectModel({ gpuOptions: [] });
const form = useRef<any>({});
const intl = useIntl();
const [selectedModel, setSelectedModel] = useState<any>({});
@@ -161,15 +164,6 @@ const AddModal: FC<AddModalProps> = (props) => {
evaluateStateRef.current = state;
};
const updateEvaluateState = (state: EvaluateProccessType) => {
const currentRequestModelId = evaluateStateRef.current.requestModelId;
setEvaluteState({
...evaluateStateRef.current,
state
});
return currentRequestModelId;
};
const handleOnValuesChange = (data: {
changedValues: any;
allValues: any;
@@ -199,71 +193,6 @@ const AddModal: FC<AddModalProps> = (props) => {
return categories || null;
};
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']),
file_name: 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;
}
console.log('handleSelectModelFile:', item, selectedModel);
const modelInfo = onSelectModel(selectedModel, props.source);
form.current?.setFieldsValue?.({
..._.omit(modelInfo, ['name']),
file_name: item.fakeName,
categories: getCategory(item)
});
// evaluate the form data when select a model file
if (item.fakeName) {
onSelectFile(item, modelInfo, manual);
}
};
const handleCancelFiles = () => {
cancelEvaluate();
modelFileRef.current?.cancelRequest();
@@ -441,22 +370,37 @@ const AddModal: FC<AddModalProps> = (props) => {
onCancel?.();
}, [onCancel]);
const initClusterId = () => {
const cluster_id =
clusterList?.find((item) => item.provider === ProviderValueMap.Custom)
?.value || clusterList?.[0]?.value;
return cluster_id;
};
const handleOnOpen = () => {
if (props.deploymentType === 'modelFiles') {
form.current?.form?.setFieldsValue({
...props.initialValues
...props.initialValues,
cluster_id: initClusterId()
});
handleOnValuesChange?.({
changedValues: {},
allValues: props.initialValues,
allValues: {
...props.initialValues,
cluster_id: initClusterId()
},
source: source
});
} else {
let backend = checkOnlyAscendNPU(props.gpuOptions)
let backend = checkOnlyAscendNPU([])
? backendOptionsMap.ascendMindie
: backendOptionsMap.vllm;
form.current?.setFieldValue?.('backend', backend);
form.current?.setFieldsValue?.({
backend,
cluster_id: initClusterId()
});
}
};
@@ -483,6 +427,9 @@ const AddModal: FC<AddModalProps> = (props) => {
useEffect(() => {
if (open) {
handleOnOpen();
form.current?.getGPUOptionList?.({
clusterId: initClusterId()
});
} else {
cancelEvaluate();
clearCahceFormValues();
@@ -495,7 +442,7 @@ const AddModal: FC<AddModalProps> = (props) => {
message: []
});
};
}, [open, props.gpuOptions.length]);
}, [open, clusterList]);
return (
<GSDrawer
@@ -534,7 +481,7 @@ const AddModal: FC<AddModalProps> = (props) => {
handleOnSelectModelAfterEvaluate
}
displayEvaluateStatus={displayEvaluateStatus}
gpuOptions={props.gpuOptions}
gpuOptions={[]}
></SearchModel>
</ColumnWrapper>
<Separator></Separator>
@@ -559,8 +506,6 @@ const AddModal: FC<AddModalProps> = (props) => {
value={{
isGGUF: isGGUF,
pageAction: action,
modelFileOptions: props.modelFileOptions,
gpuOptions: props.gpuOptions,
onValuesChange: onValuesChange
}}
>
@@ -609,12 +554,11 @@ const AddModal: FC<AddModalProps> = (props) => {
initialValues={initialValues}
source={source}
action={action}
clusterList={clusterList}
selectedModel={selectedModel}
onOk={handleOnOk}
ref={form}
isGGUF={isGGUF}
gpuOptions={props.gpuOptions}
modelFileOptions={props.modelFileOptions}
onBackendChange={handleBackendChange}
onValuesChange={onValuesChange}
></DataForm>
+5 -12
View File
@@ -1,4 +1,3 @@
import CheckboxField from '@/components/seal-form/checkbox-field';
import SealCascader from '@/components/seal-form/seal-cascader';
import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
@@ -7,8 +6,7 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import { backendOptionsMap } from '../config';
import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types';
import { useFormContext, useFormInnerContext } from '../config/form-context';
import GPUCard from './gpu-card';
const scheduleTypeTips = [
@@ -30,13 +28,8 @@ const scheduleTypeTips = [
const Performance: React.FC = () => {
const intl = useIntl();
const {
onValuesChange,
onQuantizationChange,
gpuOptions,
source,
quantizationOptions
} = useFormContext();
const { gpuOptions } = useFormInnerContext();
const { onValuesChange, onQuantizationChange } = useFormContext();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance();
@@ -167,7 +160,7 @@ const Performance: React.FC = () => {
</Form.Item>
</>
)}
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
{/* <div style={{ paddingBottom: 22, paddingLeft: 10 }}>
<Form.Item<FormData>
name="optimize_long_prompt"
valuePropName="checked"
@@ -192,7 +185,7 @@ const Performance: React.FC = () => {
})}
></CheckboxField>
</Form.Item>
</div>
</div> */}
</>
);
};
+1 -2
View File
@@ -45,8 +45,7 @@ const CheckboxField: React.FC<{
const Scaling: React.FC = () => {
const intl = useIntl();
const { onValuesChange, onQuantizationChange, source, quantizationOptions } =
useFormContext();
const { onValuesChange, onQuantizationChange } = useFormContext();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance();
@@ -199,7 +199,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
try {
const params = {
Name: `${searchInputRef.current}`,
tags: [],
tags: ['gptq'],
tasks: filterTaskRef.current
? ([ModelscopeTaskMap[filterTaskRef.current]] as string[])
: [],
+19 -130
View File
@@ -14,11 +14,6 @@ import useTableRowSelection from '@/hooks/use-table-row-selection';
import useTableSort from '@/hooks/use-table-sort';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { handleBatchRequest } from '@/utils';
import {
IS_FIRST_LOGIN,
readState,
writeState
} from '@/utils/localstore/index';
import {
DownOutlined,
QuestionCircleOutlined,
@@ -26,16 +21,7 @@ import {
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl, useNavigate } from '@umijs/max';
import {
Button,
Empty,
Input,
Select,
Space,
Tooltip,
Typography,
message
} from 'antd';
import { Button, Input, Select, Space, Tooltip, message } from 'antd';
import dayjs from 'dayjs';
import { useAtom } from 'jotai';
import _ from 'lodash';
@@ -76,7 +62,7 @@ import {
ModelInstanceListItem,
SourceType
} from '../config/types';
import { useGenerateFormEditInitialValues } from '../hooks';
import useFormInitialValues from '../hooks/use-form-initial-values';
import APIAccessInfoModal from './api-access-info';
import DeployModal from './deploy-modal';
import Instances from './instances';
@@ -88,6 +74,7 @@ interface ModelsProps {
handleNameChange: (e: any) => void;
handleShowSizeChange?: (page: number, size: number) => void;
handlePageChange: (page: number, pageSize: number | undefined) => void;
handleClusterChange: (value: number) => void;
handleDeleteSuccess: () => void;
handleCategoryChange: (val: any) => void;
onViewLogs: () => void;
@@ -103,29 +90,12 @@ interface ModelsProps {
};
deleteIds?: number[];
workerList: WorkerListItem[];
modelFileOptions: any[];
catalogList?: any[];
dataSource: ListItem[];
loading: boolean;
loadend: boolean;
total: number;
}
const clusterList = [
{
label: 'Custom',
value: 'custom'
},
{
label: 'Kubernetes',
value: 'kubernetes'
},
{
label: 'Digital Ocean',
value: 'digital_ocean'
}
];
const statusList = [
{
label: 'Running',
@@ -164,34 +134,27 @@ const Models: React.FC<ModelsProps> = ({
onCancelViewLogs,
handleCategoryChange,
handleOnToggleExpandAll,
handleClusterChange,
onStop,
onStart,
modelFileOptions,
deleteIds,
dataSource,
workerList,
catalogList,
queryParams,
loading,
loadend,
total
}) => {
const { getGPUList, generateFormValues, gpuDeviceList } =
useGenerateFormEditInitialValues();
const { getGPUOptionList, generateFormValues, clusterList, getClusterList } =
useFormInitialValues();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [updateFormInitials, setUpdateFormInitials] = useState<{
gpuOptions: any[];
modelFileOptions?: any[];
data: any;
isGGUF: boolean;
}>({
gpuOptions: [],
modelFileOptions: [],
data: {},
isGGUF: false
});
const [isFirstLogin, setIsFirstLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [expandAtom, setExpandAtom] = useAtom(modelsExpandKeysAtom);
const intl = useIntl();
const navigate = useNavigate();
@@ -218,17 +181,13 @@ const Models: React.FC<ModelsProps> = ({
width: number | string;
hasLinuxWorker?: boolean;
source: SourceType;
gpuOptions: any[];
isGGUF?: boolean;
modelFileOptions?: any[];
}>({
show: false,
hasLinuxWorker: false,
width: 600,
isGGUF: false,
source: modelSourceMap.huggingface_value as SourceType,
gpuOptions: [],
modelFileOptions: []
source: modelSourceMap.huggingface_value as SourceType
});
const currentData = useRef<ListItem>({} as ListItem);
const [currentInstance, setCurrentInstance] = useState<{
@@ -243,17 +202,6 @@ const Models: React.FC<ModelsProps> = ({
});
const modalRef = useRef<any>(null);
useEffect(() => {
if (!catalogList?.length) {
return;
}
const getFirstLoginState = async () => {
const is_first_login = await readState(IS_FIRST_LOGIN);
setIsFirstLogin(is_first_login);
};
getFirstLoginState();
}, [catalogList?.length]);
useEffect(() => {
if (deleteIds?.length) {
rowSelection.removeSelectedKey(deleteIds);
@@ -262,7 +210,9 @@ const Models: React.FC<ModelsProps> = ({
useEffect(() => {
const getData = async () => {
await getGPUList();
const res = await getClusterList();
const clusterId = res[0]?.value;
await getGPUOptionList({ clusterId });
};
getData();
return () => {
@@ -470,10 +420,8 @@ const Models: React.FC<ModelsProps> = ({
}, []);
const handleEdit = async (row: ListItem) => {
const initialValues = generateFormValues(row, gpuDeviceList.current);
const initialValues = generateFormValues(row, []);
setUpdateFormInitials({
gpuOptions: gpuDeviceList.current,
modelFileOptions: modelFileOptions,
data: initialValues,
isGGUF: row.backend === backendOptionsMap.llamaBox
});
@@ -584,9 +532,7 @@ const Models: React.FC<ModelsProps> = ({
if (config) {
setOpenDeployModal({
...config,
hasLinuxWorker: hasLinuxWorker,
gpuOptions: gpuDeviceList.current,
modelFileOptions: modelFileOptions
hasLinuxWorker: hasLinuxWorker
});
}
};
@@ -652,7 +598,10 @@ const Models: React.FC<ModelsProps> = ({
span: 3,
render: (text: string, record: ListItem) => (
<span className="flex flex-column" style={{ width: '100%' }}>
{['Custom', 'Kubernetes', 'Digital Ocean'][record.id] || 'Custom'}
{
clusterList.find((item) => item.value === record.cluster_id)
?.label
}
</span>
)
},
@@ -724,31 +673,6 @@ const Models: React.FC<ModelsProps> = ({
];
}, [sortOrder, intl, handleSelect]);
const handleOnClick = async () => {
if (isLoading) {
return;
}
const data = catalogList?.[0] || {};
try {
setIsLoading(true);
const modelData = await createModel({
data: data
});
writeState(IS_FIRST_LOGIN, false);
setIsFirstLogin(false);
setTimeout(() => {
updateExpandedRowKeys([modelData.id]);
}, 300);
message.success(intl.formatMessage({ id: 'common.message.success' }));
handleSearch?.();
} catch (error) {
// ingore
} finally {
setIsLoading(false);
}
};
const handleToggleExpandAll = useCallback(
(expanded: boolean) => {
const keys = dataSource.map((item) => item.id);
@@ -760,33 +684,6 @@ const Models: React.FC<ModelsProps> = ({
[dataSource]
);
const renderEmpty = useMemo(() => {
if (dataSource.length || !isFirstLogin || !catalogList?.length) {
return null;
}
return (
<div
className="flex-column justify-center flex-center"
style={{ height: 300 }}
>
<Empty description=""></Empty>
<Typography.Title level={4} style={{ marginBottom: 30 }}>
{intl.formatMessage({ id: 'models.table.list.empty' })}
</Typography.Title>
<div>
<Button type="primary" onClick={handleOnClick} loading={isLoading}>
<span
className="flex-center"
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'models.table.list.getStart' })
}}
></span>
</Button>
</div>
</div>
);
}, [dataSource.length, isFirstLogin, isLoading, intl]);
return (
<>
<PageContainer
@@ -831,17 +728,9 @@ const Models: React.FC<ModelsProps> = ({
style={{ width: 160 }}
size="large"
maxTagCount={1}
onChange={handleClusterChange}
options={clusterList}
></Select>
<Select
allowClear
showSearch={false}
placeholder="Running Replicas"
style={{ width: 140 }}
size="large"
maxTagCount={1}
options={statusList}
></Select>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
@@ -918,6 +807,7 @@ const Models: React.FC<ModelsProps> = ({
action={PageAction.EDIT}
title={intl.formatMessage({ id: 'models.title.edit' })}
updateFormInitials={updateFormInitials}
clusterList={clusterList}
onCancel={handleModalCancel}
onOk={handleModalOk}
></UpdateModel>
@@ -929,8 +819,7 @@ const Models: React.FC<ModelsProps> = ({
width={openDeployModal.width}
isGGUF={openDeployModal.isGGUF}
hasLinuxWorker={openDeployModal.hasLinuxWorker}
gpuOptions={openDeployModal.gpuOptions}
modelFileOptions={openDeployModal.modelFileOptions || []}
clusterList={clusterList}
onCancel={handleDeployModalCancel}
onOk={handleCreateModel}
></DeployModal>
+89 -113
View File
@@ -1,7 +1,6 @@
import ModalFooter from '@/components/modal-footer';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
@@ -10,9 +9,7 @@ import { Button, Form, Modal } from 'antd';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef } from 'react';
import {
backendLabelMap,
backendOptionsMap,
backendTipsList,
updateExcludeFields as excludeFields,
getSourceRepoConfigValue,
modelSourceMap,
@@ -24,6 +21,7 @@ import { FormData, ListItem } from '../config/types';
import HuggingFaceForm from '../forms/hugging-face';
import LocalPathForm from '../forms/local-path';
import { useCheckCompatibility } from '../hooks';
import { useGenerateGPUOptions } from '../hooks/use-form-initial-values';
import AdvanceConfig from './advance-config';
import ColumnWrapper from './column-wrapper';
import CompatibilityAlert from './compatible-alert';
@@ -34,9 +32,12 @@ type AddModalProps = {
open: boolean;
updateFormInitials: {
data?: ListItem;
gpuOptions: any[];
isGGUF: boolean;
};
clusterList: Global.BaseOption<
number,
{ provider: string; state: string | number }
>[];
onOk: (values: FormData) => void;
onCancel: () => void;
};
@@ -48,7 +49,8 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
open,
onOk,
onCancel,
updateFormInitials: { gpuOptions, isGGUF, data: formData }
clusterList,
updateFormInitials: { isGGUF, data: formData }
} = props || {};
const intl = useIntl();
const {
@@ -58,12 +60,17 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
checkTokenRef,
warningStatus
} = useCheckCompatibility();
const { getGPUOptionList, gpuOptions } = useGenerateGPUOptions();
const { getRuleMessage } = useAppUtils();
const [form] = Form.useForm();
const submitAnyway = useRef<boolean>(false);
const originFormData = useRef<any>(null);
const handleClusterChange = (value: number) => {
getGPUOptionList({ clusterId: value });
};
const setOriginalFormData = () => {
if (!originFormData.current) {
originFormData.current = _.cloneDeep(formData);
@@ -336,48 +343,53 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
onValuesChange: handleManulOnValuesChange
}}
>
<Form
name="updateModalForm"
form={form}
onFinish={handleOk}
onValuesChange={onValuesChange}
scrollToFirstError={true}
preserve={false}
clearOnDestroy={true}
initialValues={{
...formData
}}
style={{
padding: 'var(--ant-modal-content-padding)',
paddingBlock: 0
<FormInnerContext.Provider
value={{
onBackendChange: handleBackendChange,
gpuOptions: gpuOptions
}}
>
<Form.Item<FormData>
name="name"
rules={[
{
required: true,
message: getRuleMessage('input', 'common.table.name')
}
]}
<Form
name="updateModalForm"
form={form}
onFinish={handleOk}
onValuesChange={onValuesChange}
scrollToFirstError={true}
preserve={false}
clearOnDestroy={true}
initialValues={{
...formData
}}
style={{
padding: 'var(--ant-modal-content-padding)',
paddingBlock: 0
}}
>
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item>
<Form.Item<FormData>
name="source"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.source')
}
]}
>
{action === PageAction.EDIT && (
<Form.Item<FormData>
name="name"
rules={[
{
required: true,
message: getRuleMessage('input', 'common.table.name')
}
]}
>
<SealInput.Input
label={intl.formatMessage({
id: 'common.table.name'
})}
required
></SealInput.Input>
</Form.Item>
<Form.Item<FormData>
name="source"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.source')
}
]}
>
<SealSelect
disabled={true}
label={intl.formatMessage({
@@ -386,82 +398,46 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
options={sourceOptions}
required
></SealSelect>
)}
</Form.Item>
<FormInnerContext.Provider
value={{
onBackendChange: handleBackendChange,
gpuOptions: gpuOptions
}}
>
</Form.Item>
<HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm>
</FormInnerContext.Provider>
<Form.Item name="backend" rules={[{ required: true }]}>
<SealSelect
required
onChange={handleAsyncBackendChange}
label={intl.formatMessage({ id: 'models.form.backend' })}
description={<TooltipList list={backendTipsList}></TooltipList>}
options={[
<Form.Item<FormData>
name="cluster_id"
rules={[
{
label: backendLabelMap[backendOptionsMap.llamaBox],
value: backendOptionsMap.llamaBox,
disabled:
formData?.source === modelSourceMap.local_path_value
? false
: !isGGUF
},
{
label: backendLabelMap[backendOptionsMap.vllm],
value: backendOptionsMap.vllm,
disabled:
formData?.source === modelSourceMap.local_path_value ||
isVllmOrAscend
? false
: isGGUF
},
{
label: backendLabelMap[backendOptionsMap.ascendMindie],
value: backendOptionsMap.ascendMindie,
disabled:
formData?.source === modelSourceMap.local_path_value ||
isVllmOrAscend
? false
: isGGUF
},
{
label: backendLabelMap[backendOptionsMap.voxBox],
value: backendOptionsMap.voxBox,
disabled:
formData?.source !== modelSourceMap.local_path_value ||
!isVllmOrAscend
required: true,
message: getRuleMessage('select', 'Cluster', false)
}
]}
disabled={
action === PageAction.EDIT &&
formData?.source !== modelSourceMap.local_path_value &&
!isVllmOrAscend
>
{
<SealSelect
onChange={handleClusterChange}
label="Cluster"
options={clusterList}
required
></SealSelect>
}
></SealSelect>
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
<AdvanceConfig
form={form}
gpuOptions={gpuOptions}
action={PageAction.EDIT}
source={formData?.source || ''}
isGGUF={formData?.backend === backendOptionsMap.llamaBox}
></AdvanceConfig>
</Form>
<AdvanceConfig
form={form}
gpuOptions={gpuOptions}
action={PageAction.EDIT}
source={formData?.source || ''}
isGGUF={formData?.backend === backendOptionsMap.llamaBox}
></AdvanceConfig>
</Form>
</FormInnerContext.Provider>
</FormContext.Provider>
</ColumnWrapper>
</Modal>
@@ -8,7 +8,6 @@ interface FormContextProps {
pageAction: PageActionType;
sizeOptions?: Global.BaseOption<number>[];
quantizationOptions?: Global.BaseOption<string>[];
modelFileOptions?: any[];
gpuOptions?: any[];
onSizeChange?: (val: number) => void;
onQuantizationChange?: (val: string) => void;
+2
View File
@@ -20,6 +20,7 @@ export interface ListItem {
name: string;
description: string;
id: number;
cluster_id: number;
local_path?: string;
created_at: string;
updated_at: string;
@@ -69,6 +70,7 @@ export interface FormData {
description: string;
optimize_long_prompt: boolean;
enable_speculative_decoding: boolean;
cluster_id: number;
}
interface ComputedResourceClaim {
+4 -1
View File
@@ -20,6 +20,7 @@ type AddModalProps = {
width?: string | number;
hasLinuxWorker?: boolean;
workersList: Global.BaseOption<number>[];
workerOptions: any[];
onOk: (values: FormData) => void;
onCancel: () => void;
};
@@ -33,7 +34,8 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
onCancel,
hasLinuxWorker,
source,
width = 600
width = 600,
workerOptions
} = props || {};
const SEARCH_SOURCE = [
modelSourceMap.huggingface_value,
@@ -227,6 +229,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
onOk={handleOk}
source={source}
workersList={workersList}
workerOptions={workerOptions}
></TargetForm>
</>
</ColumnWrapper>
+20 -8
View File
@@ -13,11 +13,12 @@ interface TargetFormProps {
ref?: any;
workersList: Global.BaseOption<number>[];
source: string;
workerOptions: any[];
onOk: (values: any) => void;
}
const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
const { onOk, source, workersList } = props;
const { onOk, source, workersList, workerOptions } = props;
const { getRuleMessage } = useAppUtils();
const intl = useIntl();
const [form] = Form.useForm();
@@ -115,13 +116,24 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
}
]}
>
{
<SealSelect
label="Worker"
options={workersList}
required
></SealSelect>
}
<SealSelect
label="Worker"
options={workersList}
required
></SealSelect>
{/* <SealCascader
required
showSearch
expandTrigger="hover"
multiple={false}
popupClassName="cascader-popup-wrapper gpu-selector"
maxTagCount={1}
label="Worker"
options={workerOptions}
showCheckedStrategy="SHOW_CHILD"
value={form.getFieldValue(['gpu_selector', 'gpu_ids'])}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
></SealCascader> */}
</Form.Item>
{source !== modelSourceMap.local_path_value && (
<Form.Item<FormData>
+4 -156
View File
@@ -1,30 +1,19 @@
import { createAxiosToken } from '@/hooks/use-chunk-request';
import { queryModelFilesList, queryWorkersList } from '@/pages/resources/apis';
import {
WorkerStatusMap,
WorkerStatusMapValue
} from '@/pages/resources/config';
import { queryModelFilesList } from '@/pages/resources/apis';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { convertFileSize } from '@/utils';
import { useIntl } from '@umijs/max';
import { useDebounceFn } from 'ahooks';
import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { evaluationsModelSpec, queryGPUList } from '../apis';
import { evaluationsModelSpec } from '../apis';
import {
backendOptionsMap,
getSourceRepoConfigValue,
modelSourceMap,
modelTaskMap,
setSourceRepoConfigValue
modelTaskMap
} from '../config';
import { handleRecognizeAudioModel } from '../config/audio-catalog';
import {
EvaluateResult,
FormData,
GPUListItem,
ListItem
} from '../config/types';
import { EvaluateResult, FormData } from '../config/types';
export type MessageStatus = {
show: boolean;
@@ -41,123 +30,6 @@ export type WarningStausOptions = {
override?: boolean;
};
export const useGenerateFormEditInitialValues = () => {
const gpuDeviceList = useRef<any[]>([]);
const workerList = useRef<any[]>([]);
const generateCascaderOptions = (
list: GPUListItem[],
workerList: WorkerListItem[]
) => {
// pick the worker fields from gpuList
const workerFields = new Set(['worker_name', 'worker_id', 'worker_ip']);
// generate a map for workerList by name to data
const workerDataMap = new Map<string, WorkerListItem>();
for (const worker of workerList) {
workerDataMap.set(worker.name, worker);
}
const workersMap = new Map<string, GPUListItem[]>();
for (const gpu of list) {
if (!workersMap.has(gpu.worker_name)) {
workersMap.set(gpu.worker_name, []);
}
workersMap.get(gpu.worker_name)!.push(gpu);
}
const gpuSelectorList = Array.from(workersMap.entries()).map(
([workerName, items]) => {
const firstItem = items[0];
const currentState = workerDataMap.get(workerName)?.state || '';
const disDisabled = WorkerStatusMap.ready !== currentState;
return {
label: disDisabled
? `${workerName} [${WorkerStatusMapValue[currentState]}]`
: workerName,
value: workerName,
parent: true,
disabled: disDisabled,
children: items
.map((item) => ({
label: item.name,
value: item.id,
index: item.index,
...Object.fromEntries(
Object.entries(item).filter(([key]) => !workerFields.has(key))
)
}))
.sort((a, b) => a.index - b.index),
...Object.fromEntries(
Object.entries(firstItem).filter(([key]) => workerFields.has(key))
)
};
}
);
return gpuSelectorList;
};
const getGPUList = async () => {
const [gpuData, workerData] = await Promise.all([
queryGPUList({ page: 1, perPage: 100 }),
queryWorkersList({ page: 1, perPage: 100 })
]);
const gpuList = generateCascaderOptions(gpuData.items, workerData.items);
gpuDeviceList.current = gpuList;
workerList.current = workerData.items;
return gpuList;
};
const generateGPUSelector = (data: any, gpuOptions: any[]) => {
const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []);
if (gpu_ids.length === 0) {
return [];
}
const valueMap = new Map<string, string>();
gpuOptions?.forEach((item) => {
item.children?.forEach((child: any) => {
valueMap.set(child.value, item.value);
});
});
const gpuids: string[][] = gpu_ids
.map((id: string) => {
const parent = valueMap.get(id);
return parent ? [parent, id] : null;
})
.filter(Boolean) as string[][];
return data.backend === backendOptionsMap.voxBox ? gpuids[0] : gpuids;
};
const generateFormValues = (data: ListItem, gpuOptions: any[]) => {
const result = setSourceRepoConfigValue(data?.source || '', data);
const formData = {
...result.values,
categories: data?.categories?.length ? data.categories[0] : null,
scheduleType: data?.gpu_selector ? 'manual' : 'auto',
gpu_selector: data?.gpu_selector?.gpu_ids?.length
? {
gpu_ids: generateGPUSelector(data, gpuOptions)
}
: null
};
return formData;
};
return {
getGPUList,
generateFormValues,
gpuDeviceList,
workerList
};
};
export const useGenerateModelFileOptions = () => {
const getModelFileList = async () => {
try {
@@ -514,25 +386,6 @@ export const useCheckCompatibility = () => {
return null;
};
const checkRequiredValue = (allValues: any) => {
const { scheduleType } = allValues;
const gpuIds = allValues.gpu_selector?.gpu_ids || [];
const noLocalValue =
allValues.source === modelSourceMap.local_path_value &&
!allValues.local_path;
const noOllamaValue =
allValues.source === modelSourceMap.ollama_library_value &&
!allValues.ollama_library_model_name;
if (scheduleType === 'manual') {
return !gpuIds.length || noLocalValue || noOllamaValue;
}
return noLocalValue || noOllamaValue;
};
const clearCahceFormValues = () => {
cacheFormValuesRef.current = {};
};
@@ -585,11 +438,6 @@ export const useCheckCompatibility = () => {
return res;
};
const { run: debounceHandleValuesChange } = useDebounceFn(
handleOnValuesChange,
{ wait: 500 }
);
const cancelEvaluate = () => {
// update the requestId to cancel the current evaluation
updateRequestId();
@@ -0,0 +1,255 @@
import { queryClusterList } from '@/pages/cluster-management/apis';
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { queryWorkersList } from '@/pages/resources/apis';
import {
WorkerStatusMap,
WorkerStatusMapValue
} from '@/pages/resources/config';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import _ from 'lodash';
import { useState } from 'react';
import { queryGPUList } from '../apis';
import { backendOptionsMap, setSourceRepoConfigValue } from '../config';
import { GPUListItem, ListItem } from '../config/types';
interface CascaderOption {
label: string;
value: string | number;
parent?: boolean;
disabled?: boolean;
index?: number;
children?: CascaderOption[];
}
export const useGenerateGPUOptions = () => {
const [gpuOptions, setGpuOptions] = useState<CascaderOption[]>([]);
const generateCascaderGPUOptions = (
gpuList: GPUListItem[],
workerList: WorkerListItem[]
) => {
// pick the worker fields from gpuList
const workerFields = new Set(['worker_name', 'worker_id', 'worker_ip']);
// generate a map for workerList by name to data
const workerDataMap = new Map<string, WorkerListItem>();
for (const worker of workerList) {
workerDataMap.set(worker.name, worker);
}
const workersMap = new Map<string, GPUListItem[]>();
for (const gpu of gpuList) {
if (!workersMap.has(gpu.worker_name)) {
workersMap.set(gpu.worker_name, []);
}
workersMap.get(gpu.worker_name)!.push(gpu);
}
const gpuSelectorList = Array.from(workersMap.entries()).map(
([workerName, items]) => {
const firstItem = items[0];
const currentState = workerDataMap.get(workerName)?.state || '';
const disDisabled = WorkerStatusMap.ready !== currentState;
return {
label: disDisabled
? `${workerName} [${WorkerStatusMapValue[currentState]}]`
: workerName,
value: workerName,
parent: true,
disabled: disDisabled,
children: items
.map((item) => ({
label: item.name,
value: item.id,
index: item.index,
...Object.fromEntries(
Object.entries(item).filter(([key]) => !workerFields.has(key))
)
}))
.sort((a, b) => a.index - b.index),
...Object.fromEntries(
Object.entries(firstItem).filter(([key]) => workerFields.has(key))
)
};
}
);
return gpuSelectorList;
};
const getGPUOptionList = async (params?: { clusterId: number }) => {
const { clusterId } = params || {};
const [gpuData, workerData] = await Promise.all([
queryGPUList({
page: 1,
perPage: 100,
cluster_id: clusterId
}),
queryWorkersList({
page: 1,
perPage: 100,
cluster_id: clusterId
})
]);
const gpuList = generateCascaderGPUOptions(gpuData.items, workerData.items);
setGpuOptions(gpuList);
return gpuList;
};
return {
getGPUOptionList,
gpuOptions
};
};
export const useGenerateWorkerOptions = () => {
const [workerOptions, setWorkerOptions] = useState<CascaderOption[]>([]);
const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string | number }>[]
>([]);
const [workersList, setWorkersList] = useState<
Global.BaseOption<
number,
{ state: string; labels: Record<string, string> }
>[]
>([]);
const generateCascaderWorkerOptions = (
workerList: WorkerListItem[],
clusterList: ClusterListItem[]
) => {
const options = clusterList.map((cluster) => ({
label: cluster.name,
value: cluster.id,
parent: true,
children: workerList
.filter((worker) => worker.cluster_id === cluster.id)
.map((worker) => ({
disabled: WorkerStatusMap.ready !== worker.state,
label: worker.name,
value: worker.id
}))
}));
setWorkerOptions(options);
return options;
};
const getDataList = async (): Promise<
[WorkerListItem[], ClusterListItem[]]
> => {
const [workerRes, clusterRes] = await Promise.all([
queryWorkersList({
page: 1,
perPage: 100
}),
queryClusterList({
page: 1,
perPage: 100
})
]);
const workerList = workerRes.items || ([] as WorkerListItem[]);
const clusterList = clusterRes.items || ([] as ClusterListItem[]);
return [workerList, clusterList];
};
const getWorkerOptionList = async () => {
const data = await getDataList();
const [workerList, clusterList] = data;
generateCascaderWorkerOptions(workerList, clusterList);
setWorkersList(
workerList.map((item) => ({
state: item.state,
label: item.name,
value: item.id
}))
);
setClusterList(
clusterList.map((item) => ({
label: item.name,
value: item.id,
provider: item.provider,
state: item.state
}))
);
};
return {
getWorkerOptionList,
workerOptions,
clusterList,
workersList
};
};
export default function useFormInitialValues() {
const { getGPUOptionList } = useGenerateGPUOptions();
const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string | number }>[]
>([]);
const getClusterList = async (): Promise<Global.BaseOption<number>[]> => {
try {
const response = await queryClusterList({
page: 1,
perPage: 100
});
const list = response.items.map((item) => ({
label: item.name,
value: item.id,
provider: item.provider,
state: item.state
}));
setClusterList(list);
return list;
} catch (error) {
console.error('Failed to fetch cluster list:', error);
setClusterList([]);
return [];
}
};
const generateGPUSelector = (data: any, gpuOptions: any[]) => {
const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []);
if (gpu_ids.length === 0) {
return [];
}
const valueMap = new Map<string, string>();
gpuOptions?.forEach((item) => {
item.children?.forEach((child: any) => {
valueMap.set(child.value, item.value);
});
});
const gpuids: string[][] = gpu_ids
.map((id: string) => {
const parent = valueMap.get(id);
return parent ? [parent, id] : null;
})
.filter(Boolean) as string[][];
return data.backend === backendOptionsMap.voxBox ? gpuids[0] : gpuids;
};
const generateFormValues = (data: ListItem, gpuOptions: any[]) => {
const result = setSourceRepoConfigValue(data?.source || '', data);
const formData = {
...result.values,
categories: data?.categories?.length ? data.categories[0] : null,
scheduleType: data?.gpu_selector ? 'manual' : 'auto',
gpu_selector: data?.gpu_selector?.gpu_ids?.length
? {
gpu_ids: generateGPUSelector(data, gpuOptions)
}
: null
};
return formData;
};
return {
getGPUOptionList,
generateFormValues,
getClusterList,
clusterList
};
}
+35 -97
View File
@@ -3,26 +3,19 @@ import useSetChunkRequest from '@/hooks/use-chunk-request';
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
import { queryWorkersList } from '@/pages/resources/apis';
import { ListItem as WokerListItem } from '@/pages/resources/config/types';
import { IS_FIRST_LOGIN, readState } from '@/utils/localstore';
import _ from 'lodash';
import qs from 'query-string';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
MODELS_API,
MODEL_INSTANCE_API,
queryCatalogItemSpec,
queryCatalogList,
queryModelsInstances,
queryModelsList
} from './apis';
import TableList from './components/table-list';
import { backendOptionsMap } from './config';
import { ListItem } from './config/types';
import { useGenerateModelFileOptions } from './hooks';
const Models: React.FC = () => {
const { getModelFileList, generateModelFileOptions } =
useGenerateModelFileOptions();
const { setChunkRequest, createAxiosToken } = useSetChunkRequest();
const { setChunkRequest: setModelInstanceChunkRequest } =
useSetChunkRequest();
@@ -41,9 +34,7 @@ const Models: React.FC = () => {
total: 0
});
const [catalogList, setCatalogList] = useState<any[]>([]);
const [workerList, setWorkerList] = useState<WokerListItem[]>([]);
const [modelFileOptions, setModelFileOptions] = useState<any[]>([]);
const chunkRequedtRef = useRef<any>();
const chunkInstanceRequedtRef = useRef<any>();
const isPageHidden = useRef(false);
@@ -168,20 +159,20 @@ const Models: React.FC = () => {
[queryParams]
);
const handleQueryChange = (params: any) => {
setQueryParams({
...queryParams,
...params
});
fetchData({ query: { ...queryParams, ...params } });
};
const handlePageChange = useCallback(
(page: number, pageSize: number | undefined) => {
setQueryParams({
...queryParams,
handleQueryChange({
page: page,
perPage: pageSize || 10
});
fetchData({
query: {
...queryParams,
page: page,
perPage: pageSize || 10
}
});
},
[queryParams]
);
@@ -269,18 +260,10 @@ const Models: React.FC = () => {
);
const debounceUpdateFilter = _.debounce((e: any) => {
setQueryParams({
...queryParams,
handleQueryChange({
page: 1,
search: e.target.value
});
fetchData({
query: {
...queryParams,
page: 1,
search: e.target.value
}
});
createModelsChunkRequest({
search: e.target.value,
categories: queryParams.categories
@@ -289,27 +272,27 @@ const Models: React.FC = () => {
const handleNameChange = useCallback(debounceUpdateFilter, [queryParams]);
const handleCategoryChange = useCallback(
async (value: any) => {
setQueryParams({
...queryParams,
page: 1,
categories: value
});
fetchData({
query: {
...queryParams,
page: 1,
categories: value
}
});
createModelsChunkRequest({
search: queryParams.search,
categories: value
});
},
[queryParams]
);
const handleCategoryChange = async (value: any) => {
handleQueryChange({
page: 1,
categories: value
});
createModelsChunkRequest({
search: queryParams.search,
categories: value
});
};
const handleClusterChange = async (value: any) => {
handleQueryChange({
page: 1,
cluster_id: value
});
createModelsChunkRequest({
search: queryParams.search,
cluster_id: value
});
};
useEffect(() => {
let timer: any = null;
@@ -345,55 +328,12 @@ const Models: React.FC = () => {
}
};
// get catalog list
const getCataLogList = async () => {
const isFirstLogin = readState(IS_FIRST_LOGIN);
if (!isFirstLogin) {
return;
}
try {
const res: any = await queryCatalogList({
search: 'DeepSeek R1',
page: 1
});
if (!res?.items?.length) {
return [];
}
const name = _.toLower(res?.items[0]?.name).replace(/\s/g, '-') || '';
const catalogSpecs: any = await queryCatalogItemSpec({
id: res?.items[0]?.id
});
const list = catalogSpecs?.items?.map((item: any) => {
item.name = name;
return item;
});
const deepseekr1dstill = _.toLower('DeepSeek-R1-Distill-Qwen-1.5B');
const resultList = list?.filter((item: any) => {
return (
item.backend === backendOptionsMap.llamaBox &&
(_.toLower(item?.huggingface_repo_id)?.indexOf(deepseekr1dstill) >
-1 ||
_.toLower(item?.model_scope_model_id)?.indexOf(deepseekr1dstill) >
-1)
);
});
return resultList || [];
} catch (error) {
// ignore
return [];
}
};
const init = async () => {
const [modelRes, workerRes, modelFileList] = await Promise.all([
const [modelRes, workerRes] = await Promise.all([
getTableData(),
getWorkerList(),
getModelFileList()
getWorkerList()
]);
const dataList = generateModelFileOptions(
modelFileList,
workerRes.items || []
);
setDataSource({
dataList: modelRes.items || [],
loading: false,
@@ -402,7 +342,6 @@ const Models: React.FC = () => {
deletedIds: []
});
setWorkerList(workerRes.items || []);
setModelFileOptions(dataList);
clearTimeout(timer);
timer = setTimeout(() => {
@@ -464,6 +403,7 @@ const Models: React.FC = () => {
dataSource={dataSource.dataList}
handleNameChange={handleNameChange}
handleCategoryChange={handleCategoryChange}
handleClusterChange={handleClusterChange}
handleSearch={handleSearch}
handlePageChange={handlePageChange}
handleDeleteSuccess={fetchData}
@@ -478,8 +418,6 @@ const Models: React.FC = () => {
total={dataSource.total}
deleteIds={dataSource.deletedIds}
workerList={workerList}
modelFileOptions={modelFileOptions}
catalogList={catalogList}
></TableList>
</TableContext.Provider>
);