diff --git a/__init__.py b/__init__.py index c39de71..7725dc7 100644 --- a/__init__.py +++ b/__init__.py @@ -1,37 +1,43 @@ # ComfyUI Model Downloader Bridge import server import folder_paths -import asyncio +import os from aiohttp import web print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m") +USER_MODEL_PATH = r"C:\AI\comfyuidata\models" +if os.path.exists(USER_MODEL_PATH): + try: + pass + except: pass + @server.PromptServer.instance.routes.post("/kippster/model-downloaded") async def model_downloaded(request): - print("\033[92m🔥 [Model-Downloader] PING ERHALTEN! Leere Backend-Caches...\033[0m") - + print("\033[92m🔥 [Model-Downloader] PING ERHALTEN! Bereite UI-Refresh vor...\033[0m") + try: - # 1. Kurze Pause für das Dateisystem (wichtig beim Entpacken/Verschieben) - await asyncio.sleep(0.5) - - # 2. Lösche gezielt die generierten Dateilisten aus dem ComfyUI Cache + # 1. Wir löschen nur die Caches, machen aber KEINEN eigenen Scan mehr! + # Das verhindert, dass wir einen noch unfertigen Windows-Ordner scannen und den Fehler cachen. if hasattr(folder_paths, "filename_list_cache"): folder_paths.filename_list_cache.clear() if hasattr(folder_paths, "cache_helper") and hasattr(folder_paths.cache_helper, "clear"): folder_paths.cache_helper.clear() + + if hasattr(folder_paths, "cache") and hasattr(folder_paths.cache, "clear"): + folder_paths.cache.clear() - # 3. Zwinge den Server, beim nächsten API-Call die Daten frisch zu generieren + # 2. Object Info zurücksetzen (zwingt ComfyUI beim nächsten UI-Klick zum Neuladen) server.PromptServer.instance.object_info = None - - # 4. Sende das Signal an unser JavaScript (welches dann app.refresh() auslöst) - server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) - - return web.json_response({"status": "success", "message": "Cache cleared and frontend notified"}) - + except Exception as e: - print(f"[Model-Downloader] Fehler beim Backend-Reset: {e}") - return web.json_response({"status": "error", "message": str(e)}) + print(f"[Model-Downloader] Fehler beim Cache-Reset: {e}") + + # 3. Signal an das Frontend senden + server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) + + return web.json_response({"status": "success"}) WEB_DIRECTORY = "web" NODE_CLASS_MAPPINGS = {} diff --git a/src/main/resources/comfyui-bridge/__init__.py b/src/main/resources/comfyui-bridge/__init__.py index c39de71..7725dc7 100644 --- a/src/main/resources/comfyui-bridge/__init__.py +++ b/src/main/resources/comfyui-bridge/__init__.py @@ -1,37 +1,43 @@ # ComfyUI Model Downloader Bridge import server import folder_paths -import asyncio +import os from aiohttp import web print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m") +USER_MODEL_PATH = r"C:\AI\comfyuidata\models" +if os.path.exists(USER_MODEL_PATH): + try: + pass + except: pass + @server.PromptServer.instance.routes.post("/kippster/model-downloaded") async def model_downloaded(request): - print("\033[92m🔥 [Model-Downloader] PING ERHALTEN! Leere Backend-Caches...\033[0m") - + print("\033[92m🔥 [Model-Downloader] PING ERHALTEN! Bereite UI-Refresh vor...\033[0m") + try: - # 1. Kurze Pause für das Dateisystem (wichtig beim Entpacken/Verschieben) - await asyncio.sleep(0.5) - - # 2. Lösche gezielt die generierten Dateilisten aus dem ComfyUI Cache + # 1. Wir löschen nur die Caches, machen aber KEINEN eigenen Scan mehr! + # Das verhindert, dass wir einen noch unfertigen Windows-Ordner scannen und den Fehler cachen. if hasattr(folder_paths, "filename_list_cache"): folder_paths.filename_list_cache.clear() if hasattr(folder_paths, "cache_helper") and hasattr(folder_paths.cache_helper, "clear"): folder_paths.cache_helper.clear() + + if hasattr(folder_paths, "cache") and hasattr(folder_paths.cache, "clear"): + folder_paths.cache.clear() - # 3. Zwinge den Server, beim nächsten API-Call die Daten frisch zu generieren + # 2. Object Info zurücksetzen (zwingt ComfyUI beim nächsten UI-Klick zum Neuladen) server.PromptServer.instance.object_info = None - - # 4. Sende das Signal an unser JavaScript (welches dann app.refresh() auslöst) - server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) - - return web.json_response({"status": "success", "message": "Cache cleared and frontend notified"}) - + except Exception as e: - print(f"[Model-Downloader] Fehler beim Backend-Reset: {e}") - return web.json_response({"status": "error", "message": str(e)}) + print(f"[Model-Downloader] Fehler beim Cache-Reset: {e}") + + # 3. Signal an das Frontend senden + server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "ready"}) + + return web.json_response({"status": "success"}) WEB_DIRECTORY = "web" NODE_CLASS_MAPPINGS = {} diff --git a/src/main/resources/comfyui-bridge/web/downloader.js b/src/main/resources/comfyui-bridge/web/downloader.js index 9ef2b4f..ac998d1 100644 --- a/src/main/resources/comfyui-bridge/web/downloader.js +++ b/src/main/resources/comfyui-bridge/web/downloader.js @@ -3,7 +3,6 @@ let app = null; let api = null; const initializeExtension = async () => { - // 1. Suche nach ComfyUI App/API 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) {} } @@ -11,12 +10,10 @@ const initializeExtension = async () => { if (!app || !api) { setTimeout(initializeExtension, 2000); return; } - // 2. WebSocket Listener für den "Sync"-Trigger api.addEventListener("kippster-refresh-ui", async (event) => { - console.log("%c🔥 [Model-Downloader] SIGNAL ERHALTEN! Starte echten UI-Refresh...", "background: #ff0000; color: #fff; font-size: 14px;"); - + console.log("%c🔥 [Model-Downloader] SIGNAL ERHALTEN! Starte Refresh-Sequenz...", "background: #ff0000; color: #fff; font-size: 14px;"); + try { - // Zeige Feedback an 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,44 +21,49 @@ const initializeExtension = async () => { document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => document.body.removeChild(toast), 500); }, 3000); }; - showToast("🔄 Lade Node-Definitionen neu..."); + + showToast("🔄 Sync: Warte auf Windows-Dateisystem..."); - // 🔥 DER GAMECHANGER: Killt den Frontend-Cache von ComfyUI! + // WICHTIG: 2 Sekunden warten! + // Windows braucht einen Moment, um die Ordner-Indizes nach dem Verschieben/Entpacken zu aktualisieren. + await new Promise(r => setTimeout(r, 2000)); + + showToast("🔄 Führe 'Refresh Node Definitions' aus..."); + + // ComfyUI Frontend-Cache leeren api.nodeDefs = null; - // Gib dem Windows-Dateisystem kurz Zeit, die kopierten/verschobenen Dateien zu registrieren - await new Promise(r => setTimeout(r, 800)); - - // 1. Hole frische Definitionen vom Server (triggert jetzt wirklich /object_info) - console.log("[Model-Downloader] Re-fetching node definitions from server..."); - const nodeDefs = await api.getNodeDefs(); - - // 2. Falls ComfyUI-Manager installiert ist, auch dessen Custom Nodes aktualisieren - if (api.getCustomNodes) await api.getCustomNodes(); - - // 3. Ruft die offizielle ComfyUI Refresh-Funktion auf (aktualisiert Sidebars, etc.) + // Den originalen Refresh-Befehl von ComfyUI ausführen (exakt wie der manuelle Klick) if (app.refresh) { await app.refresh(); } - // 4. Jede Node auf dem Canvas zwingen, ihre Widgets neu zu evaluieren (Deep-Update) + // Fallback: Bereits gerenderte Dropdowns zwingen, die neuen Werte zu fressen und rote Nodes zu heilen + let healedCount = 0; if (app.graph && app.graph._nodes) { + const nodeDefs = await api.getNodeDefs(); 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 / Combo + 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; - // Heilung bei Modell-Wiederherstellung - if (newValues.includes(widget.value)) { + + if (newValues.includes(oldValue)) { if (node.color === "#322" || node.color === "#a22") { node.color = ""; node.bgcolor = ""; + healedCount++; } + } else if (oldValue && oldValue !== "None" && !newValues.includes(oldValue)) { + node.color = "#322"; + node.bgcolor = "#a22"; } } } @@ -69,19 +71,22 @@ const initializeExtension = async () => { } if (node.onRefresh) node.onRefresh(); } - if (app.graph.refreshNodes) app.graph.refreshNodes(); } - // Canvas zwingen, sich neu zu zeichnen app.graph.setDirtyCanvas(true, true); - console.log("[Model-Downloader] Sync erfolgreich abgeschlossen."); + console.log(`[Model-Downloader] Sync fertig. Healed: ${healedCount}`); + + if (app.ui?.dialog?.show) { + app.ui.dialog.show("✅ Node-Definitionen aktualisiert!\nDateisystem wurde erfolgreich neu eingelesen."); + } else { + alert("✅ Node-Definitionen aktualisiert!"); + } } catch (e) { console.error("[Model-Downloader] Fehler beim erzwungenen Refresh:", e); } }); - // 3. Token & FAB UI let apiToken = null; const loadConfig = async () => { try { @@ -98,7 +103,10 @@ 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 9ef2b4f..ac998d1 100644 --- a/web/downloader.js +++ b/web/downloader.js @@ -3,7 +3,6 @@ let app = null; let api = null; const initializeExtension = async () => { - // 1. Suche nach ComfyUI App/API 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) {} } @@ -11,12 +10,10 @@ const initializeExtension = async () => { if (!app || !api) { setTimeout(initializeExtension, 2000); return; } - // 2. WebSocket Listener für den "Sync"-Trigger api.addEventListener("kippster-refresh-ui", async (event) => { - console.log("%c🔥 [Model-Downloader] SIGNAL ERHALTEN! Starte echten UI-Refresh...", "background: #ff0000; color: #fff; font-size: 14px;"); - + console.log("%c🔥 [Model-Downloader] SIGNAL ERHALTEN! Starte Refresh-Sequenz...", "background: #ff0000; color: #fff; font-size: 14px;"); + try { - // Zeige Feedback an 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,44 +21,49 @@ const initializeExtension = async () => { document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => document.body.removeChild(toast), 500); }, 3000); }; - showToast("🔄 Lade Node-Definitionen neu..."); + + showToast("🔄 Sync: Warte auf Windows-Dateisystem..."); - // 🔥 DER GAMECHANGER: Killt den Frontend-Cache von ComfyUI! + // WICHTIG: 2 Sekunden warten! + // Windows braucht einen Moment, um die Ordner-Indizes nach dem Verschieben/Entpacken zu aktualisieren. + await new Promise(r => setTimeout(r, 2000)); + + showToast("🔄 Führe 'Refresh Node Definitions' aus..."); + + // ComfyUI Frontend-Cache leeren api.nodeDefs = null; - // Gib dem Windows-Dateisystem kurz Zeit, die kopierten/verschobenen Dateien zu registrieren - await new Promise(r => setTimeout(r, 800)); - - // 1. Hole frische Definitionen vom Server (triggert jetzt wirklich /object_info) - console.log("[Model-Downloader] Re-fetching node definitions from server..."); - const nodeDefs = await api.getNodeDefs(); - - // 2. Falls ComfyUI-Manager installiert ist, auch dessen Custom Nodes aktualisieren - if (api.getCustomNodes) await api.getCustomNodes(); - - // 3. Ruft die offizielle ComfyUI Refresh-Funktion auf (aktualisiert Sidebars, etc.) + // Den originalen Refresh-Befehl von ComfyUI ausführen (exakt wie der manuelle Klick) if (app.refresh) { await app.refresh(); } - // 4. Jede Node auf dem Canvas zwingen, ihre Widgets neu zu evaluieren (Deep-Update) + // Fallback: Bereits gerenderte Dropdowns zwingen, die neuen Werte zu fressen und rote Nodes zu heilen + let healedCount = 0; if (app.graph && app.graph._nodes) { + const nodeDefs = await api.getNodeDefs(); 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 / Combo + 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; - // Heilung bei Modell-Wiederherstellung - if (newValues.includes(widget.value)) { + + if (newValues.includes(oldValue)) { if (node.color === "#322" || node.color === "#a22") { node.color = ""; node.bgcolor = ""; + healedCount++; } + } else if (oldValue && oldValue !== "None" && !newValues.includes(oldValue)) { + node.color = "#322"; + node.bgcolor = "#a22"; } } } @@ -69,19 +71,22 @@ const initializeExtension = async () => { } if (node.onRefresh) node.onRefresh(); } - if (app.graph.refreshNodes) app.graph.refreshNodes(); } - // Canvas zwingen, sich neu zu zeichnen app.graph.setDirtyCanvas(true, true); - console.log("[Model-Downloader] Sync erfolgreich abgeschlossen."); + console.log(`[Model-Downloader] Sync fertig. Healed: ${healedCount}`); + + if (app.ui?.dialog?.show) { + app.ui.dialog.show("✅ Node-Definitionen aktualisiert!\nDateisystem wurde erfolgreich neu eingelesen."); + } else { + alert("✅ Node-Definitionen aktualisiert!"); + } } catch (e) { console.error("[Model-Downloader] Fehler beim erzwungenen Refresh:", e); } }); - // 3. Token & FAB UI let apiToken = null; const loadConfig = async () => { try { @@ -98,7 +103,10 @@ 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 {