fix: improve ComfyUI model refresh logic and bridge synchronization
This commit is contained in:
+51
@@ -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"]
|
||||
|
||||
@@ -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"})
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+87
-96
@@ -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 = `
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:50px; margin-bottom:15px;">🚀❌</div>
|
||||
<h2 style="margin-top:0; color:#ffcc00;">Downloader nicht erreichbar</h2>
|
||||
<p style="line-height:1.5;">Die Model Downloader App antwortet nicht. Bitte stelle sicher, dass sie läuft.</p>
|
||||
<a href="https://sourceforge.net/projects/comfymodeldownloader/" target="_blank" style="display:block; background:#ffcc00; color:black; text-decoration:none; padding:10px; border-radius:5px; font-weight:bold; margin-bottom:20px;">📥 Download via SourceForge</a>
|
||||
<button id="tki-error-close" style="background:#444; color:white; border:none; padding:10px 20px; border-radius:5px; cursor:pointer;">Schließen</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(dialog);
|
||||
dialog.showModal();
|
||||
dialog.querySelector("#tki-error-close").onclick = () => { dialog.close(); document.body.removeChild(dialog); };
|
||||
};
|
||||
|
||||
const sendWorkflow = async () => {
|
||||
if (!app?.graph) return notify("❌ Fehler: ComfyUI Graph nicht bereit.");
|
||||
if (!apiToken) return notify("❌ Sicherheits-Fehler: Kein API-Token gefunden.");
|
||||
const workflow = app.graph.serialize();
|
||||
try {
|
||||
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(workflow)
|
||||
});
|
||||
if (response.ok) notify("🚀 Workflow erfolgreich gesendet!");
|
||||
else notify("❌ Fehler: Downloader abgelehnt (Status: " + response.status + ")");
|
||||
} catch (e) { showConnectionError(); }
|
||||
};
|
||||
|
||||
// UI Buttons
|
||||
const addUI = () => {
|
||||
if (!document.getElementById("tki-downloader-fab")) {
|
||||
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(0,0,0,0.5); border: 2px solid white;";
|
||||
fab.onclick = sendWorkflow;
|
||||
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 {
|
||||
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())
|
||||
});
|
||||
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);
|
||||
}
|
||||
const menu = document.querySelector(".comfy-menu");
|
||||
if (menu && !document.getElementById("tki-downloader-classic")) {
|
||||
const btn = document.createElement("button");
|
||||
btn.id = "tki-downloader-classic";
|
||||
btn.textContent = "🚀 Downloader";
|
||||
btn.style.background = "#ffcc00"; btn.style.color = "white";
|
||||
btn.onclick = sendWorkflow;
|
||||
menu.appendChild(btn);
|
||||
}
|
||||
if (app?.ui?.menu?.addPrimaryAction) {
|
||||
app.ui.menu.addPrimaryAction({ id: "tki.model-downloader.send", icon: "pi pi-download", label: "Send to Downloader", callback: sendWorkflow });
|
||||
}
|
||||
};
|
||||
|
||||
addUI();
|
||||
};
|
||||
|
||||
// Register the extension
|
||||
const register = () => {
|
||||
// We try multiple ways to hook into ComfyUI
|
||||
if (window.app && window.app.registerExtension) {
|
||||
window.app.registerExtension({ name: "TKI.ModelDownloader", async setup() { await initializeExtension(); } });
|
||||
} else {
|
||||
// Fallback for manual load
|
||||
if (document.readyState === "complete") initializeExtension();
|
||||
else window.addEventListener("load", initializeExtension);
|
||||
}
|
||||
};
|
||||
|
||||
register();
|
||||
|
||||
Reference in New Issue
Block a user