diff --git a/src/main/java/de/tki/comfymodels/service/impl/DefaultDownloadManager.java b/src/main/java/de/tki/comfymodels/service/impl/DefaultDownloadManager.java
index d52359d..659006e 100644
--- a/src/main/java/de/tki/comfymodels/service/impl/DefaultDownloadManager.java
+++ b/src/main/java/de/tki/comfymodels/service/impl/DefaultDownloadManager.java
@@ -113,12 +113,27 @@ public class DefaultDownloadManager implements IDownloadManager {
} finally {
if (finishedCalled.compareAndSet(false, true)) {
+ notifyComfyUI(); // NEU: Ping abfeuern
onFinished.run();
}
}
}).start();
}
+ // NEU: Benachrichtigt ComfyUI
+ private void notifyComfyUI() {
+ try {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://127.0.0.1:8188/kippster/model-downloaded"))
+ .POST(HttpRequest.BodyPublishers.noBody())
+ .build();
+ // Fire and forget
+ httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding());
+ } catch (Exception e) {
+ // Wenn ComfyUI nicht läuft, ignorieren wir das leise
+ }
+ }
+
private boolean isSelected(int index) {
return currentSelection == null || (index < currentSelection.length && currentSelection[index]);
}
diff --git a/src/main/resources/comfyui-bridge/__init__.py b/src/main/resources/comfyui-bridge/__init__.py
index f16fd69..65e4d33 100644
--- a/src/main/resources/comfyui-bridge/__init__.py
+++ b/src/main/resources/comfyui-bridge/__init__.py
@@ -4,14 +4,13 @@ from aiohttp import web
print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m")
+# NEU: Der API-Endpunkt für den Java-Ping
@server.PromptServer.instance.routes.post("/kippster/model-downloaded")
async def model_downloaded(request):
- try:
- # Broadcast event to all connected clients
- server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "New models available!"})
- return web.json_response({"status": "success", "message": "UI refresh triggered"})
- except Exception as e:
- return web.json_response({"status": "error", "message": str(e)}, status=500)
+ print("[Model-Downloader] Download finished trigger received from Java!")
+ # Sendet das Signal ans Frontend
+ server.PromptServer.instance.send_sync("kippster-refresh-ui", {"message": "New models available!"})
+ return web.json_response({"status": "success", "message": "UI refresh triggered"})
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 c2b5754..b93b778 100644
--- a/src/main/resources/comfyui-bridge/web/downloader.js
+++ b/src/main/resources/comfyui-bridge/web/downloader.js
@@ -310,6 +310,33 @@ const initializeExtension = async () => {
addFAB(); addClassic(); addV2Action();
};
+// NEU: Wir laden die API
+const setupListener = async () => {
+ try {
+ const { api } = await import("../../scripts/api.js");
+ api.addEventListener("kippster-refresh-ui", (event) => {
+ console.log("%c[Model-Downloader] Triggering UI Refresh!", "background: green; color: white;");
+
+ if (app && app.refresh) {
+ app.refresh(); // Magischer Reload-Befehl
+
+ // Kleines Popup für den User
+ 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);
+ };
+ notify("✅ Downloads abgeschlossen! Modelle sind jetzt in ComfyUI verfügbar.");
+ }
+ });
+ console.log("[Model-Downloader] WebSocket Listener aktiv.");
+ } catch (e) {
+ console.error("[Model-Downloader] Konnte API nicht für Listener laden.", e);
+ }
+};
+
+setupListener();
+
if (app) {
app.registerExtension({ name: "TKI.ModelDownloader", async setup() { await initializeExtension(); } });
} else if (document.readyState === "complete") {
diff --git a/web/downloader.js b/web/downloader.js
index d13570c..d6c2eba 100644
--- a/web/downloader.js
+++ b/web/downloader.js
@@ -1,5 +1,6 @@
-// Try different ways to get the ComfyUI app object
+// Try different ways to get the ComfyUI app and api objects
let app = null;
+let api = null;
const tryImports = async () => {
const paths = [
@@ -7,6 +8,11 @@ const tryImports = async () => {
"/scripts/app.js",
"../../../scripts/app.js"
];
+ const apiPaths = [
+ "../../scripts/api.js",
+ "/scripts/api.js",
+ "../../../scripts/api.js"
+ ];
for (const path of paths) {
try {
@@ -18,72 +24,87 @@ const tryImports = async () => {
}
} catch (e) {}
}
+
+ for (const path of apiPaths) {
+ try {
+ const mod = await import(path);
+ if (mod.api) {
+ api = mod.api;
+ console.log(`[Model-Downloader] Successfully imported api from ${path}`);
+ break;
+ }
+ } catch (e) {}
+ }
};
await tryImports();
-console.log("%c[Model-Downloader] DEBUG: JS File Loaded", "background: #222; color: #ffcc00; font-size: 20px;");
-
// Define setup logic that can be called by extension or as fallback
const initializeExtension = async () => {
- console.log("%c[Model-Downloader] DEBUG: Initializing Extension Logic", "background: #222; color: #ffcc00; font-size: 16px;");
-
let apiToken = null;
- // Load token from local config.json (generated during installation)
const loadConfig = async () => {
try {
- // Use import.meta.url to get the correct relative path for the extension
const baseUrl = new URL(".", import.meta.url).href;
const configUrl = `${baseUrl}config.json?v=${Date.now()}`;
-
- console.log("[Model-Downloader] Fetching config from:", configUrl);
+ console.log(`[Model-Downloader] Loading config from: ${configUrl}`);
const response = await fetch(configUrl);
if (response.ok) {
const config = await response.json();
apiToken = config.token;
if (apiToken) {
- console.log("[Model-Downloader] Pre-shared Token loaded.");
+ const masked = apiToken.substring(0, 4) + "..." + apiToken.substring(apiToken.length - 4);
+ console.log(`[Model-Downloader] Token loaded successfully: ${masked}`);
} else {
console.warn("[Model-Downloader] Token in config.json is empty!");
}
} else {
- console.warn("[Model-Downloader] config.json not found (Status: " + response.status + "). Did you run the installer in the Java App?");
+ console.error(`[Model-Downloader] Could not load config.json (Status: ${response.status})`);
}
} catch (e) {
console.error("[Model-Downloader] Failed to load config.json:", e);
}
};
- // Trigger config load in background
loadConfig();
- const sendWorkflow = async () => {
- if (!app || !app.graph) {
- console.error("[Model-Downloader] App or Graph not ready.");
- const notify = (msg) => {
- if (window.comfyAPI?.dialog?.show) window.comfyAPI.dialog.show(msg);
- else alert(msg);
- };
- notify("❌ Fehler: ComfyUI Graph nicht bereit.");
- return;
- }
- const workflow = app.graph.serialize();
- console.log("[Model-Downloader] Workflow:", workflow);
-
- 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 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 = `
+
+
🚀❌
+
Downloader nicht erreichbar
+
Die Model Downloader App antwortet nicht. Bitte stelle sicher, dass:
+
+ - Die Java-App gestartet wurde.
+ - Port 12345 nicht blockiert wird.
+
+
Noch nicht installiert? Lade die App hier herunter:
+
📥 Download via SourceForge
+
+
+ `;
+ document.body.appendChild(dialog);
+ dialog.showModal();
+ dialog.querySelector("#tki-error-close").onclick = () => {
+ dialog.close();
+ document.body.removeChild(dialog);
};
+ };
- if (!apiToken) {
- notify("❌ Sicherheits-Fehler: Kein API-Token gefunden. Bitte führe den Installer in der Java-App aus.");
- return;
- }
+ const sendWorkflow = async () => {
+ if (!app || !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 {
- console.log("[Model-Downloader] Sending request to 127.0.0.1:12345...");
const response = await fetch("http://127.0.0.1:12345/import", {
method: "POST",
mode: "cors",
@@ -93,98 +114,219 @@ const initializeExtension = async () => {
},
body: JSON.stringify(workflow)
});
-
- if (response.ok) {
- notify("🚀 Workflow erfolgreich gesendet!");
- } else if (response.status === 401) {
- notify("❌ Sicherheits-Fehler: Token ungültig. Bitte Downloader neu starten.");
- apiToken = null;
- } else {
- notify("❌ Fehler: Downloader abgelehnt (Status: " + response.status + ")");
- }
+ if (response.ok) notify("🚀 Workflow erfolgreich gesendet!");
+ else notify("❌ Fehler: Downloader abgelehnt (Status: " + response.status + ")");
} catch (e) {
- notify("❌ Keine Verbindung zum Downloader-Tool! Läuft die Java-App?");
+ showConnectionError();
}
};
- // Strategy 1: The Modern FAB (Always works if DOM is ready)
+ const openCleanupDialog = async () => {
+ if (!apiToken) return notify("❌ Sicherheits-Fehler: Kein API-Token gefunden.");
+
+ const dialog = document.createElement("dialog");
+ dialog.id = "tki-cleanup-dialog";
+ dialog.style = "background:#222; color:white; border:2px solid #ffcc00; border-radius:10px; padding:20px; width:800px; max-height:80vh; overflow:hidden; font-family:sans-serif; box-shadow:0 0 30px rgba(0,0,0,0.5); z-index:20001;";
+
+ dialog.innerHTML = `
+
+
🧹 Lokale Modelle aufräumen
+
+
+
Lade lokale Modelle...
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(dialog);
+ dialog.showModal();
+
+ const refreshData = async () => {
+ const loading = dialog.querySelector("#tki-cleanup-loading");
+ const container = dialog.querySelector("#tki-cleanup-table-container");
+ loading.style.display = "block";
+ container.style.display = "none";
+
+ try {
+ const response = await fetch("http://127.0.0.1:12345/api/models/local", {
+ headers: { "Authorization": `Bearer ${apiToken}` }
+ });
+ const models = await response.json();
+
+ const tbody = dialog.querySelector("#tki-cleanup-tbody");
+ tbody.innerHTML = models.map(m => `
+
+ |
+ ${m.name} |
+ ${m.type} |
+ ${m.size} |
+
+ `).join("");
+
+ loading.style.display = "none";
+ container.style.display = "block";
+
+ // Wire up select all
+ const selectAll = dialog.querySelector("#tki-select-all");
+ const checkboxes = dialog.querySelectorAll(".tki-model-select");
+ selectAll.checked = false;
+ selectAll.onchange = () => checkboxes.forEach(cb => cb.checked = selectAll.checked);
+
+ } catch (e) {
+ dialog.close();
+ document.body.removeChild(dialog);
+ showConnectionError();
+ }
+ };
+
+ refreshData();
+
+ dialog.querySelector("#tki-cleanup-cancel").onclick = () => {
+ dialog.close();
+ document.body.removeChild(dialog);
+ };
+
+ dialog.querySelector("#tki-cleanup-archive").onclick = async () => {
+ const checkboxes = dialog.querySelectorAll(".tki-model-select");
+ const selectedPaths = Array.from(checkboxes)
+ .filter(cb => cb.checked)
+ .map(cb => cb.getAttribute("data-path"));
+
+ if (selectedPaths.length === 0) return alert("Bitte wähle mindestens ein Modell aus.");
+
+ const status = dialog.querySelector("#tki-cleanup-status");
+ const btn = dialog.querySelector("#tki-cleanup-archive");
+ btn.disabled = true;
+ status.textContent = `Archiviere ${selectedPaths.length} Dateien...`;
+
+ try {
+ const res = await fetch("http://127.0.0.1:12345/api/models/archive", {
+ method: "POST",
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiToken}` },
+ body: JSON.stringify(selectedPaths)
+ });
+ if (res.ok) {
+ const data = await res.json();
+ alert(`Erfolgreich ${data.archived} Modelle archiviert!`);
+ status.textContent = "";
+ btn.disabled = false;
+ refreshData(); // Reload list
+ } else {
+ throw new Error("API Fehler (Status: " + res.status + ")");
+ }
+ } catch (e) {
+ console.error("[Model-Downloader] Archive error:", e);
+ btn.disabled = false;
+ status.textContent = "";
+ if (e.message.includes("fetch") || e.name === "TypeError") {
+ dialog.close();
+ document.body.removeChild(dialog);
+ showConnectionError();
+ } else {
+ alert("Fehler: " + e.message);
+ }
+ }
+ };
+ };
+
const addFAB = () => {
if (document.getElementById("tki-downloader-fab")) return;
- console.log("[Model-Downloader] Adding FAB...");
const fab = document.createElement("div");
fab.id = "tki-downloader-fab";
fab.innerHTML = "🚀";
- fab.title = "Send to Model Downloader";
- 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,105,180,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.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; transition: transform 0.2s;";
fab.onclick = sendWorkflow;
document.body.appendChild(fab);
};
- // Strategy 2: Primary Action (V2 UI)
const addV2Action = () => {
if (app?.ui?.menu?.addPrimaryAction) {
- console.log("[Model-Downloader] Adding to V2 Menu...");
- app.ui.menu.addPrimaryAction({
- id: "tki.model-downloader.send",
- icon: "pi pi-download",
- label: "Send to Downloader",
- callback: sendWorkflow
- });
- }
- if (app?.ui?.toolbar?.addPrimaryAction) {
- console.log("[Model-Downloader] Adding to V2 Toolbar...");
- app.ui.toolbar.addPrimaryAction({
- id: "tki.model-downloader.send-toolbar",
- icon: "pi pi-download",
- label: "Send to Downloader",
- callback: sendWorkflow
- });
+ app.ui.menu.addPrimaryAction({ id: "tki.model-downloader.send", icon: "pi pi-download", label: "Send to Downloader", callback: sendWorkflow });
+ app.ui.menu.addPrimaryAction({ id: "tki.model-downloader.cleanup", icon: "pi pi-trash", label: "Aufräumen / Archivieren", callback: openCleanupDialog });
}
};
- // Strategy 3: Classic Menu
const addClassic = () => {
const menu = document.querySelector(".comfy-menu");
if (menu) {
- if (document.getElementById("tki-downloader-classic")) return;
- console.log("[Model-Downloader] Adding to Classic Menu...");
- 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 (!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 (!document.getElementById("tki-cleanup-classic")) {
+ const cleanBtn = document.createElement("button");
+ cleanBtn.id = "tki-cleanup-classic";
+ cleanBtn.textContent = "🧹 Aufräumen";
+ cleanBtn.style.background = "#444"; cleanBtn.style.color = "white"; cleanBtn.style.marginLeft = "5px";
+ cleanBtn.onclick = openCleanupDialog;
+ menu.appendChild(cleanBtn);
+ }
}
};
- // Run immediately
- addFAB();
- addClassic();
- addV2Action();
-
- // Retry with delays to ensure UI is ready
- setTimeout(addFAB, 1000);
- setTimeout(addFAB, 5000);
- setTimeout(addClassic, 2000);
- setTimeout(addV2Action, 3000);
+ addFAB(); addClassic(); addV2Action();
};
-if (app) {
- app.registerExtension({
- name: "TKI.ModelDownloader",
- async setup() {
- await initializeExtension();
- }
- });
-} else {
- console.warn("[Model-Downloader] App object not found. Falling back to manual initialization.");
- // Fallback if app is not found or registration fails
- if (document.readyState === "complete") {
- initializeExtension();
- } else {
- window.addEventListener("load", initializeExtension);
+// NEU: Wir laden die API
+const setupListener = async () => {
+ try {
+ const { api } = await import("../../scripts/api.js");
+ api.addEventListener("kippster-refresh-ui", (event) => {
+ console.log("%c[Model-Downloader] Triggering UI Refresh!", "background: green; color: white;");
+
+ if (app && app.refresh) {
+ app.refresh(); // Magischer Reload-Befehl
+
+ // Kleines Popup für den User
+ 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);
+ };
+ notify("✅ Downloads abgeschlossen! Modelle sind jetzt in ComfyUI verfügbar.");
+ }
+ });
+ console.log("[Model-Downloader] WebSocket Listener aktiv.");
+ } catch (e) {
+ console.error("[Model-Downloader] Konnte API nicht für Listener laden.", e);
}
+};
+
+setupListener();
+
+if (app) {
+ app.registerExtension({ name: "TKI.ModelDownloader", async setup() { await initializeExtension(); } });
+} else if (document.readyState === "complete") {
+ initializeExtension();
+} else {
+ window.addEventListener("load", initializeExtension);
}