feihong-code 7.0.0 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.github/workflows/ci.yml +180 -140
  2. package/.github/workflows/release.yml +200 -0
  3. package/CHANGELOG.md +94 -65
  4. package/README.md +609 -586
  5. package/dist/agent/browser-agent.js +354 -0
  6. package/dist/agent/browser-agent.js.map +1 -0
  7. package/dist/agent/change-manager.js +433 -0
  8. package/dist/agent/change-manager.js.map +1 -0
  9. package/dist/agent/code-rag.js +293 -0
  10. package/dist/agent/code-rag.js.map +1 -0
  11. package/dist/agent/code-writer.js +13 -1
  12. package/dist/agent/code-writer.js.map +1 -1
  13. package/dist/agent/completion-engine.js +553 -0
  14. package/dist/agent/completion-engine.js.map +1 -0
  15. package/dist/agent/completion-postprocess.js +109 -0
  16. package/dist/agent/completion-postprocess.js.map +1 -0
  17. package/dist/agent/context-budget.js +121 -0
  18. package/dist/agent/context-budget.js.map +1 -0
  19. package/dist/agent/context-compactor.js +235 -10
  20. package/dist/agent/context-compactor.js.map +1 -1
  21. package/dist/agent/custom-agent.js +368 -0
  22. package/dist/agent/custom-agent.js.map +1 -0
  23. package/dist/agent/design-to-code.js +218 -0
  24. package/dist/agent/design-to-code.js.map +1 -0
  25. package/dist/agent/event-driven-agent.js +359 -0
  26. package/dist/agent/event-driven-agent.js.map +1 -0
  27. package/dist/agent/git-integration.js +324 -0
  28. package/dist/agent/git-integration.js.map +1 -0
  29. package/dist/agent/layered-memory.js +329 -0
  30. package/dist/agent/layered-memory.js.map +1 -0
  31. package/dist/agent/lint.js +121 -0
  32. package/dist/agent/lint.js.map +1 -0
  33. package/dist/agent/multi-agent.js +359 -0
  34. package/dist/agent/multi-agent.js.map +1 -0
  35. package/dist/agent/orchestrator.js +136 -25
  36. package/dist/agent/orchestrator.js.map +1 -1
  37. package/dist/agent/prompts.js +54 -54
  38. package/dist/agent/quality-gate.js +139 -0
  39. package/dist/agent/quality-gate.js.map +1 -1
  40. package/dist/agent/self-correction.js +274 -0
  41. package/dist/agent/self-correction.js.map +1 -0
  42. package/dist/agent/self-heal.js +47 -17
  43. package/dist/agent/self-heal.js.map +1 -1
  44. package/dist/agent/solo-agent.js +321 -0
  45. package/dist/agent/solo-agent.js.map +1 -0
  46. package/dist/agent/symbol-index.js +258 -28
  47. package/dist/agent/symbol-index.js.map +1 -1
  48. package/dist/agent/team-collaboration.js +252 -0
  49. package/dist/agent/team-collaboration.js.map +1 -0
  50. package/dist/agent/type-checker.js +229 -0
  51. package/dist/agent/type-checker.js.map +1 -0
  52. package/dist/cli/commands.js +9 -0
  53. package/dist/cli/commands.js.map +1 -1
  54. package/dist/cli/index.js +9 -0
  55. package/dist/cli/index.js.map +1 -1
  56. package/dist/cli/multi-pane-tui.js +217 -0
  57. package/dist/cli/multi-pane-tui.js.map +1 -0
  58. package/dist/cli/run.js +130 -40
  59. package/dist/cli/run.js.map +1 -1
  60. package/dist/cli/tui-run.js +79 -0
  61. package/dist/cli/tui-run.js.map +1 -0
  62. package/dist/cli/tui.js.map +1 -1
  63. package/dist/cli/version.js +1 -1
  64. package/dist/integrations/collaboration.js +219 -0
  65. package/dist/integrations/collaboration.js.map +1 -0
  66. package/dist/integrations/figma.js +307 -0
  67. package/dist/integrations/figma.js.map +1 -0
  68. package/dist/integrations/sso.js +327 -0
  69. package/dist/integrations/sso.js.map +1 -0
  70. package/dist/knowledge/library.js +301 -0
  71. package/dist/knowledge/library.js.map +1 -0
  72. package/dist/lsp/lsp-client.js +183 -0
  73. package/dist/lsp/lsp-client.js.map +1 -0
  74. package/dist/lsp/lsp-service.js +159 -0
  75. package/dist/lsp/lsp-service.js.map +1 -0
  76. package/dist/plugins/manager.js +380 -0
  77. package/dist/plugins/manager.js.map +1 -0
  78. package/dist/runtime/event-log.js.map +1 -1
  79. package/dist/shared/i18n.js +108 -100
  80. package/dist/shared/i18n.js.map +1 -1
  81. package/dist/tools/browser/browser.tool.js +217 -0
  82. package/dist/tools/browser/browser.tool.js.map +1 -0
  83. package/dist/tools/generator/code-generator.js +40 -40
  84. package/dist/tools/generator/test-generator.js +9 -9
  85. package/dist/tools/index.js +2 -0
  86. package/dist/tools/index.js.map +1 -1
  87. package/dist/tools/shell/exec.js +73 -11
  88. package/dist/tools/shell/exec.js.map +1 -1
  89. package/dist/tools/shell/run-shell.tool.js +8 -2
  90. package/dist/tools/shell/run-shell.tool.js.map +1 -1
  91. package/dist/training/completion-evaluator.js +213 -0
  92. package/dist/training/completion-evaluator.js.map +1 -0
  93. package/dist/training/fim-data.js +248 -0
  94. package/dist/training/fim-data.js.map +1 -0
  95. package/dist/voice/voice-programming.js +223 -0
  96. package/dist/voice/voice-programming.js.map +1 -0
  97. package/dist/web/extra-apis.js +440 -0
  98. package/dist/web/extra-apis.js.map +1 -0
  99. package/dist/web/public/css/style.css +1729 -1671
  100. package/dist/web/public/index.html +1242 -990
  101. package/dist/web/public/js/api.js +555 -382
  102. package/dist/web/public/js/app.js +2210 -2210
  103. package/dist/web/public/js/mock-api.js +234 -0
  104. package/dist/web/public/js/monaco-editor.js +363 -0
  105. package/dist/web/public/js/ui.js +2251 -1059
  106. package/dist/web/public/js/utils.js +214 -214
  107. package/dist/web/public/manifest.json +59 -0
  108. package/dist/web/public/service-worker.js +173 -0
  109. package/dist/web/server.js +1116 -45
  110. package/dist/web/server.js.map +1 -1
  111. package/dist/web/task-queue.js +22 -2
  112. package/dist/web/task-queue.js.map +1 -1
  113. package/docs/BENCHMARK_REPORT_en.md +386 -329
  114. package/docs/BENCHMARK_REPORT_zh.md +386 -329
  115. package/docs/DPA.md +63 -0
  116. package/docs/DPA/346/225/260/346/215/256/345/244/204/347/220/206/345/215/217/350/256/256/346/250/241/346/235/277_v1.0.md +67 -0
  117. package/docs/SECURITY_WHITEPAPER.md +74 -0
  118. package/docs/SWE_BENCH_REPORT.md +55 -0
  119. package/docs/TECHNICAL_SPEC_en.md +292 -292
  120. package/docs/TECHNICAL_SPEC_zh.md +292 -292
  121. package/docs/UPDATE_NOTES_en.md +159 -159
  122. package/docs/UPDATE_NOTES_zh.md +159 -159
  123. package/docs/UPGRADE_7.2.0.md +309 -0
  124. package/docs/UPGRADE_GUIDE_7_5.md +86 -0
  125. package/docs/geo-feihong-code.html +391 -0
  126. package/docs//345/205/250/351/235/242/345/244/215/347/233/230_50/350/275/256/346/267/261/345/272/246/345/256/241/350/256/241_v7.2.0.md +208 -0
  127. package/docs//345/205/250/351/235/242/345/244/215/347/233/230_6/351/241/271/351/207/215/347/202/271/346/240/270/346/237/245_v7.2.0.md +152 -0
  128. package/docs//345/205/250/351/235/242/345/244/215/347/233/230_/350/277/220/350/241/214/346/227/266/345/256/236/346/265/213/347/211/210_v7.2.0.md +271 -0
  129. package/docs//345/220/210/350/247/204/350/256/244/350/257/201/345/207/206/345/244/207/346/270/205/345/215/225_v7.2.0.md +59 -0
  130. package/docs//345/256/211/345/205/250/347/231/275/347/232/256/344/271/246_v7.2.0.md +82 -0
  131. package/electron/main.js +404 -404
  132. package/package.json +175 -167
  133. package/templates/skills/api-design/SKILL.md +27 -0
  134. package/templates/skills/code-review/SKILL.md +35 -0
  135. package/templates/skills/dependency-upgrade/SKILL.md +29 -0
  136. package/templates/skills/doc-gen/SKILL.md +29 -0
  137. package/templates/skills/git-flow/SKILL.md +26 -0
  138. package/templates/skills/onboarding/SKILL.md +27 -0
  139. package/templates/skills/performance/SKILL.md +29 -0
  140. package/templates/skills/refactor/SKILL.md +28 -0
  141. package/templates/skills/security-audit/SKILL.md +28 -0
  142. package/templates/skills/test-gen/SKILL.md +30 -0
  143. package/tool-schema.json +197 -197
  144. package/dist/hello.js +0 -14
  145. package/dist/hello.js.map +0 -1
  146. package/dist/self-evolve/hook.ts +0 -80
  147. package/dist/self-evolve/manager.d.ts +0 -8
  148. package/dist/web/web-config.js +0 -143
  149. package/dist/web/web-config.js.map +0 -1
