Added run monitoring and fixed some focus settings
Added a way to monitor success or error and run a custom script
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
 
|
||||
|
||||
**HALXP-Comfy** (High Availability & Layout eXtension Pack) is a professional UI suite for ComfyUI designed to enhance workflow management.
|
||||
**HALXP-Comfy** is a professional UX suite for ComfyUI designed to enhance workflow management.
|
||||
|
||||
This extension is the starting point for a growing collection of UI tools. Currently, it features **HALXP Focus**, a smart navigation utility, with more enhancements currently in development.
|
||||
This extension is the starting point for a growing collection of tools. Currently, it features **HALXP Focus**, a way to auto-focus the current running node and , **HALXP RunMonitor** taht lets you run a script on success or error with more enhancements currently in development.
|
||||
|
||||
**Verified working on:** Legacy UI, ComfyUI V1 (Nodes 2.0), and Portable Distributions.
|
||||
|
||||
@@ -21,7 +21,18 @@ A smart "Focus" button injected into your main menu bar.
|
||||
* **Visual Feedback:** Features a status indicator (Green dot = Active) so you always know when auto-follow is engaged.
|
||||
* **V1 Compatible:** Works natively with the new Vue.js frontend and PrimeVue menus.
|
||||
|
||||
### 2. More Coming Soon
|
||||
### 2. HALXP RunMonitor
|
||||
A way to run a custom script on workflow success or failure
|
||||
* Go in the Settings under HALXP-Comfy and set a batch file to run or anything you want
|
||||
* (play a wav, open a website, etc.) separately for sucess and errors - doesn't cover static validation on launch
|
||||
* ex: With my phone provider (Free Mobile in France), I have an API to send SMS so I can go do other things and get notified when my workflow ends
|
||||
* batch file content example:
|
||||
* start "" "https://smsapi.free-mobile.fr/sendmsg?user=XXXXXXXXXX&pass=YYYYYYYYYYY&msg=Comfy%%20Error%%20!"
|
||||
* **Smart Docking:** Automatically detects the ComfyUI Manager button and injects itself seamlessly beside it.
|
||||
* **Visual Feedback:** Features a status indicator (Green dot = Active) so you always know when Monitor is engaged.
|
||||
* **V1 Compatible:** Works natively with the new Vue.js frontend and PrimeVue menus.
|
||||
|
||||
### 3. More Coming Soon
|
||||
HALXP is built as a modular suite. Expect future updates to include additional layout and debugging tools to further improve your ComfyUI experience.
|
||||
|
||||
## 📦 Installation
|
||||
@@ -35,4 +46,4 @@ HALXP is built as a modular suite. Expect future updates to include additional l
|
||||
1. Navigate to your `ComfyUI/custom_nodes/` directory.
|
||||
2. Clone this repository:
|
||||
```bash
|
||||
git clone [https://github.com/HAL-XP/halxp-comfy](https://github.com/HAL-XP/halxp-comfy)
|
||||
git clone [https://github.com/YOUR_USERNAME/halxp-comfyui](https://github.com/YOUR_USERNAME/halxp-comfyui)
|
||||
+76
-8
@@ -1,12 +1,80 @@
|
||||
# HALXP - High Availability & Layout eXtension Pack
|
||||
# Registers the web directory for JS extensions
|
||||
import server
|
||||
import subprocess
|
||||
import asyncio
|
||||
from aiohttp import web
|
||||
|
||||
# --- CONFIG STORAGE ---
|
||||
halxp_config = {
|
||||
"enabled": False,
|
||||
"success_path": "",
|
||||
"error_path": ""
|
||||
}
|
||||
|
||||
# --- STATE TRACKING ---
|
||||
run_has_error = False
|
||||
|
||||
# --- 1. API ROUTE ---
|
||||
@server.PromptServer.instance.routes.post("/halxp/update_config")
|
||||
async def update_halxp_config(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
halxp_config.update(data)
|
||||
return web.json_response({"status": "ok"})
|
||||
except Exception as e:
|
||||
print(f"[HALXP-Core] Error updating config: {e}")
|
||||
return web.json_response({"status": "error"}, status=500)
|
||||
|
||||
# --- 2. MESSAGE HOOK ---
|
||||
original_send_json = server.PromptServer.instance.send_json
|
||||
|
||||
async def intercepted_send_json(self, event, data, sid=None):
|
||||
global run_has_error
|
||||
|
||||
# 1. Execute Original Behavior
|
||||
await original_send_json(event, data, sid)
|
||||
|
||||
# 2. Check RunMonitor Logic
|
||||
if halxp_config.get("enabled"):
|
||||
try:
|
||||
# --- START: SAFETY RESET ---
|
||||
# Just in case the last run didn't clear the flag, we clear it on start too.
|
||||
if event == "execution_start":
|
||||
run_has_error = False
|
||||
|
||||
# --- DETECT ERROR ---
|
||||
elif event == "execution_error":
|
||||
run_has_error = True
|
||||
cmd = halxp_config.get("error_path")
|
||||
if cmd and cmd.strip():
|
||||
print(f"[HALXP-RunMonitor] ❌ Workflow Error. Running: {cmd}")
|
||||
subprocess.Popen(cmd, shell=True)
|
||||
|
||||
# --- DETECT FINISH (SUCCESS or FAIL) ---
|
||||
# 'executing' with node=None means the prompt is done.
|
||||
elif event == "executing" and data.get("node") is None:
|
||||
|
||||
# Only run success script if NO error occurred
|
||||
if not run_has_error:
|
||||
cmd = halxp_config.get("success_path")
|
||||
if cmd and cmd.strip():
|
||||
print(f"[HALXP-RunMonitor] ✅ Workflow Finished. Running: {cmd}")
|
||||
subprocess.Popen(cmd, shell=True)
|
||||
|
||||
# --- CRITICAL FIX: ALWAYS RESET STATE HERE ---
|
||||
# Once the run is finished, we must clear the error flag
|
||||
# so the NEXT run starts fresh.
|
||||
if run_has_error:
|
||||
print(f"[HALXP-RunMonitor] 🔄 Run finished with errors. Resetting state for next run.")
|
||||
run_has_error = False
|
||||
|
||||
except Exception as e:
|
||||
print(f"[HALXP-RunMonitor] ⚠️ Failed to execute monitoring script: {e}")
|
||||
|
||||
# Apply the hook
|
||||
server.PromptServer.instance.send_json = intercepted_send_json.__get__(server.PromptServer.instance, server.PromptServer)
|
||||
|
||||
# --- STANDARD NODE MAPPING ---
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
# Export empty dictionaries so ComfyUI doesn't warn about missing mappings
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
__all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
print("\033[34m[HALXP-ComfyUI] JS Extensions Loaded successfully.\033[0m")
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
+66
-129
@@ -1,183 +1,115 @@
|
||||
// \ComfyUI\custom_nodes\better-focus\js\universal_focus.js
|
||||
// console.log("%c[HALXP-Comfy-Focus] Loaded", "background: purple; color: white; font-weight: bold;");
|
||||
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.HALXP-ComfyUI.Focus",
|
||||
name: "Comfy.HALXP.Focus",
|
||||
async setup() {
|
||||
// --- CONFIG ---
|
||||
// --- CONSTANTS ---
|
||||
const SMOOTHNESS = 0.15;
|
||||
const SETTING_ID = "HALXP-Comfy.Focus.DisplayButton";
|
||||
|
||||
// Clean IDs
|
||||
const SETTING_DISPLAY_ID = "HALXP.Focus.DisplayButton";
|
||||
const SETTING_AUTO_ID = "HALXP.Focus.AutoEnable";
|
||||
|
||||
// --- STATE ---
|
||||
let isFocusEnabled = false;
|
||||
let isWorkflowRunning = false;
|
||||
let activeNode = null;
|
||||
let animationFrameId = null;
|
||||
let focusBtn = null;
|
||||
let dot = null;
|
||||
|
||||
// --- 0. REGISTER SETTING ---
|
||||
// --- 1. REGISTER SETTINGS ---
|
||||
app.ui.settings.addSetting({
|
||||
id: SETTING_ID,
|
||||
id: SETTING_DISPLAY_ID,
|
||||
name: "Display Focus Button",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
// MUST be an array.
|
||||
// Element 0 = Tab Name (HALXP-Comfy)
|
||||
// Element 1 = Section Header (Focus)
|
||||
category: ['HALXP-Comfy', 'Focus'],
|
||||
// 3rd element 'Display' ensures uniqueness
|
||||
category: ['HALXP-Comfy', 'Focus', 'Display'],
|
||||
tooltip: "Show or hide the HALXP Focus button in the top menu.",
|
||||
onChange: (value) => {
|
||||
if (value) {
|
||||
// Try to inject immediately if turned on
|
||||
injectNextToManager();
|
||||
} else {
|
||||
// Remove button and disable focus if turned off
|
||||
if (focusBtn) {
|
||||
focusBtn.remove();
|
||||
// Also force stop focus mode if it was active
|
||||
if (isFocusEnabled) setFocusState(false);
|
||||
}
|
||||
if (value) injectNextToManager();
|
||||
else {
|
||||
if (focusBtn) focusBtn.remove();
|
||||
if (isFocusEnabled) setFocusState(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- 1. CREATE BUTTON ---
|
||||
app.ui.settings.addSetting({
|
||||
id: SETTING_AUTO_ID,
|
||||
name: "Auto-Focus on Run",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
// 3rd element 'Auto' ensures uniqueness
|
||||
category: ['HALXP-Comfy', 'Focus', 'Auto'],
|
||||
tooltip: "Automatically enable Focus mode when a workflow starts executing.",
|
||||
});
|
||||
|
||||
// --- 2. BUTTON LOGIC ---
|
||||
function createButton() {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = "Focus";
|
||||
btn.id = "halxp-focus-btn";
|
||||
|
||||
// CSS: Flex-safe, visible, matches Manager style
|
||||
btn.style.cssText = `
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
margin: 0 4px; /* Space between Focus and Manager */
|
||||
|
||||
background-color: var(--comfy-input-bg, #333);
|
||||
color: var(--fg-color, #fff);
|
||||
border: 1px solid var(--border-color, #555);
|
||||
border-radius: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
|
||||
/* CRITICAL VISIBILITY SETTINGS */
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
z-index: 9999;
|
||||
pointer-events: auto;
|
||||
|
||||
/* DEBUG: REMOVE THIS RED BORDER LATER IF YOU WANT */
|
||||
position: relative; display: inline-flex; align-items: center; justify-content: center;
|
||||
padding: 0 10px; margin: 0 4px; background-color: var(--comfy-input-bg, #333);
|
||||
color: var(--fg-color, #fff); border: 1px solid var(--border-color, #555);
|
||||
border-radius: 4px; cursor: pointer; font-family: sans-serif; font-size: 13px;
|
||||
font-weight: 600; flex-shrink: 0; white-space: nowrap; z-index: 9999; pointer-events: auto;
|
||||
border: 1px solid #d3c1c1ff;
|
||||
`;
|
||||
|
||||
const d = document.createElement("span");
|
||||
d.style.cssText = `
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
background-color: #666; margin-right: 8px;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
d.style.cssText = `width: 8px; height: 8px; border-radius: 50%; background-color: #666; margin-right: 8px; display: inline-block; flex-shrink: 0;`;
|
||||
btn.prepend(d);
|
||||
|
||||
btn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleFocus();
|
||||
};
|
||||
|
||||
btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); toggleFocus(); };
|
||||
dot = d;
|
||||
return btn;
|
||||
}
|
||||
|
||||
// --- 2. FIND MANAGER & INJECT ---
|
||||
function injectNextToManager() {
|
||||
// Check Settings first. If setting is false, do not inject.
|
||||
const shouldDisplay = app.ui.settings.getSettingValue(SETTING_ID, true);
|
||||
if (!shouldDisplay) return;
|
||||
|
||||
// Stop if we already exist
|
||||
if (document.getElementById("halxp-focus-btn")) return;
|
||||
|
||||
// 1. Find the Manager Button
|
||||
// We look for any button that strictly contains the text "Manager"
|
||||
const allButtons = Array.from(document.querySelectorAll("button"));
|
||||
const shouldDisplay = app.ui.settings.getSettingValue(SETTING_DISPLAY_ID, true);
|
||||
if (!shouldDisplay || document.getElementById("halxp-focus-btn")) return;
|
||||
|
||||
const allButtons = Array.from(document.querySelectorAll("button"));
|
||||
const managerBtn = allButtons.find(b => {
|
||||
// Must be visible
|
||||
if (!b.offsetParent) return false;
|
||||
|
||||
const text = b.innerText.trim();
|
||||
return text === "Manager" || text === "ComfyUI Manager";
|
||||
});
|
||||
|
||||
// 2. Inject
|
||||
if (managerBtn && managerBtn.parentNode) {
|
||||
// console.log("[Visual Assist] Found Manager Button. Attaching...");
|
||||
|
||||
if (!focusBtn) focusBtn = createButton();
|
||||
|
||||
// Match the height of the Manager button exactly
|
||||
const h = managerBtn.offsetHeight;
|
||||
if (h > 0) focusBtn.style.height = `${h}px`;
|
||||
else focusBtn.style.height = "28px";
|
||||
|
||||
// Insert BEFORE the Manager button (to its left)
|
||||
focusBtn.style.height = h > 0 ? `${h}px` : "28px";
|
||||
managerBtn.parentNode.insertBefore(focusBtn, managerBtn);
|
||||
|
||||
// Restore state appearance if we were somehow already enabled
|
||||
if (isFocusEnabled) {
|
||||
dot.style.backgroundColor = "#4CAF50";
|
||||
dot.style.boxShadow = "0 0 6px #4CAF50";
|
||||
focusBtn.style.color = "#fff";
|
||||
focusBtn.style.borderColor = "#4CAF50";
|
||||
}
|
||||
|
||||
return;
|
||||
if (isFocusEnabled) updateButtonStyle(true);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. LOOP ---
|
||||
// We poll every 500ms because the Manager itself loads slowly.
|
||||
setInterval(injectNextToManager, 500);
|
||||
|
||||
|
||||
// --- 4. FOCUS LOGIC (Standard) ---
|
||||
function toggleFocus() {
|
||||
setFocusState(!isFocusEnabled);
|
||||
}
|
||||
// --- 3. FOCUS CORE LOGIC ---
|
||||
function toggleFocus() { setFocusState(!isFocusEnabled); }
|
||||
|
||||
function setFocusState(enabled) {
|
||||
isFocusEnabled = enabled;
|
||||
// Guard: If button doesn't exist (setting off), we can't style it.
|
||||
if (!focusBtn || !dot) return;
|
||||
if (focusBtn && dot) updateButtonStyle(enabled);
|
||||
if (enabled && activeNode) requestAnimationFrame(smoothFollow);
|
||||
else if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; }
|
||||
}
|
||||
|
||||
if (isFocusEnabled) {
|
||||
dot.style.backgroundColor = "#4CAF50";
|
||||
dot.style.boxShadow = "0 0 6px #4CAF50";
|
||||
focusBtn.style.color = "#fff";
|
||||
focusBtn.style.borderColor = "#4CAF50";
|
||||
if (activeNode) requestAnimationFrame(smoothFollow);
|
||||
function updateButtonStyle(enabled) {
|
||||
if (!dot || !focusBtn) return;
|
||||
if (enabled) {
|
||||
dot.style.backgroundColor = "#4CAF50"; dot.style.boxShadow = "0 0 6px #4CAF50";
|
||||
focusBtn.style.color = "#fff"; focusBtn.style.borderColor = "#4CAF50";
|
||||
} else {
|
||||
dot.style.backgroundColor = "#666";
|
||||
dot.style.boxShadow = "none";
|
||||
focusBtn.style.color = "var(--fg-color, #ccc)";
|
||||
focusBtn.style.borderColor = "#d3c1c1ff"; // Back to disabled
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
dot.style.backgroundColor = "#666"; dot.style.boxShadow = "none";
|
||||
focusBtn.style.color = "var(--fg-color, #ccc)"; focusBtn.style.borderColor = "#d3c1c1ff";
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for canvas drag
|
||||
// Disable on manual drag
|
||||
const waitForCanvas = setInterval(() => {
|
||||
if (app.canvas && app.canvas.canvas) {
|
||||
clearInterval(waitForCanvas);
|
||||
@@ -187,13 +119,21 @@ app.registerExtension({
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// --- 4. EXECUTION LOOP ---
|
||||
api.addEventListener("executing", (e) => {
|
||||
if (e.detail) {
|
||||
if (!isWorkflowRunning) {
|
||||
isWorkflowRunning = true;
|
||||
if (app.ui.settings.getSettingValue(SETTING_AUTO_ID, true)) setFocusState(true);
|
||||
}
|
||||
activeNode = app.graph.getNodeById(e.detail);
|
||||
if (isFocusEnabled && activeNode) {
|
||||
app.canvas.setDirty(true, true);
|
||||
if (!animationFrameId) requestAnimationFrame(smoothFollow);
|
||||
}
|
||||
} else {
|
||||
isWorkflowRunning = false;
|
||||
activeNode = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,14 +141,11 @@ app.registerExtension({
|
||||
if (isFocusEnabled && activeNode) {
|
||||
const canvas = app.canvas;
|
||||
const ds = canvas.ds;
|
||||
const screenCenterX = canvas.canvas.clientWidth / 2;
|
||||
const screenCenterY = canvas.canvas.clientHeight / 2;
|
||||
const nodeCenterX = activeNode.pos[0] + activeNode.size[0] / 2;
|
||||
const nodeCenterY = activeNode.pos[1] + activeNode.size[1] / 2;
|
||||
const targetOffsetX = (screenCenterX / ds.scale) - nodeCenterX;
|
||||
const targetOffsetY = (screenCenterY / ds.scale) - nodeCenterY;
|
||||
ds.offset[0] += (targetOffsetX - ds.offset[0]) * SMOOTHNESS;
|
||||
ds.offset[1] += (targetOffsetY - ds.offset[1]) * SMOOTHNESS;
|
||||
const center = [canvas.canvas.clientWidth / 2, canvas.canvas.clientHeight / 2];
|
||||
const nodeCenter = [activeNode.pos[0] + activeNode.size[0] / 2, activeNode.pos[1] + activeNode.size[1] / 2];
|
||||
const target = [(center[0] / ds.scale) - nodeCenter[0], (center[1] / ds.scale) - nodeCenter[1]];
|
||||
ds.offset[0] += (target[0] - ds.offset[0]) * SMOOTHNESS;
|
||||
ds.offset[1] += (target[1] - ds.offset[1]) * SMOOTHNESS;
|
||||
canvas.setDirty(true, true);
|
||||
animationFrameId = requestAnimationFrame(smoothFollow);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.HALXP.RunMonitor",
|
||||
async setup() {
|
||||
console.log("[HALXP-RunMonitor] Initializing...");
|
||||
|
||||
// --- CONSTANTS ---
|
||||
const ID_ENABLE = "HALXP.RunMonitor.Enable";
|
||||
const ID_SUCCESS = "HALXP.RunMonitor.SuccessPath";
|
||||
const ID_ERROR = "HALXP.RunMonitor.ErrorPath";
|
||||
|
||||
// --- STATE & UI ELEMENTS ---
|
||||
let monitorBtn = null;
|
||||
let monitorDot = null;
|
||||
|
||||
// --- 1. CORE LOGIC: SYNC & UPDATE ---
|
||||
|
||||
// Updates the visuals of the button (Green/Gray) based on state
|
||||
function updateButtonVisuals(enabled) {
|
||||
if (!monitorBtn || !monitorDot) return;
|
||||
|
||||
if (enabled) {
|
||||
// ACTIVE STATE (Green)
|
||||
monitorDot.style.backgroundColor = "#4CAF50";
|
||||
monitorDot.style.boxShadow = "0 0 6px #4CAF50";
|
||||
monitorBtn.style.color = "#fff";
|
||||
monitorBtn.style.borderColor = "#4CAF50";
|
||||
monitorBtn.title = "Monitor: ON";
|
||||
} else {
|
||||
// INACTIVE STATE (Gray/Default)
|
||||
monitorDot.style.backgroundColor = "#666";
|
||||
monitorDot.style.boxShadow = "none";
|
||||
monitorBtn.style.color = "var(--fg-color, #ccc)";
|
||||
monitorBtn.style.borderColor = "#d3c1c1ff"; // Matches Focus default border
|
||||
monitorBtn.title = "Monitor: OFF";
|
||||
}
|
||||
}
|
||||
|
||||
// Sends config to Python backend AND updates UI
|
||||
function syncConfigToBackend() {
|
||||
const enabled = app.ui.settings.getSettingValue(ID_ENABLE, false);
|
||||
const config = {
|
||||
enabled: enabled,
|
||||
success_path: app.ui.settings.getSettingValue(ID_SUCCESS, ""),
|
||||
error_path: app.ui.settings.getSettingValue(ID_ERROR, "")
|
||||
};
|
||||
|
||||
// 1. Update the UI Button immediately
|
||||
updateButtonVisuals(enabled);
|
||||
|
||||
// 2. Send to Backend
|
||||
api.fetchApi("/halxp/update_config", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
}
|
||||
|
||||
// --- 2. REGISTER SETTINGS ---
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id: ID_ENABLE,
|
||||
name: "Enable External Monitoring",
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
category: ['HALXP-Comfy', 'Run Monitor', 'Enable'],
|
||||
tooltip: "Enable to run scripts on workflow status change.",
|
||||
onChange: syncConfigToBackend // Triggers when changed via Menu OR Button
|
||||
});
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id: ID_SUCCESS,
|
||||
name: "Success Command Path",
|
||||
type: "text",
|
||||
defaultValue: "",
|
||||
category: ['HALXP-Comfy', 'Run Monitor', 'Success'],
|
||||
tooltip: "Full path to a .bat/.sh to run on success.",
|
||||
onChange: syncConfigToBackend
|
||||
});
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id: ID_ERROR,
|
||||
name: "Error Command Path",
|
||||
type: "text",
|
||||
defaultValue: "",
|
||||
category: ['HALXP-Comfy', 'Run Monitor', 'Error'],
|
||||
tooltip: "Full path to a .bat/.sh to run on error.",
|
||||
onChange: syncConfigToBackend
|
||||
});
|
||||
|
||||
// --- 3. UI BUTTON CREATION (Copied from Focus) ---
|
||||
|
||||
function createButton() {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = "Monitor";
|
||||
btn.id = "halxp-monitor-btn";
|
||||
|
||||
// EXACT STYLE COPY FROM FOCUS.JS
|
||||
btn.style.cssText = `
|
||||
position: relative; display: inline-flex; align-items: center; justify-content: center;
|
||||
padding: 0 10px; margin: 0 4px; background-color: var(--comfy-input-bg, #333);
|
||||
color: var(--fg-color, #fff); border: 1px solid var(--border-color, #555);
|
||||
border-radius: 4px; cursor: pointer; font-family: sans-serif; font-size: 13px;
|
||||
font-weight: 600; flex-shrink: 0; white-space: nowrap; z-index: 9999; pointer-events: auto;
|
||||
border: 1px solid #d3c1c1ff;
|
||||
`;
|
||||
|
||||
// Create the Status Dot
|
||||
const d = document.createElement("span");
|
||||
d.style.cssText = `width: 8px; height: 8px; border-radius: 50%; background-color: #666; margin-right: 8px; display: inline-block; flex-shrink: 0;`;
|
||||
|
||||
btn.prepend(d);
|
||||
monitorDot = d;
|
||||
|
||||
// Handle Click: Toggle the setting (which triggers onChange -> syncConfigToBackend)
|
||||
btn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const current = app.ui.settings.getSettingValue(ID_ENABLE, false);
|
||||
app.ui.settings.setSettingValue(ID_ENABLE, !current);
|
||||
};
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
function injectButton() {
|
||||
// Prevent duplicates
|
||||
if (document.getElementById("halxp-monitor-btn")) return;
|
||||
|
||||
// Find the "Manager" button or any existing button to latch onto
|
||||
const allButtons = Array.from(document.querySelectorAll("button"));
|
||||
const targetBtn = allButtons.find(b => {
|
||||
if (!b.offsetParent) return false;
|
||||
const text = b.innerText.trim();
|
||||
// Try to place it near Manager, or Focus if it loaded first
|
||||
return text === "Manager" || text === "ComfyUI Manager" || text === "Focus";
|
||||
});
|
||||
|
||||
if (targetBtn && targetBtn.parentNode) {
|
||||
monitorBtn = createButton();
|
||||
|
||||
// Match height of the neighbor
|
||||
const h = targetBtn.offsetHeight;
|
||||
monitorBtn.style.height = h > 0 ? `${h}px` : "28px";
|
||||
|
||||
// Insert BEFORE the target (Standardizes menu flow)
|
||||
// If you want it specifically AFTER Focus but BEFORE Manager, this generally works
|
||||
// because extensions load in order.
|
||||
targetBtn.parentNode.insertBefore(monitorBtn, targetBtn);
|
||||
|
||||
// Set initial visual state
|
||||
const isEnabled = app.ui.settings.getSettingValue(ID_ENABLE, false);
|
||||
updateButtonVisuals(isEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. INITIALIZATION ---
|
||||
|
||||
// Attempt to inject button periodically until UI is ready
|
||||
setInterval(injectButton, 500);
|
||||
|
||||
// Sync backend on load (wait slightly for settings to load)
|
||||
setTimeout(syncConfigToBackend, 1000);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user