Files
new-api/web/src/hooks/model-deployments/useDeploymentResources.js
T
Seefs be567ef7c9 fix: fix model deployment style issues, lint problems, and i18n gaps. (#2556)
* fix: fix model deployment style issues, lint problems, and i18n gaps.

* fix: adjust the key not to be displayed on the frontend, tested via the backend.

* fix: adjust the sidebar configuration logic to use the default configuration items if they are not defined.
2026-01-03 12:37:50 +08:00

313 lines
9.3 KiB
JavaScript

/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useState, useCallback } from 'react';
import { API } from '../../helpers';
import { showError } from '../../helpers';
export const useDeploymentResources = () => {
const [hardwareTypes, setHardwareTypes] = useState([]);
const [hardwareTotalAvailable, setHardwareTotalAvailable] = useState(0);
const [locations, setLocations] = useState([]);
const [locationsTotalAvailable, setLocationsTotalAvailable] = useState(0);
const [availableReplicas, setAvailableReplicas] = useState([]);
const [priceEstimation, setPriceEstimation] = useState(null);
const [loadingHardware, setLoadingHardware] = useState(false);
const [loadingLocations, setLoadingLocations] = useState(false);
const [loadingReplicas, setLoadingReplicas] = useState(false);
const [loadingPrice, setLoadingPrice] = useState(false);
const fetchHardwareTypes = useCallback(async () => {
try {
setLoadingHardware(true);
const response = await API.get('/api/deployments/hardware-types');
if (response.data.success) {
const { hardware_types: hardwareList = [], total_available } =
response.data.data || {};
const normalizedHardware = hardwareList.map((hardware) => {
const availableCountValue = Number(hardware.available_count);
const availableCount = Number.isNaN(availableCountValue)
? 0
: availableCountValue;
const availableBool =
typeof hardware.available === 'boolean'
? hardware.available
: availableCount > 0;
return {
...hardware,
available: availableBool,
available_count: availableCount,
};
});
const providedTotal = Number(total_available);
const fallbackTotal = normalizedHardware.reduce(
(acc, item) =>
acc +
(Number.isNaN(item.available_count) ? 0 : item.available_count),
0,
);
const hasProvidedTotal =
total_available !== undefined &&
total_available !== null &&
total_available !== '' &&
!Number.isNaN(providedTotal);
setHardwareTypes(normalizedHardware);
setHardwareTotalAvailable(
hasProvidedTotal ? providedTotal : fallbackTotal,
);
return normalizedHardware;
} else {
showError('获取硬件类型失败: ' + response.data.message);
setHardwareTotalAvailable(0);
return [];
}
} catch (error) {
showError('获取硬件类型失败: ' + error.message);
setHardwareTotalAvailable(0);
return [];
} finally {
setLoadingHardware(false);
}
}, []);
const fetchLocations = useCallback(async (hardwareId, gpuCount = 1) => {
if (!hardwareId) {
setLocations([]);
setLocationsTotalAvailable(0);
return [];
}
try {
setLoadingLocations(true);
const response = await API.get(
`/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}`,
);
if (response.data.success) {
const replicas = response.data.data?.replicas || [];
const nextLocationsMap = new Map();
replicas.forEach((replica) => {
const rawId = replica?.location_id ?? replica?.location?.id;
if (rawId === null || rawId === undefined) return;
const mapKey = String(rawId);
if (nextLocationsMap.has(mapKey)) return;
const rawIso2 =
replica?.iso2 ?? replica?.location_iso2 ?? replica?.location?.iso2;
const iso2 = rawIso2 ? String(rawIso2).toUpperCase() : '';
const name =
replica?.location_name ??
replica?.location?.name ??
replica?.name ??
String(rawId);
nextLocationsMap.set(mapKey, {
id: rawId,
name: String(name),
iso2,
region:
replica?.region ??
replica?.location_region ??
replica?.location?.region,
country:
replica?.country ??
replica?.location_country ??
replica?.location?.country,
code:
replica?.code ??
replica?.location_code ??
replica?.location?.code,
available: Number(replica?.available_count) || 0,
});
});
const normalizedLocations = Array.from(nextLocationsMap.values());
setLocations(normalizedLocations);
setLocationsTotalAvailable(
normalizedLocations.reduce(
(acc, item) => acc + (item.available || 0),
0,
),
);
return normalizedLocations;
} else {
showError('获取部署位置失败: ' + response.data.message);
setLocationsTotalAvailable(0);
return [];
}
} catch (error) {
showError('获取部署位置失败: ' + error.message);
setLocationsTotalAvailable(0);
return [];
} finally {
setLoadingLocations(false);
}
}, []);
const fetchAvailableReplicas = useCallback(
async (hardwareId, gpuCount = 1) => {
if (!hardwareId) {
setAvailableReplicas([]);
return [];
}
try {
setLoadingReplicas(true);
const response = await API.get(
`/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}`,
);
if (response.data.success) {
const replicas = response.data.data.replicas || [];
setAvailableReplicas(replicas);
return replicas;
} else {
showError('获取可用资源失败: ' + response.data.message);
setAvailableReplicas([]);
return [];
}
} catch (error) {
console.error('Load available replicas error:', error);
setAvailableReplicas([]);
return [];
} finally {
setLoadingReplicas(false);
}
},
[],
);
const calculatePrice = useCallback(async (params) => {
const {
locationIds,
hardwareId,
gpusPerContainer,
durationHours,
replicaCount,
} = params;
if (
!locationIds?.length ||
!hardwareId ||
!gpusPerContainer ||
!durationHours ||
!replicaCount
) {
setPriceEstimation(null);
return null;
}
try {
setLoadingPrice(true);
const requestData = {
location_ids: locationIds,
hardware_id: hardwareId,
gpus_per_container: gpusPerContainer,
duration_hours: durationHours,
replica_count: replicaCount,
};
const response = await API.post(
'/api/deployments/price-estimation',
requestData,
);
if (response.data.success) {
const estimation = response.data.data;
setPriceEstimation(estimation);
return estimation;
} else {
showError('价格计算失败: ' + response.data.message);
setPriceEstimation(null);
return null;
}
} catch (error) {
console.error('Price calculation error:', error);
setPriceEstimation(null);
return null;
} finally {
setLoadingPrice(false);
}
}, []);
const checkClusterNameAvailability = useCallback(async (name) => {
if (!name?.trim()) return false;
try {
const response = await API.get(
`/api/deployments/check-name?name=${encodeURIComponent(name.trim())}`,
);
if (response.data.success) {
return response.data.data.available;
} else {
showError('检查名称可用性失败: ' + response.data.message);
return false;
}
} catch (error) {
console.error('Check cluster name availability error:', error);
return false;
}
}, []);
const createDeployment = useCallback(async (deploymentData) => {
try {
const response = await API.post('/api/deployments', deploymentData);
if (response.data.success) {
return response.data.data;
} else {
throw new Error(response.data.message || '创建部署失败');
}
} catch (error) {
throw error;
}
}, []);
return {
// Data
hardwareTypes,
hardwareTotalAvailable,
locations,
locationsTotalAvailable,
availableReplicas,
priceEstimation,
// Loading states
loadingHardware,
loadingLocations,
loadingReplicas,
loadingPrice,
// Functions
fetchHardwareTypes,
fetchLocations,
fetchAvailableReplicas,
calculatePrice,
checkClusterNameAvailability,
createDeployment,
// Clear functions
clearPriceEstimation: () => setPriceEstimation(null),
clearAvailableReplicas: () => setAvailableReplicas([]),
};
};
export default useDeploymentResources;