Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 73 additions & 12 deletions webapp/packages/main/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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'));
Expand All @@ -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');
Expand All @@ -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();
}
});

Expand All @@ -124,6 +179,11 @@ app.on('second-instance', () => {
}
});

// 系统关机或应用退出前标记为手动退出,防止报错
app.on('before-quit', () => {
isManualExit = true;
});

// 窗口关闭处理
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
Expand Down Expand Up @@ -306,5 +366,6 @@ app.whenReady()
app.on('will-quit', () => {
globalShortcut.unregisterAll();
console.log('[GlobalShortcut] All shortcuts unregistered');
isManualExit = true;
nkas.kill(() => {});
});