feat(product-source): HoloLake Desktop v0.5.0 — Electron 桌面知识库应用
铸渊 2026-08-08 开发,冰朔架构决策: - 知识库放桌面,不放服务器 - Forgejo 是 Git 引擎,光湖自己维护更新,不接上游 - 不部署 Forgejo 源码,只引用 MANIFEST + 离线包 架构: - Electron v35(Mac arm64)+ Vite + React 19 - 复用 guanghu-knowledge-base 前端组件 - Electron 主进程自动启动 Express + Git 引擎后端 - 数据存储:~/Library/Application Support/HoloLake Era/data/ - Forgejo 离线包(桌面)作为 Git 远程仓库对接 构建: - npx electron-builder --mac --dir → HoloLake Era.app - 签名后续用 Developer ID 补(CSC_IDENTITY_AUTO_DISCOVERY=false) 文件: - electron/main.ts(Electron 主进程:窗口+服务器+生命周期) - electron/preload.ts(IPC 安全桥梁:Forgejo 远程管理接口) - forgejo/INTEGRATION.md(Forgejo 集成声明 + 更新策略) - forgejo/MANIFEST.sha256(离线包校验清单) - forgejo/icon.icns(应用图标,来自 HoloLake Era v0.4.6) - vite.config.ts(复用知识库前端,构建到 dist/) - tsconfig.electron.json(Electron TypeScript 配置)
This commit is contained in:
parent
ecfeca40e2
commit
047f37e87e
10 changed files with 7617 additions and 0 deletions
7
product-source/hololake-desktop/.gitignore
vendored
Normal file
7
product-source/hololake-desktop/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
dist-electron/
|
||||||
|
release/
|
||||||
|
*.tsbuildinfo
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
148
product-source/hololake-desktop/electron/main.ts
Normal file
148
product-source/hololake-desktop/electron/main.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
26
product-source/hololake-desktop/electron/preload.ts
Normal file
26
product-source/hololake-desktop/electron/preload.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* HoloLake Desktop · Preload 脚本
|
||||||
|
*
|
||||||
|
* 在渲染进程和主进程之间建立安全桥梁。
|
||||||
|
* 渲染进程不能直接访问 Node.js API,只能通过这个桥梁通信。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { contextBridge, ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
// 暴露给渲染进程的安全 API
|
||||||
|
contextBridge.exposeInMainWorld('hololake', {
|
||||||
|
// 应用信息
|
||||||
|
platform: process.platform,
|
||||||
|
version: '0.5.0',
|
||||||
|
|
||||||
|
// 数据目录
|
||||||
|
getDataPath: () => ipcRenderer.invoke('get-data-path'),
|
||||||
|
|
||||||
|
// Forgejo 远程仓库管理(预留接口)
|
||||||
|
forgejo: {
|
||||||
|
getRemotes: () => ipcRenderer.invoke('forgejo:get-remotes'),
|
||||||
|
addRemote: (url: string) => ipcRenderer.invoke('forgejo:add-remote', url),
|
||||||
|
push: () => ipcRenderer.invoke('forgejo:push'),
|
||||||
|
pull: () => ipcRenderer.invoke('forgejo:pull'),
|
||||||
|
},
|
||||||
|
});
|
||||||
25
product-source/hololake-desktop/forgejo/INTEGRATION.md
Normal file
25
product-source/hololake-desktop/forgejo/INTEGRATION.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Forgejo 代码频道集成声明
|
||||||
|
#
|
||||||
|
# 本文件声明 HoloLake Desktop 与 Forgejo 代码频道的关系。
|
||||||
|
#
|
||||||
|
# 光湖代码频道离线包位于:
|
||||||
|
# ~/Desktop/光湖代码频道-Forgejo-16.0.1-完整离线包/
|
||||||
|
#
|
||||||
|
# 包含:
|
||||||
|
# forgejo-16.0.1-linux-amd64 — 服务器端二进制(部署用,不进桌面 app)
|
||||||
|
# guanghu-code-channel.bundle — 光湖定制版 Forgejo(git bundle)
|
||||||
|
# forgejo-upstream-all.bundle — 上游源码(不接更新,不部署)
|
||||||
|
# forgejo-release-key.asc — GPG 签名密钥
|
||||||
|
# forgejo-16.0.1-linux-amd64.asc — 二进制签名
|
||||||
|
# MANIFEST.sha256 — 校验清单
|
||||||
|
#
|
||||||
|
# 集成方式:
|
||||||
|
# 1. 桌面 app 使用本地 git 作为引擎(simple-git)
|
||||||
|
# 2. 用户可配置 Forgejo 服务器为 remote,实现同步/备份
|
||||||
|
# 3. Forgejo 由光湖自己维护更新,不跟踪上游
|
||||||
|
# 4. guanghu-code-channel.bundle 是光湖定制版,包含光湖协议适配
|
||||||
|
#
|
||||||
|
# 更新策略:
|
||||||
|
# - 不接 Forgejo 上游自动更新
|
||||||
|
# - 光湖团队评估 Forgejo 新版功能后,拆出有用部分自行集成
|
||||||
|
# - Forgejo 的 Git 引擎逐渐适配光湖协议(HLDP/GLS)
|
||||||
5
product-source/hololake-desktop/forgejo/MANIFEST.sha256
Normal file
5
product-source/hololake-desktop/forgejo/MANIFEST.sha256
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
7a4c568136650c10498a9d3d62c7fd630a0cf09c166293ebd78708248f6398fc forgejo-16.0.1-linux-amd64
|
||||||
|
1c0ca36df3adb0a7692b6bdc84d7886001ca0c6d0408e67c9d232d2f33cecc71 forgejo-16.0.1-linux-amd64.asc
|
||||||
|
6fae8894c671ce2397cb35fe40c324f73deade6b4cb3cd6cedd1d2b248e0e3ea forgejo-release-key.asc
|
||||||
|
c33bd074d9b2896259e86ebe03ad31ccdd8ff71897beed4320081fa03b15381f forgejo-upstream-all.bundle
|
||||||
|
fc53740259d108128e69f5a809cec438ecf3158175617574ba55b8612c5eaa6c guanghu-code-channel.bundle
|
||||||
BIN
product-source/hololake-desktop/forgejo/icon.icns
Normal file
BIN
product-source/hololake-desktop/forgejo/icon.icns
Normal file
Binary file not shown.
7301
product-source/hololake-desktop/package-lock.json
generated
Normal file
7301
product-source/hololake-desktop/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
64
product-source/hololake-desktop/package.json
Normal file
64
product-source/hololake-desktop/package.json
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"name": "hololake-desktop",
|
||||||
|
"version": "0.5.0",
|
||||||
|
"description": "HoloLake Era 桌面版 — Git 驱动的知识库管理",
|
||||||
|
"main": "dist-electron/main.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"",
|
||||||
|
"dev:server": "tsx watch ../guanghu-knowledge-base/server/index.ts",
|
||||||
|
"dev:client": "vite",
|
||||||
|
"dev:electron": "electron .",
|
||||||
|
"build": "vite build && tsc -p tsconfig.electron.json",
|
||||||
|
"pack": "npm run build && electron-builder --mac --dir",
|
||||||
|
"dist": "npm run build && electron-builder --mac",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"simple-git": "^3.27.0",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"marked": "^15.0.0",
|
||||||
|
"diff": "^7.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/diff": "^6.0.0",
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"concurrently": "^9.1.0",
|
||||||
|
"electron": "^35.0.0",
|
||||||
|
"electron-builder": "^26.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.7.0",
|
||||||
|
"vite": "^6.2.0"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.guanghu.hololake-desktop",
|
||||||
|
"productName": "HoloLake Era",
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"icon": "forgejo/icon.icns",
|
||||||
|
"target": [
|
||||||
|
{ "target": "dmg", "arch": ["arm64"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"asar": false,
|
||||||
|
"files": [
|
||||||
|
"dist/**/*",
|
||||||
|
"dist-electron/**/*",
|
||||||
|
"forgejo/MANIFEST.sha256",
|
||||||
|
"!**/.DS_Store",
|
||||||
|
"!**/._*"
|
||||||
|
],
|
||||||
|
"directories": {
|
||||||
|
"output": "release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
product-source/hololake-desktop/tsconfig.electron.json
Normal file
13
product-source/hololake-desktop/tsconfig.electron.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "./dist-electron",
|
||||||
|
"rootDir": "./electron"
|
||||||
|
},
|
||||||
|
"include": ["electron/**/*"]
|
||||||
|
}
|
||||||
28
product-source/hololake-desktop/vite.config.ts
Normal file
28
product-source/hololake-desktop/vite.config.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// 复用知识库前端组件
|
||||||
|
'@': path.resolve(__dirname, '../guanghu-knowledge-base/src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
root: path.resolve(__dirname, '../guanghu-knowledge-base'),
|
||||||
|
server: {
|
||||||
|
port: 5180,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3890',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(__dirname, 'dist'),
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue