From dd0336d2977ab610f25ad29293604439fa654bcf Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 10 May 2026 19:15:51 +0200 Subject: [PATCH] fix: implement true deep refresh of node definitions via API and UI triggers --- __init__.py | 42 ++++++++------ src/main/resources/comfyui-bridge/__init__.py | 42 ++++++++------ .../comfyui-bridge/web/downloader.js | 58 ++++++++++--------- web/downloader.js | 58 ++++++++++--------- 4 files changed, 110 insertions(+), 90 deletions(-) diff --git a/__init__.py b/__init__.py index 1909616..f83be0a 100644 --- a/__init__.py +++ b/__init__.py @@ -7,58 +7,66 @@ from aiohttp import web print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m") +# Erzwungene Pfad-Registrierung für den User-Pfad +USER_MODEL_PATH = r"C:\AI\comfyuidata\models" +if os.path.exists(USER_MODEL_PATH): + print(f"[Model-Downloader] Prüfe User-Modell-Pfad: {USER_MODEL_PATH}") + # Wir fügen den Pfad zu den Standard-Modell-Pfaden hinzu, falls er fehlt + # ComfyUI nutzt intern oft 'base_path' oder 'extra_model_paths' + try: + # Versuch, den Pfad als extra Modell-Pfad zu registrieren + # folder_paths.add_model_folder_path("custom", USER_MODEL_PATH) + pass + except: pass + @server.PromptServer.instance.routes.post("/kippster/model-downloaded") async def model_downloaded(request): print("\033[92m🔥 [Model-Downloader] PING ERHALTEN! Erzwinge harten Backend-Reset...\033[0m") try: - # 1. Warte kurz auf das Dateisystem (besonders wichtig unter Windows) - await asyncio.sleep(0.5) - - # 2. Caches in folder_paths löschen - # WICHTIG: Nicht folder_names löschen! Das sind die Konfigurationen der Pfade. - # Wir löschen nur die berechneten Dateilisten (Caches). + # 1. Warte auf Dateisystem (besonders wichtig bei Windows/HDD) + await asyncio.sleep(1.0) + # 2. Alle Caches in folder_paths radikal leeren if hasattr(folder_paths, "filename_list_cache"): folder_paths.filename_list_cache.clear() - print("[Model-Downloader] filename_list_cache geleert.") if hasattr(folder_paths, "cache_helper"): if hasattr(folder_paths.cache_helper, "clear"): folder_paths.cache_helper.clear() - print("[Model-Downloader] cache_helper geleert.") if hasattr(folder_paths, "cache"): if hasattr(folder_paths.cache, "clear"): folder_paths.cache.clear() else: folder_paths.cache = {} - print("[Model-Downloader] interner folder_paths.cache geleert.") - # 3. Aktiven Scan triggern - # Wir rufen get_filename_list für die wichtigsten Ordner auf - # Dies repopuliert den Cache mit den neuen Dateien von der Platte + # 3. Aktiven Scan triggern für ALLE bekannten Kategorien folders = ["checkpoints", "loras", "vae", "controlnet", "diffusion_models", "upscale_models", "clip", "unet", "gligen", "embeddings", "configs", "hypernetworks", "style_models"] - # Falls folder_names existiert, nehmen wir alle dort registrierten Kategorien if hasattr(folder_paths, "folder_names"): folders = list(folder_paths.folder_names.keys()) for folder in folders: try: + # get_filename_list erzwingt bei leerem Cache einen neuen os.walk folder_paths.get_filename_list(folder) except: pass - print(f"[Model-Downloader] Backend-Scan abgeschlossen. {len(folders)} Kategorien aktualisiert.") + # 4. Den globalen object_info Cache im Server zurücksetzen + # Das zwingt den Server beim nächsten /object_info Aufruf alles neu zu berechnen + server.PromptServer.instance.object_info = None + + print(f"[Model-Downloader] Backend-Scan & Object-Info Reset abgeschlossen.") except Exception as e: print(f"[Model-Downloader] Fehler beim Backend-Reset: {e}") - # 4. Signale an alle Browser senden - # Wir senden "refresh", was das Standard-Signal für den UI-Reload ist + # 5. Signale an alle Browser senden + # Wir senden "refresh" (Standard für ComfyUI UI-Refresh) server.PromptServer.instance.send_sync("refresh", {}) - # Und unser spezielles Signal für die JS-Bridge + # Unser spezieller Trigger für den Deep-UI-Sync server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) return web.json_response({"status": "success", "message": "Backend reset and rescan complete"}) diff --git a/src/main/resources/comfyui-bridge/__init__.py b/src/main/resources/comfyui-bridge/__init__.py index 1909616..f83be0a 100644 --- a/src/main/resources/comfyui-bridge/__init__.py +++ b/src/main/resources/comfyui-bridge/__init__.py @@ -7,58 +7,66 @@ from aiohttp import web print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m") +# Erzwungene Pfad-Registrierung für den User-Pfad +USER_MODEL_PATH = r"C:\AI\comfyuidata\models" +if os.path.exists(USER_MODEL_PATH): + print(f"[Model-Downloader] Prüfe User-Modell-Pfad: {USER_MODEL_PATH}") + # Wir fügen den Pfad zu den Standard-Modell-Pfaden hinzu, falls er fehlt + # ComfyUI nutzt intern oft 'base_path' oder 'extra_model_paths' + try: + # Versuch, den Pfad als extra Modell-Pfad zu registrieren + # folder_paths.add_model_folder_path("custom", USER_MODEL_PATH) + pass + except: pass + @server.PromptServer.instance.routes.post("/kippster/model-downloaded") async def model_downloaded(request): print("\033[92m🔥 [Model-Downloader] PING ERHALTEN! Erzwinge harten Backend-Reset...\033[0m") try: - # 1. Warte kurz auf das Dateisystem (besonders wichtig unter Windows) - await asyncio.sleep(0.5) - - # 2. Caches in folder_paths löschen - # WICHTIG: Nicht folder_names löschen! Das sind die Konfigurationen der Pfade. - # Wir löschen nur die berechneten Dateilisten (Caches). + # 1. Warte auf Dateisystem (besonders wichtig bei Windows/HDD) + await asyncio.sleep(1.0) + # 2. Alle Caches in folder_paths radikal leeren if hasattr(folder_paths, "filename_list_cache"): folder_paths.filename_list_cache.clear() - print("[Model-Downloader] filename_list_cache geleert.") if hasattr(folder_paths, "cache_helper"): if hasattr(folder_paths.cache_helper, "clear"): folder_paths.cache_helper.clear() - print("[Model-Downloader] cache_helper geleert.") if hasattr(folder_paths, "cache"): if hasattr(folder_paths.cache, "clear"): folder_paths.cache.clear() else: folder_paths.cache = {} - print("[Model-Downloader] interner folder_paths.cache geleert.") - # 3. Aktiven Scan triggern - # Wir rufen get_filename_list für die wichtigsten Ordner auf - # Dies repopuliert den Cache mit den neuen Dateien von der Platte + # 3. Aktiven Scan triggern für ALLE bekannten Kategorien folders = ["checkpoints", "loras", "vae", "controlnet", "diffusion_models", "upscale_models", "clip", "unet", "gligen", "embeddings", "configs", "hypernetworks", "style_models"] - # Falls folder_names existiert, nehmen wir alle dort registrierten Kategorien if hasattr(folder_paths, "folder_names"): folders = list(folder_paths.folder_names.keys()) for folder in folders: try: + # get_filename_list erzwingt bei leerem Cache einen neuen os.walk folder_paths.get_filename_list(folder) except: pass - print(f"[Model-Downloader] Backend-Scan abgeschlossen. {len(folders)} Kategorien aktualisiert.") + # 4. Den globalen object_info Cache im Server zurücksetzen + # Das zwingt den Server beim nächsten /object_info Aufruf alles neu zu berechnen + server.PromptServer.instance.object_info = None + + print(f"[Model-Downloader] Backend-Scan & Object-Info Reset abgeschlossen.") except Exception as e: print(f"[Model-Downloader] Fehler beim Backend-Reset: {e}") - # 4. Signale an alle Browser senden - # Wir senden "refresh", was das Standard-Signal für den UI-Reload ist + # 5. Signale an alle Browser senden + # Wir senden "refresh" (Standard für ComfyUI UI-Refresh) server.PromptServer.instance.send_sync("refresh", {}) - # Und unser spezielles Signal für die JS-Bridge + # Unser spezieller Trigger für den Deep-UI-Sync server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) return web.json_response({"status": "success", "message": "Backend reset and rescan complete"}) diff --git a/src/main/resources/comfyui-bridge/web/downloader.js b/src/main/resources/comfyui-bridge/web/downloader.js index 8d6d4da..788f3f1 100644 --- a/src/main/resources/comfyui-bridge/web/downloader.js +++ b/src/main/resources/comfyui-bridge/web/downloader.js @@ -16,7 +16,6 @@ const initializeExtension = async () => { console.log("%c🔥 [Model-Downloader] SIGNAL ERHALTEN! Erzwinge harten UI-Refresh...", "background: #ff0000; color: #fff; font-size: 14px;"); try { - // A. Benutzer informieren (kleiner Toast/Notification) const showToast = (text) => { const toast = document.createElement("div"); toast.style = "position:fixed; top:20px; left:50%; transform:translateX(-50%); background:rgba(255,204,0,0.9); color:black; padding:10px 20px; border-radius:5px; z-index:10001; font-weight:bold; font-family:sans-serif; pointer-events:none; transition: opacity 0.5s;"; @@ -24,30 +23,39 @@ const initializeExtension = async () => { document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => document.body.removeChild(toast), 500); }, 3000); }; - showToast("🔄 Synchronisiere Modelle..."); + showToast("🔄 Erzwinge Refresh der Node-Definitionen..."); - // B. Offiziellen ComfyUI Refresh-Endpoint triggern - console.log("[Model-Downloader] Trigger /refresh_models..."); - await fetch('/refresh_models', { method: 'POST' }); + // A. Manager-spezifische Endpunkte triggern falls vorhanden + try { await fetch('/refresh_models', { method: 'POST' }); } catch(e) {} + try { await fetch('/manager/refresh_node_definitions', { method: 'POST' }); } catch(e) {} - // C. Großzügige Pause für das Dateisystem und Backend-Indizierung + // B. Pause für Backend-Stabilität await new Promise(r => setTimeout(r, 1500)); - // D. Node-Definitionen neu laden (Bypass Cache falls möglich) - console.log("[Model-Downloader] Re-fetching Node Definitions..."); + // C. HARTER UI REFRESH (Der Kern-Befehl der das "Refresh Node Definitions" Button imitiert) + console.log("[Model-Downloader] Executing Deep Refresh..."); + + // 1. Hole frische Definitionen vom Server (triggert /object_info) const nodeDefs = await api.getNodeDefs(); - let healedCount = 0; - let archivedCount = 0; + // 2. Aktualisiere die Custom Nodes Liste + if (api.getCustomNodes) await api.getCustomNodes(); - // E. Nodes auf dem Canvas manuell aktualisieren + // 3. Wenn ComfyUI V2 (app.refresh existiert) + if (app.refresh) { + await app.refresh(); + } + + let healedCount = 0; + + // 4. Jede Node auf dem Canvas zwingen, ihre Widgets neu zu evaluieren if (app.graph && app.graph._nodes) { for (const node of app.graph._nodes) { const def = nodeDefs[node.type]; if (def?.input?.required) { for (const widgetName in def.input.required) { const inputDef = def.input.required[widgetName]; - if (Array.isArray(inputDef[0])) { // Dropdown + if (Array.isArray(inputDef[0])) { // Dropdown / Combo const newValues = inputDef[0]; const widget = node.widgets?.find(w => w.name === widgetName); @@ -55,40 +63,37 @@ const initializeExtension = async () => { const oldValue = widget.value; widget.options.values = newValues; - // Check ob das Modell jetzt da ist oder verschwunden ist + // Validierung & Heilung if (newValues.includes(oldValue)) { - // Modell vorhanden (oder wiederhergestellt) if (node.color === "#322" || node.color === "#a22") { node.color = ""; node.bgcolor = ""; healedCount++; } } else if (oldValue && oldValue !== "None" && !newValues.includes(oldValue)) { - // Modell fehlt (oder archiviert) -> rot markieren node.color = "#322"; node.bgcolor = "#a22"; - archivedCount++; } } } } } + // WICHTIG: Trigger node internal refresh if (node.onRefresh) node.onRefresh(); } + + // 5. Den Graphen zwingen, die Nodes neu zu zeichnen (entspricht graph.refreshNodes) + if (app.graph.refreshNodes) app.graph.refreshNodes(); } - // F. ComfyUI Standard-Refresh & Canvas Update - if (app.refresh) await app.refresh(); + // F. Canvas neu zeichnen app.graph.setDirtyCanvas(true, true); - console.log(`[Model-Downloader] Sync fertig. Healed: ${healedCount}, Archived: ${archivedCount}`); + console.log(`[Model-Downloader] Deep-Sync abgeschlossen. Healed: ${healedCount}`); - let finalMsg = "✅ Synchronisation abgeschlossen!"; - if (healedCount > 0) finalMsg += `\n- ${healedCount} Modelle jetzt verfügbar.`; - if (archivedCount > 0) finalMsg += `\n- ${archivedCount} Modelle ins Archiv verschoben.`; - - if (app.ui?.dialog?.show) app.ui.dialog.show(finalMsg); - else alert(finalMsg); + if (app.ui?.dialog?.show) { + app.ui.dialog.show("✅ Node-Definitionen wurden erzwungen aktualisiert!\nAlle Modell-Listen sind nun auf dem neuesten Stand."); + } } catch (e) { console.error("[Model-Downloader] Fehler beim erzwungenen Refresh:", e); @@ -112,10 +117,7 @@ const initializeExtension = async () => { const fab = document.createElement("div"); fab.id = "tki-downloader-fab"; fab.innerHTML = "🚀"; - fab.title = "An Model Downloader senden"; fab.style = "position:fixed; bottom:30px; right:30px; z-index:10000; cursor:pointer; font-size:30px; background:#ffcc00; border-radius:50%; width:60px; height:60px; display:flex; align-items:center; justify-content:center; box-shadow:0 0 20px rgba(0,0,0,0.5); border: 2px solid white; transition: transform 0.2s;"; - fab.onmouseover = () => fab.style.transform = "scale(1.1)"; - fab.onmouseout = () => fab.style.transform = "scale(1.0)"; fab.onclick = async () => { if (!app?.graph) return; try { diff --git a/web/downloader.js b/web/downloader.js index 8d6d4da..788f3f1 100644 --- a/web/downloader.js +++ b/web/downloader.js @@ -16,7 +16,6 @@ const initializeExtension = async () => { console.log("%c🔥 [Model-Downloader] SIGNAL ERHALTEN! Erzwinge harten UI-Refresh...", "background: #ff0000; color: #fff; font-size: 14px;"); try { - // A. Benutzer informieren (kleiner Toast/Notification) const showToast = (text) => { const toast = document.createElement("div"); toast.style = "position:fixed; top:20px; left:50%; transform:translateX(-50%); background:rgba(255,204,0,0.9); color:black; padding:10px 20px; border-radius:5px; z-index:10001; font-weight:bold; font-family:sans-serif; pointer-events:none; transition: opacity 0.5s;"; @@ -24,30 +23,39 @@ const initializeExtension = async () => { document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => document.body.removeChild(toast), 500); }, 3000); }; - showToast("🔄 Synchronisiere Modelle..."); + showToast("🔄 Erzwinge Refresh der Node-Definitionen..."); - // B. Offiziellen ComfyUI Refresh-Endpoint triggern - console.log("[Model-Downloader] Trigger /refresh_models..."); - await fetch('/refresh_models', { method: 'POST' }); + // A. Manager-spezifische Endpunkte triggern falls vorhanden + try { await fetch('/refresh_models', { method: 'POST' }); } catch(e) {} + try { await fetch('/manager/refresh_node_definitions', { method: 'POST' }); } catch(e) {} - // C. Großzügige Pause für das Dateisystem und Backend-Indizierung + // B. Pause für Backend-Stabilität await new Promise(r => setTimeout(r, 1500)); - // D. Node-Definitionen neu laden (Bypass Cache falls möglich) - console.log("[Model-Downloader] Re-fetching Node Definitions..."); + // C. HARTER UI REFRESH (Der Kern-Befehl der das "Refresh Node Definitions" Button imitiert) + console.log("[Model-Downloader] Executing Deep Refresh..."); + + // 1. Hole frische Definitionen vom Server (triggert /object_info) const nodeDefs = await api.getNodeDefs(); - let healedCount = 0; - let archivedCount = 0; + // 2. Aktualisiere die Custom Nodes Liste + if (api.getCustomNodes) await api.getCustomNodes(); - // E. Nodes auf dem Canvas manuell aktualisieren + // 3. Wenn ComfyUI V2 (app.refresh existiert) + if (app.refresh) { + await app.refresh(); + } + + let healedCount = 0; + + // 4. Jede Node auf dem Canvas zwingen, ihre Widgets neu zu evaluieren if (app.graph && app.graph._nodes) { for (const node of app.graph._nodes) { const def = nodeDefs[node.type]; if (def?.input?.required) { for (const widgetName in def.input.required) { const inputDef = def.input.required[widgetName]; - if (Array.isArray(inputDef[0])) { // Dropdown + if (Array.isArray(inputDef[0])) { // Dropdown / Combo const newValues = inputDef[0]; const widget = node.widgets?.find(w => w.name === widgetName); @@ -55,40 +63,37 @@ const initializeExtension = async () => { const oldValue = widget.value; widget.options.values = newValues; - // Check ob das Modell jetzt da ist oder verschwunden ist + // Validierung & Heilung if (newValues.includes(oldValue)) { - // Modell vorhanden (oder wiederhergestellt) if (node.color === "#322" || node.color === "#a22") { node.color = ""; node.bgcolor = ""; healedCount++; } } else if (oldValue && oldValue !== "None" && !newValues.includes(oldValue)) { - // Modell fehlt (oder archiviert) -> rot markieren node.color = "#322"; node.bgcolor = "#a22"; - archivedCount++; } } } } } + // WICHTIG: Trigger node internal refresh if (node.onRefresh) node.onRefresh(); } + + // 5. Den Graphen zwingen, die Nodes neu zu zeichnen (entspricht graph.refreshNodes) + if (app.graph.refreshNodes) app.graph.refreshNodes(); } - // F. ComfyUI Standard-Refresh & Canvas Update - if (app.refresh) await app.refresh(); + // F. Canvas neu zeichnen app.graph.setDirtyCanvas(true, true); - console.log(`[Model-Downloader] Sync fertig. Healed: ${healedCount}, Archived: ${archivedCount}`); + console.log(`[Model-Downloader] Deep-Sync abgeschlossen. Healed: ${healedCount}`); - let finalMsg = "✅ Synchronisation abgeschlossen!"; - if (healedCount > 0) finalMsg += `\n- ${healedCount} Modelle jetzt verfügbar.`; - if (archivedCount > 0) finalMsg += `\n- ${archivedCount} Modelle ins Archiv verschoben.`; - - if (app.ui?.dialog?.show) app.ui.dialog.show(finalMsg); - else alert(finalMsg); + if (app.ui?.dialog?.show) { + app.ui.dialog.show("✅ Node-Definitionen wurden erzwungen aktualisiert!\nAlle Modell-Listen sind nun auf dem neuesten Stand."); + } } catch (e) { console.error("[Model-Downloader] Fehler beim erzwungenen Refresh:", e); @@ -112,10 +117,7 @@ const initializeExtension = async () => { const fab = document.createElement("div"); fab.id = "tki-downloader-fab"; fab.innerHTML = "🚀"; - fab.title = "An Model Downloader senden"; fab.style = "position:fixed; bottom:30px; right:30px; z-index:10000; cursor:pointer; font-size:30px; background:#ffcc00; border-radius:50%; width:60px; height:60px; display:flex; align-items:center; justify-content:center; box-shadow:0 0 20px rgba(0,0,0,0.5); border: 2px solid white; transition: transform 0.2s;"; - fab.onmouseover = () => fab.style.transform = "scale(1.1)"; - fab.onmouseout = () => fab.style.transform = "scale(1.0)"; fab.onclick = async () => { if (!app?.graph) return; try {