148 lines
3.8 KiB
TypeScript
148 lines
3.8 KiB
TypeScript
|
|
/**
|
|||
|
|
* HoloLake Desktop · Electron 主进程
|
|||
|
|
*
|
|||
|
|
* 职责:
|
|||
|
|
* 1. 创建应用窗口
|
|||
|
|
* 2. 启动知识库后端服务器(Express + Git 引擎)
|
|||
|
|
* 3. 管理应用生命周期
|
|||
|
|
*
|
|||
|
|
* 架构:
|
|||
|
|
* Electron Main Process
|
|||
|
|
* ├── 启动 Express 服务器(Git 引擎层,端口 3890)
|
|||
|
|
* ├── 创建 BrowserWindow(加载 Vite 前端或打包后的 dist)
|
|||
|
|
* └── 数据存储:~/Library/Application Support/HoloLake Era/data/
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { app, BrowserWindow, shell, dialog } from 'electron';
|
|||
|
|
import path from 'path';
|
|||
|
|
import { spawn, ChildProcess } from 'child_process';
|
|||
|
|
|
|||
|
|
// ─── 配置 ───
|
|||
|
|
|
|||
|
|
const isDev = process.env.NODE_ENV !== 'production';
|
|||
|
|
const SERVER_PORT = 3890;
|
|||
|
|
const CLIENT_PORT = 5180;
|
|||
|
|
|
|||
|
|
// 数据目录:macOS 标准位置
|
|||
|
|
const DATA_DIR = path.join(app.getPath('userData'), 'data');
|
|||
|
|
const KB_REPO_PATH = path.join(DATA_DIR, 'knowledge-base');
|
|||
|
|
|
|||
|
|
// ─── 后端服务器 ───
|
|||
|
|
|
|||
|
|
let serverProcess: ChildProcess | null = null;
|
|||
|
|
|
|||
|
|
function startServer(): Promise<void> {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// 在开发模式下,服务器由 concurrently 启动
|
|||
|
|
if (isDev && process.env.KB_SERVER_RUNNING === '1') {
|
|||
|
|
resolve();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const serverScript = path.resolve(__dirname, '../../guanghu-knowledge-base/server/index.ts');
|
|||
|
|
|
|||
|
|
serverProcess = spawn('npx', ['tsx', serverScript], {
|
|||
|
|
env: {
|
|||
|
|
...process.env,
|
|||
|
|
KB_PORT: String(SERVER_PORT),
|
|||
|
|
KB_REPO_PATH,
|
|||
|
|
NODE_ENV: 'production',
|
|||
|
|
},
|
|||
|
|
cwd: path.resolve(__dirname, '../..'),
|
|||
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
serverProcess.stdout?.on('data', (data: Buffer) => {
|
|||
|
|
const msg = data.toString().trim();
|
|||
|
|
console.log(`[KB Server] ${msg}`);
|
|||
|
|
if (msg.includes('已启动')) resolve();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
serverProcess.stderr?.on('data', (data: Buffer) => {
|
|||
|
|
console.error(`[KB Server Error] ${data.toString().trim()}`);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
serverProcess.on('error', (err: Error) => {
|
|||
|
|
console.error('服务器启动失败:', err);
|
|||
|
|
reject(err);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
serverProcess.on('exit', (code: number | null) => {
|
|||
|
|
console.log(`[KB Server] 退出,code=${code}`);
|
|||
|
|
serverProcess = null;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 超时兜底
|
|||
|
|
setTimeout(() => resolve(), 5000);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function stopServer(): void {
|
|||
|
|
if (serverProcess) {
|
|||
|
|
serverProcess.kill('SIGTERM');
|
|||
|
|
serverProcess = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 窗口 ───
|
|||
|
|
|
|||
|
|
let mainWindow: BrowserWindow | null = null;
|
|||
|
|
|
|||
|
|
function createWindow(): void {
|
|||
|
|
mainWindow = new BrowserWindow({
|
|||
|
|
width: 1280,
|
|||
|
|
height: 860,
|
|||
|
|
minWidth: 800,
|
|||
|
|
minHeight: 600,
|
|||
|
|
titleBarStyle: 'hiddenInset',
|
|||
|
|
trafficLightPosition: { x: 16, y: 16 },
|
|||
|
|
backgroundColor: '#0d1117',
|
|||
|
|
webPreferences: {
|
|||
|
|
preload: path.join(__dirname, 'preload.js'),
|
|||
|
|
contextIsolation: true,
|
|||
|
|
nodeIntegration: false,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (isDev) {
|
|||
|
|
mainWindow.loadURL(`http://localhost:${CLIENT_PORT}`);
|
|||
|
|
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
|||
|
|
} else {
|
|||
|
|
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 外部链接用系统浏览器打开
|
|||
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|||
|
|
if (url.startsWith('http')) shell.openExternal(url);
|
|||
|
|
return { action: 'deny' };
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
mainWindow.on('closed', () => {
|
|||
|
|
mainWindow = null;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 应用生命周期 ───
|
|||
|
|
|
|||
|
|
app.whenReady().then(async () => {
|
|||
|
|
try {
|
|||
|
|
await startServer();
|
|||
|
|
createWindow();
|
|||
|
|
} catch (err) {
|
|||
|
|
dialog.showErrorBox('启动失败', `知识库引擎启动失败:${err}`);
|
|||
|
|
app.quit();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
app.on('activate', () => {
|
|||
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
app.on('window-all-closed', () => {
|
|||
|
|
stopServer();
|
|||
|
|
if (process.platform !== 'darwin') app.quit();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
app.on('before-quit', () => {
|
|||
|
|
stopServer();
|
|||
|
|
});
|