b10f1f7b85
* wip ionet integrate * wip ionet integrate * wip ionet integrate * ollama wip * wip * feat: ionet integration & ollama manage * fix merge conflict * wip * fix: test conn cors * wip * fix ionet * fix ionet * wip * fix model select * refactor: Remove `pkg/ionet` test files and update related Go source and web UI model deployment components. * feat: Enhance model deployment UI with styling improvements, updated text, and a new description component. * Revert "feat: Enhance model deployment UI with styling improvements, updated text, and a new description component." This reverts commit 8b75cb5bf0d1a534b339df8c033be9a6c7df7964.
267 lines
8.4 KiB
JavaScript
267 lines
8.4 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 () => {
|
|
try {
|
|
setLoadingLocations(true);
|
|
const response = await API.get('/api/deployments/locations');
|
|
if (response.data.success) {
|
|
const { locations: locationsList = [], total } = response.data.data || {};
|
|
const normalizedLocations = locationsList.map((location) => {
|
|
const iso2 = (location.iso2 || '').toString().toUpperCase();
|
|
const availableValue = Number(location.available);
|
|
const available = Number.isNaN(availableValue) ? 0 : availableValue;
|
|
|
|
return {
|
|
...location,
|
|
iso2,
|
|
available,
|
|
};
|
|
});
|
|
const providedTotal = Number(total);
|
|
const fallbackTotal = normalizedLocations.reduce(
|
|
(acc, item) => acc + (Number.isNaN(item.available) ? 0 : item.available),
|
|
0,
|
|
);
|
|
const hasProvidedTotal =
|
|
total !== undefined &&
|
|
total !== null &&
|
|
total !== '' &&
|
|
!Number.isNaN(providedTotal);
|
|
|
|
setLocations(normalizedLocations);
|
|
setLocationsTotalAvailable(
|
|
hasProvidedTotal ? providedTotal : fallbackTotal,
|
|
);
|
|
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;
|