diff --git a/web/public/locales/en/views/system.json b/web/public/locales/en/views/system.json
index 1ba46153e..3b0a395e9 100644
--- a/web/public/locales/en/views/system.json
+++ b/web/public/locales/en/views/system.json
@@ -51,6 +51,7 @@
"gpuMemory": "GPU Memory",
"gpuEncoder": "GPU Encoder",
"gpuDecoder": "GPU Decoder",
+ "gpuTemperature": "GPU Temperature",
"gpuInfo": {
"vainfoOutput": {
"title": "Vainfo Output",
@@ -77,6 +78,7 @@
},
"npuUsage": "NPU Usage",
"npuMemory": "NPU Memory",
+ "npuTemperature": "NPU Temperature",
"intelGpuWarning": {
"title": "Intel GPU Stats Warning",
"message": "GPU stats unavailable",
diff --git a/web/src/types/stats.ts b/web/src/types/stats.ts
index 1fd38a1c3..8b22849be 100644
--- a/web/src/types/stats.ts
+++ b/web/src/types/stats.ts
@@ -61,11 +61,13 @@ export type GpuStats = {
enc?: string;
dec?: string;
pstate?: string;
+ temp?: number;
};
export type NpuStats = {
npu: number;
mem: string;
+ temp?: number;
};
export type GpuInfo = "vainfo" | "nvinfo";
diff --git a/web/src/views/system/GeneralMetrics.tsx b/web/src/views/system/GeneralMetrics.tsx
index 8c358e55a..e7dfbc2cf 100644
--- a/web/src/views/system/GeneralMetrics.tsx
+++ b/web/src/views/system/GeneralMetrics.tsx
@@ -368,6 +368,40 @@ export default function GeneralMetrics({
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
+ const gpuTempSeries = useMemo(() => {
+ if (!statsHistory) {
+ return [];
+ }
+
+ const series: {
+ [key: string]: { name: string; data: { x: number; y: number }[] };
+ } = {};
+ let hasValidGpu = false;
+
+ statsHistory.forEach((stats, statsIdx) => {
+ if (!stats) {
+ return;
+ }
+
+ Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
+ if (!(key in series)) {
+ series[key] = { name: key, data: [] };
+ }
+
+ if (stats.temp !== undefined) {
+ hasValidGpu = true;
+ series[key].data.push({ x: statsIdx + 1, y: stats.temp });
+ }
+ });
+ });
+
+ if (!hasValidGpu) {
+ return [];
+ }
+
+ return Object.keys(series).length > 0 ? Object.values(series) : undefined;
+ }, [statsHistory]);
+
// Check if Intel GPU has all 0% usage values (known bug)
const showIntelGpuWarning = useMemo(() => {
if (!statsHistory || statsHistory.length < 3) {
@@ -448,6 +482,40 @@ export default function GeneralMetrics({
return Object.keys(series).length > 0 ? Object.values(series) : [];
}, [statsHistory]);
+ const npuTempSeries = useMemo(() => {
+ if (!statsHistory) {
+ return [];
+ }
+
+ const series: {
+ [key: string]: { name: string; data: { x: number; y: number }[] };
+ } = {};
+ let hasValidNpu = false;
+
+ statsHistory.forEach((stats, statsIdx) => {
+ if (!stats) {
+ return;
+ }
+
+ Object.entries(stats.npu_usages || {}).forEach(([key, stats]) => {
+ if (!(key in series)) {
+ series[key] = { name: key, data: [] };
+ }
+
+ if (stats.temp !== undefined) {
+ hasValidNpu = true;
+ series[key].data.push({ x: statsIdx + 1, y: stats.temp });
+ }
+ });
+ });
+
+ if (!hasValidNpu) {
+ return [];
+ }
+
+ return Object.keys(series).length > 0 ? Object.values(series) : undefined;
+ }, [statsHistory]);
+
// other processes stats
const hardwareType = useMemo(() => {
@@ -670,6 +738,7 @@ export default function GeneralMetrics({
className={cn(
"mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2",
gpuEncSeries?.length && "md:grid-cols-4",
+ gpuTempSeries?.length && "md:grid-cols-3",
)}
>
{statsHistory[0]?.gpu_usages && (
@@ -804,6 +873,30 @@ export default function GeneralMetrics({
) : (