fix: resolve race condition by adding 2s delay in frontend and removing pre-scan in backend

This commit is contained in:
Thomas
2026-05-10 19:54:09 +02:00
parent fecc44c694
commit f9fafbfc9e
4 changed files with 112 additions and 84 deletions
+22 -16
View File
@@ -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 = {}
+22 -16
View File
@@ -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 = {}
@@ -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 {
+34 -26
View File
@@ -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 {