feihong-code 7.2.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 (249) hide show
  1. package/.github/workflows/ci.yml +180 -140
  2. package/.github/workflows/release.yml +200 -173
  3. package/CHANGELOG.md +94 -115
  4. package/README.md +609 -586
  5. package/dist/agent/completion-engine.js +76 -45
  6. package/dist/agent/completion-engine.js.map +1 -1
  7. package/dist/agent/completion-postprocess.js +109 -0
  8. package/dist/agent/completion-postprocess.js.map +1 -0
  9. package/dist/agent/context-budget.js +121 -0
  10. package/dist/agent/context-budget.js.map +1 -0
  11. package/dist/agent/context-compactor.js +47 -0
  12. package/dist/agent/context-compactor.js.map +1 -1
  13. package/dist/agent/custom-agent.js +52 -52
  14. package/dist/agent/design-to-code.js +40 -40
  15. package/dist/agent/lint.js +121 -0
  16. package/dist/agent/lint.js.map +1 -0
  17. package/dist/agent/multi-agent.js +44 -44
  18. package/dist/agent/orchestrator.js +20 -20
  19. package/dist/agent/prompts.js +54 -54
  20. package/dist/agent/self-correction.js +24 -24
  21. package/dist/agent/self-heal.js +27 -27
  22. package/dist/cli/commands.js +9 -0
  23. package/dist/cli/commands.js.map +1 -1
  24. package/dist/cli/index.js +9 -0
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/multi-pane-tui.js +217 -0
  27. package/dist/cli/multi-pane-tui.js.map +1 -0
  28. package/dist/cli/run.js +129 -40
  29. package/dist/cli/run.js.map +1 -1
  30. package/dist/cli/tui-run.js +79 -0
  31. package/dist/cli/tui-run.js.map +1 -0
  32. package/dist/cli/tui.js.map +1 -1
  33. package/dist/cli/version.js +1 -1
  34. package/dist/lsp/lsp-client.js +183 -0
  35. package/dist/lsp/lsp-client.js.map +1 -0
  36. package/dist/lsp/lsp-service.js +159 -0
  37. package/dist/lsp/lsp-service.js.map +1 -0
  38. package/dist/plugins/manager.js +50 -2
  39. package/dist/plugins/manager.js.map +1 -1
  40. package/dist/shared/i18n.js +108 -100
  41. package/dist/shared/i18n.js.map +1 -1
  42. package/dist/tools/generator/code-generator.js +40 -40
  43. package/dist/tools/generator/test-generator.js +9 -9
  44. package/dist/tools/shell/exec.js +23 -1
  45. package/dist/tools/shell/exec.js.map +1 -1
  46. package/dist/web/extra-apis.js +440 -0
  47. package/dist/web/extra-apis.js.map +1 -0
  48. package/dist/web/public/css/style.css +1715 -1715
  49. package/dist/web/public/index.html +1242 -1200
  50. package/dist/web/public/js/api.js +555 -496
  51. package/dist/web/public/js/app.js +2210 -2210
  52. package/dist/web/public/js/mock-api.js +234 -234
  53. package/dist/web/public/js/monaco-editor.js +363 -203
  54. package/dist/web/public/js/ui.js +2251 -1981
  55. package/dist/web/public/js/utils.js +214 -214
  56. package/dist/web/public/manifest.json +59 -59
  57. package/dist/web/public/service-worker.js +173 -173
  58. package/dist/web/server.js +97 -45
  59. package/dist/web/server.js.map +1 -1
  60. package/docs/BENCHMARK_REPORT_en.md +386 -329
  61. package/docs/BENCHMARK_REPORT_zh.md +386 -329
  62. package/docs/DPA.md +63 -0
  63. 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
  64. package/docs/SECURITY_WHITEPAPER.md +74 -0
  65. package/docs/SWE_BENCH_REPORT.md +55 -0
  66. package/docs/TECHNICAL_SPEC_en.md +292 -292
  67. package/docs/TECHNICAL_SPEC_zh.md +292 -292
  68. package/docs/UPDATE_NOTES_en.md +159 -159
  69. package/docs/UPDATE_NOTES_zh.md +159 -159
  70. package/docs/UPGRADE_7.2.0.md +309 -0
  71. package/docs/UPGRADE_GUIDE_7_5.md +86 -0
  72. package/docs/geo-feihong-code.html +391 -0
  73. 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
  74. 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
  75. 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
  76. 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
  77. package/docs//345/256/211/345/205/250/347/231/275/347/232/256/344/271/246_v7.2.0.md +82 -0
  78. package/electron/main.js +404 -602
  79. package/package.json +175 -172
  80. package/templates/skills/api-design/SKILL.md +27 -0
  81. package/templates/skills/code-review/SKILL.md +35 -0
  82. package/templates/skills/dependency-upgrade/SKILL.md +29 -0
  83. package/templates/skills/doc-gen/SKILL.md +29 -0
  84. package/templates/skills/git-flow/SKILL.md +26 -0
  85. package/templates/skills/onboarding/SKILL.md +27 -0
  86. package/templates/skills/performance/SKILL.md +29 -0
  87. package/templates/skills/refactor/SKILL.md +28 -0
  88. package/templates/skills/security-audit/SKILL.md +28 -0
  89. package/templates/skills/test-gen/SKILL.md +30 -0
  90. package/tool-schema.json +197 -197
  91. package/dist/hello.js +0 -14
  92. package/dist/hello.js.map +0 -1
  93. package/dist/self-evolve/hook.ts +0 -80
  94. package/dist/self-evolve/manager.d.ts +0 -8
  95. package/dist/web/public/vendor/monaco/vs/abap-D-t0cyap.js +0 -1
  96. package/dist/web/public/vendor/monaco/vs/apex-CcIm7xu6.js +0 -1
  97. package/dist/web/public/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +0 -97
  98. package/dist/web/public/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +0 -30
  99. package/dist/web/public/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +0 -30
  100. package/dist/web/public/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +0 -474
  101. package/dist/web/public/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +0 -62
  102. package/dist/web/public/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +0 -67722
  103. package/dist/web/public/vendor/monaco/vs/azcli-BA0tQDCg.js +0 -1
  104. package/dist/web/public/vendor/monaco/vs/basic-languages/monaco.contribution.js +0 -1
  105. package/dist/web/public/vendor/monaco/vs/bat-C397hTD6.js +0 -1
  106. package/dist/web/public/vendor/monaco/vs/bicep-DF5aW17k.js +0 -2
  107. package/dist/web/public/vendor/monaco/vs/cameligo-plsz8qhj.js +0 -1
  108. package/dist/web/public/vendor/monaco/vs/clojure-Y2auQMzK.js +0 -1
  109. package/dist/web/public/vendor/monaco/vs/coffee-Bu45yuWE.js +0 -1
  110. package/dist/web/public/vendor/monaco/vs/cpp-CkKPQIni.js +0 -1
  111. package/dist/web/public/vendor/monaco/vs/csharp-CX28MZyh.js +0 -1
  112. package/dist/web/public/vendor/monaco/vs/csp-D8uWnyxW.js +0 -1
  113. package/dist/web/public/vendor/monaco/vs/css-CaeNmE3S.js +0 -3
  114. package/dist/web/public/vendor/monaco/vs/css.worker-CyhWkhHo.js +0 -1
  115. package/dist/web/public/vendor/monaco/vs/cssMode-CV6Ay48H.js +0 -1
  116. package/dist/web/public/vendor/monaco/vs/cypher-DVThT8BS.js +0 -1
  117. package/dist/web/public/vendor/monaco/vs/dart-CmGfCvrO.js +0 -1
  118. package/dist/web/public/vendor/monaco/vs/dockerfile-CZqqYdch.js +0 -1
  119. package/dist/web/public/vendor/monaco/vs/ecl-30fUercY.js +0 -1
  120. package/dist/web/public/vendor/monaco/vs/editor/editor.main.css +0 -1
  121. package/dist/web/public/vendor/monaco/vs/editor/editor.main.js +0 -1
  122. package/dist/web/public/vendor/monaco/vs/editor/editor.worker.js +0 -1
  123. package/dist/web/public/vendor/monaco/vs/editor-KLE6jdfb.js +0 -831
  124. package/dist/web/public/vendor/monaco/vs/editor.js +0 -1
  125. package/dist/web/public/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +0 -35
  126. package/dist/web/public/vendor/monaco/vs/elixir-xjPaIfzF.js +0 -1
  127. package/dist/web/public/vendor/monaco/vs/flow9-DqtmStfK.js +0 -1
  128. package/dist/web/public/vendor/monaco/vs/freemarker2-FWreY7v7.js +0 -3
  129. package/dist/web/public/vendor/monaco/vs/fsharp-BOMdg4U1.js +0 -1
  130. package/dist/web/public/vendor/monaco/vs/go-D_hbi-Jt.js +0 -1
  131. package/dist/web/public/vendor/monaco/vs/graphql-CKUU4kLG.js +0 -1
  132. package/dist/web/public/vendor/monaco/vs/handlebars-CZi4pHCH.js +0 -1
  133. package/dist/web/public/vendor/monaco/vs/hcl-DTaboeZW.js +0 -1
  134. package/dist/web/public/vendor/monaco/vs/html-j_7ZTNRU.js +0 -1
  135. package/dist/web/public/vendor/monaco/vs/html.worker-CA3iAimZ.js +0 -1
  136. package/dist/web/public/vendor/monaco/vs/htmlMode-27by_KM4.js +0 -1
  137. package/dist/web/public/vendor/monaco/vs/index-049xrXrp.js +0 -1
  138. package/dist/web/public/vendor/monaco/vs/index-CBVt3dzv.js +0 -5
  139. package/dist/web/public/vendor/monaco/vs/index.js +0 -1
  140. package/dist/web/public/vendor/monaco/vs/ini-CsNwO04R.js +0 -1
  141. package/dist/web/public/vendor/monaco/vs/initialize-DL0l1TGY.js +0 -1
  142. package/dist/web/public/vendor/monaco/vs/java-CI4ZMsH9.js +0 -1
  143. package/dist/web/public/vendor/monaco/vs/javascript-DTJl4Jn1.js +0 -1
  144. package/dist/web/public/vendor/monaco/vs/json.worker-BizpAl9O.js +0 -1
  145. package/dist/web/public/vendor/monaco/vs/jsonMode-DLUiMUrI.js +0 -1
  146. package/dist/web/public/vendor/monaco/vs/julia-BwzEvaQw.js +0 -1
  147. package/dist/web/public/vendor/monaco/vs/kotlin-IUYPiTV8.js +0 -1
  148. package/dist/web/public/vendor/monaco/vs/language/css/css.worker.js +0 -66
  149. package/dist/web/public/vendor/monaco/vs/language/css/monaco.contribution.js +0 -1
  150. package/dist/web/public/vendor/monaco/vs/language/html/html.worker.js +0 -443
  151. package/dist/web/public/vendor/monaco/vs/language/html/monaco.contribution.js +0 -1
  152. package/dist/web/public/vendor/monaco/vs/language/json/json.worker.js +0 -17
  153. package/dist/web/public/vendor/monaco/vs/language/json/monaco.contribution.js +0 -1
  154. package/dist/web/public/vendor/monaco/vs/language/typescript/monaco.contribution.js +0 -1
  155. package/dist/web/public/vendor/monaco/vs/language/typescript/ts.worker.js +0 -67693
  156. package/dist/web/public/vendor/monaco/vs/less-C0eDYdqa.js +0 -2
  157. package/dist/web/public/vendor/monaco/vs/lexon-iON-Kj97.js +0 -1
  158. package/dist/web/public/vendor/monaco/vs/liquid-CLJYelW6.js +0 -1
  159. package/dist/web/public/vendor/monaco/vs/loader.js +0 -1368
  160. package/dist/web/public/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +0 -2
  161. package/dist/web/public/vendor/monaco/vs/lua-DtygF91M.js +0 -1
  162. package/dist/web/public/vendor/monaco/vs/m3-CsR4AuFi.js +0 -1
  163. package/dist/web/public/vendor/monaco/vs/main-BEx-Fmlo.js +0 -15
  164. package/dist/web/public/vendor/monaco/vs/main-DsK8pnKg.js +0 -3
  165. package/dist/web/public/vendor/monaco/vs/markdown-C_rD0bIw.js +0 -1
  166. package/dist/web/public/vendor/monaco/vs/mdx-CpzZIPHF.js +0 -1
  167. package/dist/web/public/vendor/monaco/vs/mips-CiYP61RB.js +0 -1
  168. package/dist/web/public/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +0 -1
  169. package/dist/web/public/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +0 -1
  170. package/dist/web/public/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +0 -1
  171. package/dist/web/public/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +0 -1
  172. package/dist/web/public/vendor/monaco/vs/msdax-C38-sJlp.js +0 -1
  173. package/dist/web/public/vendor/monaco/vs/mysql-CdtbpvbG.js +0 -1
  174. package/dist/web/public/vendor/monaco/vs/nls/lang/cs.d.ts +0 -1
  175. package/dist/web/public/vendor/monaco/vs/nls/lang/cs.js +0 -9
  176. package/dist/web/public/vendor/monaco/vs/nls/lang/de.d.ts +0 -1
  177. package/dist/web/public/vendor/monaco/vs/nls/lang/de.js +0 -9
  178. package/dist/web/public/vendor/monaco/vs/nls/lang/es.d.ts +0 -1
  179. package/dist/web/public/vendor/monaco/vs/nls/lang/es.js +0 -9
  180. package/dist/web/public/vendor/monaco/vs/nls/lang/fr.d.ts +0 -1
  181. package/dist/web/public/vendor/monaco/vs/nls/lang/fr.js +0 -9
  182. package/dist/web/public/vendor/monaco/vs/nls/lang/it.d.ts +0 -1
  183. package/dist/web/public/vendor/monaco/vs/nls/lang/it.js +0 -9
  184. package/dist/web/public/vendor/monaco/vs/nls/lang/ja.d.ts +0 -1
  185. package/dist/web/public/vendor/monaco/vs/nls/lang/ja.js +0 -9
  186. package/dist/web/public/vendor/monaco/vs/nls/lang/ko.d.ts +0 -1
  187. package/dist/web/public/vendor/monaco/vs/nls/lang/ko.js +0 -9
  188. package/dist/web/public/vendor/monaco/vs/nls/lang/pl.d.ts +0 -1
  189. package/dist/web/public/vendor/monaco/vs/nls/lang/pl.js +0 -9
  190. package/dist/web/public/vendor/monaco/vs/nls/lang/pt-br.d.ts +0 -1
  191. package/dist/web/public/vendor/monaco/vs/nls/lang/pt-br.js +0 -9
  192. package/dist/web/public/vendor/monaco/vs/nls/lang/ru.d.ts +0 -1
  193. package/dist/web/public/vendor/monaco/vs/nls/lang/ru.js +0 -9
  194. package/dist/web/public/vendor/monaco/vs/nls/lang/tr.d.ts +0 -1
  195. package/dist/web/public/vendor/monaco/vs/nls/lang/tr.js +0 -9
  196. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-cn.d.ts +0 -1
  197. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-cn.js +0 -9
  198. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-tw.d.ts +0 -1
  199. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-tw.js +0 -9
  200. package/dist/web/public/vendor/monaco/vs/nls.messages-loader.js +0 -1
  201. package/dist/web/public/vendor/monaco/vs/objective-c-CntZFaHX.js +0 -1
  202. package/dist/web/public/vendor/monaco/vs/pascal-r6kuqfl_.js +0 -1
  203. package/dist/web/public/vendor/monaco/vs/pascaligo-BiXoTmXh.js +0 -1
  204. package/dist/web/public/vendor/monaco/vs/perl-DABw_TcH.js +0 -1
  205. package/dist/web/public/vendor/monaco/vs/pgsql-me_jFXeX.js +0 -1
  206. package/dist/web/public/vendor/monaco/vs/php-D_kh-9LK.js +0 -1
  207. package/dist/web/public/vendor/monaco/vs/pla-VfZjczW0.js +0 -1
  208. package/dist/web/public/vendor/monaco/vs/postiats-BBSzz8Pk.js +0 -1
  209. package/dist/web/public/vendor/monaco/vs/powerquery-Dt-g_2cc.js +0 -1
  210. package/dist/web/public/vendor/monaco/vs/powershell-B-7ap1zc.js +0 -1
  211. package/dist/web/public/vendor/monaco/vs/protobuf-BmtuEB1A.js +0 -2
  212. package/dist/web/public/vendor/monaco/vs/pug-BRpRNeEb.js +0 -1
  213. package/dist/web/public/vendor/monaco/vs/python-CqWUUgfu.js +0 -1
  214. package/dist/web/public/vendor/monaco/vs/qsharp-BzsFaUU9.js +0 -1
  215. package/dist/web/public/vendor/monaco/vs/r-f8dDdrp4.js +0 -1
  216. package/dist/web/public/vendor/monaco/vs/razor-hok5y9Km.js +0 -1
  217. package/dist/web/public/vendor/monaco/vs/redis-fvZQY4PI.js +0 -1
  218. package/dist/web/public/vendor/monaco/vs/redshift-45Et0LQi.js +0 -1
  219. package/dist/web/public/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +0 -1
  220. package/dist/web/public/vendor/monaco/vs/ruby-CZO8zYTz.js +0 -1
  221. package/dist/web/public/vendor/monaco/vs/rust-Bfetafyc.js +0 -1
  222. package/dist/web/public/vendor/monaco/vs/sb-3GYllVck.js +0 -1
  223. package/dist/web/public/vendor/monaco/vs/scala-foMgrKo1.js +0 -1
  224. package/dist/web/public/vendor/monaco/vs/scheme-CHdMtr7p.js +0 -1
  225. package/dist/web/public/vendor/monaco/vs/scss-C1cmLt9V.js +0 -3
  226. package/dist/web/public/vendor/monaco/vs/shell-ClXCKCEW.js +0 -1
  227. package/dist/web/public/vendor/monaco/vs/solidity-MZ6ExpPy.js +0 -1
  228. package/dist/web/public/vendor/monaco/vs/sophia-DWkuSsPQ.js +0 -1
  229. package/dist/web/public/vendor/monaco/vs/sparql-AUGFYSyk.js +0 -1
  230. package/dist/web/public/vendor/monaco/vs/sql-32GpJSV2.js +0 -1
  231. package/dist/web/public/vendor/monaco/vs/st-CuDFIVZ_.js +0 -1
  232. package/dist/web/public/vendor/monaco/vs/swift-t-PfMj7W.js +0 -1
  233. package/dist/web/public/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +0 -1
  234. package/dist/web/public/vendor/monaco/vs/tcl-D74tq1nH.js +0 -1
  235. package/dist/web/public/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +0 -61
  236. package/dist/web/public/vendor/monaco/vs/ts.worker-2QLmBukE.js +0 -1
  237. package/dist/web/public/vendor/monaco/vs/tsMode-B0S_kp2q.js +0 -11
  238. package/dist/web/public/vendor/monaco/vs/twig-C6taOxMV.js +0 -1
  239. package/dist/web/public/vendor/monaco/vs/typescript-_2t_511t.js +0 -1
  240. package/dist/web/public/vendor/monaco/vs/typespec-8OfoLt6R.js +0 -1
  241. package/dist/web/public/vendor/monaco/vs/vb-Dyb2648j.js +0 -1
  242. package/dist/web/public/vendor/monaco/vs/wgsl-BhLXMOR0.js +0 -298
  243. package/dist/web/public/vendor/monaco/vs/workers-BBttULjf.js +0 -1
  244. package/dist/web/public/vendor/monaco/vs/xml-cZjNDqDT.js +0 -1
  245. package/dist/web/public/vendor/monaco/vs/yaml-A1fOIdH6.js +0 -1
  246. package/dist/web/web-config.js +0 -143
  247. package/dist/web/web-config.js.map +0 -1
  248. package/docs/DEPLOYMENT.md +0 -382
  249. package/docs/MODEL_FINETUNING.md +0 -405
