Spaces:
Sleeping
Sleeping
| 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(); | |
| } | |
| }); | |