const { app, BrowserWindow, dialog, ipcMain, shell, nativeImage } = require("electron"); const path = require("path"); const fs = require("fs"); const { spawn } = require("child_process"); const BACKEND_URL = "http://127.0.0.1:8008/health"; let backendProcess = null; function projectRoot() { return path.resolve(__dirname, "..", ".."); } function isDev() { return process.env.IMAGEFORGE_DEV === "1"; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function waitForBackend(timeoutMs = 20000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { try { const response = await fetch(BACKEND_URL); if (response.ok) { return true; } } catch (_) {} await sleep(500); } return false; } function startBackend() { const root = projectRoot(); const backendModule = "backend.app.main"; const pythonCandidates = process.platform === "win32" ? [ path.join(root, ".venv", "Scripts", "python.exe"), path.join(root, "..", ".venv", "Scripts", "python.exe"), "py", "python", ] : [ path.join(root, ".venv", "bin", "python"), path.join(root, "..", ".venv", "bin", "python"), "python3", "python", ]; for (const command of pythonCandidates) { if (command.includes(path.sep) && !fs.existsSync(command)) { continue; } try { const args = command === "py" ? ["-3", "-m", backendModule] : ["-m", backendModule]; const child = spawn(command, args, { cwd: root, env: { ...process.env, IMAGEFORGE_HOST: process.env.IMAGEFORGE_HOST || "127.0.0.1", IMAGEFORGE_PORT: process.env.IMAGEFORGE_PORT || "8008", IMAGEFORGE_CORS_ORIGINS: process.env.IMAGEFORGE_CORS_ORIGINS || "http://localhost:5173", IMAGEFORGE_CACHE_ROOT: process.env.IMAGEFORGE_CACHE_ROOT || path.join(root, ".cache"), IMAGEFORGE_TMP_ROOT: process.env.IMAGEFORGE_TMP_ROOT || path.join(root, ".cache", "tmp"), }, stdio: "ignore", detached: false, }); child.once("error", () => {}); backendProcess = child; return; } catch (_) {} } } async function createWindow() { startBackend(); const healthy = await waitForBackend(); if (!healthy) { dialog.showErrorBox( "Backend not reachable", "ImageForge konnte das lokale Backend nicht starten. Bitte Python und requirements installieren." ); } const win = new BrowserWindow({ width: 1600, height: 920, minWidth: 1100, minHeight: 700, webPreferences: { contextIsolation: true, preload: path.join(__dirname, "preload.js"), nodeIntegration: false, }, }); const devServerUrl = process.env.VITE_DEV_SERVER_URL; if (devServerUrl) { await win.loadURL(devServerUrl); } else { await win.loadFile(path.join(__dirname, "..", "dist", "index.html")); } } ipcMain.handle("open-folder", async (_event, folderPath) => { await shell.openPath(folderPath); }); ipcMain.handle("save-image", async (_event, sourcePath, defaultName) => { const result = await dialog.showSaveDialog({ title: "Save image", defaultPath: defaultName, filters: [ { name: "PNG", extensions: ["png"] }, { name: "JPEG", extensions: ["jpg", "jpeg"] }, ], }); if (result.canceled || !result.filePath) { return false; } const ext = path.extname(result.filePath).toLowerCase(); if (ext === ".jpg" || ext === ".jpeg") { const img = nativeImage.createFromPath(sourcePath); fs.writeFileSync(result.filePath, img.toJPEG(92)); } else { fs.copyFileSync(sourcePath, result.filePath); } return true; }); ipcMain.handle("show-error", async (_event, title, message) => { await dialog.showMessageBox({ type: "error", title, message, }); }); ipcMain.handle("pick-image", async () => { const result = await dialog.showOpenDialog({ title: "Select source image", properties: ["openFile"], filters: [{ name: "Images", extensions: ["png", "jpg", "jpeg", "webp", "bmp"] }], }); if (result.canceled || result.filePaths.length === 0) { return null; } return result.filePaths[0]; }); ipcMain.handle("read-image-data-url", async (_event, sourcePath) => { try { const root = projectRoot(); const outputRoot = path.resolve(root, "output"); const resolved = path.resolve(String(sourcePath || "")); if (resolved !== outputRoot && !resolved.startsWith(`${outputRoot}${path.sep}`)) { return null; } if (!fs.existsSync(resolved)) { return null; } const ext = path.extname(resolved).toLowerCase(); const mimeMap = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".bmp": "image/bmp", }; const mime = mimeMap[ext] || "application/octet-stream"; const data = fs.readFileSync(resolved); return `data:${mime};base64,${data.toString("base64")}`; } catch { return null; } }); app.whenReady().then(createWindow); app.on("window-all-closed", () => { if (backendProcess && !backendProcess.killed) { backendProcess.kill(); } if (process.platform !== "darwin") { app.quit(); } });