Files
plantai-desktop/main.js
T

158 lines
4.5 KiB
JavaScript
Raw Normal View History

// main.js —— Electron 主进程(瘦客户端壳)。
// 启动流程:读后端各服务地址配置 → 起本地静态 + /api 反代服务器(仅监听 127.0.0.1)→ 建窗口加载。
// 前端 ui/ 源码零改动:相对路径 /api 与 SSE 流靠「同源」天然可用,后端 CORS 也无需改。
// 反代功能上只走 bff(BFF 是前端唯一入口);qa/rag/vstore/paper/bioflow 地址在设置里可改、持久化,
// 实际的各服务再路由由 BFF 自己的 config/.env 完成(见 README)。
const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron');
const path = require('path');
const fs = require('fs');
const { createServer } = require('./server');
const { loadServices, saveServices } = require('./config');
let mainWindow = null;
let settingsWindow = null;
let server = null;
let currentOrigin = null;
/** 前端构建产物目录:开发态在 desktop/renderer-dist,打包态在 resources/renderer-dist(extraResources)。 */
function staticDir() {
return app.isPackaged
? path.join(process.resourcesPath, 'renderer-dist')
: path.join(__dirname, 'renderer-dist');
}
function startServer(bffUrl) {
return new Promise((resolve, reject) => {
const srv = createServer({ staticDir: staticDir(), backendUrl: bffUrl });
srv.once('error', reject);
srv.listen(0, '127.0.0.1', () => {
server = srv;
resolve(`http://127.0.0.1:${srv.address().port}`);
});
});
}
function stopServer() {
if (server) {
server.close();
server = null;
}
}
async function restartServer(bffUrl) {
stopServer();
currentOrigin = await startServer(bffUrl);
return currentOrigin;
}
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 860,
minWidth: 960,
minHeight: 640,
title: '海古纪 PlantAI',
backgroundColor: '#0f1115',
show: false,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
mainWindow.loadURL(currentOrigin);
mainWindow.once('ready-to-show', () => mainWindow.show());
mainWindow.on('closed', () => {
mainWindow = null;
});
}
function openSettingsWindow() {
if (settingsWindow) {
settingsWindow.focus();
return;
}
settingsWindow = new BrowserWindow({
width: 500,
height: 560,
resizable: false,
minimizable: false,
maximizable: false,
title: '后端服务设置',
parent: mainWindow || undefined,
modal: !!mainWindow,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
settingsWindow.loadFile(path.join(__dirname, 'settings.html'));
settingsWindow.on('closed', () => {
settingsWindow = null;
});
}
function buildMenu() {
const template = [
{
label: '设置',
submenu: [
{ label: '后端服务地址…', click: openSettingsWindow },
{ type: 'separator' },
{ role: 'reload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'quit', label: '退出' },
],
},
];
if (process.platform === 'darwin') {
template.unshift({
label: app.name,
submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'quit' }],
});
}
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
app.whenReady().then(async () => {
buildMenu();
const dir = staticDir();
if (!fs.existsSync(path.join(dir, 'index.html'))) {
dialog.showErrorBox(
'缺少前端构建产物',
`未找到 ${dir}\n\n请先构建前端:\n cd ui && npm run build\n cd desktop && npm run build:ui`
);
app.quit();
return;
}
ipcMain.handle('desktop:get-config', () => loadServices(app.getPath('userData')));
ipcMain.handle('desktop:set-config', async (_event, services) => {
// 用户显式保存后,文件配置优先于环境变量(否则 env 会一直盖住用户设置)
delete process.env.PLANTAI_BFF_URL;
const saved = saveServices(app.getPath('userData'), services);
await restartServer(saved.bff);
if (mainWindow) await mainWindow.loadURL(currentOrigin);
return saved;
});
const services = loadServices(app.getPath('userData'));
currentOrigin = await startServer(services.bff);
createMainWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createMainWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('will-quit', stopServer);