167 lines
4.7 KiB
JavaScript
167 lines
4.7 KiB
JavaScript
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) };
|
|
}
|
|
}
|
|
);
|