diff --git a/webapp/packages/main/src/index.ts b/webapp/packages/main/src/index.ts index 82e9ec62..8a2a68fb 100644 --- a/webapp/packages/main/src/index.ts +++ b/webapp/packages/main/src/index.ts @@ -1,4 +1,4 @@ -import { app, Menu, Tray, BrowserWindow, ipcMain, globalShortcut } from 'electron'; +import { app, Menu, Tray, BrowserWindow, ipcMain, globalShortcut, dialog } from 'electron'; import { URL } from 'url'; import { PyShell } from '/@/pyshell'; import { webuiArgs, webuiPath, dpiScaling, webuiUrl, nkasPath } from '/@/config'; @@ -31,8 +31,25 @@ if (import.meta.env.MODE === 'development') { // 启动 Python 服务 let nkas = new PyShell(webuiPath, webuiArgs); -nkas.end(function (err: string) { - // if (err) throw err; +let stderrLog = ''; +let isReady = false; +let isManualExit = false; + +nkas.end(function (err: any) { + if (isManualExit) return; + if (err) { + dialog.showErrorBox( + 'NKAS Backend Error', + `Python process exited unexpectedly.\n\nError:\n${err}\n\nLast logs:\n${stderrLog.slice(-500)}` + ); + app.quit(); + } else if (!isReady) { + dialog.showErrorBox( + 'NKAS Backend Error', + `Python process exited without error before startup.\n\nLast logs:\n${stderrLog.slice(-500)}` + ); + app.quit(); + } }); let mainWindow: BrowserWindow | null = null; @@ -70,9 +87,10 @@ const createWindow = async () => { ipcMain.on('window-max', () => mainWindow?.isMaximized() ? mainWindow?.restore() : mainWindow?.maximize() ); - ipcMain.on('window-close', () => - nkas.kill(() => mainWindow?.close()) - ); + ipcMain.on('window-close', () => { + isManualExit = true; + nkas.kill(() => mainWindow?.close()); + }); // 托盘菜单 const tray = new Tray(path.join(__dirname, 'icon.png')); @@ -81,7 +99,10 @@ const createWindow = async () => { { label: 'Hide', click: () => mainWindow?.hide() }, { label: 'Exit', - click: () => nkas.kill(() => mainWindow?.close()) + click: () => { + isManualExit = true; + nkas.kill(() => mainWindow?.close()); + } } ]); tray.setToolTip('NKAS'); @@ -96,22 +117,56 @@ if (!dpiScaling) { app.commandLine.appendSwitch('force-device-scale-factor', '1'); } -/** - * 加载应用 URL - */ +// 加载应用 URL function loadURL() { const pageUrl = import.meta.env.MODE === 'development' && import.meta.env.VITE_DEV_SERVER_URL !== undefined ? import.meta.env.VITE_DEV_SERVER_URL : new URL('../renderer/dist/index.html', 'file://' + __dirname).toString(); - mainWindow?.loadURL(pageUrl); + mainWindow?.loadURL(pageUrl).catch((e) => { + console.error('Failed to load URL, retrying...', e); + setTimeout(loadURL, 1000); + }); +} + +function checkServerReady(retries = 20) { + const { hostname, port } = new URL(webuiUrl); + const req = require('http').request({ + hostname, + port, + path: '/', + method: 'HEAD', + timeout: 1000 + }, (res: any) => { + if (res.statusCode >= 200 && res.statusCode < 400) { + loadURL(); + } else { + if (retries > 0) setTimeout(() => checkServerReady(retries - 1), 500); + else loadURL(); // Try loading anyway if retries exhausted + } + }); + + req.on('error', () => { + if (retries > 0) setTimeout(() => checkServerReady(retries - 1), 500); + else loadURL(); // Try loading anyway + }); + + req.end(); } // Python 服务启动检测 nkas.on('stderr', function (message: string) { + stderrLog += message + '\n'; if (message.includes('Application startup complete') || message.includes('bind on address')) { + isReady = true; nkas.removeAllListeners('stderr'); - loadURL(); + checkServerReady(); + } else if (message.includes('Address already in use') || message.includes('port is already allocated')) { + dialog.showErrorBox( + 'Port Conflict', + 'The port is already in use. Please change the WebuiPort in config/deploy.yaml or close the application using that port.' + ); + app.quit(); } }); @@ -124,6 +179,11 @@ app.on('second-instance', () => { } }); +// 系统关机或应用退出前标记为手动退出,防止报错 +app.on('before-quit', () => { + isManualExit = true; +}); + // 窗口关闭处理 app.on('window-all-closed', () => { if (process.platform !== 'darwin') { @@ -306,5 +366,6 @@ app.whenReady() app.on('will-quit', () => { globalShortcut.unregisterAll(); console.log('[GlobalShortcut] All shortcuts unregistered'); + isManualExit = true; nkas.kill(() => {}); });