chore: display available clusters

This commit is contained in:
jialin
2025-09-16 20:32:26 +08:00
parent c9c9401b0b
commit d063dfd652
14 changed files with 121 additions and 39 deletions
+7
View File
@@ -16,3 +16,10 @@ export const getRequestId = () => {
const store = getDefaultStore();
return store.get(requestIdAtom);
};
export const clusterListAtom = atom<
{
label: string;
value: number;
}[]
>([]);
+4 -1
View File
@@ -167,5 +167,8 @@ export default {
'models.form.gpuCount': 'GPUs per Replica',
'models.form.gpuType': 'GPU Type',
'models.form.optimizeLongPrompt': 'Optimize Long Prompt',
'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding'
'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding',
'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
'models.form.check.otherClustersAvailable':
'Available clusters: {clusters}. Please switch cluster.'
};
+7 -2
View File
@@ -164,11 +164,16 @@ export default {
'models.form.gpuCount': '各レプリカのGPU数',
'models.form.gpuType': 'GPU タイプ',
'models.form.optimizeLongPrompt': '長いプロンプトを最適化',
'models.form.enableSpeculativeDecoding': '推測デコーディングを有効にする'
'models.form.enableSpeculativeDecoding': '推測デコーディングを有効にする',
'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
'models.form.check.otherClustersAvailable':
'Available clusters: {clusters}. Please switch cluster.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.ollama.deprecated.title': 'Deprecation Notice',
// 2. 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
// 3. 'models.backend.mindie.310p':'Ascend 310P only supports FP16, so you need to set --dtype=float16.'
// 3. 'models.backend.mindie.310p':'Ascend 310P only supports FP16, so you need to set --dtype=float16.',
// 4. 'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
// 5. 'models.form.check.otherClustersAvailable': 'Available clusters: {clusters}. Please switch cluster.'
// ========== End of To-Do List ==========
+7 -2
View File
@@ -166,7 +166,10 @@ export default {
'models.form.gpuCount': 'GPUs per Replica',
'models.form.gpuType': 'GPU Type',
'models.form.optimizeLongPrompt': 'Optimize Long Prompt',
'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding'
'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding',
'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
'models.form.check.otherClustersAvailable':
'Available clusters: {clusters}. Please switch cluster.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -176,5 +179,7 @@ export default {
// 4. 'models.form.gpuCount': 'GPUs per Replica',
// 5. 'models.form.gpuType': 'GPU Type',
// 6. 'models.form.optimizeLongPrompt': 'Optimize Long Prompt',
// 7. 'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding'
// 7. 'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding',
// 8. 'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
// 9. 'models.form.check.otherClustersAvailable': 'Available clusters: {clusters}. Please switch cluster.'
// ========== End of To-Do List ==========
+4 -1
View File
@@ -157,5 +157,8 @@ export default {
'models.form.gpuCount': '每副本 GPU 数量',
'models.form.gpuType': 'GPU 类型',
'models.form.optimizeLongPrompt': '优化长提示',
'models.form.enableSpeculativeDecoding': '启用推测解码'
'models.form.enableSpeculativeDecoding': '启用推测解码',
'models.form.check.clusterUnavailable': '当前集群不可用',
'models.form.check.otherClustersAvailable':
'可用的集群有: {clusters}。请切换集群。'
};
+19 -19
View File
@@ -376,23 +376,23 @@ export async function evaluationsModelSpec(
},
options: { token: any }
) {
return request<{ results: EvaluateResult[] }>(`${MODEL_EVALUATIONS}`, {
method: 'POST',
data,
cancelToken: options?.token
});
}
const result = await request<{ results: EvaluateResult[] }>(
`${MODEL_EVALUATIONS}`,
{
method: 'POST',
data,
cancelToken: options?.token
}
);
// export const evaluationsModelSpec = async (
// data: {
// model_specs: EvaluateSpec[];
// },
// options: { token: any }
// ) => {
// const response = await fetch(`v1/${MODEL_EVALUATIONS}`, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(data)
// });
// return response.json();
// };
const resultList = result?.results || [];
return {
results: resultList.map((item) => {
return {
...item,
cluster_id: data.model_specs?.[0]?.cluster_id || undefined
};
})
};
}
+1 -2
View File
@@ -169,7 +169,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
getGPUOptionList(params);
}
};
}, []);
});
return (
<FormInnerContext.Provider
@@ -242,7 +242,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
<HuggingFaceForm></HuggingFaceForm>
<LocalPathForm></LocalPathForm>
<Form.Item<FormData>
name="cluster_id"
rules={[
@@ -486,6 +486,9 @@ const AddModal: React.FC<AddModalProps> = (props) => {
if (open) {
setTimeout(() => {
fetchSpecData();
form.current?.getGPUOptionList?.({
clusterId: initClusterId()
});
}, 100);
}
return () => {
@@ -477,6 +477,10 @@ const AddModal: FC<AddModalProps> = (props) => {
onSelectModelAfterEvaluate={
handleOnSelectModelAfterEvaluate
}
clusterId={
form.current?.getFieldValue?.('cluster_id') ||
initClusterId()
}
displayEvaluateStatus={displayEvaluateStatus}
gpuOptions={[]}
></SearchModel>
@@ -40,19 +40,12 @@ const PaginationMain = styled(Pagination)`
}
`;
const IconFontWrapper = styled.div`
.ant-pagination-item-link {
display: flex !important;
align-items: center;
justify-content: center;
}
`;
interface SearchInputProps {
hasLinuxWorker?: boolean;
modelSource: string;
isDownload?: boolean;
gpuOptions?: any[];
clusterId: number;
setLoadingModel?: (flag: boolean) => void;
onSourceChange?: (source: string) => void;
onSelectModel: (model: any, manul?: boolean) => void;
@@ -70,6 +63,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
isDownload,
hasLinuxWorker,
gpuOptions,
clusterId,
setLoadingModel,
onSelectModel,
onSelectModelAfterEvaluate,
@@ -295,6 +289,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
return {
...backendObj,
cluster_id: clusterId,
source: modelSource,
...(modelSource === modelSourceMap.huggingface_value
? {
@@ -256,6 +256,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
useEffect(() => {
if (open && formData) {
setOriginalFormData();
getGPUOptionList({ clusterId: formData.cluster_id });
}
if (!open) {
checkTokenRef.current?.cancel?.();
+8
View File
@@ -192,6 +192,7 @@ export interface CatalogSpec {
export interface EvaluateSpec {
source?: string;
cluster_id?: number;
huggingface_repo_id?: string;
huggingface_filename?: string;
ollama_library_model_name?: string;
@@ -231,4 +232,11 @@ export interface EvaluateResult {
ram: number;
vram: number;
};
cluster_id?: number;
resource_claim_by_cluster_id?: {
[key: number]: {
ram: number;
vram: number;
};
};
}
+48 -4
View File
@@ -1,8 +1,10 @@
import { clusterListAtom } from '@/atoms/models';
import { createAxiosToken } from '@/hooks/use-chunk-request';
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 { useAtomValue } from 'jotai';
import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { evaluationsModelSpec } from '../apis';
@@ -148,6 +150,7 @@ export const useCheckCompatibility = () => {
const requestIdRef = useRef(0);
const updateStatusTimer = useRef<any>(null);
const isLockWarningStatus = useRef<boolean>(false);
const clusterList = useAtomValue(clusterListAtom);
const [warningStatus, setWarningStatus] = useState<MessageStatus>({
show: false,
title: '',
@@ -220,10 +223,25 @@ export const useCheckCompatibility = () => {
}
};
const getAvailableClusters = (ids: string[]) => {
const clusterNames: string[] = [];
clusterList.forEach?.((item: { value: number; label: string }) => {
if (ids.includes(item.value.toString())) {
clusterNames.push(item.label);
}
});
return clusterNames.join(', ');
};
const getCurrentCluster = (id: number) => {
const cluster = clusterList.find?.((item) => item.value === id);
return cluster?.label || '';
};
const handleCheckCompatibility = (
evaluateResult: EvaluateResult | null
): MessageStatus => {
console.log('handleCheckCompatibility', evaluateResult);
console.log('handleCheckCompatibility', clusterList, evaluateResult);
if (!evaluateResult) {
return {
show: false,
@@ -234,7 +252,8 @@ export const useCheckCompatibility = () => {
compatible,
compatibility_messages = [],
scheduling_messages = [],
resource_claim,
resource_claim_by_cluster_id,
cluster_id,
error,
error_message
} = evaluateResult || {};
@@ -248,8 +267,18 @@ export const useCheckCompatibility = () => {
};
}
const resourceClaimMap = new Map(
Object.entries(resource_claim_by_cluster_id || {})
);
// current cluster resource claim
const resource_claim = resourceClaimMap.get(`${cluster_id}`);
const hasClaim = !!resource_claim?.ram || !!resource_claim?.vram;
// current cluster is not available, but other clusters are available
const othersAvailable = !hasClaim && resourceClaimMap.size > 0;
let msgData = {
title:
scheduling_messages?.length > 0
@@ -260,6 +289,7 @@ export const useCheckCompatibility = () => {
? scheduling_messages
: compatibility_messages?.join(' ')
};
if (hasClaim) {
const ram = convertFileSize(resource_claim.ram, 2);
const vram = convertFileSize(resource_claim.vram, 2);
@@ -274,11 +304,25 @@ export const useCheckCompatibility = () => {
title: intl.formatMessage({ id: 'models.form.check.passed' }),
message: intl.formatMessage({ id: messageId }, { ram, vram })
};
} else if (othersAvailable) {
msgData = {
title: intl.formatMessage({
id: 'models.form.check.clusterUnavailable'
}),
message: intl.formatMessage(
{
id: 'models.form.check.otherClustersAvailable'
},
{
clusters: getAvailableClusters(Array.from(resourceClaimMap.keys()))
}
)
};
}
return {
show: !compatible || hasClaim,
type: !compatible ? 'warning' : 'success',
show: !compatible || hasClaim || othersAvailable,
type: !compatible || othersAvailable ? 'warning' : 'success',
...msgData
};
};
@@ -1,3 +1,4 @@
import { clusterListAtom } from '@/atoms/models';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { queryWorkersList } from '@/pages/resources/apis';
@@ -6,6 +7,7 @@ import {
WorkerStatusMapValue
} from '@/pages/resources/config';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { useAtom } from 'jotai';
import _ from 'lodash';
import { useState } from 'react';
import { queryGPUList } from '../apis';
@@ -188,6 +190,7 @@ export const useGenerateWorkerOptions = () => {
export default function useFormInitialValues() {
const { getGPUOptionList } = useGenerateGPUOptions();
const [, setClusterListAtom] = useAtom(clusterListAtom);
const [clusterList, setClusterList] = useState<
Global.BaseOption<number, { provider: string; state: string | number }>[]
@@ -206,10 +209,12 @@ export default function useFormInitialValues() {
state: item.state
}));
setClusterList(list);
setClusterListAtom(list);
return list;
} catch (error) {
console.error('Failed to fetch cluster list:', error);
setClusterList([]);
setClusterListAtom([]);
return [];
}
};