diff --git a/__init__.py b/__init__.py index c3b2092..25e99d5 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,56 @@ # ComfyUI Model Downloader Bridge +import server +import folder_paths +import os +import asyncio +from aiohttp import web + print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m") + +@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. Dateisystem-Cache löschen (Kurze Pause fĂŒr Windows-Dateisystem-Handles) + await asyncio.sleep(0.5) + + # Robusteres Löschen der Caches in folder_paths + if hasattr(folder_paths, "folder_names"): + for k in list(folder_paths.folder_names.keys()): + folder_paths.folder_names[k] = [] + + if hasattr(folder_paths, "base_path_layers"): + for layer in folder_paths.base_path_layers.values(): + if hasattr(layer, "folder_names"): + layer.folder_names.clear() + + if hasattr(folder_paths, "cache"): + folder_paths.cache.clear() + + # 2. Den Definitions-Cache von ComfyUI löschen + # Dies zwingt ComfyUI, die Node-Metadaten (inkl. Modell-Listen) neu zu generieren + server.PromptServer.instance.object_info = None + + # 3. Aktiven Scan triggern + # Durch den Aufruf von get_filename_list wird ComfyUI gezwungen, die Ordner neu zu indizieren + for folder in ["checkpoints", "loras", "vae", "controlnet", "diffusion_models", "upscale_models", "clip", "unet", "gligen"]: + try: + folder_paths.get_filename_list(folder) + except: pass + + print("[Model-Downloader] Backend-Reset und Scan abgeschlossen.") + + except Exception as e: + print(f"[Model-Downloader] Fehler beim Reset: {e}") + + # 4. Signale an alle Browser (jetzt mit frischen Daten) + # "refresh" ist ein Standard-Signal, "kippster-refresh-ui" ist unser spezieller Trigger fĂŒr Deep-Update + server.PromptServer.instance.send_sync("refresh", {}) + server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) + + return web.json_response({"status": "success", "message": "Backend reset and rescan complete"}) + WEB_DIRECTORY = "web" NODE_CLASS_MAPPINGS = {} __all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS"] diff --git a/src/main/resources/comfyui-bridge/__init__.py b/src/main/resources/comfyui-bridge/__init__.py index 758cb82..25e99d5 100644 --- a/src/main/resources/comfyui-bridge/__init__.py +++ b/src/main/resources/comfyui-bridge/__init__.py @@ -12,20 +12,29 @@ async def model_downloaded(request): print("\033[92mđ„ [Model-Downloader] PING ERHALTEN! Erzwinge harten Backend-Reset...\033[0m") try: - # 1. Dateisystem-Cache löschen (Windows braucht hier Zeit) + # 1. Dateisystem-Cache löschen (Kurze Pause fĂŒr Windows-Dateisystem-Handles) await asyncio.sleep(0.5) + + # Robusteres Löschen der Caches in folder_paths + if hasattr(folder_paths, "folder_names"): + for k in list(folder_paths.folder_names.keys()): + folder_paths.folder_names[k] = [] + if hasattr(folder_paths, "base_path_layers"): for layer in folder_paths.base_path_layers.values(): - layer.folder_names.clear() - if hasattr(folder_paths, "folder_names"): - folder_paths.folder_names.clear() + if hasattr(layer, "folder_names"): + layer.folder_names.clear() + + if hasattr(folder_paths, "cache"): + folder_paths.cache.clear() # 2. Den Definitions-Cache von ComfyUI löschen - # Das zwingt ComfyUI, die Dropdown-Listen fĂŒr den Browser komplett neu zu generieren + # Dies zwingt ComfyUI, die Node-Metadaten (inkl. Modell-Listen) neu zu generieren server.PromptServer.instance.object_info = None # 3. Aktiven Scan triggern - for folder in ["checkpoints", "loras", "vae", "controlnet", "diffusion_models", "upscale_models", "clip"]: + # Durch den Aufruf von get_filename_list wird ComfyUI gezwungen, die Ordner neu zu indizieren + for folder in ["checkpoints", "loras", "vae", "controlnet", "diffusion_models", "upscale_models", "clip", "unet", "gligen"]: try: folder_paths.get_filename_list(folder) except: pass @@ -36,6 +45,7 @@ async def model_downloaded(request): print(f"[Model-Downloader] Fehler beim Reset: {e}") # 4. Signale an alle Browser (jetzt mit frischen Daten) + # "refresh" ist ein Standard-Signal, "kippster-refresh-ui" ist unser spezieller Trigger fĂŒr Deep-Update server.PromptServer.instance.send_sync("refresh", {}) server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) diff --git a/src/main/resources/comfyui-bridge/web/downloader.js b/src/main/resources/comfyui-bridge/web/downloader.js index 95fb8f8..8a0c5a8 100644 --- a/src/main/resources/comfyui-bridge/web/downloader.js +++ b/src/main/resources/comfyui-bridge/web/downloader.js @@ -3,6 +3,7 @@ let app = null; let api = null; const initializeExtension = async () => { + // 1. Try to find ComfyUI app and api objects const paths = ["../../scripts/app.js", "/scripts/app.js", "../../../scripts/app.js"]; const apiPaths = ["../../scripts/api.js", "/scripts/api.js", "../../../scripts/api.js"]; for (const path of paths) { try { const mod = await import(path); if (mod.app) { app = mod.app; break; } } catch (e) {} } @@ -10,40 +11,43 @@ const initializeExtension = async () => { if (!app || !api) { setTimeout(initializeExtension, 2000); return; } + // 2. Setup WebSocket Listener for Deep-Sync api.addEventListener("kippster-refresh-ui", async (event) => { console.log("%cđ„ [Model-Downloader] SIGNAL EMPFANGEN! Starte Deep-Node-Update...", "background: #ff0000; color: #fff; font-size: 14px;"); try { - // 1. Backend Zeit zum Scannen geben - await new Promise(r => setTimeout(r, 800)); + // Backend Zeit zum Scannen geben (wichtig fĂŒr Windows HDDs) + await new Promise(r => setTimeout(r, 1000)); - // 2. Definitionen neu laden (Zwingt den Browser die neuesten Modell-Listen zu kennen) + // Definitionen neu laden (Zwingt den Browser die neuesten Modell-Listen vom Backend anzufragen) const nodeDefs = await api.getNodeDefs(); + let updatedCount = 0; - // 3. Bestehende Nodes auf dem Canvas aktualisieren + // Bestehende Nodes auf dem Canvas aktualisieren if (app.graph && app.graph._nodes) { - console.log("[Model-Downloader] Aktualisiere Dropdowns in " + app.graph._nodes.length + " Nodes..."); for (const node of app.graph._nodes) { const def = nodeDefs[node.type]; if (def && def.input && def.input.required) { for (const widgetName in def.input.required) { const inputDef = def.input.required[widgetName]; - // Falls es ein Dropdown (Combo) ist + // Falls es ein Dropdown (Combo) ist (Array an Position 0) if (Array.isArray(inputDef[0])) { const newValues = inputDef[0]; const widget = node.widgets?.find(w => w.name === widgetName); + if (widget && widget.type === "combo") { + const oldValue = widget.value; widget.options.values = newValues; - // Validierung: Ist das aktuell gewĂ€hlte Modell noch da? - if (newValues.includes(widget.value)) { - // Modell vorhanden -> Node "heilen" (Farbe zurĂŒcksetzen) + // Validierung: Ist das aktuell gewĂ€hlte Modell jetzt verfĂŒgbar? + if (newValues.includes(oldValue)) { if (node.color === "#322" || node.color === "#a22") { node.color = ""; node.bgcolor = ""; + updatedCount++; } - } else if (widget.value && widget.value !== "None") { - // Modell fehlt -> Node rot markieren (ComfyUI Standard fĂŒr Fehler) + } else if (oldValue && oldValue !== "None" && !newValues.includes(oldValue)) { + // Markiere fehlende Modelle rot node.color = "#322"; node.bgcolor = "#a22"; } @@ -58,15 +62,21 @@ const initializeExtension = async () => { if (app.refresh) await app.refresh(); app.graph.setDirtyCanvas(true, true); - if (app.ui?.dialog?.show) { - app.ui.dialog.show("â Synchronisation abgeschlossen! Die Modell-Listen wurden aktualisiert."); - } + console.log(`[Model-Downloader] Deep-Sync fertig. ${updatedCount} Nodes geheilt.`); + + const msg = updatedCount > 0 + ? `â Synchronisation abgeschlossen!\n${updatedCount} Nodes wurden aktualisiert und sind jetzt bereit.` + : "â Synchronisation abgeschlossen! Die Modell-Listen sind auf dem neuesten Stand."; + + if (app.ui?.dialog?.show) app.ui.dialog.show(msg); + else alert(msg); } catch (e) { console.error("[Model-Downloader] Fehler beim Deep-Node-Update:", e); } }); + // 3. Load Token and setup UI let apiToken = null; const loadConfig = async () => { try { @@ -83,16 +93,24 @@ const initializeExtension = async () => { const fab = document.createElement("div"); fab.id = "tki-downloader-fab"; fab.innerHTML = "đ"; - 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(255,204,0,0.5); border: 2px solid white;"; + 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 { - await fetch("http://127.0.0.1:12345/import", { + const response = await fetch("http://127.0.0.1:12345/import", { method: "POST", mode: "cors", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiToken}` }, body: JSON.stringify(app.graph.serialize()) }); - } catch (e) {} + if (response.ok) { + if (app.ui?.dialog?.show) app.ui.dialog.show("đ Workflow an Downloader gesendet!"); + } + } catch (e) { + alert("â Downloader App nicht erreichbar! LĂ€uft sie?"); + } }; document.body.appendChild(fab); } diff --git a/web/downloader.js b/web/downloader.js index e27c7f0..8a0c5a8 100644 --- a/web/downloader.js +++ b/web/downloader.js @@ -3,136 +3,127 @@ let app = null; let api = null; const initializeExtension = async () => { - console.log("[Model-Downloader] Initializing..."); - // 1. Try to find ComfyUI app and api objects const paths = ["../../scripts/app.js", "/scripts/app.js", "../../../scripts/app.js"]; const apiPaths = ["../../scripts/api.js", "/scripts/api.js", "../../../scripts/api.js"]; + for (const path of paths) { try { const mod = await import(path); if (mod.app) { app = mod.app; break; } } catch (e) {} } + for (const path of apiPaths) { try { const mod = await import(path); if (mod.api) { api = mod.api; break; } } catch (e) {} } - for (const path of paths) { + if (!app || !api) { setTimeout(initializeExtension, 2000); return; } + + // 2. Setup WebSocket Listener for Deep-Sync + api.addEventListener("kippster-refresh-ui", async (event) => { + console.log("%cđ„ [Model-Downloader] SIGNAL EMPFANGEN! Starte Deep-Node-Update...", "background: #ff0000; color: #fff; font-size: 14px;"); + try { - const mod = await import(path); - if (mod.app) { app = mod.app; break; } - } catch (e) {} - } + // Backend Zeit zum Scannen geben (wichtig fĂŒr Windows HDDs) + await new Promise(r => setTimeout(r, 1000)); - for (const path of apiPaths) { - try { - const mod = await import(path); - if (mod.api) { api = mod.api; break; } - } catch (e) {} - } - - if (!app) { - console.warn("[Model-Downloader] Could not find ComfyUI app object. Retrying in 2s..."); - setTimeout(initializeExtension, 2000); - return; - } - - // 2. Setup WebSocket Listener for Auto-Refresh - if (api) { - console.log("%c[Model-Downloader] WebSocket Listener verknĂŒpft!", "color: #00ff00; font-weight: bold;"); - api.addEventListener("kippster-refresh-ui", (event) => { - console.log("%cđ„ [Model-Downloader] SIGNAL EMPFANGEN! Lade UI neu...", "background: #ff0000; color: #fff; font-size: 14px;"); - if (app && typeof app.refresh === 'function') { - app.refresh(); - const msg = "â Downloads abgeschlossen! Modelle sind jetzt verfĂŒgbar."; - if (app.ui?.dialog?.show) app.ui.dialog.show(msg); - else alert(msg); + // Definitionen neu laden (Zwingt den Browser die neuesten Modell-Listen vom Backend anzufragen) + const nodeDefs = await api.getNodeDefs(); + let updatedCount = 0; + + // Bestehende Nodes auf dem Canvas aktualisieren + if (app.graph && app.graph._nodes) { + for (const node of app.graph._nodes) { + const def = nodeDefs[node.type]; + if (def && def.input && def.input.required) { + for (const widgetName in def.input.required) { + const inputDef = def.input.required[widgetName]; + // Falls es ein Dropdown (Combo) ist (Array an Position 0) + if (Array.isArray(inputDef[0])) { + const newValues = inputDef[0]; + const widget = node.widgets?.find(w => w.name === widgetName); + + if (widget && widget.type === "combo") { + const oldValue = widget.value; + widget.options.values = newValues; + + // Validierung: Ist das aktuell gewĂ€hlte Modell jetzt verfĂŒgbar? + if (newValues.includes(oldValue)) { + if (node.color === "#322" || node.color === "#a22") { + node.color = ""; + node.bgcolor = ""; + updatedCount++; + } + } else if (oldValue && oldValue !== "None" && !newValues.includes(oldValue)) { + // Markiere fehlende Modelle rot + node.color = "#322"; + node.bgcolor = "#a22"; + } + } + } + } + } + if (node.onRefresh) node.onRefresh(); + } } - }); - } - // 3. Load Token and setup UI components + if (app.refresh) await app.refresh(); + app.graph.setDirtyCanvas(true, true); + + console.log(`[Model-Downloader] Deep-Sync fertig. ${updatedCount} Nodes geheilt.`); + + const msg = updatedCount > 0 + ? `â Synchronisation abgeschlossen!\n${updatedCount} Nodes wurden aktualisiert und sind jetzt bereit.` + : "â Synchronisation abgeschlossen! Die Modell-Listen sind auf dem neuesten Stand."; + + if (app.ui?.dialog?.show) app.ui.dialog.show(msg); + else alert(msg); + + } catch (e) { + console.error("[Model-Downloader] Fehler beim Deep-Node-Update:", e); + } + }); + + // 3. Load Token and setup UI let apiToken = null; const loadConfig = async () => { try { const baseUrl = new URL(".", import.meta.url).href; const configUrl = `${baseUrl}config.json?v=${Date.now()}`; const response = await fetch(configUrl); - if (response.ok) { - const config = await response.json(); - apiToken = config.token; - } - } catch (e) { console.error("[Model-Downloader] Config load error:", e); } + if (response.ok) { const config = await response.json(); apiToken = config.token; } + } catch (e) {} }; await loadConfig(); - const notify = (msg) => { - if (app?.ui?.dialog?.show) app.ui.dialog.show(msg); - else if (window.comfyAPI?.dialog?.show) window.comfyAPI.dialog.show(msg); - else alert(msg); - }; - - const showConnectionError = () => { - const dialog = document.createElement("dialog"); - dialog.style = "background:#222; color:white; border:2px solid #ffcc00; border-radius:10px; padding:20px; width:450px; font-family:sans-serif; box-shadow:0 0 30px rgba(0,0,0,0.5); z-index:20002;"; - dialog.innerHTML = ` -
Die Model Downloader App antwortet nicht. Bitte stelle sicher, dass sie lÀuft.
- đ„ Download via SourceForge - -