This commit is contained in:
minorQAQ committed 2026-08-14 15:35:05 +08:00
1 parent 731768deb9
commit 863abe7c76
18 files changed
+2740 -114

No files matched your search

+67 -22
View File
@@ -30,11 +30,31 @@ const SRC_DIR = isPackaged
// 配置文件路径 (src/agent/config/env_config.json)
const CONFIG_PATH = path.join(SRC_DIR, 'agent', 'config', 'env_config.json');
const CONFIG_DIST_PATH = path.join(SRC_DIR, 'agent', 'config', 'env_config_dist.json');
const THEME_CONFIG_PATH = path.join(SRC_DIR, 'agent', 'config', 'theme_config.json');
const HOST = '127.0.0.1';
const PORT = 8765;
const BASE_URL = `http://${HOST}:${PORT}`;
// ==================== 主题检测(启动玻璃层随主题色变化) ====================
// 与前端 themes.js 的 dark 标记保持一致
const DARK_THEMES = new Set(['vscode-dark', 'cyber-neon', 'deep-space-indigo', 'pixel-gold']);
function getThemeMode() {
try {
if (fs.existsSync(THEME_CONFIG_PATH)) {
const cfg = JSON.parse(fs.readFileSync(THEME_CONFIG_PATH, 'utf-8'));
const id = cfg.theme || '';
if (DARK_THEMES.has(id)) return 'dark';
if (id) {
// 未知主题按 id 推断(与 themes.js 的回退逻辑一致)
return (!id.includes('light') && !id.includes('solarized')) ? 'dark' : 'light';
}
}
} catch {}
return 'dark'; // 默认深色,与窗口默认底色一致
}
// ==================== 全局状态 ====================
let mainWindow = null;
let pythonProcess = null;
@@ -166,11 +186,13 @@ function findSystemPython() {
}
// ==================== FastAPI 生命周期 ====================
let _fastapiStderr = ''; // 累积 stderr 用于超时诊断
let _fastapiStderr = ''; // 累积 stderr 用于超时诊断
let _fastapiReadySettled = false; // 健康检查已结束(成功或超时),停止轮询
function startFastAPI() {
return new Promise((resolve, reject) => {
killPortProcess(PORT);
_fastapiReadySettled = false;
const pythonExe = findSystemPython();
@@ -234,16 +256,22 @@ function startFastAPI() {
}
function waitForReady(resolve, reject, retriesLeft) {
if (_fastapiReadySettled) return;
if (retriesLeft <= 0) {
_fastapiReadySettled = true;
reject(new Error(
`FastAPI 启动超时\n\nPython: ${findSystemPython()}\n工作目录: ${SRC_DIR}\n\n最后 stderr 输出:\n${_fastapiStderr || '(无输出)'}`
));
return;
}
const req = http.get(`${BASE_URL}/api/health`, (res) => {
res.resume(); // 及时排空响应体,避免 keep-alive 悬挂
if (res.statusCode === 200) {
console.log('[Electron] FastAPI Ready');
resolve();
if (!_fastapiReadySettled) {
_fastapiReadySettled = true;
console.log('[Electron] FastAPI Ready');
resolve();
}
} else {
retry();
}
@@ -251,7 +279,10 @@ function waitForReady(resolve, reject, retriesLeft) {
req.on('error', () => retry());
req.setTimeout(2000, () => { req.destroy(); retry(); });
function retry() {
setTimeout(() => waitForReady(resolve, reject, retriesLeft - 1), 1000);
// 已就绪/已超时后不再轮询,防止反复打印日志
setTimeout(() => {
if (!_fastapiReadySettled) waitForReady(resolve, reject, retriesLeft - 1);
}, 1000);
}
}
@@ -273,14 +304,16 @@ function stopFastAPI() {
}
// ==================== 窗口管理 ====================
async function createMainWindow() {
async function createMainWindow(themeMode) {
themeMode = themeMode || getThemeMode();
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
title: 'minor Agent',
backgroundColor: '#1a1a2e',
backgroundColor: themeMode === 'dark' ? '#1a1a2e' : '#eef1f7',
frame: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
@@ -294,7 +327,9 @@ async function createMainWindow() {
await mainWindow.webContents.session.clearCache();
await mainWindow.webContents.session.clearStorageData({ storages: ['caches', 'serviceworkers'] });
mainWindow.loadURL(BASE_URL);
// 先显示本地过渡页:毛玻璃 + 中央图标(无任何按钮),主题随 theme_config.json 变化;
// FastAPI 就绪后再切到应用页;应用页自带的同款玻璃覆盖层会无缝接手
mainWindow.loadFile(path.join(__dirname, 'splash.html'), { query: { theme: themeMode } });
mainWindow.once('ready-to-show', () => mainWindow.show());
@@ -304,6 +339,26 @@ async function createMainWindow() {
require('electron').shell.openExternal(url).catch(() => {});
return { action: 'deny' };
});
return mainWindow;
}
// 启动主流程:立即弹出毛玻璃窗口 → 后台启动 FastAPI → 就绪后加载应用页
async function launchMainFlow() {
const themeMode = getThemeMode();
try {
// 1. 创建并显示主窗口(本地过渡页,秒开,无按钮),主题随 theme_config.json
await createMainWindow(themeMode);
// 2. 后台启动 FastAPI(窗口已先显示,用户不会盯着空白等待)
await startFastAPI();
if (!mainWindow) return; // 启动期间用户已关闭窗口(window-all-closed 会走退出流程)
// 3. 后端就绪后加载应用页;带上主题参数,保证应用页首帧即为当前主题(避免深色闪屏),
// 页面加载完成后由前端播放玻璃层淡出动画
mainWindow.loadURL(`${BASE_URL}?theme=${themeMode}`);
} catch (err) {
dialog.showErrorBox('启动失败', `无法启动 minor Agent:\n${err.message}`);
app.quit();
}
}
// ==================== 应用生命周期 ====================
@@ -342,14 +397,8 @@ app.whenReady().then(async () => {
// 首次运行:打开配置窗口(Python 环境安装 + LLM 配置)
createSetupWindow();
} else {
// 非首次:直接启动 FastAPI 和主窗口
try {
await startFastAPI();
await createMainWindow();
} catch (err) {
dialog.showErrorBox('启动失败', `无法启动 minor Agent:\n${err.message}`);
app.quit();
}
// 非首次:立即显示毛玻璃启动窗口,同时后台启动 FastAPI 和主流程
await launchMainFlow();
}
});
@@ -376,13 +425,9 @@ ipcMain.on('launch-app', async () => {
setupWindow = null;
}
// 启动 FastAPI 并创建主窗口
// 立即显示毛玻璃启动窗口,后台启动 FastAPI 并加载应用页
try {
await startFastAPI();
await createMainWindow();
} catch (err) {
dialog.showErrorBox('启动失败', `无法启动 minor Agent:\n${err.message}`);
app.quit();
await launchMainFlow();
} finally {
isLaunching = false;
}
@@ -888,7 +933,7 @@ app.on('before-quit', () => {
app.on('activate', () => {
if (mainWindow === null) {
createMainWindow();
launchMainFlow();
}
});
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>minor Agent</title>
<!-- 与 index.html 内 #appSplash 覆盖层共用同一套玻璃层样式,切换时视觉无缝 -->
<link rel="stylesheet" href="../src/web/css/splash.css" />
<script>
// 主题随动:main.js 读取 theme_config.json 后通过 ?theme=dark|light 传入,
// 这里在 <html> 上挂 theme-dark / theme-light 类(与 index.html 的 body 类名一致)
(function () {
var m = new URLSearchParams(location.search).get("theme");
if (m === "light") document.documentElement.classList.add("theme-light");
else document.documentElement.classList.add("theme-dark");
})();
</script>
</head>
<body class="splash-page">
<!-- 毛玻璃层 + 居中图标(无边框、无任何按钮);后端就绪切到应用页后,由应用页的同款覆盖层接手 -->
<div class="app-splash">
<svg class="app-splash-icon" version="1.0" xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 800 800" preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,800.000000) scale(0.100000,-0.100000)" fill="#0f172a" stroke="none">
<path d="M2465 5660 c-595 -6 -552 7 -574 -176 -14 -111 -14 -269 -1 -294 5 -10 38 -28 73 -39 81 -27 136 -63 167 -109 l25 -36 -3 -1008 c-2 -1095 0 -1047 -57 -1101 -29 -27 -71 -45 -167 -71 -86 -24 -91 -38 -86 -245 4 -180 11 -208 61 -245 28 -21 40 -21 510 -24 595 -4 657 3 702 71 17 26 55 270 55 353 0 40 -5 59 -20 74 -24 24 -40 27 -151 34 -106 6 -157 27 -182 76 -17 32 -18 55 -13 275 4 132 11 344 16 470 6 127 10 436 9 688 -1 252 1 460 4 463 2 3 11 -23 19 -58 28 -117 169 -645 233 -873 28 -97 69 -243 169 -605 124 -445 247 -872 262 -910 19 -48 41 -59 137 -71 90 -12 332 -4 384 12 67 21 69 24 184 444 105 383 226 810 352 1245 30 102 78 266 106 365 29 99 65 225 81 280 16 55 39 138 52 185 20 75 23 82 29 55 10 -46 10 -515 -1 -755 -5 -118 -14 -381 -20 -585 -12 -405 -23 -573 -40 -622 -14 -40 -59 -69 -131 -84 -79 -17 -104 -33 -129 -85 -21 -42 -22 -56 -17 -154 11 -185 30 -240 95 -267 49 -20 388 -32 945 -33 463 0 466 0 492 63 7 18 19 102 25 188 17 220 3 247 -135 269 -166 26 -196 73 -216 340 -17 221 -5 1727 14 1798 24 90 71 123 209 147 46 8 84 15 86 15 2 0 10 17 19 37 21 52 30 294 13 391 -17 103 -25 107 -228 116 -152 6 -1174 -9 -1299 -20 -33 -3 -69 -11 -78 -18 -10 -7 -45 -108 -91 -261 -93 -314 -88 -297 -179 -625 -138 -493 -163 -583 -211 -752 -26 -93 -51 -168 -55 -168 -5 0 -9 7 -9 15 0 14 -85 313 -155 545 -146 484 -296 985 -335 1123 -30 108 -54 141 -109 157 -20 5 -108 9 -196 8 -88 -1 -376 -5 -640 -8z m-135 -123 c-69 -18 -135 -38 -147 -45 -24 -13 -38 -67 -49 -194 -4 -47 -11 -79 -19 -82 -19 -7 -74 31 -85 58 -17 45 -23 171 -11 209 24 72 69 86 276 86 l160 0 -125 -32z m2356 7 c-14 -15 -26 -32 -26 -39 0 -6 15 -29 33 -51 32 -37 57 -76 57 -89 0 -3 -10 -1 -21 6 -37 19 -138 7 -199 -25 -30 -16 -60 -36 -67 -45 -11 -15 -12 -15 -13 0 0 23 56 158 78 189 23 32 116 80 155 80 l30 0 -27 -26z m1165 -5 c20 -5 43 -17 51 -26 25 -30 48 -101 48 -149 0 -40 -2 -45 -15 -34 -8 7 -15 18 -15 25 0 7 -18 32 -39 56 -31 36 -53 49 -107 67 -83 27 -190 48 -304 59 l-85 8 85 6 c108 7 334 0 381 -12z m-2587 -22 c14 -10 146 -380 146 -411 0 -19 -113 135 -175 238 -67 113 -79 157 -47 175 24 14 55 13 76 -2z m2590 -212 l6 -51 -60 1 -59 0 38 35 c21 19 41 46 46 59 10 32 21 15 29 -44z m-3594 -245 c0 -22 -4 -28 -15 -24 -15 6 -20 35 -8 47 14 15 23 6 23 -23z m3311 -103 c-16 -16 -26 40 -10 60 13 15 14 13 17 -17 2 -18 -1 -37 -7 -43z m-24 -4 c-3 -10 -13 -53 -22 -95 -9 -43 -19 -78 -22 -78 -8 0 5 134 17 164 11 29 34 36 27 9z m-1960 -2228 c40 -77 68 -103 134 -120 74 -20 123 -19 169 5 42 21 108 98 141 162 11 21 22 38 25 38 11 0 -6 -101 -28 -157 -21 -58 -96 -176 -130 -205 -16 -15 -39 -18 -122 -18 -55 0 -106 4 -111 8 -13 9 -57 77 -82 127 -44 87 -100 292 -117 430 -5 42 2 28 42 -80 27 -71 63 -157 79 -190z m2173 73 c0 -13 -23 -5 -28 10 -2 7 2 10 12 6 9 -3 16 -11 16 -16z m-3741 -130 c-1 -19 11 -56 27 -88 l29 -55 60 -5 c46 -4 54 -7 35 -12 -13 -4 -36 -5 -50 -1 l-25 5 20 -20 c11 -12 47 -33 80 -46 l60 -25 -70 -1 c-79 0 -194 16 -211 31 -8 6 -8 20 -1 47 7 27 6 50 -5 86 -13 43 -13 53 1 82 21 46 52 47 50 2z m2677 14 c6 -4 19 -32 27 -62 21 -71 59 -108 167 -160 l85 -41 -65 6 c-227 22 -244 24 -269 43 -29 21 -59 110 -60 180 l-1 42 53 0 c28 0 57 -4 63 -8z m1204 -254 c-1 -34 -3 -39 -11 -26 -8 15 -3 68 7 68 2 0 4 -19 4 -42z m-86 2 c-16 -26 -44 -48 -44 -34 0 5 52 63 58 64 2 0 -5 -13 -14 -30z"/>
</g>
</svg>
</div>
</body>
</html>