feat: embed ComfyUI bridge resources into JAR for standalone operation
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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"]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user