diff --git a/__init__.py b/__init__.py index 79305f5..de955b3 100644 --- a/__init__.py +++ b/__init__.py @@ -11,7 +11,8 @@ halxp_config = { } # --- STATE TRACKING --- -run_has_error = False +# We store the ID of the prompt that just failed. +last_failed_prompt_id = None # --- 1. API ROUTE --- @server.PromptServer.instance.routes.post("/halxp/update_config") @@ -19,6 +20,8 @@ async def update_halxp_config(request): try: data = await request.json() halxp_config.update(data) + # Debug Log to confirm settings are received + print(f"[HALXP-Core] Settings Updated: Enabled={halxp_config['enabled']}") return web.json_response({"status": "ok"}) except Exception as e: print(f"[HALXP-Core] Error updating config: {e}") @@ -28,7 +31,7 @@ async def update_halxp_config(request): original_send_json = server.PromptServer.instance.send_json async def intercepted_send_json(self, event, data, sid=None): - global run_has_error + global last_failed_prompt_id # 1. Execute Original Behavior await original_send_json(event, data, sid) @@ -36,39 +39,37 @@ async def intercepted_send_json(self, event, data, sid=None): # 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 + if event == "execution_error": + # Record which prompt ID failed + current_id = data.get("prompt_id") + last_failed_prompt_id = current_id + + print(f"[HALXP-Monitor] ❌ Error detected for Prompt ID: {current_id}") + cmd = halxp_config.get("error_path") if cmd and cmd.strip(): - print(f"[HALXP-RunMonitor] ❌ Workflow Error. Running: {cmd}") + print(f"[HALXP-Monitor] 🚀 Running Error Script...") subprocess.Popen(cmd, shell=True) - # --- DETECT FINISH (SUCCESS or FAIL) --- - # 'executing' with node=None means the prompt is done. + # --- DETECT FINISH --- + # 'executing' with node=None means the prompt is fully done elif event == "executing" and data.get("node") is None: + finished_id = data.get("prompt_id") - # Only run success script if NO error occurred - if not run_has_error: + # LOGIC: Only run success if this ID is NOT the one that just failed + if finished_id != last_failed_prompt_id: cmd = halxp_config.get("success_path") if cmd and cmd.strip(): - print(f"[HALXP-RunMonitor] ✅ Workflow Finished. Running: {cmd}") + print(f"[HALXP-Monitor] ✅ Workflow Finished (Success). Running Script...") 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 + else: + print(f"[HALXP-Monitor] ✅ Workflow Finished, but no Success Path configured.") + else: + print(f"[HALXP-Monitor] â„šī¸ Workflow Finished, but skipping Success Script (This was the failed run).") except Exception as e: - print(f"[HALXP-RunMonitor] âš ī¸ Failed to execute monitoring script: {e}") + print(f"[HALXP-Monitor] âš ī¸ Script Execution Failed: {e}") # Apply the hook server.PromptServer.instance.send_json = intercepted_send_json.__get__(server.PromptServer.instance, server.PromptServer) diff --git a/js/halxp-runmonitor.js b/js/halxp-runmonitor.js index d11c2d3..910a75f 100644 --- a/js/halxp-runmonitor.js +++ b/js/halxp-runmonitor.js @@ -7,6 +7,7 @@ app.registerExtension({ console.log("[HALXP-RunMonitor] Initializing..."); // --- CONSTANTS --- + const ID_DISPLAY = "HALXP.RunMonitor.DisplayButton"; const ID_ENABLE = "HALXP.RunMonitor.Enable"; const ID_SUCCESS = "HALXP.RunMonitor.SuccessPath"; const ID_ERROR = "HALXP.RunMonitor.ErrorPath"; @@ -15,21 +16,19 @@ app.registerExtension({ let monitorBtn = null; let monitorDot = null; - // --- 1. CORE LOGIC: VISUALS --- + // --- 1. CORE LOGIC --- - // Directly update the button based on the boolean passed to it function updateButtonVisuals(isEnabled) { if (!monitorBtn || !monitorDot) return; - if (isEnabled) { - // ACTIVE STATE (Green) + // Green (ON) 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) + // Gray (OFF) monitorDot.style.backgroundColor = "#666"; monitorDot.style.boxShadow = "none"; monitorBtn.style.color = "var(--fg-color, #ccc)"; @@ -38,65 +37,44 @@ app.registerExtension({ } } - // --- 2. CORE LOGIC: BACKEND SYNC --- + // UPDATED: Now accepts an optional override for the enabled state + function pushConfigToBackend(enabledOverride) { + // If an override is provided (from onChange), use it. + // Otherwise, fall back to reading the setting (for init/manual calls). + let isEnabled; + if (enabledOverride !== undefined && enabledOverride !== null) { + isEnabled = enabledOverride; + } else { + isEnabled = app.ui.settings.getSettingValue(ID_ENABLE, false); + } - function pushConfigToBackend() { - // We can safely read all values here for the Python backend const config = { - enabled: app.ui.settings.getSettingValue(ID_ENABLE, false), + enabled: isEnabled, success_path: app.ui.settings.getSettingValue(ID_SUCCESS, ""), error_path: app.ui.settings.getSettingValue(ID_ERROR, "") }; + // Send to Python api.fetchApi("/halxp/update_config", { method: "POST", body: JSON.stringify(config) }); } - // --- 3. 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: (value) => { - // CRITICAL FIX: Use 'value' directly. Do not re-read from settings. - updateButtonVisuals(value); - pushConfigToBackend(); + // --- 2. UI HELPERS --- + + function removeButton() { + if (monitorBtn) { + monitorBtn.remove(); + monitorBtn = null; + monitorDot = null; } - }); - - 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: pushConfigToBackend - }); - - 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: pushConfigToBackend - }); - - // --- 4. UI BUTTON CREATION --- + } function createButton() { const btn = document.createElement("button"); btn.textContent = "Monitor"; btn.id = "halxp-monitor-btn"; - 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); @@ -105,26 +83,23 @@ app.registerExtension({ 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;`; - btn.prepend(d); monitorDot = d; - // CLICK HANDLER: - // We simply toggle the setting. The setting's onChange will handle the visuals. 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() { + const shouldDisplay = app.ui.settings.getSettingValue(ID_DISPLAY, true); + if (!shouldDisplay) { removeButton(); return; } if (document.getElementById("halxp-monitor-btn")) return; const allButtons = Array.from(document.querySelectorAll("button")); @@ -136,27 +111,56 @@ app.registerExtension({ if (targetBtn && targetBtn.parentNode) { monitorBtn = createButton(); - const h = targetBtn.offsetHeight; monitorBtn.style.height = h > 0 ? `${h}px` : "28px"; - targetBtn.parentNode.insertBefore(monitorBtn, targetBtn); - // INITIAL SYNC: Read from memory once on creation - const currentVal = app.ui.settings.getSettingValue(ID_ENABLE, false); - updateButtonVisuals(currentVal); + // Visual Sync + updateButtonVisuals(app.ui.settings.getSettingValue(ID_ENABLE, false)); } } - // --- 5. INITIALIZATION --- + // --- 3. REGISTER SETTINGS --- + + app.ui.settings.addSetting({ + id: ID_DISPLAY, name: "Display Monitor Button", type: "boolean", defaultValue: true, + category: ['HALXP-Comfy', 'Run Monitor', 'Display'], + onChange: (value) => { value ? injectButton() : removeButton(); } + }); + + app.ui.settings.addSetting({ + id: ID_ENABLE, name: "Enable External Monitoring", type: "boolean", defaultValue: false, + category: ['HALXP-Comfy', 'Run Monitor', 'Enable'], + onChange: (value) => { + updateButtonVisuals(value); + // PASS VALUE DIRECTLY HERE to prevent race condition + pushConfigToBackend(value); + } + }); + + app.ui.settings.addSetting({ + id: ID_SUCCESS, name: "Success Command Path", type: "text", defaultValue: "", + category: ['HALXP-Comfy', 'Run Monitor', 'Success'], + // We pass undefined here so it reads from settings, as string updates are usually slower/safer + onChange: () => pushConfigToBackend() + }); + + app.ui.settings.addSetting({ + id: ID_ERROR, name: "Error Command Path", type: "text", defaultValue: "", + category: ['HALXP-Comfy', 'Run Monitor', 'Error'], + onChange: () => pushConfigToBackend() + }); + + // --- 4. INITIALIZATION --- setInterval(injectButton, 500); - // Safety Sync on load + // --- FORCE SYNC ON LOAD --- setTimeout(() => { - const currentVal = app.ui.settings.getSettingValue(ID_ENABLE, false); - updateButtonVisuals(currentVal); - pushConfigToBackend(); - }, 2000); + const isEnabled = app.ui.settings.getSettingValue(ID_ENABLE, false); + updateButtonVisuals(isEnabled); + pushConfigToBackend(isEnabled); // Pass explicit value + console.log("[HALXP-RunMonitor] Force-synced config to backend."); + }, 100); } }); \ No newline at end of file