package/electron/main.js CHANGED
@@ -1,602 +1,404 @@
1
- /**
2
- * 飞虹 Code Electron 桌面版主进程(简化稳定版)
3
- * 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
4
- */
5
-
6
- const { app, BrowserWindow, shell, Tray, Menu, ipcMain, dialog, clipboard, Notification, session, desktopCapturer, screen, globalShortcut } = require('electron');
7
- const { spawn } = require('child_process');
8
- const { existsSync, writeFileSync, readFileSync } = require('fs');
9
- const { join, resolve } = require('path');
10
- const http = require('http');
11
- const os = require('os');
12
-
13
- // 禁用安全警告
14
- process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
15
-
16
- // 配置
17
- const PORT = parseInt(process.env.FH_WEB_PORT || '8081');
18
- const isDev = !app.isPackaged;
19
-
20
- // 全局变量
21
- let mainWindow = null;
22
- let serverProcess = null;
23
- let tray = null;
24
-
25
- // 获取应用根目录
26
- function getAppPath() {
27
- if (isDev) return join(__dirname, '..');
28
- return process.resourcesPath ? join(process.resourcesPath, 'app') : app.getAppPath();
29
- }
30
- const APP_PATH = getAppPath();
31
-
32
- /**
33
- * 等待服务器就绪
34
- */
35
- function waitForServer(port, timeoutMs) {
36
- return new Promise((resolve, reject) => {
37
- const startTime = Date.now();
38
- const check = () => {
39
- const req = http.get(`http://127.0.0.1:${port}/api/health`, (res) => {
40
- if (res.statusCode === 200) {
41
- resolve();
42
- } else {
43
- res.resume();
44
- retry();
45
- }
46
- });
47
- req.on('error', () => retry());
48
- req.setTimeout(2000, () => { req.destroy(); retry(); });
49
- };
50
- const retry = () => {
51
- if (Date.now() - startTime > timeoutMs) {
52
- reject(new Error('服务器启动超时'));
53
- } else {
54
- setTimeout(check, 500);
55
- }
56
- };
57
- check();
58
- });
59
- }
60
-
61
- /**
62
- * 启动内置 Web 服务器
63
- */
64
- function startServer() {
65
- return new Promise((resolve, reject) => {
66
- const serverEntry = join(APP_PATH, 'dist', 'cli', 'index.js');
67
- if (!existsSync(serverEntry)) {
68
- reject(new Error('未找到服务器入口文件,请先运行 npm run build'));
69
- return;
70
- }
71
-
72
- console.log('[Electron] 启动服务器,端口: ' + PORT);
73
-
74
- let serverLog = '';
75
- let started = false;
76
-
77
- serverProcess = spawn('node', [serverEntry, 'serve'], {
78
- cwd: APP_PATH,
79
- env: { ...process.env, FH_WEB_PORT: String(PORT) },
80
- stdio: ['ignore', 'pipe', 'pipe']
81
- });
82
-
83
- serverProcess.stdout.on('data', (data) => {
84
- const text = data.toString();
85
- serverLog += text;
86
- console.log('[Server] ' + text.trim());
87
- });
88
-
89
- serverProcess.stderr.on('data', (data) => {
90
- const text = data.toString();
91
- serverLog += text;
92
- console.error('[Server Error] ' + text.trim());
93
- });
94
-
95
- serverProcess.on('error', (err) => {
96
- if (!started) reject(new Error('启动服务器失败: ' + err.message));
97
- });
98
-
99
- serverProcess.on('exit', (code) => {
100
- console.log('[Electron] 服务器退出,代码: ' + code);
101
- serverProcess = null;
102
- if (!started) {
103
- reject(new Error('服务器启动失败,退出代码: ' + code + '\n\n日志:\n' + serverLog.slice(-2000)));
104
- }
105
- });
106
-
107
- // 等待服务器就绪
108
- waitForServer(PORT, 60000).then(() => {
109
- started = true;
110
- console.log('[Electron] 服务器已就绪');
111
- resolve();
112
- }).catch((err) => {
113
- if (!started) reject(new Error(err.message + '\n\n服务器日志:\n' + serverLog.slice(-2000)));
114
- });
115
- });
116
- }
117
-
118
- /**
119
- * 创建主窗口
120
- */
121
- function createWindow() {
122
- mainWindow = new BrowserWindow({
123
- width: 1400,
124
- height: 900,
125
- minWidth: 1024,
126
- minHeight: 680,
127
- title: '飞虹 Code',
128
- webPreferences: {
129
- preload: join(__dirname, 'preload.js'),
130
- contextIsolation: true,
131
- nodeIntegration: false
132
- },
133
- show: false,
134
- backgroundColor: '#1a1a2e'
135
- });
136
-
137
- const url = `http://127.0.0.1:${PORT}/`;
138
- console.log('[Electron] 加载页面: ' + url);
139
- mainWindow.loadURL(url);
140
-
141
- mainWindow.once('ready-to-show', () => {
142
- mainWindow.show();
143
- });
144
-
145
- // 外部链接用系统浏览器打开
146
- mainWindow.webContents.setWindowOpenHandler(({ url }) => {
147
- shell.openExternal(url);
148
- return { action: 'deny' };
149
- });
150
-
151
- // 关闭时最小化到托盘
152
- mainWindow.on('close', (e) => {
153
- if (!app.isQuitting) {
154
- e.preventDefault();
155
- mainWindow.hide();
156
- }
157
- });
158
-
159
- mainWindow.on('closed', () => {
160
- mainWindow = null;
161
- });
162
- }
163
-
164
- /**
165
- * 创建系统托盘
166
- */
167
- function createTray() {
168
- try {
169
- const iconPath = join(__dirname, 'icon.png');
170
- let trayIcon;
171
- if (existsSync(iconPath)) {
172
- trayIcon = require('electron').nativeImage.createFromPath(iconPath);
173
- } else {
174
- trayIcon = require('electron').nativeImage.createEmpty();
175
- }
176
-
177
- tray = new Tray(trayIcon);
178
- tray.setToolTip('飞虹 Code');
179
-
180
- const isAutoLaunch = app.getLoginItemSettings().openAtLogin;
181
-
182
- const contextMenu = Menu.buildFromTemplate([
183
- { label: '显示主窗口', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } } },
184
- { label: '隐藏到托盘', click: () => { if (mainWindow) mainWindow.hide(); } },
185
- { type: 'separator' },
186
- { label: '新建任务', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); mainWindow.webContents.send('app:new-task'); } } },
187
- { label: '快速补全', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); mainWindow.webContents.send('app:quick-complete'); } } },
188
- { type: 'separator' },
189
- {
190
- label: '开机自启动',
191
- type: 'checkbox',
192
- checked: isAutoLaunch,
193
- click: (menuItem) => {
194
- app.setLoginItemSettings({ openAtLogin: menuItem.checked });
195
- console.log('[Electron] 开机自启动设置:', menuItem.checked);
196
- }
197
- },
198
- { label: '检查更新', click: () => { checkForUpdates(); } },
199
- { type: 'separator' },
200
- { label: '关于飞虹 Code', click: () => { showAboutDialog(); } },
201
- { label: '退出', click: () => { app.isQuitting = true; app.quit(); } }
202
- ]);
203
- tray.setContextMenu(contextMenu);
204
-
205
- tray.on('click', () => {
206
- if (mainWindow) {
207
- if (mainWindow.isVisible()) mainWindow.hide();
208
- else { mainWindow.show(); mainWindow.focus(); }
209
- }
210
- });
211
-
212
- // 双击托盘显示主窗口
213
- tray.on('double-click', () => {
214
- if (mainWindow) { mainWindow.show(); mainWindow.focus(); }
215
- });
216
- } catch (e) {
217
- console.warn('[Electron] 创建托盘失败: ' + e.message);
218
- }
219
- }
220
-
221
- /**
222
- * 创建应用菜单(关键:没有菜单会导致复制粘贴等快捷键失效)
223
- */
224
- function createMenu() {
225
- const template = [
226
- {
227
- label: '文件',
228
- submenu: [
229
- { label: '刷新', role: 'reload' },
230
- { label: '强制刷新', role: 'forceReload' },
231
- { type: 'separator' },
232
- { label: '退出', role: 'quit' }
233
- ]
234
- },
235
- {
236
- label: '编辑',
237
- submenu: [
238
- { label: '撤销', role: 'undo' },
239
- { label: '重做', role: 'redo' },
240
- { type: 'separator' },
241
- { label: '剪切', role: 'cut' },
242
- { label: '复制', role: 'copy' },
243
- { label: '粘贴', role: 'paste' },
244
- { label: '全选', role: 'selectAll' }
245
- ]
246
- },
247
- {
248
- label: '视图',
249
- submenu: [
250
- { label: '放大', role: 'zoomIn' },
251
- { label: '缩小', role: 'zoomOut' },
252
- { label: '重置缩放', role: 'resetZoom' },
253
- { type: 'separator' },
254
- { label: '全屏', role: 'togglefullscreen' },
255
- { label: '开发者工具', role: 'toggleDevTools' }
256
- ]
257
- },
258
- {
259
- label: '窗口',
260
- submenu: [
261
- { label: '最小化', role: 'minimize' },
262
- { label: '关闭', role: 'close' }
263
- ]
264
- },
265
- {
266
- label: '帮助',
267
- submenu: [
268
- { label: '关于飞虹 Code', click: () => {
269
- dialog.showMessageBox(mainWindow, {
270
- type: 'info',
271
- title: '关于飞虹 Code',
272
- message: '飞虹 Code v0.6.1',
273
- detail: '终端 AI 编程智能体\n晋江市飞虹智科技企业管理有限公司'
274
- });
275
- }}
276
- ]
277
- }
278
- ];
279
- const menu = Menu.buildFromTemplate(template);
280
- Menu.setApplicationMenu(menu);
281
- }
282
-
283
- /**
284
- * 设置权限(麦克风、摄像头、屏幕捕获)
285
- */
286
- function setupPermissions() {
287
- // 允许所有权限请求(语音输入、视频通话、截图等)
288
- session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
289
- console.log('[Electron] 权限请求: ' + permission);
290
- // 允许所有权限
291
- callback(true);
292
- });
293
-
294
- // 屏幕捕获:自动选择主屏幕,不需要每次都请求权限
295
- session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
296
- console.log('[Electron] 屏幕捕获请求,自动选择主屏幕');
297
- desktopCapturer.getSources({ types: ['screen'] }).then(sources => {
298
- if (sources.length > 0) {
299
- // 自动选择第一个屏幕(主屏幕)
300
- callback({ video: sources[0] });
301
- } else {
302
- callback({});
303
- }
304
- }).catch(err => {
305
- console.error('[Electron] 获取屏幕源失败:', err.message);
306
- callback({});
307
- });
308
- });
309
- }
310
-
311
- /**
312
- * 设置 IPC
313
- */
314
- function setupIpc() {
315
- ipcMain.on('window:minimize', () => { if (mainWindow) mainWindow.minimize(); });
316
- ipcMain.on('window:maximize', () => { if (mainWindow) mainWindow.maximize(); });
317
- ipcMain.on('window:unmaximize', () => { if (mainWindow) mainWindow.unmaximize(); });
318
- ipcMain.on('window:close', () => { if (mainWindow) mainWindow.close(); });
319
- ipcMain.handle('window:isMaximized', () => mainWindow ? mainWindow.isMaximized() : false);
320
-
321
- ipcMain.on('app:quit', () => { app.isQuitting = true; app.quit(); });
322
- ipcMain.on('app:restart', () => { app.relaunch(); app.exit(0); });
323
- ipcMain.handle('app:getPath', (_, name) => { try { return app.getPath(name); } catch { return null; } });
324
-
325
- ipcMain.handle('shell:openExternal', (_, url) => shell.openExternal(url));
326
- ipcMain.handle('shell:openPath', (_, path) => shell.openPath(path));
327
-
328
- ipcMain.handle('dialog:showOpen', (_, options) => dialog.showOpenDialog(mainWindow, options));
329
- ipcMain.handle('dialog:showSave', (_, options) => dialog.showSaveDialog(mainWindow, options));
330
- ipcMain.handle('dialog:showMessage', (_, options) => dialog.showMessageBox(mainWindow, options));
331
-
332
- ipcMain.handle('clipboard:writeText', (_, text) => { clipboard.writeText(text); return true; });
333
- ipcMain.handle('clipboard:readText', () => clipboard.readText());
334
-
335
- ipcMain.handle('notification:show', (_, { title, body }) => {
336
- if (Notification.isSupported()) { new Notification({ title, body }).show(); return true; }
337
- return false;
338
- });
339
-
340
- // 截图功能:截取主屏幕,返回 dataURL
341
- ipcMain.handle('screenshot:capture', async () => {
342
- try {
343
- const primaryDisplay = screen.getPrimaryDisplay();
344
- const { width, height } = primaryDisplay.size;
345
- const sources = await desktopCapturer.getSources({
346
- types: ['screen'],
347
- thumbnailSize: { width, height }
348
- });
349
- const primarySource = sources.find(s => s.display_id === String(primaryDisplay.id)) || sources[0];
350
- if (!primarySource) throw new Error('未找到屏幕源');
351
- return {
352
- success: true,
353
- dataUrl: primarySource.thumbnail.toDataURL(),
354
- width: width,
355
- height: height
356
- };
357
- } catch (err) {
358
- return { success: false, error: err.message };
359
- }
360
- });
361
-
362
- // 全局快捷键
363
- ipcMain.handle('shortcuts:getAll', () => {
364
- return [
365
- { accelerator: 'Ctrl+Shift+Space', description: '显示/隐藏主窗口' },
366
- { accelerator: 'Ctrl+Shift+K', description: '新建任务' },
367
- { accelerator: 'Ctrl+Shift+L', description: '快速补全' },
368
- ];
369
- });
370
-
371
- // 开机自启
372
- ipcMain.handle('autolaunch:get', () => {
373
- return app.getLoginItemSettings().openAtLogin;
374
- });
375
- ipcMain.handle('autolaunch:set', (_, enabled) => {
376
- app.setLoginItemSettings({ openAtLogin: enabled });
377
- return app.getLoginItemSettings().openAtLogin;
378
- });
379
-
380
- // 应用信息
381
- ipcMain.handle('app:info', () => {
382
- return {
383
- version: app.getVersion(),
384
- name: app.getName(),
385
- path: app.getAppPath(),
386
- userData: app.getPath('userData'),
387
- platform: process.platform,
388
- arch: process.arch,
389
- nodeVersion: process.versions.node,
390
- electronVersion: process.versions.electron,
391
- chromeVersion: process.versions.chrome,
392
- };
393
- });
394
- }
395
-
396
- /**
397
- * 注册全局快捷键
398
- */
399
- function registerGlobalShortcuts() {
400
- try {
401
- // Ctrl+Shift+Space: 快速唤起飞虹 Code(显示/隐藏主窗口)
402
- const ret1 = globalShortcut.register('CommandOrControl+Shift+Space', () => {
403
- if (mainWindow) {
404
- if (mainWindow.isVisible() && mainWindow.isFocused()) {
405
- mainWindow.hide();
406
- } else {
407
- mainWindow.show();
408
- mainWindow.focus();
409
- }
410
- }
411
- });
412
-
413
- // Ctrl+Shift+K: 新建任务
414
- const ret2 = globalShortcut.register('CommandOrControl+Shift+K', () => {
415
- if (mainWindow) {
416
- mainWindow.show();
417
- mainWindow.focus();
418
- mainWindow.webContents.send('app:new-task');
419
- }
420
- });
421
-
422
- // Ctrl+Shift+L: 快速补全
423
- const ret3 = globalShortcut.register('CommandOrControl+Shift+L', () => {
424
- if (mainWindow) {
425
- mainWindow.show();
426
- mainWindow.focus();
427
- mainWindow.webContents.send('app:quick-complete');
428
- }
429
- });
430
-
431
- if (ret1 && ret2 && ret3) {
432
- console.log('[Electron] 全局快捷键注册成功');
433
- } else {
434
- console.warn('[Electron] 部分全局快捷键注册失败');
435
- }
436
- } catch (e) {
437
- console.warn('[Electron] 全局快捷键注册异常: ' + e.message);
438
- }
439
- }
440
-
441
- /**
442
- * 检查更新(简化版,实际应使用 electron-updater)
443
- */
444
- function checkForUpdates() {
445
- console.log('[Electron] 检查更新...');
446
- // 简化版:显示当前版本,实际应使用 electron-updater
447
- const version = app.getVersion();
448
- dialog.showMessageBox(mainWindow, {
449
- type: 'info',
450
- title: '检查更新',
451
- message: `当前版本: v${version}`,
452
- detail: '飞虹 Code 会在有新版本时自动通知您。\n\n您也可以访问官网下载最新版本。',
453
- buttons: ['确定', '访问官网'],
454
- defaultId: 0,
455
- cancelId: 0
456
- }).then(({ response }) => {
457
- if (response === 1) {
458
- shell.openExternal('https://feihong-code.com');
459
- }
460
- });
461
- }
462
-
463
- /**
464
- * 显示关于对话框
465
- */
466
- function showAboutDialog() {
467
- const version = app.getVersion();
468
- dialog.showMessageBox(mainWindow, {
469
- type: 'info',
470
- title: '关于飞虹 Code',
471
- message: `飞虹 Code v${version}`,
472
- detail: '终端 AI 编程智能体\n晋江市飞虹智科技企业管理有限公司\n飞扬企源研发中心 · 负责人:吴赐虹\n\n技术栈:TypeScript + Electron + Express\n核心能力:多模型路由、企业级 RBAC、全自动 SWE Agent',
473
- buttons: ['确定', '官方网站', 'GitHub'],
474
- defaultId: 0,
475
- cancelId: 0
476
- }).then(({ response }) => {
477
- if (response === 1) shell.openExternal('https://feihong-code.com');
478
- if (response === 2) shell.openExternal('https://github.com/feihong-code');
479
- });
480
- }
481
-
482
- /**
483
- * 设置深度链接(fhcode:// 协议)
484
- */
485
- function setupDeepLink() {
486
- if (process.defaultApp) {
487
- if (process.argv.length >= 2) {
488
- app.setAsDefaultProtocolClient('fhcode', process.execPath, [resolve(process.argv[1])]);
489
- }
490
- } else {
491
- app.setAsDefaultProtocolClient('fhcode');
492
- }
493
- console.log('[Electron] 深度链接协议已注册: fhcode://');
494
- }
495
-
496
- /**
497
- * 处理深度链接 URL
498
- */
499
- function handleDeepLink(url) {
500
- console.log('[Electron] 深度链接:', url);
501
- try {
502
- const parsed = new URL(url);
503
- const action = parsed.hostname;
504
- const params = Object.fromEntries(parsed.searchParams);
505
-
506
- if (mainWindow) {
507
- mainWindow.show();
508
- mainWindow.focus();
509
- mainWindow.webContents.send('app:deep-link', { action, params });
510
- }
511
- } catch (e) {
512
- console.error('[Electron] 深度链接解析失败:', e.message);
513
- }
514
- }
515
-
516
- // App 就绪
517
- app.whenReady().then(async () => {
518
- console.log('[Electron] App 已就绪');
519
- console.log('[Electron] 应用路径: ' + APP_PATH);
520
-
521
- try {
522
- setupIpc();
523
- setupPermissions();
524
- setupDeepLink();
525
- createMenu();
526
- await startServer();
527
- createWindow();
528
- createTray();
529
- registerGlobalShortcuts();
530
-
531
- app.on('activate', () => {
532
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
533
- });
534
- } catch (err) {
535
- console.error('[Electron] 启动失败: ' + err.message);
536
-
537
- // 显示错误窗口
538
- const errorWin = new BrowserWindow({
539
- width: 700,
540
- height: 500,
541
- title: '启动失败',
542
- webPreferences: { nodeIntegration: true, contextIsolation: false }
543
- });
544
-
545
- const errorHtml = `
546
- <html><body style="font-family:Microsoft YaHei,sans-serif;padding:30px;background:#1a1a2e;color:#fff;margin:0;">
547
- <h2 style="color:#ff6b6b;">飞虹 Code 启动失败</h2>
548
- <p style="color:#aaa;">错误详情:</p>
549
- <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>
550
- <p style="color:#888;margin-top:20px;">请检查:1) 是否已运行 npm run build 2) 端口 ${PORT} 是否被占用 3) Node.js 版本是否 >= 18</p>
551
- <p style="color:#666;margin-top:20px;font-size:12px;">关闭此窗口后退出</p>
552
- </body></html>
553
- `;
554
- errorWin.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(errorHtml));
555
- errorWin.on('closed', () => app.quit());
556
- }
557
- });
558
-
559
- // 所有窗口关闭时不退出(保持托盘运行)
560
- app.on('window-all-closed', () => {});
561
-
562
- // 退出前注销全局快捷键并停止服务器
563
- app.on('before-quit', () => {
564
- app.isQuitting = true;
565
- globalShortcut.unregisterAll();
566
- if (serverProcess) {
567
- serverProcess.kill();
568
- serverProcess = null;
569
- }
570
- });
571
-
572
- // 处理深度链接(Windows/Linux)
573
- app.on('second-instance', (event, commandLine) => {
574
- if (mainWindow) {
575
- if (mainWindow.isMinimized()) mainWindow.restore();
576
- mainWindow.show();
577
- mainWindow.focus();
578
- }
579
- // 查找深度链接 URL
580
- const deepLink = commandLine.find(arg => arg.startsWith('fhcode://'));
581
- if (deepLink) handleDeepLink(deepLink);
582
- });
583
-
584
- // 处理深度链接(macOS)
585
- app.on('open-url', (event, url) => {
586
- event.preventDefault();
587
- handleDeepLink(url);
588
- });
589
-
590
- // 防止多开
591
- const gotLock = app.requestSingleInstanceLock();
592
- if (!gotLock) {
593
- app.quit();
594
- } else {
595
- app.on('second-instance', () => {
596
- if (mainWindow) {
597
- if (mainWindow.isMinimized()) mainWindow.restore();
598
- mainWindow.show();
599
- mainWindow.focus();
600
- }
601
- });
602
- }
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
+ }