package/electron/main.js CHANGED
@@ -1,404 +1,404 @@
1
- /**
2
- * 飞虹 Code Electron 桌面版主进程(简化稳定版)
3
- * 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
4
- */
5
-
6
- const { app, BrowserWindow, shell, Tray, Menu, ipcMain, dialog, clipboard, Notification, session, desktopCapturer, screen } = require('electron');
7
- const { spawn } = require('child_process');
8
- const { existsSync } = require('fs');
9
- const { join } = require('path');
10
- const http = require('http');
11
-
12
- // 禁用安全警告
13
- process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
14
-
15
- // 配置
16
- const PORT = parseInt(process.env.FH_WEB_PORT || '8081');
17
- const isDev = !app.isPackaged;
18
-
19
- // 全局变量
20
- let mainWindow = null;
21
- let serverProcess = null;
22
- let tray = null;
23
-
24
- // 获取应用根目录
25
- function getAppPath() {
26
- if (isDev) return join(__dirname, '..');
27
- return process.resourcesPath ? join(process.resourcesPath, 'app') : app.getAppPath();
28
- }
29
- const APP_PATH = getAppPath();
30
-
31
- /**
32
- * 等待服务器就绪
33
- */
34
- function waitForServer(port, timeoutMs) {
35
- return new Promise((resolve, reject) => {
36
- const startTime = Date.now();
37
- const check = () => {
38
- const req = http.get(`http://127.0.0.1:${port}/api/health`, (res) => {
39
- if (res.statusCode === 200) {
40
- resolve();
41
- } else {
42
- res.resume();
43
- retry();
44
- }
45
- });
46
- req.on('error', () => retry());
47
- req.setTimeout(2000, () => { req.destroy(); retry(); });
48
- };
49
- const retry = () => {
50
- if (Date.now() - startTime > timeoutMs) {
51
- reject(new Error('服务器启动超时'));
52
- } else {
53
- setTimeout(check, 500);
54
- }
55
- };
56
- check();
57
- });
58
- }
59
-
60
- /**
61
- * 启动内置 Web 服务器
62
- */
63
- function startServer() {
64
- return new Promise((resolve, reject) => {
65
- const serverEntry = join(APP_PATH, 'dist', 'cli', 'index.js');
66
- if (!existsSync(serverEntry)) {
67
- reject(new Error('未找到服务器入口文件,请先运行 npm run build'));
68
- return;
69
- }
70
-
71
- console.log('[Electron] 启动服务器,端口: ' + PORT);
72
-
73
- let serverLog = '';
74
- let started = false;
75
-
76
- serverProcess = spawn('node', [serverEntry, 'serve'], {
77
- cwd: APP_PATH,
78
- env: { ...process.env, FH_WEB_PORT: String(PORT) },
79
- stdio: ['ignore', 'pipe', 'pipe']
80
- });
81
-
82
- serverProcess.stdout.on('data', (data) => {
83
- const text = data.toString();
84
- serverLog += text;
85
- console.log('[Server] ' + text.trim());
86
- });
87
-
88
- serverProcess.stderr.on('data', (data) => {
89
- const text = data.toString();
90
- serverLog += text;
91
- console.error('[Server Error] ' + text.trim());
92
- });
93
-
94
- serverProcess.on('error', (err) => {
95
- if (!started) reject(new Error('启动服务器失败: ' + err.message));
96
- });
97
-
98
- serverProcess.on('exit', (code) => {
99
- console.log('[Electron] 服务器退出,代码: ' + code);
100
- serverProcess = null;
101
- if (!started) {
102
- reject(new Error('服务器启动失败,退出代码: ' + code + '\n\n日志:\n' + serverLog.slice(-2000)));
103
- }
104
- });
105
-
106
- // 等待服务器就绪
107
- waitForServer(PORT, 60000).then(() => {
108
- started = true;
109
- console.log('[Electron] 服务器已就绪');
110
- resolve();
111
- }).catch((err) => {
112
- if (!started) reject(new Error(err.message + '\n\n服务器日志:\n' + serverLog.slice(-2000)));
113
- });
114
- });
115
- }
116
-
117
- /**
118
- * 创建主窗口
119
- */
120
- function createWindow() {
121
- mainWindow = new BrowserWindow({
122
- width: 1400,
123
- height: 900,
124
- minWidth: 1024,
125
- minHeight: 680,
126
- title: '飞虹 Code',
127
- webPreferences: {
128
- preload: join(__dirname, 'preload.js'),
129
- contextIsolation: true,
130
- nodeIntegration: false
131
- },
132
- show: false,
133
- backgroundColor: '#1a1a2e'
134
- });
135
-
136
- const url = `http://127.0.0.1:${PORT}/`;
137
- console.log('[Electron] 加载页面: ' + url);
138
- mainWindow.loadURL(url);
139
-
140
- mainWindow.once('ready-to-show', () => {
141
- mainWindow.show();
142
- });
143
-
144
- // 外部链接用系统浏览器打开
145
- mainWindow.webContents.setWindowOpenHandler(({ url }) => {
146
- shell.openExternal(url);
147
- return { action: 'deny' };
148
- });
149
-
150
- // 关闭时最小化到托盘
151
- mainWindow.on('close', (e) => {
152
- if (!app.isQuitting) {
153
- e.preventDefault();
154
- mainWindow.hide();
155
- }
156
- });
157
-
158
- mainWindow.on('closed', () => {
159
- mainWindow = null;
160
- });
161
- }
162
-
163
- /**
164
- * 创建系统托盘
165
- */
166
- function createTray() {
167
- try {
168
- const iconPath = join(__dirname, 'icon.png');
169
- let trayIcon;
170
- if (existsSync(iconPath)) {
171
- trayIcon = require('electron').nativeImage.createFromPath(iconPath);
172
- } else {
173
- trayIcon = require('electron').nativeImage.createEmpty();
174
- }
175
-
176
- tray = new Tray(trayIcon);
177
- tray.setToolTip('飞虹 Code');
178
-
179
- const contextMenu = Menu.buildFromTemplate([
180
- { label: '显示主窗口', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } } },
181
- { type: 'separator' },
182
- { label: '退出', click: () => { app.isQuitting = true; app.quit(); } }
183
- ]);
184
- tray.setContextMenu(contextMenu);
185
-
186
- tray.on('click', () => {
187
- if (mainWindow) {
188
- if (mainWindow.isVisible()) mainWindow.hide();
189
- else { mainWindow.show(); mainWindow.focus(); }
190
- }
191
- });
192
- } catch (e) {
193
- console.warn('[Electron] 创建托盘失败: ' + e.message);
194
- }
195
- }
196
-
197
- /**
198
- * 创建应用菜单(关键:没有菜单会导致复制粘贴等快捷键失效)
199
- */
200
- function createMenu() {
201
- const template = [
202
- {
203
- label: '文件',
204
- submenu: [
205
- { label: '刷新', role: 'reload' },
206
- { label: '强制刷新', role: 'forceReload' },
207
- { type: 'separator' },
208
- { label: '退出', role: 'quit' }
209
- ]
210
- },
211
- {
212
- label: '编辑',
213
- submenu: [
214
- { label: '撤销', role: 'undo' },
215
- { label: '重做', role: 'redo' },
216
- { type: 'separator' },
217
- { label: '剪切', role: 'cut' },
218
- { label: '复制', role: 'copy' },
219
- { label: '粘贴', role: 'paste' },
220
- { label: '全选', role: 'selectAll' }
221
- ]
222
- },
223
- {
224
- label: '视图',
225
- submenu: [
226
- { label: '放大', role: 'zoomIn' },
227
- { label: '缩小', role: 'zoomOut' },
228
- { label: '重置缩放', role: 'resetZoom' },
229
- { type: 'separator' },
230
- { label: '全屏', role: 'togglefullscreen' },
231
- { label: '开发者工具', role: 'toggleDevTools' }
232
- ]
233
- },
234
- {
235
- label: '窗口',
236
- submenu: [
237
- { label: '最小化', role: 'minimize' },
238
- { label: '关闭', role: 'close' }
239
- ]
240
- },
241
- {
242
- label: '帮助',
243
- submenu: [
244
- { label: '关于飞虹 Code', click: () => {
245
- dialog.showMessageBox(mainWindow, {
246
- type: 'info',
247
- title: '关于飞虹 Code',
248
- message: '飞虹 Code v0.6.1',
249
- detail: '终端 AI 编程智能体\n晋江市飞虹智科技企业管理有限公司'
250
- });
251
- }}
252
- ]
253
- }
254
- ];
255
- const menu = Menu.buildFromTemplate(template);
256
- Menu.setApplicationMenu(menu);
257
- }
258
-
259
- /**
260
- * 设置权限(麦克风、摄像头、屏幕捕获)
261
- */
262
- function setupPermissions() {
263
- // 允许所有权限请求(语音输入、视频通话、截图等)
264
- session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
265
- console.log('[Electron] 权限请求: ' + permission);
266
- // 允许所有权限
267
- callback(true);
268
- });
269
-
270
- // 屏幕捕获:自动选择主屏幕,不需要每次都请求权限
271
- session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
272
- console.log('[Electron] 屏幕捕获请求,自动选择主屏幕');
273
- desktopCapturer.getSources({ types: ['screen'] }).then(sources => {
274
- if (sources.length > 0) {
275
- // 自动选择第一个屏幕(主屏幕)
276
- callback({ video: sources[0] });
277
- } else {
278
- callback({});
279
- }
280
- }).catch(err => {
281
- console.error('[Electron] 获取屏幕源失败:', err.message);
282
- callback({});
283
- });
284
- });
285
- }
286
-
287
- /**
288
- * 设置 IPC
289
- */
290
- function setupIpc() {
291
- ipcMain.on('window:minimize', () => { if (mainWindow) mainWindow.minimize(); });
292
- ipcMain.on('window:maximize', () => { if (mainWindow) mainWindow.maximize(); });
293
- ipcMain.on('window:unmaximize', () => { if (mainWindow) mainWindow.unmaximize(); });
294
- ipcMain.on('window:close', () => { if (mainWindow) mainWindow.close(); });
295
- ipcMain.handle('window:isMaximized', () => mainWindow ? mainWindow.isMaximized() : false);
296
-
297
- ipcMain.on('app:quit', () => { app.isQuitting = true; app.quit(); });
298
- ipcMain.on('app:restart', () => { app.relaunch(); app.exit(0); });
299
- ipcMain.handle('app:getPath', (_, name) => { try { return app.getPath(name); } catch { return null; } });
300
-
301
- ipcMain.handle('shell:openExternal', (_, url) => shell.openExternal(url));
302
- ipcMain.handle('shell:openPath', (_, path) => shell.openPath(path));
303
-
304
- ipcMain.handle('dialog:showOpen', (_, options) => dialog.showOpenDialog(mainWindow, options));
305
- ipcMain.handle('dialog:showSave', (_, options) => dialog.showSaveDialog(mainWindow, options));
306
- ipcMain.handle('dialog:showMessage', (_, options) => dialog.showMessageBox(mainWindow, options));
307
-
308
- ipcMain.handle('clipboard:writeText', (_, text) => { clipboard.writeText(text); return true; });
309
- ipcMain.handle('clipboard:readText', () => clipboard.readText());
310
-
311
- ipcMain.handle('notification:show', (_, { title, body }) => {
312
- if (Notification.isSupported()) { new Notification({ title, body }).show(); return true; }
313
- return false;
314
- });
315
-
316
- // 截图功能:截取主屏幕,返回 dataURL
317
- ipcMain.handle('screenshot:capture', async () => {
318
- try {
319
- const primaryDisplay = screen.getPrimaryDisplay();
320
- const { width, height } = primaryDisplay.size;
321
- const sources = await desktopCapturer.getSources({
322
- types: ['screen'],
323
- thumbnailSize: { width, height }
324
- });
325
- const primarySource = sources.find(s => s.display_id === String(primaryDisplay.id)) || sources[0];
326
- if (!primarySource) throw new Error('未找到屏幕源');
327
- return {
328
- success: true,
329
- dataUrl: primarySource.thumbnail.toDataURL(),
330
- width: width,
331
- height: height
332
- };
333
- } catch (err) {
334
- return { success: false, error: err.message };
335
- }
336
- });
337
- }
338
-
339
- // App 就绪
340
- app.whenReady().then(async () => {
341
- console.log('[Electron] App 已就绪');
342
- console.log('[Electron] 应用路径: ' + APP_PATH);
343
-
344
- try {
345
- setupIpc();
346
- setupPermissions();
347
- createMenu();
348
- await startServer();
349
- createWindow();
350
- createTray();
351
-
352
- app.on('activate', () => {
353
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
354
- });
355
- } catch (err) {
356
- console.error('[Electron] 启动失败: ' + err.message);
357
-
358
- // 显示错误窗口
359
- const errorWin = new BrowserWindow({
360
- width: 700,
361
- height: 500,
362
- title: '启动失败',
363
- webPreferences: { nodeIntegration: true, contextIsolation: false }
364
- });
365
-
366
- const errorHtml = `
367
- <html><body style="font-family:Microsoft YaHei,sans-serif;padding:30px;background:#1a1a2e;color:#fff;margin:0;">
368
- <h2 style="color:#ff6b6b;">飞虹 Code 启动失败</h2>
369
- <p style="color:#aaa;">错误详情:</p>
370
- <pre style="background:#0d1117;padding:15px;border-radius:8px;overflow:auto;white-space:pre-wrap;font-size:12px;max-height:300px;">${String(err.message).replace(/</g, '&lt;')}</pre>
371
- <p style="color:#888;margin-top:20px;">请检查:1) 是否已运行 npm run build 2) 端口 ${PORT} 是否被占用 3) Node.js 版本是否 >= 18</p>
372
- <p style="color:#666;margin-top:20px;font-size:12px;">关闭此窗口后退出</p>
373
- </body></html>
374
- `;
375
- errorWin.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(errorHtml));
376
- errorWin.on('closed', () => app.quit());
377
- }
378
- });
379
-
380
- // 所有窗口关闭时不退出(保持托盘运行)
381
- app.on('window-all-closed', () => {});
382
-
383
- // 退出前停止服务器
384
- app.on('before-quit', () => {
385
- app.isQuitting = true;
386
- if (serverProcess) {
387
- serverProcess.kill();
388
- serverProcess = null;
389
- }
390
- });
391
-
392
- // 防止多开
393
- const gotLock = app.requestSingleInstanceLock();
394
- if (!gotLock) {
395
- app.quit();
396
- } else {
397
- app.on('second-instance', () => {
398
- if (mainWindow) {
399
- if (mainWindow.isMinimized()) mainWindow.restore();
400
- mainWindow.show();
401
- mainWindow.focus();
402
- }
403
- });
404
- }
1
+ /**
2
+ * 飞虹 Code Electron 桌面版主进程(简化稳定版)
3
+ * 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
4
+ */
5
+
6
+ const { app, BrowserWindow, shell, Tray, Menu, ipcMain, dialog, clipboard, Notification, session, desktopCapturer, screen } = require('electron');
7
+ const { spawn } = require('child_process');
8
+ const { existsSync } = require('fs');
9
+ const { join } = require('path');
10
+ const http = require('http');
11
+
12
+ // 禁用安全警告
13
+ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
14
+
15
+ // 配置
16
+ const PORT = parseInt(process.env.FH_WEB_PORT || '8081');
17
+ const isDev = !app.isPackaged;
18
+
19
+ // 全局变量
20
+ let mainWindow = null;
21
+ let serverProcess = null;
22
+ let tray = null;
23
+
24
+ // 获取应用根目录
25
+ function getAppPath() {
26
+ if (isDev) return join(__dirname, '..');
27
+ return process.resourcesPath ? join(process.resourcesPath, 'app') : app.getAppPath();
28
+ }
29
+ const APP_PATH = getAppPath();
30
+
31
+ /**
32
+ * 等待服务器就绪
33
+ */
34
+ function waitForServer(port, timeoutMs) {
35
+ return new Promise((resolve, reject) => {
36
+ const startTime = Date.now();
37
+ const check = () => {
38
+ const req = http.get(`http://127.0.0.1:${port}/api/health`, (res) => {
39
+ if (res.statusCode === 200) {
40
+ resolve();
41
+ } else {
42
+ res.resume();
43
+ retry();
44
+ }
45
+ });
46
+ req.on('error', () => retry());
47
+ req.setTimeout(2000, () => { req.destroy(); retry(); });
48
+ };
49
+ const retry = () => {
50
+ if (Date.now() - startTime > timeoutMs) {
51
+ reject(new Error('服务器启动超时'));
52
+ } else {
53
+ setTimeout(check, 500);
54
+ }
55
+ };
56
+ check();
57
+ });
58
+ }
59
+
60
+ /**
61
+ * 启动内置 Web 服务器
62
+ */
63
+ function startServer() {
64
+ return new Promise((resolve, reject) => {
65
+ const serverEntry = join(APP_PATH, 'dist', 'cli', 'index.js');
66
+ if (!existsSync(serverEntry)) {
67
+ reject(new Error('未找到服务器入口文件,请先运行 npm run build'));
68
+ return;
69
+ }
70
+
71
+ console.log('[Electron] 启动服务器,端口: ' + PORT);
72
+
73
+ let serverLog = '';
74
+ let started = false;
75
+
76
+ serverProcess = spawn('node', [serverEntry, 'serve'], {
77
+ cwd: APP_PATH,
78
+ env: { ...process.env, FH_WEB_PORT: String(PORT) },
79
+ stdio: ['ignore', 'pipe', 'pipe']
80
+ });
81
+
82
+ serverProcess.stdout.on('data', (data) => {
83
+ const text = data.toString();
84
+ serverLog += text;
85
+ console.log('[Server] ' + text.trim());
86
+ });
87
+
88
+ serverProcess.stderr.on('data', (data) => {
89
+ const text = data.toString();
90
+ serverLog += text;
91
+ console.error('[Server Error] ' + text.trim());
92
+ });
93
+
94
+ serverProcess.on('error', (err) => {
95
+ if (!started) reject(new Error('启动服务器失败: ' + err.message));
96
+ });
97
+
98
+ serverProcess.on('exit', (code) => {
99
+ console.log('[Electron] 服务器退出,代码: ' + code);
100
+ serverProcess = null;
101
+ if (!started) {
102
+ reject(new Error('服务器启动失败,退出代码: ' + code + '\n\n日志:\n' + serverLog.slice(-2000)));
103
+ }
104
+ });
105
+
106
+ // 等待服务器就绪
107
+ waitForServer(PORT, 60000).then(() => {
108
+ started = true;
109
+ console.log('[Electron] 服务器已就绪');
110
+ resolve();
111
+ }).catch((err) => {
112
+ if (!started) reject(new Error(err.message + '\n\n服务器日志:\n' + serverLog.slice(-2000)));
113
+ });
114
+ });
115
+ }
116
+
117
+ /**
118
+ * 创建主窗口
119
+ */
120
+ function createWindow() {
121
+ mainWindow = new BrowserWindow({
122
+ width: 1400,
123
+ height: 900,
124
+ minWidth: 1024,
125
+ minHeight: 680,
126
+ title: '飞虹 Code',
127
+ webPreferences: {
128
+ preload: join(__dirname, 'preload.js'),
129
+ contextIsolation: true,
130
+ nodeIntegration: false
131
+ },
132
+ show: false,
133
+ backgroundColor: '#1a1a2e'
134
+ });
135
+
136
+ const url = `http://127.0.0.1:${PORT}/`;
137
+ console.log('[Electron] 加载页面: ' + url);
138
+ mainWindow.loadURL(url);
139
+
140
+ mainWindow.once('ready-to-show', () => {
141
+ mainWindow.show();
142
+ });
143
+
144
+ // 外部链接用系统浏览器打开
145
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
146
+ shell.openExternal(url);
147
+ return { action: 'deny' };
148
+ });
149
+
150
+ // 关闭时最小化到托盘
151
+ mainWindow.on('close', (e) => {
152
+ if (!app.isQuitting) {
153
+ e.preventDefault();
154
+ mainWindow.hide();
155
+ }
156
+ });
157
+
158
+ mainWindow.on('closed', () => {
159
+ mainWindow = null;
160
+ });
161
+ }
162
+
163
+ /**
164
+ * 创建系统托盘
165
+ */
166
+ function createTray() {
167
+ try {
168
+ const iconPath = join(__dirname, 'icon.png');
169
+ let trayIcon;
170
+ if (existsSync(iconPath)) {
171
+ trayIcon = require('electron').nativeImage.createFromPath(iconPath);
172
+ } else {
173
+ trayIcon = require('electron').nativeImage.createEmpty();
174
+ }
175
+
176
+ tray = new Tray(trayIcon);
177
+ tray.setToolTip('飞虹 Code');
178
+
179
+ const contextMenu = Menu.buildFromTemplate([
180
+ { label: '显示主窗口', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } } },
181
+ { type: 'separator' },
182
+ { label: '退出', click: () => { app.isQuitting = true; app.quit(); } }
183
+ ]);
184
+ tray.setContextMenu(contextMenu);
185
+
186
+ tray.on('click', () => {
187
+ if (mainWindow) {
188
+ if (mainWindow.isVisible()) mainWindow.hide();
189
+ else { mainWindow.show(); mainWindow.focus(); }
190
+ }
191
+ });
192
+ } catch (e) {
193
+ console.warn('[Electron] 创建托盘失败: ' + e.message);
194
+ }
195
+ }
196
+
197
+ /**
198
+ * 创建应用菜单(关键:没有菜单会导致复制粘贴等快捷键失效)
199
+ */
200
+ function createMenu() {
201
+ const template = [
202
+ {
203
+ label: '文件',
204
+ submenu: [
205
+ { label: '刷新', role: 'reload' },
206
+ { label: '强制刷新', role: 'forceReload' },
207
+ { type: 'separator' },
208
+ { label: '退出', role: 'quit' }
209
+ ]
210
+ },
211
+ {
212
+ label: '编辑',
213
+ submenu: [
214
+ { label: '撤销', role: 'undo' },
215
+ { label: '重做', role: 'redo' },
216
+ { type: 'separator' },
217
+ { label: '剪切', role: 'cut' },
218
+ { label: '复制', role: 'copy' },
219
+ { label: '粘贴', role: 'paste' },
220
+ { label: '全选', role: 'selectAll' }
221
+ ]
222
+ },
223
+ {
224
+ label: '视图',
225
+ submenu: [
226
+ { label: '放大', role: 'zoomIn' },
227
+ { label: '缩小', role: 'zoomOut' },
228
+ { label: '重置缩放', role: 'resetZoom' },
229
+ { type: 'separator' },
230
+ { label: '全屏', role: 'togglefullscreen' },
231
+ { label: '开发者工具', role: 'toggleDevTools' }
232
+ ]
233
+ },
234
+ {
235
+ label: '窗口',
236
+ submenu: [
237
+ { label: '最小化', role: 'minimize' },
238
+ { label: '关闭', role: 'close' }
239
+ ]
240
+ },
241
+ {
242
+ label: '帮助',
243
+ submenu: [
244
+ { label: '关于飞虹 Code', click: () => {
245
+ dialog.showMessageBox(mainWindow, {
246
+ type: 'info',
247
+ title: '关于飞虹 Code',
248
+ message: '飞虹 Code v0.6.1',
249
+ detail: '终端 AI 编程智能体\n晋江市飞虹智科技企业管理有限公司'
250
+ });
251
+ }}
252
+ ]
253
+ }
254
+ ];
255
+ const menu = Menu.buildFromTemplate(template);
256
+ Menu.setApplicationMenu(menu);
257
+ }
258
+
259
+ /**
260
+ * 设置权限(麦克风、摄像头、屏幕捕获)
261
+ */
262
+ function setupPermissions() {
263
+ // 允许所有权限请求(语音输入、视频通话、截图等)
264
+ session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
265
+ console.log('[Electron] 权限请求: ' + permission);
266
+ // 允许所有权限
267
+ callback(true);
268
+ });
269
+
270
+ // 屏幕捕获:自动选择主屏幕,不需要每次都请求权限
271
+ session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
272
+ console.log('[Electron] 屏幕捕获请求,自动选择主屏幕');
273
+ desktopCapturer.getSources({ types: ['screen'] }).then(sources => {
274
+ if (sources.length > 0) {
275
+ // 自动选择第一个屏幕(主屏幕)
276
+ callback({ video: sources[0] });
277
+ } else {
278
+ callback({});
279
+ }
280
+ }).catch(err => {
281
+ console.error('[Electron] 获取屏幕源失败:', err.message);
282
+ callback({});
283
+ });
284
+ });
285
+ }
286
+
287
+ /**
288
+ * 设置 IPC
289
+ */
290
+ function setupIpc() {
291
+ ipcMain.on('window:minimize', () => { if (mainWindow) mainWindow.minimize(); });
292
+ ipcMain.on('window:maximize', () => { if (mainWindow) mainWindow.maximize(); });
293
+ ipcMain.on('window:unmaximize', () => { if (mainWindow) mainWindow.unmaximize(); });
294
+ ipcMain.on('window:close', () => { if (mainWindow) mainWindow.close(); });
295
+ ipcMain.handle('window:isMaximized', () => mainWindow ? mainWindow.isMaximized() : false);
296
+
297
+ ipcMain.on('app:quit', () => { app.isQuitting = true; app.quit(); });
298
+ ipcMain.on('app:restart', () => { app.relaunch(); app.exit(0); });
299
+ ipcMain.handle('app:getPath', (_, name) => { try { return app.getPath(name); } catch { return null; } });
300
+
301
+ ipcMain.handle('shell:openExternal', (_, url) => shell.openExternal(url));
302
+ ipcMain.handle('shell:openPath', (_, path) => shell.openPath(path));
303
+
304
+ ipcMain.handle('dialog:showOpen', (_, options) => dialog.showOpenDialog(mainWindow, options));
305
+ ipcMain.handle('dialog:showSave', (_, options) => dialog.showSaveDialog(mainWindow, options));
306
+ ipcMain.handle('dialog:showMessage', (_, options) => dialog.showMessageBox(mainWindow, options));
307
+
308
+ ipcMain.handle('clipboard:writeText', (_, text) => { clipboard.writeText(text); return true; });
309
+ ipcMain.handle('clipboard:readText', () => clipboard.readText());
310
+
311
+ ipcMain.handle('notification:show', (_, { title, body }) => {
312
+ if (Notification.isSupported()) { new Notification({ title, body }).show(); return true; }
313
+ return false;
314
+ });
315
+
316
+ // 截图功能:截取主屏幕,返回 dataURL
317
+ ipcMain.handle('screenshot:capture', async () => {
318
+ try {
319
+ const primaryDisplay = screen.getPrimaryDisplay();
320
+ const { width, height } = primaryDisplay.size;
321
+ const sources = await desktopCapturer.getSources({
322
+ types: ['screen'],
323
+ thumbnailSize: { width, height }
324
+ });
325
+ const primarySource = sources.find(s => s.display_id === String(primaryDisplay.id)) || sources[0];
326
+ if (!primarySource) throw new Error('未找到屏幕源');
327
+ return {
328
+ success: true,
329
+ dataUrl: primarySource.thumbnail.toDataURL(),
330
+ width: width,
331
+ height: height
332
+ };
333
+ } catch (err) {
334
+ return { success: false, error: err.message };
335
+ }
336
+ });
337
+ }
338
+
339
+ // App 就绪
340
+ app.whenReady().then(async () => {
341
+ console.log('[Electron] App 已就绪');
342
+ console.log('[Electron] 应用路径: ' + APP_PATH);
343
+
344
+ try {
345
+ setupIpc();
346
+ setupPermissions();
347
+ createMenu();
348
+ await startServer();
349
+ createWindow();
350
+ createTray();
351
+
352
+ app.on('activate', () => {
353
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
354
+ });
355
+ } catch (err) {
356
+ console.error('[Electron] 启动失败: ' + err.message);
357
+
358
+ // 显示错误窗口
359
+ const errorWin = new BrowserWindow({
360
+ width: 700,
361
+ height: 500,
362
+ title: '启动失败',
363
+ webPreferences: { nodeIntegration: true, contextIsolation: false }
364
+ });
365
+
366
+ const errorHtml = `
367
+ <html><body style="font-family:Microsoft YaHei,sans-serif;padding:30px;background:#1a1a2e;color:#fff;margin:0;">
368
+ <h2 style="color:#ff6b6b;">飞虹 Code 启动失败</h2>
369
+ <p style="color:#aaa;">错误详情:</p>
370
+ <pre style="background:#0d1117;padding:15px;border-radius:8px;overflow:auto;white-space:pre-wrap;font-size:12px;max-height:300px;">${String(err.message).replace(/</g, '&lt;')}</pre>
371
+ <p style="color:#888;margin-top:20px;">请检查:1) 是否已运行 npm run build 2) 端口 ${PORT} 是否被占用 3) Node.js 版本是否 >= 18</p>
372
+ <p style="color:#666;margin-top:20px;font-size:12px;">关闭此窗口后退出</p>
373
+ </body></html>
374
+ `;
375
+ errorWin.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(errorHtml));
376
+ errorWin.on('closed', () => app.quit());
377
+ }
378
+ });
379
+
380
+ // 所有窗口关闭时不退出(保持托盘运行)
381
+ app.on('window-all-closed', () => {});
382
+
383
+ // 退出前停止服务器
384
+ app.on('before-quit', () => {
385
+ app.isQuitting = true;
386
+ if (serverProcess) {
387
+ serverProcess.kill();
388
+ serverProcess = null;
389
+ }
390
+ });
391
+
392
+ // 防止多开
393
+ const gotLock = app.requestSingleInstanceLock();
394
+ if (!gotLock) {
395
+ app.quit();
396
+ } else {
397
+ app.on('second-instance', () => {
398
+ if (mainWindow) {
399
+ if (mainWindow.isMinimized()) mainWindow.restore();
400
+ mainWindow.show();
401
+ mainWindow.focus();
402
+ }
403
+ });
404
+ }