diff --git a/src/atoms/models.ts b/src/atoms/models.ts
index 436f3b43..5437f91b 100644
--- a/src/atoms/models.ts
+++ b/src/atoms/models.ts
@@ -16,3 +16,10 @@ export const getRequestId = () => {
const store = getDefaultStore();
return store.get(requestIdAtom);
};
+
+export const clusterListAtom = atom<
+ {
+ label: string;
+ value: number;
+ }[]
+>([]);
diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts
index da51318e..728bed19 100644
--- a/src/locales/en-US/models.ts
+++ b/src/locales/en-US/models.ts
@@ -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.'
};
diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts
index 5b336a86..2d6dba78 100644
--- a/src/locales/ja-JP/models.ts
+++ b/src/locales/ja-JP/models.ts
@@ -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 related GitHub issue.`
-// 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 ==========
diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts
index 50b10179..c0d610d5 100644
--- a/src/locales/ru-RU/models.ts
+++ b/src/locales/ru-RU/models.ts
@@ -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 ==========
diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts
index c50751ea..778a5b24 100644
--- a/src/locales/zh-CN/models.ts
+++ b/src/locales/zh-CN/models.ts
@@ -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}。请切换集群。'
};
diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts
index 03562d1a..ca23f84a 100644
--- a/src/pages/llmodels/apis/index.ts
+++ b/src/pages/llmodels/apis/index.ts
@@ -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
+ };
+ })
+ };
+}
diff --git a/src/pages/llmodels/components/data-form.tsx b/src/pages/llmodels/components/data-form.tsx
index 02d9c25d..53eff857 100644
--- a/src/pages/llmodels/components/data-form.tsx
+++ b/src/pages/llmodels/components/data-form.tsx
@@ -169,7 +169,7 @@ const DataForm: React.FC = forwardRef((props, ref) => {
getGPUOptionList(params);
}
};
- }, []);
+ });
return (
= forwardRef((props, ref) => {
-
name="cluster_id"
rules={[
diff --git a/src/pages/llmodels/components/deploy-builtin-modal.tsx b/src/pages/llmodels/components/deploy-builtin-modal.tsx
index 82631c24..9598aa87 100644
--- a/src/pages/llmodels/components/deploy-builtin-modal.tsx
+++ b/src/pages/llmodels/components/deploy-builtin-modal.tsx
@@ -486,6 +486,9 @@ const AddModal: React.FC = (props) => {
if (open) {
setTimeout(() => {
fetchSpecData();
+ form.current?.getGPUOptionList?.({
+ clusterId: initClusterId()
+ });
}, 100);
}
return () => {
diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx
index 4f56b44d..57258f32 100644
--- a/src/pages/llmodels/components/deploy-modal.tsx
+++ b/src/pages/llmodels/components/deploy-modal.tsx
@@ -477,6 +477,10 @@ const AddModal: FC = (props) => {
onSelectModelAfterEvaluate={
handleOnSelectModelAfterEvaluate
}
+ clusterId={
+ form.current?.getFieldValue?.('cluster_id') ||
+ initClusterId()
+ }
displayEvaluateStatus={displayEvaluateStatus}
gpuOptions={[]}
>
diff --git a/src/pages/llmodels/components/search-model.tsx b/src/pages/llmodels/components/search-model.tsx
index ed2b94f3..99ede4b1 100644
--- a/src/pages/llmodels/components/search-model.tsx
+++ b/src/pages/llmodels/components/search-model.tsx
@@ -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 = (props) => {
isDownload,
hasLinuxWorker,
gpuOptions,
+ clusterId,
setLoadingModel,
onSelectModel,
onSelectModelAfterEvaluate,
@@ -295,6 +289,7 @@ const SearchModel: React.FC = (props) => {
return {
...backendObj,
+ cluster_id: clusterId,
source: modelSource,
...(modelSource === modelSourceMap.huggingface_value
? {
diff --git a/src/pages/llmodels/components/update-modal.tsx b/src/pages/llmodels/components/update-modal.tsx
index 90effb07..50623391 100644
--- a/src/pages/llmodels/components/update-modal.tsx
+++ b/src/pages/llmodels/components/update-modal.tsx
@@ -256,6 +256,7 @@ const UpdateModal: React.FC = (props) => {
useEffect(() => {
if (open && formData) {
setOriginalFormData();
+ getGPUOptionList({ clusterId: formData.cluster_id });
}
if (!open) {
checkTokenRef.current?.cancel?.();
diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts
index beb084b2..0663e016 100644
--- a/src/pages/llmodels/config/types.ts
+++ b/src/pages/llmodels/config/types.ts
@@ -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;
+ };
+ };
}
diff --git a/src/pages/llmodels/hooks/index.ts b/src/pages/llmodels/hooks/index.ts
index e6b7a684..083121b9 100644
--- a/src/pages/llmodels/hooks/index.ts
+++ b/src/pages/llmodels/hooks/index.ts
@@ -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(null);
const isLockWarningStatus = useRef(false);
+ const clusterList = useAtomValue(clusterListAtom);
const [warningStatus, setWarningStatus] = useState({
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
};
};
diff --git a/src/pages/llmodels/hooks/use-form-initial-values.ts b/src/pages/llmodels/hooks/use-form-initial-values.ts
index 8ef4e3ac..b399ca64 100644
--- a/src/pages/llmodels/hooks/use-form-initial-values.ts
+++ b/src/pages/llmodels/hooks/use-form-initial-values.ts
@@ -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[]
@@ -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 [];
}
};