@lobehub/chat 1.82.10 → 1.83.1

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 (195) hide show
  1. package/.env.desktop +1 -2
  2. package/.github/workflows/{release-desktop.yml → desktop-pr-build.yml} +59 -137
  3. package/.github/workflows/release-desktop-beta.yml +194 -0
  4. package/CHANGELOG.md +42 -0
  5. package/apps/desktop/.i18nrc.js +31 -0
  6. package/apps/desktop/Development.md +47 -0
  7. package/apps/desktop/README.md +6 -0
  8. package/apps/desktop/build/Icon-beta.icns +0 -0
  9. package/apps/desktop/build/Icon-nightly.icns +0 -0
  10. package/apps/desktop/build/Icon.icns +0 -0
  11. package/apps/desktop/build/entitlements.mac.plist +12 -0
  12. package/apps/desktop/build/favicon.ico +0 -0
  13. package/apps/desktop/build/icon-beta.png +0 -0
  14. package/apps/desktop/build/icon-dev.png +0 -0
  15. package/apps/desktop/build/icon-nightly.ico +0 -0
  16. package/apps/desktop/build/icon-nightly.png +0 -0
  17. package/apps/desktop/build/icon.ico +0 -0
  18. package/apps/desktop/build/icon.png +0 -0
  19. package/apps/desktop/dev-app-update.yml +6 -0
  20. package/apps/desktop/electron-builder.js +92 -0
  21. package/apps/desktop/electron.vite.config.ts +40 -0
  22. package/apps/desktop/package.json +72 -0
  23. package/apps/desktop/pnpm-workspace.yaml +5 -0
  24. package/apps/desktop/resources/error.html +136 -0
  25. package/apps/desktop/resources/locales/ar/common.json +32 -0
  26. package/apps/desktop/resources/locales/ar/dialog.json +31 -0
  27. package/apps/desktop/resources/locales/ar/menu.json +70 -0
  28. package/apps/desktop/resources/locales/bg-BG/common.json +32 -0
  29. package/apps/desktop/resources/locales/bg-BG/dialog.json +31 -0
  30. package/apps/desktop/resources/locales/bg-BG/menu.json +70 -0
  31. package/apps/desktop/resources/locales/de-DE/common.json +32 -0
  32. package/apps/desktop/resources/locales/de-DE/dialog.json +31 -0
  33. package/apps/desktop/resources/locales/de-DE/menu.json +70 -0
  34. package/apps/desktop/resources/locales/en-US/common.json +32 -0
  35. package/apps/desktop/resources/locales/en-US/dialog.json +31 -0
  36. package/apps/desktop/resources/locales/en-US/menu.json +70 -0
  37. package/apps/desktop/resources/locales/es-ES/common.json +32 -0
  38. package/apps/desktop/resources/locales/es-ES/dialog.json +31 -0
  39. package/apps/desktop/resources/locales/es-ES/menu.json +70 -0
  40. package/apps/desktop/resources/locales/fa-IR/common.json +32 -0
  41. package/apps/desktop/resources/locales/fa-IR/dialog.json +31 -0
  42. package/apps/desktop/resources/locales/fa-IR/menu.json +70 -0
  43. package/apps/desktop/resources/locales/fr-FR/common.json +32 -0
  44. package/apps/desktop/resources/locales/fr-FR/dialog.json +31 -0
  45. package/apps/desktop/resources/locales/fr-FR/menu.json +70 -0
  46. package/apps/desktop/resources/locales/it-IT/common.json +32 -0
  47. package/apps/desktop/resources/locales/it-IT/dialog.json +31 -0
  48. package/apps/desktop/resources/locales/it-IT/menu.json +70 -0
  49. package/apps/desktop/resources/locales/ja-JP/common.json +32 -0
  50. package/apps/desktop/resources/locales/ja-JP/dialog.json +31 -0
  51. package/apps/desktop/resources/locales/ja-JP/menu.json +70 -0
  52. package/apps/desktop/resources/locales/ko-KR/common.json +32 -0
  53. package/apps/desktop/resources/locales/ko-KR/dialog.json +31 -0
  54. package/apps/desktop/resources/locales/ko-KR/menu.json +70 -0
  55. package/apps/desktop/resources/locales/nl-NL/common.json +32 -0
  56. package/apps/desktop/resources/locales/nl-NL/dialog.json +31 -0
  57. package/apps/desktop/resources/locales/nl-NL/menu.json +70 -0
  58. package/apps/desktop/resources/locales/pl-PL/common.json +32 -0
  59. package/apps/desktop/resources/locales/pl-PL/dialog.json +31 -0
  60. package/apps/desktop/resources/locales/pl-PL/menu.json +70 -0
  61. package/apps/desktop/resources/locales/pt-BR/common.json +32 -0
  62. package/apps/desktop/resources/locales/pt-BR/dialog.json +31 -0
  63. package/apps/desktop/resources/locales/pt-BR/menu.json +70 -0
  64. package/apps/desktop/resources/locales/ru-RU/common.json +32 -0
  65. package/apps/desktop/resources/locales/ru-RU/dialog.json +31 -0
  66. package/apps/desktop/resources/locales/ru-RU/menu.json +70 -0
  67. package/apps/desktop/resources/locales/tr-TR/common.json +32 -0
  68. package/apps/desktop/resources/locales/tr-TR/dialog.json +31 -0
  69. package/apps/desktop/resources/locales/tr-TR/menu.json +70 -0
  70. package/apps/desktop/resources/locales/vi-VN/common.json +32 -0
  71. package/apps/desktop/resources/locales/vi-VN/dialog.json +31 -0
  72. package/apps/desktop/resources/locales/vi-VN/menu.json +70 -0
  73. package/apps/desktop/resources/locales/zh-CN/common.json +32 -0
  74. package/apps/desktop/resources/locales/zh-CN/dialog.json +31 -0
  75. package/apps/desktop/resources/locales/zh-CN/menu.json +70 -0
  76. package/apps/desktop/resources/locales/zh-TW/common.json +32 -0
  77. package/apps/desktop/resources/locales/zh-TW/dialog.json +31 -0
  78. package/apps/desktop/resources/locales/zh-TW/menu.json +70 -0
  79. package/apps/desktop/resources/splash.html +88 -0
  80. package/apps/desktop/scripts/i18nWorkflow/const.ts +18 -0
  81. package/apps/desktop/scripts/i18nWorkflow/genDefaultLocale.ts +35 -0
  82. package/apps/desktop/scripts/i18nWorkflow/genDiff.ts +57 -0
  83. package/apps/desktop/scripts/i18nWorkflow/index.ts +35 -0
  84. package/apps/desktop/scripts/i18nWorkflow/utils.ts +54 -0
  85. package/apps/desktop/scripts/pglite-server.ts +14 -0
  86. package/apps/desktop/src/common/routes.ts +78 -0
  87. package/apps/desktop/src/main/appBrowsers.ts +47 -0
  88. package/apps/desktop/src/main/const/dir.ts +29 -0
  89. package/apps/desktop/src/main/const/env.ts +3 -0
  90. package/apps/desktop/src/main/const/store.ts +22 -0
  91. package/apps/desktop/src/main/controllers/AuthCtr.ts +390 -0
  92. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +95 -0
  93. package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +9 -0
  94. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +380 -0
  95. package/apps/desktop/src/main/controllers/MenuCtr.ts +29 -0
  96. package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +335 -0
  97. package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +321 -0
  98. package/apps/desktop/src/main/controllers/ShortcutCtr.ts +19 -0
  99. package/apps/desktop/src/main/controllers/SystemCtr.ts +93 -0
  100. package/apps/desktop/src/main/controllers/UpdaterCtr.ts +43 -0
  101. package/apps/desktop/src/main/controllers/UploadFileCtr.ts +34 -0
  102. package/apps/desktop/src/main/controllers/_template.ts +9 -0
  103. package/apps/desktop/src/main/controllers/index.ts +58 -0
  104. package/apps/desktop/src/main/core/App.ts +370 -0
  105. package/apps/desktop/src/main/core/Browser.ts +345 -0
  106. package/apps/desktop/src/main/core/BrowserManager.ts +154 -0
  107. package/apps/desktop/src/main/core/I18nManager.ts +185 -0
  108. package/apps/desktop/src/main/core/IoCContainer.ts +12 -0
  109. package/apps/desktop/src/main/core/MenuManager.ts +64 -0
  110. package/apps/desktop/src/main/core/ShortcutManager.ts +173 -0
  111. package/apps/desktop/src/main/core/StoreManager.ts +89 -0
  112. package/apps/desktop/src/main/core/UpdaterManager.ts +321 -0
  113. package/apps/desktop/src/main/index.ts +5 -0
  114. package/apps/desktop/src/main/locales/default/common.ts +34 -0
  115. package/apps/desktop/src/main/locales/default/dialog.ts +33 -0
  116. package/apps/desktop/src/main/locales/default/index.ts +11 -0
  117. package/apps/desktop/src/main/locales/default/menu.ts +72 -0
  118. package/apps/desktop/src/main/locales/resources.ts +35 -0
  119. package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.ts +10 -0
  120. package/apps/desktop/src/main/menus/impls/linux.ts +243 -0
  121. package/apps/desktop/src/main/menus/impls/macOS.ts +360 -0
  122. package/apps/desktop/src/main/menus/impls/windows.ts +226 -0
  123. package/apps/desktop/src/main/menus/index.ts +34 -0
  124. package/apps/desktop/src/main/menus/types.ts +28 -0
  125. package/apps/desktop/src/main/modules/fileSearch/impl/macOS.ts +577 -0
  126. package/apps/desktop/src/main/modules/fileSearch/index.ts +23 -0
  127. package/apps/desktop/src/main/modules/fileSearch/type.ts +27 -0
  128. package/apps/desktop/src/main/modules/updater/configs.ts +22 -0
  129. package/apps/desktop/src/main/modules/updater/utils.ts +33 -0
  130. package/apps/desktop/src/main/services/fileSearchSrv.ts +35 -0
  131. package/apps/desktop/src/main/services/fileSrv.ts +255 -0
  132. package/apps/desktop/src/main/services/index.ts +9 -0
  133. package/apps/desktop/src/main/shortcuts/config.ts +18 -0
  134. package/apps/desktop/src/main/shortcuts/index.ts +1 -0
  135. package/apps/desktop/src/main/types/fileSearch.ts +51 -0
  136. package/apps/desktop/src/main/types/store.ts +14 -0
  137. package/apps/desktop/src/main/utils/file-system.ts +15 -0
  138. package/apps/desktop/src/main/utils/logger.ts +44 -0
  139. package/apps/desktop/src/main/utils/next-electron-rsc.ts +383 -0
  140. package/apps/desktop/src/preload/electronApi.ts +18 -0
  141. package/apps/desktop/src/preload/index.ts +14 -0
  142. package/apps/desktop/src/preload/invoke.ts +10 -0
  143. package/apps/desktop/src/preload/routeInterceptor.ts +162 -0
  144. package/apps/desktop/tsconfig.json +21 -0
  145. package/changelog/v1.json +14 -0
  146. package/package.json +1 -1
  147. package/packages/electron-client-ipc/src/events/remoteServer.ts +11 -4
  148. package/packages/electron-client-ipc/src/types/dataSync.ts +15 -0
  149. package/packages/electron-client-ipc/src/types/index.ts +2 -1
  150. package/packages/electron-client-ipc/src/types/proxyTRPCRequest.ts +21 -0
  151. package/packages/electron-server-ipc/src/const.ts +3 -3
  152. package/packages/electron-server-ipc/src/ipcClient.test.ts +7 -6
  153. package/packages/electron-server-ipc/src/ipcClient.ts +17 -8
  154. package/packages/electron-server-ipc/src/ipcServer.ts +7 -3
  155. package/scripts/electronWorkflow/setDesktopVersion.ts +60 -43
  156. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
  157. package/src/components/Analytics/Desktop.tsx +19 -0
  158. package/src/components/Analytics/index.tsx +3 -0
  159. package/src/database/core/db-adaptor.ts +4 -1
  160. package/src/database/core/electron.ts +317 -0
  161. package/src/{app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx → features/ElectronTitlebar/Connection/ConnectionMode.tsx} +24 -21
  162. package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/Option.tsx +3 -5
  163. package/src/{app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx → features/ElectronTitlebar/Connection/RemoteStatus.tsx} +10 -7
  164. package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/index.tsx +4 -4
  165. package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/UpdateModal.tsx +2 -1
  166. package/src/libs/trpc/client/async.ts +6 -0
  167. package/src/libs/trpc/client/edge.ts +6 -0
  168. package/src/libs/trpc/client/helpers/desktopRemoteRPCFetch.ts +72 -0
  169. package/src/libs/trpc/client/index.ts +1 -0
  170. package/src/libs/trpc/client/lambda.ts +10 -1
  171. package/src/libs/trpc/client/tools.ts +6 -0
  172. package/src/server/globalConfig/index.ts +0 -3
  173. package/src/server/modules/ElectronIPCClient/index.ts +3 -1
  174. package/src/server/routers/desktop/index.ts +2 -0
  175. package/src/server/routers/desktop/mcp.ts +47 -0
  176. package/src/server/routers/lambda/user.ts +38 -23
  177. package/src/server/routers/tools/mcp.ts +0 -6
  178. package/src/services/electron/remoteServer.ts +4 -4
  179. package/src/services/mcp.ts +17 -7
  180. package/src/services/upload.ts +9 -0
  181. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +11 -2
  182. package/src/store/chat/slices/builtinTool/actions/localFile.ts +110 -53
  183. package/src/store/electron/actions/sync.ts +20 -19
  184. package/src/store/electron/initialState.ts +3 -3
  185. package/src/store/electron/selectors/sync.ts +6 -3
  186. package/src/store/electron/store.ts +2 -0
  187. package/src/store/file/slices/upload/action.ts +11 -3
  188. package/src/store/tool/selectors/tool.ts +10 -1
  189. package/src/utils/fetch/headers.ts +27 -0
  190. package/src/utils/fetch/index.ts +2 -0
  191. package/src/utils/fetch/request.ts +28 -0
  192. package/packages/electron-client-ipc/src/types/remoteServer.ts +0 -8
  193. /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/Waiting.tsx +0 -0
  194. /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/UpdateNotification.tsx +0 -0
  195. /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/index.tsx +0 -0
