fix: complete HoloLake Agent and Forgejo desktop runtime

This commit is contained in:
冰朔 2026-08-08 10:39:18 +08:00
commit 46bdd0ca73
15 changed files with 1217 additions and 638 deletions

View file

@ -13,9 +13,10 @@
* ~/Library/Application Support/HoloLake Era/data/
*/
import { app, BrowserWindow, shell, dialog } from 'electron';
import { app, BrowserWindow, shell, dialog, ipcMain, safeStorage } from 'electron';
import path from 'path';
import { spawn, ChildProcess } from 'child_process';
import fs from 'fs';
// ─── 配置 ───
@ -26,6 +27,31 @@ const CLIENT_PORT = 5180;
// 数据目录macOS 标准位置
const DATA_DIR = path.join(app.getPath('userData'), 'data');
const KB_REPO_PATH = path.join(DATA_DIR, 'knowledge-base');
const MODEL_CONFIG_PATH = path.join(app.getPath('userData'), 'model-config.json');
interface ModelConfigFile {
baseUrl: string;
model: string;
encryptedKey?: string;
}
function readModelConfig(): ModelConfigFile {
try {
return JSON.parse(fs.readFileSync(MODEL_CONFIG_PATH, 'utf8')) as ModelConfigFile;
} catch {
return { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o' };
}
}
function applyModelConfig(): ModelConfigFile {
const config = readModelConfig();
process.env.HOLOLAKE_LLM_BASE = config.baseUrl;
process.env.HOLOLAKE_LLM_MODEL = config.model;
if (config.encryptedKey && safeStorage.isEncryptionAvailable()) {
process.env.HOLOLAKE_LLM_KEY = safeStorage.decryptString(Buffer.from(config.encryptedKey, 'base64'));
}
return config;
}
// ─── 后端服务器 ───
@ -73,7 +99,7 @@ function startServer(): Promise<void> {
try {
process.env.KB_PORT = String(SERVER_PORT);
process.env.KB_REPO_PATH = KB_REPO_PATH;
const serverBundle = path.join(__dirname, 'server-bundle.js');
const serverBundle = path.join(__dirname, 'server-bundle.cjs');
require(serverBundle);
console.log(`[KB Server] 内嵌启动,端口 ${SERVER_PORT}`);
resolve();
@ -106,7 +132,7 @@ function createWindow(): void {
trafficLightPosition: { x: 16, y: 16 },
backgroundColor: '#0d1117',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true,
nodeIntegration: false,
},
@ -130,10 +156,37 @@ function createWindow(): void {
});
}
ipcMain.handle('get-data-path', () => DATA_DIR);
ipcMain.handle('agent:get-config', () => {
const config = readModelConfig();
return {
baseUrl: config.baseUrl,
model: config.model,
configured: Boolean(config.encryptedKey && safeStorage.isEncryptionAvailable()),
};
});
ipcMain.handle('agent:save-config', (_event, input: { baseUrl: string; model: string; apiKey?: string }) => {
const baseUrl = String(input.baseUrl || '').trim().replace(/\/$/, '');
const model = String(input.model || '').trim();
if (!/^https:\/\//i.test(baseUrl)) throw new Error('模型服务地址必须使用 HTTPS');
if (!model) throw new Error('模型名称不能为空');
if (!safeStorage.isEncryptionAvailable()) throw new Error('macOS 加密存储当前不可用');
const previous = readModelConfig();
const encryptedKey = input.apiKey
? safeStorage.encryptString(String(input.apiKey)).toString('base64')
: previous.encryptedKey;
if (!encryptedKey) throw new Error('请填写模型密钥');
fs.mkdirSync(path.dirname(MODEL_CONFIG_PATH), { recursive: true });
fs.writeFileSync(MODEL_CONFIG_PATH, JSON.stringify({ baseUrl, model, encryptedKey }), { mode: 0o600 });
applyModelConfig();
return { baseUrl, model, configured: true };
});
// ─── 应用生命周期 ───
app.whenReady().then(async () => {
try {
applyModelConfig();
await startServer();
createWindow();
} catch (err) {

View file

@ -16,11 +16,11 @@ contextBridge.exposeInMainWorld('hololake', {
// 数据目录
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'),
agent: {
getConfig: () => ipcRenderer.invoke('agent:get-config'),
saveConfig: (config: { baseUrl: string; model: string; apiKey?: string }) =>
ipcRenderer.invoke('agent:save-config', config),
},
// Forgejo 通过本地知识库 API 管理;此桥只保留非敏感应用信息。
});

View file

@ -23,3 +23,9 @@
# - 不接 Forgejo 上游自动更新
# - 光湖团队评估 Forgejo 新版功能后,拆出有用部分自行集成
# - Forgejo 的 Git 引擎逐渐适配光湖协议HLDP/GLS
#
# 2026-08-08 运行实现:
# - GitEngine 已实现状态、配置 remote、fetch、仅快进 pull、显式确认 push
# - 桌面 UI 已提供连接与同步状态;认证继续使用本机钥匙串或 SSH
# - Agent 只获得只读 inspect_repository 工具,不拥有自动 push 权限
# - 本地 API 仅监听 127.0.0.1,不向局域网暴露

File diff suppressed because it is too large Load diff

View file

@ -2,30 +2,32 @@
"name": "hololake-desktop",
"version": "0.5.0",
"description": "HoloLake Era 桌面版 — Git 驱动的知识库管理",
"main": "dist-electron/main.js",
"main": "dist-electron/main.cjs",
"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",
"build:electron": "esbuild electron/main.ts --bundle --platform=node --format=cjs --external:electron --outfile=dist-electron/main.cjs && esbuild electron/preload.ts --bundle --platform=node --format=cjs --external:electron --outfile=dist-electron/preload.cjs",
"build:server": "NODE_PATH=./node_modules esbuild ../guanghu-knowledge-base/server/index.ts --bundle --platform=node --format=cjs --outfile=dist-electron/server-bundle.cjs",
"build": "vite build && npm run build:electron && npm run build:server",
"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",
"diff": "^9.0.0",
"express": "^5.1.0",
"gray-matter": "^4.0.3",
"marked": "^15.0.0",
"diff": "^7.0.0"
"simple-git": "^3.27.0"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/cors": "^2.8.17",
"@types/diff": "^6.0.0",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
@ -33,6 +35,7 @@
"concurrently": "^9.1.0",
"electron": "^35.0.0",
"electron-builder": "^26.0.0",
"esbuild": "^0.25.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tsx": "^4.19.0",
@ -47,10 +50,15 @@
"icon": "forgejo/icon.icns",
"identity": "bei sun (825A9L3G7Q)",
"target": [
{ "target": "dmg", "arch": ["arm64"] }
{
"target": "dmg",
"arch": [
"arm64"
]
}
]
},
"asar": false,
"asar": true,
"files": [
"dist/**/*",
"dist-electron/**/*",

View file

@ -3,11 +3,17 @@ import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
base: './',
plugins: [react()],
resolve: {
alias: {
// 复用知识库前端组件
'@': path.resolve(__dirname, '../guanghu-knowledge-base/src'),
'react/jsx-runtime': path.resolve(__dirname, 'node_modules/react/jsx-runtime.js'),
'react-dom/client': path.resolve(__dirname, 'node_modules/react-dom/client.js'),
'react-dom': path.resolve(__dirname, 'node_modules/react-dom/index.js'),
'react': path.resolve(__dirname, 'node_modules/react/index.js'),
'marked': path.resolve(__dirname, 'node_modules/marked/lib/marked.esm.js'),
},
},
root: path.resolve(__dirname, '../guanghu-knowledge-base'),