Add Modal Credits Widget for ComfyUI

This commit is contained in:
Antique3e
2025-10-21 22:18:34 +05:30
committed by GitHub
parent 0fe8ed0391
commit 70640e7e81
+266
View File
@@ -0,0 +1,266 @@
/**
* ComfyUI Modal Credits Monitor - Web Extension
* Place this file in: custom_nodes/ComfyUI-ModalCredits/web/modal_credits.js
*/
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
class ModalCreditsWidget {
constructor() {
this.element = null;
this.batteryIcon = null;
this.creditsText = null;
this.updateInterval = null;
this.init();
}
init() {
// Create the header widget
this.createElement();
// Add to header
this.addToHeader();
// Start update loop
this.startUpdating();
// Add styles
this.addStyles();
}
createElement() {
// Main container
this.element = document.createElement('div');
this.element.id = 'modal-credits-widget';
this.element.className = 'modal-credits-container';
// Battery icon container
const batteryContainer = document.createElement('div');
batteryContainer.className = 'battery-container';
batteryContainer.title = 'Click for details';
batteryContainer.onclick = () => this.showDetails();
// Battery outer shell
const batteryShell = document.createElement('div');
batteryShell.className = 'battery-shell';
// Battery fill (the colored part)
this.batteryFill = document.createElement('div');
this.batteryFill.className = 'battery-fill';
this.batteryFill.style.width = '80%';
// Battery tip
const batteryTip = document.createElement('div');
batteryTip.className = 'battery-tip';
// Credits text
this.creditsText = document.createElement('span');
this.creditsText.className = 'credits-text';
this.creditsText.textContent = '$80.00';
// Assemble battery
batteryShell.appendChild(this.batteryFill);
batteryContainer.appendChild(batteryShell);
batteryContainer.appendChild(batteryTip);
// Assemble widget
this.element.appendChild(batteryContainer);
this.element.appendChild(this.creditsText);
}
addToHeader() {
// Wait for DOM to be ready
const addToDOM = () => {
const menuBar = document.querySelector('.comfy-menu') ||
document.querySelector('.comfyui-menu') ||
document.querySelector('#queue-button')?.parentElement;
if (menuBar) {
menuBar.appendChild(this.element);
} else {
// If menu not found, append to body as fallback
document.body.appendChild(this.element);
this.element.style.position = 'fixed';
this.element.style.top = '10px';
this.element.style.right = '10px';
this.element.style.zIndex = '9999';
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addToDOM);
} else {
setTimeout(addToDOM, 100);
}
}
async updateCredits() {
try {
// Fetch credit status from backend
const response = await api.fetchApi('/modal_credits/status');
if (response.ok) {
const data = await response.json();
this.updateDisplay(data);
}
} catch (error) {
console.error('Failed to update Modal credits:', error);
}
}
updateDisplay(data) {
const percentage = data.percentage || 0;
const remaining = data.remaining_credits || 0;
// Update battery fill
this.batteryFill.style.width = `${percentage}%`;
// Update color based on percentage
if (percentage > 50) {
this.batteryFill.style.backgroundColor = '#4ade80'; // green
} else if (percentage > 20) {
this.batteryFill.style.backgroundColor = '#fbbf24'; // yellow
} else {
this.batteryFill.style.backgroundColor = '#ef4444'; // red
}
// Update text
this.creditsText.textContent = `$${remaining.toFixed(2)}`;
// Add warning pulse if low
if (percentage <= 20) {
this.element.classList.add('low-credits');
} else {
this.element.classList.remove('low-credits');
}
// Store data for details view
this.currentData = data;
}
showDetails() {
if (!this.currentData) return;
const data = this.currentData;
const hours = (data.session_duration / 3600).toFixed(2);
const message = `
💰 Modal Credits Details
━━━━━━━━━━━━━━━━━━━━━━━━
💵 Remaining: $${data.remaining_credits.toFixed(2)}
📊 Used: $${data.used_credits.toFixed(2)}
💳 Initial: $${data.initial_credits.toFixed(2)}
🔋 Battery: ${data.percentage}%
🎮 GPU: ${data.gpu_type}
💲 Cost: $${data.cost_per_hour.toFixed(2)}/hour
⏱️ Session: ${hours} hours
🧮 Estimated time remaining:
${data.remaining_credits > 0 ? (data.remaining_credits / data.cost_per_hour).toFixed(2) + ' hours' : 'Out of credits!'}
`.trim();
alert(message);
}
startUpdating() {
// Update immediately
this.updateCredits();
// Then update every second
this.updateInterval = setInterval(() => {
this.updateCredits();
}, 1000);
}
addStyles() {
const style = document.createElement('style');
style.textContent = `
.modal-credits-container {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 12px;
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
margin-left: 8px;
cursor: pointer;
transition: background 0.3s;
}
.modal-credits-container:hover {
background: rgba(0, 0, 0, 0.5);
}
.battery-container {
display: flex;
align-items: center;
gap: 1px;
}
.battery-shell {
width: 30px;
height: 14px;
border: 2px solid #fff;
border-radius: 3px;
padding: 1px;
background: rgba(255, 255, 255, 0.1);
position: relative;
overflow: hidden;
}
.battery-fill {
height: 100%;
background-color: #4ade80;
border-radius: 1px;
transition: width 0.5s ease, background-color 0.5s ease;
}
.battery-tip {
width: 3px;
height: 8px;
background: #fff;
border-radius: 0 2px 2px 0;
}
.credits-text {
color: #fff;
font-size: 13px;
font-weight: 600;
font-family: monospace;
min-width: 50px;
text-align: right;
}
.low-credits {
animation: pulse-red 2s infinite;
}
@keyframes pulse-red {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
`;
document.head.appendChild(style);
}
}
// Initialize when ComfyUI is ready
app.registerExtension({
name: "ModalCredits.Widget",
async setup() {
// Create widget instance
new ModalCreditsWidget();
}
});
// Add API endpoint handler
api.addEventListener("status", ({detail}) => {
// Handle any status updates if needed
});
console.log("Modal Credits Monitor loaded");