mirror of
https://github.com/ostris/ai-toolkit.git
synced 2026-01-26 16:39:47 +00:00
142 lines
4.0 KiB
TypeScript
142 lines
4.0 KiB
TypeScript
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
import { GPUApiResponse } from '@/types';
|
|
import Loading from '@/components/Loading';
|
|
import GPUWidget from '@/components/GPUWidget';
|
|
import { apiClient } from '@/utils/api';
|
|
|
|
const GpuMonitor: React.FC = () => {
|
|
const [gpuData, setGpuData] = useState<GPUApiResponse | null>(null);
|
|
const [loading, setLoading] = useState<boolean>(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
|
const isFetchingGpuRef = useRef(false);
|
|
|
|
useEffect(() => {
|
|
const fetchGpuInfo = async () => {
|
|
if (isFetchingGpuRef.current) {
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
isFetchingGpuRef.current = true;
|
|
apiClient
|
|
.get('/api/gpu')
|
|
.then(res => res.data)
|
|
.then(data => {
|
|
setGpuData(data);
|
|
setLastUpdated(new Date());
|
|
setError(null);
|
|
})
|
|
.catch(err => {
|
|
setError(`Failed to fetch GPU data: ${err instanceof Error ? err.message : String(err)}`);
|
|
})
|
|
.finally(() => {
|
|
isFetchingGpuRef.current = false;
|
|
setLoading(false);
|
|
});
|
|
};
|
|
|
|
// Fetch immediately on component mount
|
|
fetchGpuInfo();
|
|
|
|
// Set up interval to fetch every 1 seconds
|
|
const intervalId = setInterval(fetchGpuInfo, 1000);
|
|
|
|
// Clean up interval on component unmount
|
|
return () => clearInterval(intervalId);
|
|
}, []);
|
|
|
|
const getGridClasses = (gpuCount: number): string => {
|
|
switch (gpuCount) {
|
|
case 1:
|
|
return 'grid-cols-1';
|
|
case 2:
|
|
return 'grid-cols-2';
|
|
case 3:
|
|
return 'grid-cols-3';
|
|
case 4:
|
|
return 'grid-cols-4';
|
|
case 5:
|
|
case 6:
|
|
return 'grid-cols-3';
|
|
case 7:
|
|
case 8:
|
|
return 'grid-cols-4';
|
|
case 9:
|
|
case 10:
|
|
return 'grid-cols-5';
|
|
default:
|
|
return 'grid-cols-3';
|
|
}
|
|
};
|
|
|
|
console.log('state', {
|
|
loading,
|
|
gpuData,
|
|
error,
|
|
lastUpdated,
|
|
});
|
|
|
|
const content = useMemo(() => {
|
|
if (loading && !gpuData) {
|
|
return <Loading />;
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="bg-red-900 border border-red-600 text-red-200 px-4 py-3 rounded relative" role="alert">
|
|
<strong className="font-bold">Error!</strong>
|
|
<span className="block sm:inline"> {error}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!gpuData) {
|
|
return (
|
|
<div className="bg-yellow-900 border border-yellow-700 text-yellow-300 px-4 py-3 rounded relative" role="alert">
|
|
<span className="block sm:inline">No GPU data available.</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!gpuData.hasNvidiaSmi) {
|
|
return (
|
|
<div className="bg-yellow-900 border border-yellow-700 text-yellow-300 px-4 py-3 rounded relative" role="alert">
|
|
<strong className="font-bold">No NVIDIA GPUs detected!</strong>
|
|
<span className="block sm:inline"> nvidia-smi is not available on this system.</span>
|
|
{gpuData.error && <p className="mt-2 text-sm">{gpuData.error}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (gpuData.gpus.length === 0) {
|
|
return (
|
|
<div className="bg-yellow-900 border border-yellow-700 text-yellow-300 px-4 py-3 rounded relative" role="alert">
|
|
<span className="block sm:inline">No GPUs found, but nvidia-smi is available.</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const gridClass = getGridClasses(gpuData?.gpus?.length || 1);
|
|
|
|
return (
|
|
<div className={`grid ${gridClass} gap-3`}>
|
|
{gpuData.gpus.map((gpu, idx) => (
|
|
<GPUWidget key={idx} gpu={gpu} />
|
|
))}
|
|
</div>
|
|
);
|
|
}, [loading, gpuData, error]);
|
|
|
|
return (
|
|
<div className="w-full">
|
|
<div className="flex justify-between items-center mb-2">
|
|
<h1 className="text-md">GPU Monitor</h1>
|
|
<div className="text-xs text-gray-500">Last updated: {lastUpdated?.toLocaleTimeString()}</div>
|
|
</div>
|
|
{content}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default GpuMonitor;
|