const path = require("path"); const fs = require("fs"); const { app, BrowserWindow, ipcMain, dialog } = require("electron"); const { runGeneration } = require("../core/generator"); const isDev = process.env.NODE_ENV === "development" || !app.isPackaged; const configPath = path.join(app.getPath("userData"), "config_nano_banana.json"); const defaultConfig = { api_key: "", api_create_url: "https://api.wuyinkeji.com/api/async/image_nanoBanana2", api_result_url: "https://api.wuyinkeji.com/api/async/detail", poll_interval_seconds: 10, max_wait_seconds: 300, r2_account_id: "", r2_access_key_id: "", r2_secret_access_key: "", r2_bucket: "", r2_public_url: "", default_save_dir: "", extra_params: [], }; function loadConfig() { try { const raw = fs.readFileSync(configPath, "utf8"); const data = JSON.parse(raw); return { ...defaultConfig, ...data }; } catch { const fallback = path.join(app.getAppPath(), "config_nano_banana.json"); try { if (fs.existsSync(fallback)) { const raw = fs.readFileSync(fallback, "utf8"); const data = JSON.parse(raw); return { ...defaultConfig, ...data }; } } catch (_) {} return { ...defaultConfig }; } } function saveConfig(config) { fs.mkdirSync(path.dirname(configPath), { recursive: true }); fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8"); } function createWindow() { const win = new BrowserWindow({ width: 900, height: 680, minWidth: 640, minHeight: 480, webPreferences: { preload: path.join(__dirname, "preload.js"), contextIsolation: true, nodeIntegration: false, }, title: "NanoBanana 生图", show: false, }); win.once("ready-to-show", () => win.show()); if (isDev) { win.loadURL("http://localhost:5173"); win.webContents.openDevTools(); } else { win.loadFile(path.join(__dirname, "../dist/index.html")); } } app.whenReady().then(() => { createWindow(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); }); ipcMain.handle("getConfig", () => loadConfig()); ipcMain.handle("saveConfig", (_e, config) => { const current = loadConfig(); const merged = { ...current, ...config }; saveConfig(merged); return loadConfig(); }); ipcMain.handle("selectDirectory", async (_e, defaultPath) => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ["openDirectory"], defaultPath: defaultPath || undefined, }); if (canceled || !filePaths.length) return null; return filePaths[0]; }); ipcMain.handle("selectReferenceFiles", async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ["openFile", "multiSelections"], filters: [ { name: "图片", extensions: ["jpg", "jpeg", "png", "webp", "bmp", "gif"] }, ], }); if (canceled || !filePaths.length) return []; return filePaths; }); ipcMain.handle("getImageDataUrl", async (_e, filePath) => { if (!filePath || typeof filePath !== "string") return null; try { const ext = filePath.replace(/^.*\./, "").toLowerCase(); const mime = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", bmp: "image/bmp", gif: "image/gif" }[ext] || "image/jpeg"; const buf = fs.readFileSync(filePath); return `data:${mime};base64,${buf.toString("base64")}`; } catch { return null; } }); ipcMain.handle( "startGeneration", async ( _e, { prompt, size, aspectRatio, refFilePaths, saveDir, extraParams, } ) => { const config = loadConfig(); const win = BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0]; const send = (step, message) => { if (win && !win.isDestroyed()) win.webContents.send("generationProgress", { step, message }); }; const extra = Array.isArray(config.extra_params) ? config.extra_params.reduce((acc, { key, value }) => { if (key != null && key !== "") acc[key] = value; return acc; }, {}) : {}; const mergedExtra = { ...extra, ...(extraParams || {}) }; try { const result = await runGeneration( config, { prompt, size: size || config.size || "1K", aspectRatio: aspectRatio || config.aspectRatio || "auto", refFilePaths: refFilePaths || [], saveDir: saveDir || config.default_save_dir, extraParams: Object.keys(mergedExtra).length ? mergedExtra : undefined, }, send ); return { success: true, ...result }; } catch (err) { return { success: false, error: err.message || String(err) }; } } );