@@ -0,0 +1,360 @@
1
+ import { Menu, MenuItemConstructorOptions, app, shell } from 'electron';
2
+ import * as path from 'node:path';
3
+
4
+ import { isDev } from '@/const/env';
5
+
6
+ import type { IMenuPlatform, MenuOptions } from '../types';
7
+ import { BaseMenuPlatform } from './BaseMenuPlatform';
8
+
9
+ export class MacOSMenu extends BaseMenuPlatform implements IMenuPlatform {
10
+ private appMenu: Menu | null = null;
11
+ private trayMenu: Menu | null = null;
12
+
13
+ buildAndSetAppMenu(options?: MenuOptions): Menu {
14
+ const template = this.getAppMenuTemplate(options);
15
+
16
+ this.appMenu = Menu.buildFromTemplate(template);
17
+
18
+ Menu.setApplicationMenu(this.appMenu);
19
+
20
+ return this.appMenu;
21
+ }
22
+
23
+ buildContextMenu(type: string, data?: any): Menu {
24
+ let template: MenuItemConstructorOptions[];
25
+ switch (type) {
26
+ case 'chat': {
27
+ template = this.getChatContextMenuTemplate(data);
28
+ break;
29
+ }
30
+ case 'editor': {
31
+ template = this.getEditorContextMenuTemplate(data);
32
+ break;
33
+ }
34
+ default: {
35
+ template = this.getDefaultContextMenuTemplate();
36
+ }
37
+ }
38
+ return Menu.buildFromTemplate(template);
39
+ }
40
+
41
+ buildTrayMenu(): Menu {
42
+ const template = this.getTrayMenuTemplate();
43
+ this.trayMenu = Menu.buildFromTemplate(template);
44
+ return this.trayMenu;
45
+ }
46
+
47
+ refresh(options?: MenuOptions): void {
48
+ // 重建应用菜单
49
+ this.buildAndSetAppMenu(options);
50
+ // 如果托盘菜单存在,也重建它(如果需要动态更新)
51
+ // this.trayMenu = this.buildTrayMenu();
52
+ // 需要考虑如何更新现有托盘图标的菜单
53
+ }
54
+
55
+ // --- 私有方法:定义菜单模板和逻辑 ---
56
+
57
+ private getAppMenuTemplate(options?: MenuOptions): MenuItemConstructorOptions[] {
58
+ const appName = app.getName();
59
+ const showDev = isDev || options?.showDevItems;
60
+
61
+ // 创建命名空间翻译函数
62
+ const t = this.app.i18n.ns('menu');
63
+
64
+ // 添加调试日志
65
+ // console.log('[MacOSMenu] 菜单渲染, i18n实例:', !!this.app.i18n);
66
+
67
+ const template: MenuItemConstructorOptions[] = [
68
+ {
69
+ label: appName,
70
+ submenu: [
71
+ {
72
+ label: t('macOS.about', { appName }),
73
+ role: 'about',
74
+ },
75
+ {
76
+ click: () => {
77
+ this.app.updaterManager.checkForUpdates({ manual: true });
78
+ },
79
+ label: t('common.checkUpdates'),
80
+ },
81
+ { type: 'separator' },
82
+ {
83
+ accelerator: 'Command+,',
84
+ click: () => {
85
+ this.app.browserManager.showSettingsWindow();
86
+ },
87
+ label: t('macOS.preferences'),
88
+ },
89
+ { type: 'separator' },
90
+ {
91
+ label: t('macOS.services'),
92
+ role: 'services',
93
+ submenu: [],
94
+ },
95
+ { type: 'separator' },
96
+ {
97
+ accelerator: 'Command+H',
98
+ label: t('macOS.hide', { appName }),
99
+ role: 'hide',
100
+ },
101
+ {
102
+ accelerator: 'Command+Alt+H',
103
+ label: t('macOS.hideOthers'),
104
+ role: 'hideOthers',
105
+ },
106
+ {
107
+ label: t('macOS.unhide'),
108
+ role: 'unhide',
109
+ },
110
+ { type: 'separator' },
111
+ {
112
+ accelerator: 'Command+Q',
113
+ label: t('file.quit'),
114
+ role: 'quit',
115
+ },
116
+ ],
117
+ },
118
+ {
119
+ label: t('file.title'),
120
+ submenu: [
121
+ {
122
+ accelerator: 'Command+W',
123
+ label: t('window.close'),
124
+ role: 'close',
125
+ },
126
+ ],
127
+ },
128
+ {
129
+ label: t('edit.title'),
130
+ submenu: [
131
+ { accelerator: 'Command+Z', label: t('edit.undo'), role: 'undo' },
132
+ { accelerator: 'Shift+Command+Z', label: t('edit.redo'), role: 'redo' },
133
+ { type: 'separator' },
134
+ { accelerator: 'Command+X', label: t('edit.cut'), role: 'cut' },
135
+ { accelerator: 'Command+C', label: t('edit.copy'), role: 'copy' },
136
+ { accelerator: 'Command+V', label: t('edit.paste'), role: 'paste' },
137
+ { type: 'separator' },
138
+ {
139
+ label: t('edit.speech'),
140
+ submenu: [
141
+ { label: t('edit.startSpeaking'), role: 'startSpeaking' },
142
+ { label: t('edit.stopSpeaking'), role: 'stopSpeaking' },
143
+ ],
144
+ },
145
+ { type: 'separator' },
146
+ { accelerator: 'Command+A', label: t('edit.selectAll'), role: 'selectAll' },
147
+ ],
148
+ },
149
+ {
150
+ label: t('view.title'),
151
+ submenu: [
152
+ { label: t('view.reload'), role: 'reload' },
153
+ { label: t('view.forceReload'), role: 'forceReload' },
154
+ { accelerator: 'F12', label: t('dev.devTools'), role: 'toggleDevTools' },
155
+ { type: 'separator' },
156
+ { accelerator: 'Command+0', label: t('view.resetZoom'), role: 'resetZoom' },
157
+ { accelerator: 'Command+Plus', label: t('view.zoomIn'), role: 'zoomIn' },
158
+ { accelerator: 'Command+-', label: t('view.zoomOut'), role: 'zoomOut' },
159
+ { type: 'separator' },
160
+ { accelerator: 'F11', label: t('view.toggleFullscreen'), role: 'togglefullscreen' },
161
+ ],
162
+ },
163
+ {
164
+ label: t('window.title'),
165
+ role: 'windowMenu',
166
+ },
167
+ {
168
+ label: t('help.title'),
169
+ role: 'help',
170
+ submenu: [
171
+ {
172
+ click: async () => {
173
+ await shell.openExternal('https://lobehub.com');
174
+ },
175
+ label: t('help.visitWebsite'),
176
+ },
177
+ {
178
+ click: async () => {
179
+ await shell.openExternal('https://github.com/lobehub/lobe-chat');
180
+ },
181
+ label: t('help.githubRepo'),
182
+ },
183
+ {
184
+ click: async () => {
185
+ await shell.openExternal('https://github.com/lobehub/lobe-chat/issues/new/choose');
186
+ },
187
+ label: t('help.reportIssue'),
188
+ },
189
+ { type: 'separator' },
190
+ {
191
+ click: () => {
192
+ const logsPath = app.getPath('logs');
193
+ console.log(`[Menu] Opening logs directory: ${logsPath}`);
194
+ shell.openPath(logsPath).catch((err) => {
195
+ console.error(`[Menu] Error opening path ${logsPath}:`, err);
196
+ // Optionally show an error dialog to the user
197
+ });
198
+ },
199
+ label: '打开日志目录',
200
+ },
201
+ {
202
+ click: () => {
203
+ const userDataPath = app.getPath('userData');
204
+ console.log(`[Menu] Opening user data directory: ${userDataPath}`);
205
+ shell.openPath(userDataPath).catch((err) => {
206
+ console.error(`[Menu] Error opening path ${userDataPath}:`, err);
207
+ // Optionally show an error dialog to the user
208
+ });
209
+ },
210
+ label: '配置目录',
211
+ },
212
+ ],
213
+ },
214
+ ];
215
+
216
+ if (showDev) {
217
+ template.push({
218
+ label: t('dev.title'),
219
+ submenu: [
220
+ {
221
+ click: () => {
222
+ this.app.browserManager.retrieveByIdentifier('devtools').show();
223
+ },
224
+ label: t('dev.devPanel'),
225
+ },
226
+ {
227
+ click: () => {
228
+ this.app.menuManager.rebuildAppMenu();
229
+ },
230
+ label: t('dev.refreshMenu'),
231
+ },
232
+ { type: 'separator' },
233
+ {
234
+ click: () => {
235
+ const userDataPath = app.getPath('userData');
236
+ shell.openPath(userDataPath).catch((err) => {
237
+ console.error(`[Menu] Error opening path ${userDataPath}:`, err);
238
+ });
239
+ },
240
+ label: '用户配置目录',
241
+ },
242
+ {
243
+ click: () => {
244
+ // @ts-expect-error cache 目录好像暂时不在类型定义里
245
+ const cachePath = app.getPath('cache');
246
+
247
+ const updaterCachePath = path.join(cachePath, `${app.getName()}-updater`);
248
+ shell.openPath(updaterCachePath).catch((err) => {
249
+ console.error(`[Menu] Error opening path ${updaterCachePath}:`, err);
250
+ });
251
+ },
252
+ label: '更新缓存目录',
253
+ },
254
+ { type: 'separator' },
255
+ {
256
+ label: '自动更新测试模拟',
257
+ submenu: [
258
+ {
259
+ click: () => {
260
+ this.app.updaterManager.simulateUpdateAvailable();
261
+ },
262
+ label: '模拟启动后台自动下载更新(3s 下完)',
263
+ },
264
+ {
265
+ click: () => {
266
+ this.app.updaterManager.simulateDownloadProgress();
267
+ },
268
+ label: '模拟下载进度',
269
+ },
270
+ {
271
+ click: () => {
272
+ this.app.updaterManager.simulateUpdateDownloaded();
273
+ },
274
+ label: '模拟下载完成',
275
+ },
276
+ ],
277
+ },
278
+ ],
279
+ });
280
+ }
281
+
282
+ return template;
283
+ }
284
+
285
+ private getDefaultContextMenuTemplate(): MenuItemConstructorOptions[] {
286
+ const t = this.app.i18n.ns('menu');
287
+
288
+ return [
289
+ { label: t('edit.cut'), role: 'cut' },
290
+ { label: t('edit.copy'), role: 'copy' },
291
+ { label: t('edit.paste'), role: 'paste' },
292
+ { label: t('edit.selectAll'), role: 'selectAll' },
293
+ { type: 'separator' },
294
+ ];
295
+ }
296
+
297
+ private getChatContextMenuTemplate(data?: any): MenuItemConstructorOptions[] {
298
+ const t = this.app.i18n.ns('menu');
299
+ const commonT = this.app.i18n.ns('common');
300
+
301
+ const items: MenuItemConstructorOptions[] = [
302
+ { label: t('edit.cut'), role: 'cut' },
303
+ { label: t('edit.copy'), role: 'copy' },
304
+ { label: t('edit.paste'), role: 'paste' },
305
+ { type: 'separator' },
306
+ { label: t('edit.selectAll'), role: 'selectAll' },
307
+ ];
308
+
309
+ if (data?.messageId) {
310
+ items.push(
311
+ { type: 'separator' },
312
+ {
313
+ click: () => {
314
+ console.log('尝试删除消息:', data.messageId);
315
+ // 调用 MessageService (假设存在)
316
+ // const messageService = this.app.getService(MessageService);
317
+ // messageService?.deleteMessage(data.messageId);
318
+ },
319
+ label: commonT('actions.delete'),
320
+ },
321
+ );
322
+ }
323
+ return items;
324
+ }
325
+
326
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
327
+ private getEditorContextMenuTemplate(_data?: any): MenuItemConstructorOptions[] {
328
+ const t = this.app.i18n.ns('menu');
329
+
330
+ // 编辑器特定的上下文菜单
331
+ return [
332
+ { label: t('edit.cut'), role: 'cut' },
333
+ { label: t('edit.copy'), role: 'copy' },
334
+ { label: t('edit.paste'), role: 'paste' },
335
+ { type: 'separator' },
336
+ { label: t('edit.undo'), role: 'undo' },
337
+ { label: t('edit.redo'), role: 'redo' },
338
+ { type: 'separator' },
339
+ { label: t('edit.selectAll'), role: 'selectAll' },
340
+ ];
341
+ }
342
+
343
+ private getTrayMenuTemplate(): MenuItemConstructorOptions[] {
344
+ const t = this.app.i18n.ns('menu');
345
+ const appName = app.getName();
346
+
347
+ return [
348
+ {
349
+ click: () => this.app.browserManager.showMainWindow(),
350
+ label: t('tray.show', { appName }),
351
+ },
352
+ {
353
+ click: () => this.app.browserManager.retrieveByIdentifier('settings').show(),
354
+ label: t('file.preferences'),
355
+ },
356
+ { type: 'separator' },
357
+ { label: t('tray.quit'), role: 'quit' },
358
+ ];
359
+ }
360
+ }
@@ -0,0 +1,226 @@
1
+ import { Menu, MenuItemConstructorOptions, app, shell } from 'electron';
2
+
3
+ import { isDev } from '@/const/env';
4
+
5
+ import type { IMenuPlatform, MenuOptions } from '../types';
6
+ import { BaseMenuPlatform } from './BaseMenuPlatform';
7
+
8
+ export class WindowsMenu extends BaseMenuPlatform implements IMenuPlatform {
9
+ private appMenu: Menu | null = null;
10
+ private trayMenu: Menu | null = null;
11
+
12
+ buildAndSetAppMenu(options?: MenuOptions): Menu {
13
+ const template = this.getAppMenuTemplate(options);
14
+ this.appMenu = Menu.buildFromTemplate(template);
15
+ Menu.setApplicationMenu(this.appMenu);
16
+ return this.appMenu;
17
+ }
18
+
19
+ buildContextMenu(type: string, data?: any): Menu {
20
+ let template: MenuItemConstructorOptions[];
21
+ switch (type) {
22
+ case 'chat': {
23
+ template = this.getChatContextMenuTemplate(data);
24
+ break;
25
+ }
26
+ case 'editor': {
27
+ template = this.getEditorContextMenuTemplate(data);
28
+ break;
29
+ }
30
+ default: {
31
+ template = this.getDefaultContextMenuTemplate();
32
+ }
33
+ }
34
+ return Menu.buildFromTemplate(template);
35
+ }
36
+
37
+ buildTrayMenu(): Menu {
38
+ const template = this.getTrayMenuTemplate();
39
+ this.trayMenu = Menu.buildFromTemplate(template);
40
+ return this.trayMenu;
41
+ }
42
+
43
+ refresh(options?: MenuOptions): void {
44
+ this.buildAndSetAppMenu(options);
45
+ // 如果有必要更新托盘菜单,可以在这里添加逻辑
46
+ }
47
+
48
+ private getAppMenuTemplate(options?: MenuOptions): MenuItemConstructorOptions[] {
49
+ const showDev = isDev || options?.showDevItems;
50
+ const t = this.app.i18n.ns('menu');
51
+
52
+ const template: MenuItemConstructorOptions[] = [
53
+ {
54
+ label: t('file.title'),
55
+ submenu: [
56
+ {
57
+ click: () => this.app.browserManager.retrieveByIdentifier('settings').show(),
58
+ label: t('file.preferences'),
59
+ },
60
+ {
61
+ click: () => {
62
+ this.app.updaterManager.checkForUpdates({ manual: true });
63
+ },
64
+ label: t('common.checkUpdates') || '检查更新',
65
+ },
66
+ { type: 'separator' },
67
+ {
68
+ accelerator: 'Alt+F4',
69
+ label: t('window.close'),
70
+ role: 'close',
71
+ },
72
+ {
73
+ accelerator: 'Ctrl+M',
74
+ label: t('window.minimize'),
75
+ role: 'minimize',
76
+ },
77
+ { type: 'separator' },
78
+ { label: t('file.quit'), role: 'quit' },
79
+ ],
80
+ },
81
+ {
82
+ label: t('edit.title'),
83
+ submenu: [
84
+ { accelerator: 'Ctrl+Z', label: t('edit.undo'), role: 'undo' },
85
+ { accelerator: 'Ctrl+Y', label: t('edit.redo'), role: 'redo' },
86
+ { type: 'separator' },
87
+ { accelerator: 'Ctrl+X', label: t('edit.cut'), role: 'cut' },
88
+ { accelerator: 'Ctrl+C', label: t('edit.copy'), role: 'copy' },
89
+ { accelerator: 'Ctrl+V', label: t('edit.paste'), role: 'paste' },
90
+ { type: 'separator' },
91
+ { accelerator: 'Ctrl+A', label: t('edit.selectAll'), role: 'selectAll' },
92
+ ],
93
+ },
94
+ {
95
+ label: t('view.title'),
96
+ submenu: [
97
+ { accelerator: 'Ctrl+0', label: t('view.resetZoom'), role: 'resetZoom' },
98
+ { accelerator: 'Ctrl+Plus', label: t('view.zoomIn'), role: 'zoomIn' },
99
+ { accelerator: 'Ctrl+-', label: t('view.zoomOut'), role: 'zoomOut' },
100
+ { type: 'separator' },
101
+ { accelerator: 'F11', label: t('view.toggleFullscreen'), role: 'togglefullscreen' },
102
+ ],
103
+ },
104
+ {
105
+ label: t('window.title'),
106
+ submenu: [
107
+ { label: t('window.minimize'), role: 'minimize' },
108
+ { label: t('window.close'), role: 'close' },
109
+ ],
110
+ },
111
+ {
112
+ label: t('help.title'),
113
+ submenu: [
114
+ {
115
+ click: async () => {
116
+ await shell.openExternal('https://lobehub.com');
117
+ },
118
+ label: t('help.visitWebsite'),
119
+ },
120
+ {
121
+ click: async () => {
122
+ await shell.openExternal('https://github.com/lobehub/lobe-chat');
123
+ },
124
+ label: t('help.githubRepo'),
125
+ },
126
+ ],
127
+ },
128
+ ];
129
+
130
+ if (showDev) {
131
+ template.push({
132
+ label: t('dev.title'),
133
+ submenu: [
134
+ { accelerator: 'Ctrl+R', label: t('dev.reload'), role: 'reload' },
135
+ { accelerator: 'Ctrl+Shift+R', label: t('dev.forceReload'), role: 'forceReload' },
136
+ { accelerator: 'Ctrl+Shift+I', label: t('dev.devTools'), role: 'toggleDevTools' },
137
+ { type: 'separator' },
138
+ {
139
+ click: () => {
140
+ this.app.browserManager.retrieveByIdentifier('devtools').show();
141
+ },
142
+ label: t('dev.devPanel'),
143
+ },
144
+ ],
145
+ });
146
+ }
147
+
148
+ return template;
149
+ }
150
+
151
+ private getDefaultContextMenuTemplate(): MenuItemConstructorOptions[] {
152
+ const t = this.app.i18n.ns('menu');
153
+
154
+ return [
155
+ { label: t('edit.cut'), role: 'cut' },
156
+ { label: t('edit.copy'), role: 'copy' },
157
+ { label: t('edit.paste'), role: 'paste' },
158
+ { type: 'separator' },
159
+ { label: t('edit.selectAll'), role: 'selectAll' },
160
+ ];
161
+ }
162
+
163
+ private getChatContextMenuTemplate(data?: any): MenuItemConstructorOptions[] {
164
+ const t = this.app.i18n.ns('menu');
165
+ const commonT = this.app.i18n.ns('common');
166
+
167
+ const items: MenuItemConstructorOptions[] = [
168
+ { label: t('edit.copy'), role: 'copy' },
169
+ { label: t('edit.paste'), role: 'paste' },
170
+ { type: 'separator' },
171
+ { label: t('edit.selectAll'), role: 'selectAll' },
172
+ ];
173
+
174
+ if (data?.messageId) {
175
+ items.push(
176
+ { type: 'separator' },
177
+ {
178
+ click: () => {
179
+ console.log('尝试删除消息:', data.messageId);
180
+ // 调用 MessageService (假设存在)
181
+ // const messageService = this.app.getService(MessageService);
182
+ // messageService?.deleteMessage(data.messageId);
183
+ },
184
+ label: commonT('actions.delete'),
185
+ },
186
+ );
187
+ }
188
+
189
+ return items;
190
+ }
191
+
192
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
193
+ private getEditorContextMenuTemplate(_data?: any): MenuItemConstructorOptions[] {
194
+ const t = this.app.i18n.ns('menu');
195
+
196
+ return [
197
+ { label: t('edit.cut'), role: 'cut' },
198
+ { label: t('edit.copy'), role: 'copy' },
199
+ { label: t('edit.paste'), role: 'paste' },
200
+ { type: 'separator' },
201
+ { label: t('edit.undo'), role: 'undo' },
202
+ { label: t('edit.redo'), role: 'redo' },
203
+ { type: 'separator' },
204
+ { label: t('edit.selectAll'), role: 'selectAll' },
205
+ ];
206
+ }
207
+
208
+ private getTrayMenuTemplate(): MenuItemConstructorOptions[] {
209
+ const t = this.app.i18n.ns('menu');
210
+ const appName = app.getName();
211
+
212
+ return [
213
+ {
214
+ click: () => this.app.browserManager.showMainWindow(),
215
+ label: t('tray.open', { appName }),
216
+ },
217
+ { type: 'separator' },
218
+ {
219
+ click: () => this.app.browserManager.retrieveByIdentifier('settings').show(),
220
+ label: t('file.preferences'),
221
+ },
222
+ { type: 'separator' },
223
+ { label: t('tray.quit'), role: 'quit' },
224
+ ];
225
+ }
226
+ }
@@ -0,0 +1,34 @@
1
+ import { platform } from 'node:os';
2
+
3
+ import { App } from '@/core/App';
4
+
5
+ import { LinuxMenu } from './impls/linux';
6
+ import { MacOSMenu } from './impls/macOS';
7
+ import { WindowsMenu } from './impls/windows';
8
+ import { IMenuPlatform } from './types';
9
+
10
+ export type { IMenuPlatform, MenuOptions } from './types';
11
+
12
+ export const createMenuImpl = (app: App): IMenuPlatform => {
13
+ const currentPlatform = platform();
14
+
15
+ switch (currentPlatform) {
16
+ case 'darwin': {
17
+ return new MacOSMenu(app);
18
+ }
19
+ case 'win32': {
20
+ return new WindowsMenu(app);
21
+ }
22
+ case 'linux': {
23
+ return new LinuxMenu(app);
24
+ }
25
+
26
+ default: {
27
+ // 提供一个备用或抛出错误
28
+ console.warn(
29
+ `Unsupported platform for menu: ${currentPlatform}, using Windows implementation as fallback.`,
30
+ );
31
+ return new WindowsMenu(app);
32
+ }
33
+ }
34
+ };
@@ -0,0 +1,28 @@
1
+ import { Menu } from 'electron';
2
+
3
+ export interface MenuOptions {
4
+ showDevItems?: boolean;
5
+ // 其他可能的配置项
6
+ }
7
+
8
+ export interface IMenuPlatform {
9
+ /**
10
+ * 构建并设置应用菜单
11
+ */
12
+ buildAndSetAppMenu(options?: MenuOptions): Menu;
13
+
14
+ /**
15
+ * 构建上下文菜单
16
+ */
17
+ buildContextMenu(type: string, data?: any): Menu;
18
+
19
+ /**
20
+ * 构建托盘菜单
21
+ */
22
+ buildTrayMenu(): Menu;
23
+
24
+ /**
25
+ * 刷新菜单
26
+ */
27
+ refresh(options?: MenuOptions): void;
28
+ }