diff --git a/src/main/java/de/tki/comfymodels/Main.java b/src/main/java/de/tki/comfymodels/Main.java index ebef547..691118f 100644 --- a/src/main/java/de/tki/comfymodels/Main.java +++ b/src/main/java/de/tki/comfymodels/Main.java @@ -594,78 +594,44 @@ public class Main extends JFrame { configService.setComfyUIPath(comfyPath); - File targetLink = new File(customNodesDir, "comfyui-model-downloader"); - File sourceDir = new File(System.getProperty("user.dir"), "comfyui-model-downloader"); + File targetDir = new File(customNodesDir, "comfyui-model-downloader"); - // --- IMPROVEMENT 2: SOURCE VERIFICATION --- - if (!sourceDir.exists()) { - File parentSource = new File(new File(System.getProperty("user.dir")).getParent(), "comfyui-model-downloader"); - if (parentSource.exists()) sourceDir = parentSource; - } - - if (!sourceDir.exists()) { - File altSource = new File(System.getProperty("user.dir")); - if (new File(altSource, "__init__.py").exists() && new File(altSource, "web").exists()) { - sourceDir = altSource; - } - } - - if (!sourceDir.exists() || !new File(sourceDir, "__init__.py").exists()) { - JOptionPane.showMessageDialog(parentDialog, "Source extension folder is invalid or incomplete!\nLooked in: " + sourceDir.getAbsolutePath(), "Error", JOptionPane.ERROR_MESSAGE); - return; - } - try { - // --- IMPROVEMENT 3: ROBUST DELETION OF OLD DIRECTORY/LINK --- - if (targetLink.exists()) { - if (System.getProperty("os.name").toLowerCase().contains("win")) { - Runtime.getRuntime().exec("cmd /c rmdir \"" + targetLink.getAbsolutePath() + "\"").waitFor(); - if (targetLink.exists()) Runtime.getRuntime().exec("cmd /c del /F /Q \"" + targetLink.getAbsolutePath() + "\"").waitFor(); - } - if (targetLink.exists()) deleteDirectory(targetLink); + // Cleanup old directory + if (targetDir.exists()) { + deleteDirectory(targetDir); } + targetDir.mkdirs(); - boolean success = false; - String method = "Unknown"; + // Extract from resources + extractResource("/comfyui-bridge/__init__.py", new File(targetDir, "__init__.py")); + File webDir = new File(targetDir, "web"); + webDir.mkdirs(); + extractResource("/comfyui-bridge/web/downloader.js", new File(webDir, "downloader.js")); - if (System.getProperty("os.name").toLowerCase().contains("win")) { - String cmdJ = String.format("cmd /c mklink /J \"%s\" \"%s\"", targetLink.getAbsolutePath(), sourceDir.getAbsolutePath()); - if (Runtime.getRuntime().exec(cmdJ).waitFor() == 0) { - success = true; method = "Junction"; - } else { - String cmdS = String.format("cmd /c mklink /D \"%s\" \"%s\"", targetLink.getAbsolutePath(), sourceDir.getAbsolutePath()); - if (Runtime.getRuntime().exec(cmdS).waitFor() == 0) { - success = true; method = "Symlink"; - } else { - copyDirectory(sourceDir, targetLink); - success = targetLink.exists(); method = "Copy"; - } - } + writeExtensionConfig(targetDir); + + if (new File(targetDir, "__init__.py").exists()) { + String msg = "🚀 ComfyUI Bridge installed successfully!\n\n" + + "Location: " + targetDir.getAbsolutePath() + "\n\n" + + "Please RESTART ComfyUI now to see the rocket icon."; + JOptionPane.showMessageDialog(parentDialog, msg, "Success", JOptionPane.INFORMATION_MESSAGE); + parentDialog.dispose(); } else { - java.nio.file.Files.createSymbolicLink(targetLink.toPath(), sourceDir.toPath()); - success = true; method = "Symlink"; - } - - if (success) { - writeExtensionConfig(targetLink.exists() && method.equals("Copy") ? targetLink : sourceDir); - if (new File(targetLink, "__init__.py").exists()) { - String msg = "🚀 ComfyUI Bridge installed successfully!\n\n" + - "Method: " + method + "\n" + - "Location: " + targetLink.getAbsolutePath() + "\n\n" + - "Please RESTART ComfyUI now to see the rocket icon."; - JOptionPane.showMessageDialog(parentDialog, msg, "Success", JOptionPane.INFORMATION_MESSAGE); - parentDialog.dispose(); - } else { - throw new Exception("Installation successful but __init__.py not found at target!"); - } - } else { - throw new Exception("All installation methods failed."); + throw new Exception("Installation failed: __init__.py not found at target!"); } } catch (Exception e) { JOptionPane.showMessageDialog(parentDialog, "Critical failure during installation: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } + private void extractResource(String resourcePath, File destination) throws IOException { + try (InputStream is = getClass().getResourceAsStream(resourcePath)) { + if (is == null) throw new IOException("Resource not found: " + resourcePath); + Files.copy(is, destination.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + private File findCustomNodesDeep(File dir, int depth) { if (depth > 2) return null; // Limit depth to avoid performance issues File[] files = dir.listFiles(); diff --git a/src/main/resources/comfyui-bridge/__init__.py b/src/main/resources/comfyui-bridge/__init__.py new file mode 100644 index 0000000..c3b2092 --- /dev/null +++ b/src/main/resources/comfyui-bridge/__init__.py @@ -0,0 +1,5 @@ +# ComfyUI Model Downloader Bridge +print("\033[95m[Model-Downloader] UI Extension Bridge active\033[0m") +WEB_DIRECTORY = "web" +NODE_CLASS_MAPPINGS = {} +__all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS"] diff --git a/src/main/resources/comfyui-bridge/web/downloader.js b/src/main/resources/comfyui-bridge/web/downloader.js new file mode 100644 index 0000000..413f015 --- /dev/null +++ b/src/main/resources/comfyui-bridge/web/downloader.js @@ -0,0 +1,190 @@ +// Try different ways to get the ComfyUI app object +let app = null; + +const tryImports = async () => { + const paths = [ + "../../scripts/app.js", + "/scripts/app.js", + "../../../scripts/app.js" + ]; + + for (const path of paths) { + try { + const mod = await import(path); + if (mod.app) { + app = mod.app; + console.log(`[Model-Downloader] Successfully imported app from ${path}`); + break; + } + } catch (e) {} + } +}; + +await tryImports(); + +console.log("%c[Model-Downloader] DEBUG: JS File Loaded", "background: #222; color: #ff69b4; 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: #ff69b4; 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); + 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."); + } 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?"); + } + } 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); + }; + + if (!apiToken) { + notify("❌ Sicherheits-Fehler: Kein API-Token gefunden. Bitte führe den Installer in der Java-App aus."); + return; + } + + 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", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${apiToken}` + }, + 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 + ")"); + } + } catch (e) { + notify("❌ Keine Verbindung zum Downloader-Tool! Läuft die Java-App?"); + } + }; + + // Strategy 1: The Modern FAB (Always works if DOM is ready) + 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:#ff69b4; 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.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 + }); + } + }; + + // 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 = "#ff69b4"; + btn.style.color = "white"; + btn.onclick = sendWorkflow; + menu.appendChild(btn); + } + }; + + // 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); +}; + +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); + } +}