Files
2025-03-11 08:01:46 -04:00

404 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
// Create the monitor UI element
const createMonitorElement = () => {
// Create main container
const container = document.createElement("div");
container.className = "amd-gpu-monitor";
container.style.position = "absolute";
container.style.top = "40px"; // Moved down to avoid toolbar
container.style.right = "5px";
container.style.zIndex = "1000";
container.style.backgroundColor = "#1a1a1a";
container.style.color = "#fff";
container.style.padding = "10px";
container.style.borderRadius = "5px";
container.style.fontFamily = "monospace";
container.style.fontSize = "12px";
container.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.5)";
container.style.width = "220px";
container.style.userSelect = "none";
// Add title
const title = document.createElement("div");
title.style.fontWeight = "bold";
title.style.marginBottom = "5px";
title.style.display = "flex";
title.style.alignItems = "center";
title.style.justifyContent = "space-between";
title.innerHTML = '<span style="color: #ff5555;">AMD GPU Monitor</span>';
// Add collapse button
const collapseButton = document.createElement("button");
collapseButton.innerHTML = "−"; // Unicode minus sign
collapseButton.style.background = "none";
collapseButton.style.border = "none";
collapseButton.style.color = "#888";
collapseButton.style.cursor = "pointer";
collapseButton.style.fontSize = "14px";
collapseButton.style.padding = "0 5px";
collapseButton.title = "Collapse/Expand";
// Add close button
const closeButton = document.createElement("button");
closeButton.innerHTML = "×"; // Unicode times sign
closeButton.style.background = "none";
closeButton.style.border = "none";
closeButton.style.color = "#888";
closeButton.style.cursor = "pointer";
closeButton.style.fontSize = "14px";
closeButton.style.padding = "0 5px";
closeButton.title = "Close";
const buttonContainer = document.createElement("div");
buttonContainer.appendChild(collapseButton);
buttonContainer.appendChild(closeButton);
title.appendChild(buttonContainer);
container.appendChild(title);
// Content container that can be collapsed
const content = document.createElement("div");
content.className = "amd-gpu-monitor-content";
container.appendChild(content);
// GPU Utilization section
const gpuSection = document.createElement("div");
gpuSection.style.marginBottom = "8px";
const gpuLabel = document.createElement("div");
gpuLabel.textContent = "GPU Utilization:";
gpuLabel.style.marginBottom = "2px";
const gpuBarContainer = document.createElement("div");
gpuBarContainer.style.height = "15px";
gpuBarContainer.style.backgroundColor = "#333";
gpuBarContainer.style.borderRadius = "3px";
gpuBarContainer.style.position = "relative";
const gpuBar = document.createElement("div");
gpuBar.className = "amd-gpu-utilization-bar";
gpuBar.style.height = "100%";
gpuBar.style.width = "0%";
gpuBar.style.backgroundColor = "#47a0ff";
gpuBar.style.borderRadius = "3px";
gpuBar.style.transition = "width 0.5s ease-out, background-color 0.3s";
const gpuText = document.createElement("div");
gpuText.className = "amd-gpu-utilization-text";
gpuText.textContent = "0%";
gpuText.style.position = "absolute";
gpuText.style.top = "0";
gpuText.style.left = "5px";
gpuText.style.lineHeight = "15px";
gpuText.style.textShadow = "1px 1px 1px #000";
gpuBarContainer.appendChild(gpuBar);
gpuBarContainer.appendChild(gpuText);
gpuSection.appendChild(gpuLabel);
gpuSection.appendChild(gpuBarContainer);
content.appendChild(gpuSection);
// VRAM Usage section
const vramSection = document.createElement("div");
vramSection.style.marginBottom = "8px";
const vramLabel = document.createElement("div");
vramLabel.textContent = "VRAM Usage:";
vramLabel.style.marginBottom = "2px";
const vramBarContainer = document.createElement("div");
vramBarContainer.style.height = "15px";
vramBarContainer.style.backgroundColor = "#333";
vramBarContainer.style.borderRadius = "3px";
vramBarContainer.style.position = "relative";
const vramBar = document.createElement("div");
vramBar.className = "amd-vram-bar";
vramBar.style.height = "100%";
vramBar.style.width = "0%";
vramBar.style.backgroundColor = "#47a0ff";
vramBar.style.borderRadius = "3px";
vramBar.style.transition = "width 0.5s ease-out, background-color 0.3s";
const vramText = document.createElement("div");
vramText.className = "amd-vram-text";
vramText.textContent = "0MB / 0MB (0%)";
vramText.style.position = "absolute";
vramText.style.top = "0";
vramText.style.left = "5px";
vramText.style.lineHeight = "15px";
vramText.style.textShadow = "1px 1px 1px #000";
vramBarContainer.appendChild(vramBar);
vramBarContainer.appendChild(vramText);
vramSection.appendChild(vramLabel);
vramSection.appendChild(vramBarContainer);
content.appendChild(vramSection);
// Temperature section
const tempSection = document.createElement("div");
const tempLabel = document.createElement("div");
tempLabel.textContent = "GPU Temperature:";
tempLabel.style.marginBottom = "2px";
const tempBarContainer = document.createElement("div");
tempBarContainer.style.height = "15px";
tempBarContainer.style.backgroundColor = "#333";
tempBarContainer.style.borderRadius = "3px";
tempBarContainer.style.position = "relative";
const tempBar = document.createElement("div");
tempBar.className = "amd-temp-bar";
tempBar.style.height = "100%";
tempBar.style.width = "0%";
tempBar.style.backgroundColor = "#47a0ff";
tempBar.style.borderRadius = "3px";
tempBar.style.transition = "width 0.5s ease-out, background-color 0.3s";
const tempText = document.createElement("div");
tempText.className = "amd-temp-text";
tempText.textContent = "0°C";
tempText.style.position = "absolute";
tempText.style.top = "0";
tempText.style.left = "5px";
tempText.style.lineHeight = "15px";
tempText.style.textShadow = "1px 1px 1px #000";
tempBarContainer.appendChild(tempBar);
tempBarContainer.appendChild(tempText);
tempSection.appendChild(tempLabel);
tempSection.appendChild(tempBarContainer);
content.appendChild(tempSection);
// Add event listener for collapsing
let isCollapsed = false;
collapseButton.addEventListener("click", () => {
if (isCollapsed) {
content.style.display = "block";
collapseButton.innerHTML = "−";
isCollapsed = false;
} else {
content.style.display = "none";
collapseButton.innerHTML = "+";
isCollapsed = true;
}
});
// Add event listener for closing
closeButton.addEventListener("click", () => {
container.style.display = "none";
// Store the closed state in localStorage
localStorage.setItem("amd-gpu-monitor-closed", "true");
});
// Make the monitor draggable
let isDragging = false;
let dragOffsetX, dragOffsetY;
title.addEventListener("mousedown", (e) => {
// Only handle main button (left button)
if (e.button !== 0) return;
isDragging = true;
dragOffsetX = e.clientX - container.offsetLeft;
dragOffsetY = e.clientY - container.offsetTop;
// Prevent text selection during drag
e.preventDefault();
});
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const x = e.clientX - dragOffsetX;
const y = e.clientY - dragOffsetY;
// Keep monitor within window bounds
const maxX = window.innerWidth - container.offsetWidth;
const maxY = window.innerHeight - container.offsetHeight;
container.style.left = Math.max(0, Math.min(x, maxX)) + "px";
container.style.top = Math.max(0, Math.min(y, maxY)) + "px";
// We're now positioning with left instead of right
container.style.right = "auto";
});
document.addEventListener("mouseup", () => {
isDragging = false;
// Save position to localStorage
if (container.style.left && container.style.top) {
localStorage.setItem("amd-gpu-monitor-position", JSON.stringify({
left: container.style.left,
top: container.style.top
}));
}
});
// Load saved position if available
const savedPosition = localStorage.getItem("amd-gpu-monitor-position");
if (savedPosition) {
try {
const { left, top } = JSON.parse(savedPosition);
container.style.left = left;
container.style.top = top;
container.style.right = "auto";
} catch (e) {
// Silently fail and use default position
}
}
// Check if monitor was closed previously
if (localStorage.getItem("amd-gpu-monitor-closed") === "true") {
container.style.display = "none";
}
// Add a button to show the monitor again
const showButton = document.createElement("button");
showButton.textContent = "Show AMD GPU Monitor";
showButton.style.position = "fixed";
showButton.style.top = "5px";
showButton.style.right = "5px";
showButton.style.zIndex = "999";
showButton.style.padding = "5px";
showButton.style.borderRadius = "3px";
showButton.style.backgroundColor = "#333";
showButton.style.color = "#fff";
showButton.style.border = "none";
showButton.style.fontSize = "12px";
showButton.style.cursor = "pointer";
showButton.style.display = "none";
showButton.addEventListener("click", () => {
container.style.display = "block";
showButton.style.display = "none";
localStorage.removeItem("amd-gpu-monitor-closed");
});
document.body.appendChild(showButton);
// Toggle showButton visibility based on monitor visibility
const updateShowButtonVisibility = () => {
if (container.style.display === "none") {
showButton.style.display = "block";
} else {
showButton.style.display = "none";
}
};
// Create a MutationObserver to watch for changes to container's display style
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === "style") {
updateShowButtonVisibility();
}
});
});
observer.observe(container, { attributes: true });
// Initial visibility check
updateShowButtonVisibility();
return { container, gpuBar, gpuText, vramBar, vramText, tempBar, tempText };
};
// Update the monitor UI with new data
const updateMonitorUI = (monitor, data) => {
// Check if we have GPU data
if (!data || !data.gpus || data.gpus.length === 0) return;
const gpu = data.gpus[0]; // Use the first GPU
// Update GPU utilization
if (monitor.gpuBar && monitor.gpuText) {
const utilization = gpu.gpu_utilization || 0;
monitor.gpuBar.style.width = `${utilization}%`;
monitor.gpuText.textContent = `${utilization}%`;
// Change color based on utilization
if (utilization > 80) {
monitor.gpuBar.style.backgroundColor = '#ff4d4d'; // Red for high
} else if (utilization > 50) {
monitor.gpuBar.style.backgroundColor = '#ffad33'; // Orange for medium
} else {
monitor.gpuBar.style.backgroundColor = '#47a0ff'; // Blue for low
}
}
// Update VRAM usage
if (monitor.vramBar && monitor.vramText) {
const vramPercent = gpu.vram_used_percent || 0;
const vramUsed = gpu.vram_used || 0;
const vramTotal = gpu.vram_total || 1;
monitor.vramBar.style.width = `${vramPercent}%`;
// Format the text to show MB or GB
let vramUsedText = vramUsed;
let vramTotalText = vramTotal;
let unit = 'MB';
if (vramTotal >= 1024) {
vramUsedText = (vramUsed / 1024).toFixed(1);
vramTotalText = (vramTotal / 1024).toFixed(1);
unit = 'GB';
}
monitor.vramText.textContent = `${vramUsedText}${unit} / ${vramTotalText}${unit} (${vramPercent}%)`;
// Change color based on VRAM usage
if (vramPercent > 85) {
monitor.vramBar.style.backgroundColor = '#ff4d4d'; // Red for high
} else if (vramPercent > 70) {
monitor.vramBar.style.backgroundColor = '#ffad33'; // Orange for medium
} else {
monitor.vramBar.style.backgroundColor = '#47a0ff'; // Blue for low
}
}
// Update temperature
if (monitor.tempBar && monitor.tempText) {
const temp = gpu.gpu_temperature || 0;
// Assume max reasonable temp is 100°C for the progress bar
const tempPercent = Math.min(temp, 100);
monitor.tempBar.style.width = `${tempPercent}%`;
monitor.tempText.textContent = `${temp}°C`;
// Change color based on temperature
if (temp > 80) {
monitor.tempBar.style.backgroundColor = '#ff4d4d'; // Red for high
} else if (temp > 60) {
monitor.tempBar.style.backgroundColor = '#ffad33'; // Orange for medium
} else {
monitor.tempBar.style.backgroundColor = '#47a0ff'; // Blue for low
}
}
};
// Main app function
const main = () => {
// Create the monitor UI
const monitor = createMonitorElement();
document.body.appendChild(monitor.container);
// Set up WebSocket listener for GPU updates
api.addEventListener("amd_gpu_monitor", (event) => {
updateMonitorUI(monitor, event.detail);
});
};
// Wait for DOM to be loaded
app.registerExtension({
name: "amd.gpu.monitor",
async setup() {
// Wait a bit for the UI to be fully loaded
setTimeout(main, 1000);
},
});