@lobehub/lobehub 2.0.0-next.164 → 2.0.0-next.166

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 (102) hide show
  1. package/.cursor/rules/desktop-feature-implementation.mdc +31 -34
  2. package/.cursor/rules/desktop-local-tools-implement.mdc +3 -3
  3. package/.cursor/rules/desktop-window-management.mdc +56 -66
  4. package/CHANGELOG.md +50 -0
  5. package/Dockerfile +44 -52
  6. package/README.md +6 -6
  7. package/README.zh-CN.md +6 -6
  8. package/apps/desktop/Development.md +42 -46
  9. package/apps/desktop/README.md +37 -1
  10. package/apps/desktop/README.zh-CN.md +26 -1
  11. package/apps/desktop/electron.vite.config.ts +1 -0
  12. package/apps/desktop/src/main/controllers/AuthCtr.ts +4 -3
  13. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +33 -20
  14. package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +4 -2
  15. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +14 -13
  16. package/apps/desktop/src/main/controllers/MenuCtr.ts +5 -4
  17. package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +18 -19
  18. package/apps/desktop/src/main/controllers/NotificationCtr.ts +4 -3
  19. package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +5 -4
  20. package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +3 -2
  21. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +5 -4
  22. package/apps/desktop/src/main/controllers/ShortcutCtr.ts +4 -3
  23. package/apps/desktop/src/main/controllers/SystemCtr.ts +7 -37
  24. package/apps/desktop/src/main/controllers/SystemServerCtr.ts +38 -0
  25. package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -4
  26. package/apps/desktop/src/main/controllers/UpdaterCtr.ts +6 -5
  27. package/apps/desktop/src/main/controllers/UploadFileCtr.ts +3 -25
  28. package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +33 -0
  29. package/apps/desktop/src/main/controllers/__tests__/AuthCtr.test.ts +9 -1
  30. package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +29 -9
  31. package/apps/desktop/src/main/controllers/__tests__/DevtoolsCtr.test.ts +12 -3
  32. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +7 -0
  33. package/apps/desktop/src/main/controllers/__tests__/MenuCtr.test.ts +10 -0
  34. package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +10 -0
  35. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +8 -0
  36. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +8 -0
  37. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +1 -0
  38. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +10 -0
  39. package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +11 -0
  40. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +43 -73
  41. package/apps/desktop/src/main/controllers/__tests__/SystemServerCtr.test.ts +75 -0
  42. package/apps/desktop/src/main/controllers/__tests__/TrayMenuCtr.test.ts +24 -13
  43. package/apps/desktop/src/main/controllers/__tests__/UpdaterCtr.test.ts +13 -2
  44. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +29 -108
  45. package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +55 -0
  46. package/apps/desktop/src/main/controllers/_template.ts +2 -2
  47. package/apps/desktop/src/main/controllers/index.ts +5 -29
  48. package/apps/desktop/src/main/controllers/registry.ts +52 -0
  49. package/apps/desktop/src/main/core/App.ts +15 -47
  50. package/apps/desktop/src/main/core/__tests__/App.test.ts +5 -4
  51. package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +0 -5
  52. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +0 -50
  53. package/apps/desktop/src/main/exports.d.ts +8 -0
  54. package/apps/desktop/src/main/exports.ts +2 -0
  55. package/apps/desktop/src/main/global.d.ts +3 -0
  56. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +17 -8
  57. package/apps/desktop/src/main/package.json +10 -0
  58. package/apps/desktop/src/main/services/fileSrv.ts +1 -1
  59. package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +91 -0
  60. package/apps/desktop/src/main/utils/ipc/base.ts +170 -0
  61. package/apps/desktop/src/main/utils/ipc/index.ts +11 -0
  62. package/apps/desktop/src/main/utils/ipc/utility.ts +20 -0
  63. package/apps/desktop/src/preload/electronApi.ts +4 -1
  64. package/apps/desktop/src/preload/invoke.test.ts +13 -16
  65. package/apps/desktop/src/preload/invoke.ts +2 -5
  66. package/apps/desktop/src/preload/routeInterceptor.test.ts +13 -13
  67. package/apps/desktop/src/preload/routeInterceptor.ts +4 -4
  68. package/apps/desktop/tsconfig.json +15 -5
  69. package/changelog/v1.json +10 -0
  70. package/package.json +4 -3
  71. package/packages/electron-client-ipc/src/index.ts +1 -1
  72. package/packages/electron-client-ipc/src/ipc.test.ts +62 -0
  73. package/packages/electron-client-ipc/src/ipc.ts +63 -0
  74. package/packages/electron-client-ipc/src/streamInvoke.ts +7 -1
  75. package/packages/electron-client-ipc/src/types/dispatch.ts +1 -10
  76. package/packages/electron-client-ipc/vitest.config.mts +10 -0
  77. package/packages/electron-server-ipc/src/ipcClient.ts +1 -2
  78. package/packages/electron-server-ipc/src/ipcServer.ts +1 -2
  79. package/packages/electron-server-ipc/src/types/index.ts +1 -5
  80. package/pnpm-workspace.yaml +1 -1
  81. package/scripts/i18nWorkflow/const.ts +2 -2
  82. package/scripts/i18nWorkflow/i18nConfig.ts +7 -0
  83. package/scripts/i18nWorkflow/utils.ts +1 -1
  84. package/src/app/[variants]/(main)/discover/(detail)/provider/features/Sidebar/ActionButton/ProviderConfig.tsx +2 -2
  85. package/src/locales/default/setting.ts +1 -0
  86. package/src/server/modules/ElectronIPCClient/index.ts +59 -13
  87. package/src/services/electron/__tests__/devtools.test.ts +10 -6
  88. package/src/services/electron/autoUpdate.ts +5 -5
  89. package/src/services/electron/desktopNotification.ts +4 -7
  90. package/src/services/electron/devtools.ts +2 -2
  91. package/src/services/electron/file.ts +3 -2
  92. package/src/services/electron/localFileService.ts +17 -16
  93. package/src/services/electron/remoteServer.ts +7 -6
  94. package/src/services/electron/settings.ts +9 -11
  95. package/src/services/electron/system.ts +8 -6
  96. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +1 -1
  97. package/src/store/global/actions/general.ts +8 -10
  98. package/src/utils/electron/desktopRemoteRPCFetch.ts +3 -2
  99. package/src/utils/electron/ipc.ts +12 -0
  100. package/tsconfig.json +5 -0
  101. package/apps/desktop/src/main/types/ipcClientEvent.ts +0 -3
  102. package/packages/electron-client-ipc/src/dispatch.ts +0 -41
@@ -183,10 +183,18 @@ The `App.ts` class orchestrates the entire application lifecycle through key pha
183
183
  #### 🔌 Dependency Injection & Event System
184
184
 
185
185
  - **IoC Container** - WeakMap-based container for decorated controller methods
186
- - **Decorator Registration** - `@ipcClientEvent` and `@ipcServerEvent` decorators
186
+ - **Typed IPC Decorators** - `@IpcMethod` and `@IpcServerMethod` wire controller methods into type-safe channels
187
187
  - **Automatic Event Mapping** - Events registered during controller loading
188
188
  - **Service Locator** - Type-safe service and controller retrieval
189
189
 
190
+ ##### 🧠 Type-Safe IPC Flow
191
+
192
+ - **Async Context Propagation** - `src/main/utils/ipc/base.ts` captures the `IpcContext` with `AsyncLocalStorage`, so controller logic can call `getIpcContext()` anywhere inside an IPC handler without explicitly threading arguments.
193
+ - **Service Constructors Registry** - `src/main/controllers/registry.ts` exports `controllerIpcConstructors`, `DesktopIpcServices`, and `DesktopServerIpcServices`, enabling automatic typing of both renderer and server IPC proxies.
194
+ - **Renderer Proxy Helper** - `src/utils/electron/ipc.ts` exposes `ensureElectronIpc()` which lazily builds a proxy on top of `window.electronAPI.invoke`, giving React/Next.js code a type-safe API surface without exposing raw proxies in preload.
195
+ - **Server Proxy Helper** - `src/server/modules/ElectronIPCClient/index.ts` mirrors the same typing strategy for the Next.js server runtime, providing a dedicated proxy for `@IpcServerMethod` handlers.
196
+ - **Shared Typings Package** - `apps/desktop/src/main/exports.d.ts` augments `@lobechat/electron-client-ipc` so every package can consume `DesktopIpcServices` without importing desktop business code directly.
197
+
190
198
  #### 🪟 Window Management
191
199
 
192
200
  - **Theme-Aware Windows** - Automatic adaptation to system dark/light mode
@@ -235,6 +243,7 @@ The `App.ts` class orchestrates the entire application lifecycle through key pha
235
243
 
236
244
  #### 🎮 Controller Pattern
237
245
 
246
+ - **Typed IPC Decorators** - Controllers extend `ControllerModule` and expose renderer methods via `@IpcMethod`
238
247
  - **IPC Event Handling** - Processes events from renderer with decorator-based registration
239
248
  - **Lifecycle Hooks** - `beforeAppReady` and `afterAppReady` for initialization phases
240
249
  - **Type-Safe Communication** - Strong typing for all IPC events and responses
@@ -256,6 +265,33 @@ The `App.ts` class orchestrates the entire application lifecycle through key pha
256
265
  - **Context Awareness** - Events include sender context for window-specific operations
257
266
  - **Error Propagation** - Centralized error handling with proper status codes
258
267
 
268
+ ##### 🧩 Renderer IPC Helper
269
+
270
+ Renderer code uses a lightweight proxy generated at runtime to keep IPC calls type-safe without exposing raw Electron objects through `contextBridge`. Use the helper exported from `src/utils/electron/ipc.ts` to access the main-process services:
271
+
272
+ ```ts
273
+ import { ensureElectronIpc } from '@/utils/electron/ipc';
274
+
275
+ const ipc = ensureElectronIpc();
276
+ await ipc.windows.openSettingsWindow({ tab: 'provider' });
277
+ ```
278
+
279
+ The helper internally builds a proxy on top of `window.electronAPI.invoke`, so no proxy objects need to be cloned across the preload boundary.
280
+
281
+ ##### 🖥️ Server IPC Helper
282
+
283
+ Next.js (Node) modules use the same proxy pattern via `ensureElectronServerIpc` from `src/server/modules/ElectronIPCClient`. It lazily wraps the socket-based `ElectronIpcClient` so server code can call controllers with full type safety:
284
+
285
+ ```ts
286
+ import { ensureElectronServerIpc } from '@/server/modules/ElectronIPCClient';
287
+
288
+ const ipc = ensureElectronServerIpc();
289
+ const dbPath = await ipc.system.getDatabasePath();
290
+ await ipc.upload.deleteFiles(['foo.txt']);
291
+ ```
292
+
293
+ All server methods are declared via `@IpcServerMethod` and live in dedicated controller classes, keeping renderer typings clean.
294
+
259
295
  #### 🛡️ Security Features
260
296
 
261
297
  - **OAuth 2.0 + PKCE** - Secure authentication with state parameter validation
@@ -183,7 +183,7 @@ src/main/core/
183
183
  #### 🔌 依赖注入和事件系统
184
184
 
185
185
  - **IoC 容器** - 基于 WeakMap 的装饰控制器方法容器
186
- - **装饰器注册** - `@ipcClientEvent` 和 `@ipcServerEvent` 装饰器
186
+ - **装饰器注册** - `@IpcMethod` 和 `@IpcServerMethod` 装饰器
187
187
  - **自动事件映射** - 控制器加载期间注册的事件
188
188
  - **服务定位器** - 类型安全的服务和控制器检索
189
189
 
@@ -256,6 +256,31 @@ src/main/core/
256
256
  - **上下文感知** - 事件包含用于窗口特定操作的发送者上下文
257
257
  - **错误传播** - 具有适当状态码的集中错误处理
258
258
 
259
+ ##### 🧩 渲染器 IPC 助手
260
+
261
+ 渲染端通过 `src/utils/electron/ipc.ts` 提供的 `ensureElectronIpc` 获得一个运行时代理,无需在 preload 中暴露 Proxy 对象即可获得类型安全的调用体验:
262
+
263
+ ```ts
264
+ import { ensureElectronIpc } from '@/utils/electron/ipc';
265
+
266
+ const ipc = ensureElectronIpc();
267
+ await ipc.windows.openSettingsWindow({ tab: 'provider' });
268
+ ```
269
+
270
+ ##### 🖥️ Server IPC 助手
271
+
272
+ Next.js 服务端模块可通过 `ensureElectronServerIpc`(位于 `src/server/modules/ElectronIPCClient`)获得同样的类型安全代理,并复用 socket IPC 通道:
273
+
274
+ ```ts
275
+ import { ensureElectronServerIpc } from '@/server/modules/ElectronIPCClient';
276
+
277
+ const ipc = ensureElectronServerIpc();
278
+ const path = await ipc.system.getDatabasePath();
279
+ await ipc.upload.deleteFiles(['foo.txt']);
280
+ ```
281
+
282
+ 所有 `@IpcServerMethod` 方法都放在独立的控制器中,这样渲染端的类型推导不会包含这些仅供服务器调用的通道。
283
+
259
284
  #### 🛡️ 安全功能
260
285
 
261
286
  - **OAuth 2.0 + PKCE** - 具有状态参数验证的安全认证
@@ -39,6 +39,7 @@ export default defineConfig({
39
39
  resolve: {
40
40
  alias: {
41
41
  '~common': resolve(__dirname, 'src/common'),
42
+ '@': resolve(__dirname, 'src/main'),
42
43
  },
43
44
  },
44
45
  },
@@ -7,7 +7,7 @@ import { URL } from 'node:url';
7
7
  import { createLogger } from '@/utils/logger';
8
8
 
9
9
  import RemoteServerConfigCtr from './RemoteServerConfigCtr';
10
- import { ControllerModule, ipcClientEvent } from './index';
10
+ import { ControllerModule, IpcMethod } from './index';
11
11
 
12
12
  // Create logger
13
13
  const logger = createLogger('controllers:AuthCtr');
@@ -17,6 +17,7 @@ const logger = createLogger('controllers:AuthCtr');
17
17
  * Implements OAuth authorization flow using intermediate page + polling mechanism
18
18
  */
19
19
  export default class AuthCtr extends ControllerModule {
20
+ static override readonly groupName = 'auth';
20
21
  /**
21
22
  * Remote server configuration controller
22
23
  */
@@ -56,7 +57,7 @@ export default class AuthCtr extends ControllerModule {
56
57
  /**
57
58
  * Request OAuth authorization
58
59
  */
59
- @ipcClientEvent('requestAuthorization')
60
+ @IpcMethod()
60
61
  async requestAuthorization(config: DataSyncConfig) {
61
62
  // Clear any old authorization state
62
63
  this.clearAuthorizationState();
@@ -119,7 +120,7 @@ export default class AuthCtr extends ControllerModule {
119
120
  /**
120
121
  * Request Market OAuth authorization (desktop)
121
122
  */
122
- @ipcClientEvent('requestMarketAuthorization')
123
+ @IpcMethod()
123
124
  async requestMarketAuthorization(params: MarketAuthorizationParams) {
124
125
  const { authUrl } = params;
125
126
 
@@ -1,22 +1,21 @@
1
1
  import { InterceptRouteParams, OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
2
2
  import { findMatchingRoute } from '~common/routes';
3
3
 
4
- import {
5
- AppBrowsersIdentifiers,
6
- WindowTemplateIdentifiers,
7
- } from '@/appBrowsers';
8
- import { IpcClientEventSender } from '@/types/ipcClientEvent';
4
+ import { AppBrowsersIdentifiers, WindowTemplateIdentifiers } from '@/appBrowsers';
5
+ import { getIpcContext } from '@/utils/ipc';
9
6
 
10
- import { ControllerModule, ipcClientEvent, shortcut } from './index';
7
+ import { ControllerModule, IpcMethod, shortcut } from './index';
11
8
 
12
9
  export default class BrowserWindowsCtr extends ControllerModule {
10
+ static override readonly groupName = 'windows';
11
+
13
12
  @shortcut('showApp')
14
13
  async toggleMainWindow() {
15
14
  const mainWindow = this.app.browserManager.getMainWindow();
16
15
  mainWindow.toggleVisible();
17
16
  }
18
17
 
19
- @ipcClientEvent('openSettingsWindow')
18
+ @IpcMethod()
20
19
  async openSettingsWindow(options?: string | OpenSettingsWindowOptions) {
21
20
  const normalizedOptions: OpenSettingsWindowOptions =
22
21
  typeof options === 'string' || options === undefined
@@ -53,26 +52,32 @@ export default class BrowserWindowsCtr extends ControllerModule {
53
52
  }
54
53
  }
55
54
 
56
- @ipcClientEvent('closeWindow')
57
- closeWindow(data: undefined, sender: IpcClientEventSender) {
58
- this.app.browserManager.closeWindow(sender.identifier);
55
+ @IpcMethod()
56
+ closeWindow() {
57
+ this.withSenderIdentifier((identifier) => {
58
+ this.app.browserManager.closeWindow(identifier);
59
+ });
59
60
  }
60
61
 
61
- @ipcClientEvent('minimizeWindow')
62
- minimizeWindow(data: undefined, sender: IpcClientEventSender) {
63
- this.app.browserManager.minimizeWindow(sender.identifier);
62
+ @IpcMethod()
63
+ minimizeWindow() {
64
+ this.withSenderIdentifier((identifier) => {
65
+ this.app.browserManager.minimizeWindow(identifier);
66
+ });
64
67
  }
65
68
 
66
- @ipcClientEvent('maximizeWindow')
67
- maximizeWindow(data: undefined, sender: IpcClientEventSender) {
68
- this.app.browserManager.maximizeWindow(sender.identifier);
69
+ @IpcMethod()
70
+ maximizeWindow() {
71
+ this.withSenderIdentifier((identifier) => {
72
+ this.app.browserManager.maximizeWindow(identifier);
73
+ });
69
74
  }
70
75
 
71
76
  /**
72
77
  * Handle route interception requests
73
78
  * Responsible for handling route interception requests from the renderer process
74
79
  */
75
- @ipcClientEvent('interceptRoute')
80
+ @IpcMethod()
76
81
  async interceptRoute(params: InterceptRouteParams) {
77
82
  const { path, source } = params;
78
83
  console.log(
@@ -115,7 +120,7 @@ export default class BrowserWindowsCtr extends ControllerModule {
115
120
  /**
116
121
  * Create a new multi-instance window
117
122
  */
118
- @ipcClientEvent('createMultiInstanceWindow')
123
+ @IpcMethod()
119
124
  async createMultiInstanceWindow(params: {
120
125
  path: string;
121
126
  templateId: WindowTemplateIdentifiers;
@@ -149,7 +154,7 @@ export default class BrowserWindowsCtr extends ControllerModule {
149
154
  /**
150
155
  * Get all windows by template
151
156
  */
152
- @ipcClientEvent('getWindowsByTemplate')
157
+ @IpcMethod()
153
158
  async getWindowsByTemplate(templateId: string) {
154
159
  try {
155
160
  const windowIds = this.app.browserManager.getWindowsByTemplate(templateId);
@@ -169,7 +174,7 @@ export default class BrowserWindowsCtr extends ControllerModule {
169
174
  /**
170
175
  * Close all windows by template
171
176
  */
172
- @ipcClientEvent('closeWindowsByTemplate')
177
+ @IpcMethod()
173
178
  async closeWindowsByTemplate(templateId: string) {
174
179
  try {
175
180
  this.app.browserManager.closeWindowsByTemplate(templateId);
@@ -191,4 +196,12 @@ export default class BrowserWindowsCtr extends ControllerModule {
191
196
  const browser = this.app.browserManager.retrieveByIdentifier(targetWindow);
192
197
  browser.show();
193
198
  }
199
+
200
+ private withSenderIdentifier(fn: (identifier: string) => void) {
201
+ const context = getIpcContext();
202
+ if (!context) return;
203
+ const identifier = this.app.browserManager.getIdentifierByWebContents(context.sender);
204
+ if (!identifier) return;
205
+ fn(identifier);
206
+ }
194
207
  }
@@ -1,7 +1,9 @@
1
- import { ControllerModule, ipcClientEvent } from './index';
1
+ import { ControllerModule, IpcMethod } from './index';
2
2
 
3
3
  export default class DevtoolsCtr extends ControllerModule {
4
- @ipcClientEvent('openDevtools')
4
+ static override readonly groupName = 'devtools';
5
+
6
+ @IpcMethod()
5
7
  async openDevtools() {
6
8
  const devtoolsBrowser = this.app.browserManager.retrieveByIdentifier('devtools');
7
9
  devtoolsBrowser.show();
@@ -30,19 +30,20 @@ import { FileResult, SearchOptions } from '@/types/fileSearch';
30
30
  import { makeSureDirExist } from '@/utils/file-system';
31
31
  import { createLogger } from '@/utils/logger';
32
32
 
33
- import { ControllerModule, ipcClientEvent } from './index';
33
+ import { ControllerModule, IpcMethod } from './index';
34
34
 
35
35
  // Create logger
36
36
  const logger = createLogger('controllers:LocalFileCtr');
37
37
 
38
38
  export default class LocalFileCtr extends ControllerModule {
39
+ static override readonly groupName = 'localSystem';
39
40
  private get searchService() {
40
41
  return this.app.getService(FileSearchService);
41
42
  }
42
43
 
43
44
  // ==================== File Operation ====================
44
45
 
45
- @ipcClientEvent('openLocalFile')
46
+ @IpcMethod()
46
47
  async handleOpenLocalFile({ path: filePath }: OpenLocalFileParams): Promise<{
47
48
  error?: string;
48
49
  success: boolean;
@@ -59,7 +60,7 @@ export default class LocalFileCtr extends ControllerModule {
59
60
  }
60
61
  }
61
62
 
62
- @ipcClientEvent('openLocalFolder')
63
+ @IpcMethod()
63
64
  async handleOpenLocalFolder({ path: targetPath, isDirectory }: OpenLocalFolderParams): Promise<{
64
65
  error?: string;
65
66
  success: boolean;
@@ -77,7 +78,7 @@ export default class LocalFileCtr extends ControllerModule {
77
78
  }
78
79
  }
79
80
 
80
- @ipcClientEvent('readLocalFiles')
81
+ @IpcMethod()
81
82
  async readFiles({ paths }: LocalReadFilesParams): Promise<LocalReadFileResult[]> {
82
83
  logger.debug('Starting batch file reading:', { count: paths.length });
83
84
 
@@ -94,7 +95,7 @@ export default class LocalFileCtr extends ControllerModule {
94
95
  return results;
95
96
  }
96
97
 
97
- @ipcClientEvent('readLocalFile')
98
+ @IpcMethod()
98
99
  async readFile({
99
100
  path: filePath,
100
101
  loc,
@@ -192,7 +193,7 @@ export default class LocalFileCtr extends ControllerModule {
192
193
  }
193
194
  }
194
195
 
195
- @ipcClientEvent('listLocalFiles')
196
+ @IpcMethod()
196
197
  async listLocalFiles({ path: dirPath }: ListLocalFileParams): Promise<FileResult[]> {
197
198
  logger.debug('Listing directory contents:', { dirPath });
198
199
 
@@ -250,7 +251,7 @@ export default class LocalFileCtr extends ControllerModule {
250
251
  }
251
252
  }
252
253
 
253
- @ipcClientEvent('moveLocalFiles')
254
+ @IpcMethod()
254
255
  async handleMoveFiles({ items }: MoveLocalFilesParams): Promise<LocalMoveFilesResultItem[]> {
255
256
  logger.debug('Starting batch file move:', { itemsCount: items?.length });
256
257
 
@@ -355,7 +356,7 @@ export default class LocalFileCtr extends ControllerModule {
355
356
  return results;
356
357
  }
357
358
 
358
- @ipcClientEvent('renameLocalFile')
359
+ @IpcMethod()
359
360
  async handleRenameFile({
360
361
  path: currentPath,
361
362
  newName,
@@ -440,7 +441,7 @@ export default class LocalFileCtr extends ControllerModule {
440
441
  }
441
442
  }
442
443
 
443
- @ipcClientEvent('writeLocalFile')
444
+ @IpcMethod()
444
445
  async handleWriteFile({ path: filePath, content }: WriteLocalFileParams) {
445
446
  const logPrefix = `[Writing file ${filePath}]`;
446
447
  logger.debug(`${logPrefix} Starting to write file`, { contentLength: content?.length });
@@ -485,7 +486,7 @@ export default class LocalFileCtr extends ControllerModule {
485
486
  /**
486
487
  * Handle IPC event for local file search
487
488
  */
488
- @ipcClientEvent('searchLocalFiles')
489
+ @IpcMethod()
489
490
  async handleLocalFilesSearch(params: LocalSearchFilesParams): Promise<FileResult[]> {
490
491
  logger.debug('Received file search request:', {
491
492
  directory: params.directory,
@@ -523,7 +524,7 @@ export default class LocalFileCtr extends ControllerModule {
523
524
  }
524
525
  }
525
526
 
526
- @ipcClientEvent('grepContent')
527
+ @IpcMethod()
527
528
  async handleGrepContent(params: GrepContentParams): Promise<GrepContentResult> {
528
529
  const {
529
530
  pattern,
@@ -639,7 +640,7 @@ export default class LocalFileCtr extends ControllerModule {
639
640
  }
640
641
  }
641
642
 
642
- @ipcClientEvent('globLocalFiles')
643
+ @IpcMethod()
643
644
  async handleGlobFiles({
644
645
  path: searchPath = process.cwd(),
645
646
  pattern,
@@ -680,7 +681,7 @@ export default class LocalFileCtr extends ControllerModule {
680
681
 
681
682
  // ==================== File Editing ====================
682
683
 
683
- @ipcClientEvent('editLocalFile')
684
+ @IpcMethod()
684
685
  async handleEditFile({
685
686
  file_path: filePath,
686
687
  new_string,
@@ -1,10 +1,11 @@
1
- import { ControllerModule, ipcClientEvent } from './index';
1
+ import { ControllerModule, IpcMethod } from './index';
2
2
 
3
3
  export default class MenuController extends ControllerModule {
4
+ static override readonly groupName = 'menu';
4
5
  /**
5
6
  * Refresh menu
6
7
  */
7
- @ipcClientEvent('refreshAppMenu')
8
+ @IpcMethod()
8
9
  refreshAppMenu() {
9
10
  // Note: May need to decide whether to allow renderer process to refresh all menus based on specific circumstances
10
11
  return this.app.menuManager.refreshMenus();
@@ -13,7 +14,7 @@ export default class MenuController extends ControllerModule {
13
14
  /**
14
15
  * Show context menu
15
16
  */
16
- @ipcClientEvent('showContextMenu')
17
+ @IpcMethod()
17
18
  showContextMenu(params: { data?: any; type: string }) {
18
19
  return this.app.menuManager.showContextMenu(params.type, params.data);
19
20
  }
@@ -21,7 +22,7 @@ export default class MenuController extends ControllerModule {
21
22
  /**
22
23
  * Set development menu visibility
23
24
  */
24
- @ipcClientEvent('setDevMenuVisibility')
25
+ @IpcMethod()
25
26
  setDevMenuVisibility(visible: boolean) {
26
27
  // Call MenuManager method to rebuild application menu
27
28
  return this.app.menuManager.rebuildAppMenu({ showDevItems: visible });
@@ -11,7 +11,7 @@ import {
11
11
  ProxyDispatcherManager,
12
12
  ProxyTestResult,
13
13
  } from '../modules/networkProxy';
14
- import { ControllerModule, ipcClientEvent } from './index';
14
+ import { ControllerModule, IpcMethod } from './index';
15
15
 
16
16
  // Create logger
17
17
  const logger = createLogger('controllers:NetworkProxyCtr');
@@ -21,10 +21,11 @@ const logger = createLogger('controllers:NetworkProxyCtr');
21
21
  * 处理桌面应用的网络代理相关功能
22
22
  */
23
23
  export default class NetworkProxyCtr extends ControllerModule {
24
+ static override readonly groupName = 'networkProxy';
24
25
  /**
25
26
  * 获取代理设置
26
27
  */
27
- @ipcClientEvent('getProxySettings')
28
+ @IpcMethod()
28
29
  async getDesktopSettings(): Promise<NetworkProxySettings> {
29
30
  try {
30
31
  const settings = this.app.storeManager.get(
@@ -45,32 +46,30 @@ export default class NetworkProxyCtr extends ControllerModule {
45
46
  /**
46
47
  * 设置代理配置
47
48
  */
48
- @ipcClientEvent('setProxySettings')
49
- async setProxySettings(config: NetworkProxySettings): Promise<void> {
49
+ @IpcMethod()
50
+ async setProxySettings(config: Partial<NetworkProxySettings>): Promise<void> {
50
51
  try {
51
- // 验证配置
52
- const validation = ProxyConfigValidator.validate(config);
53
- if (!validation.isValid) {
54
- const errorMessage = `Invalid proxy configuration: ${validation.errors.join(', ')}`;
55
- logger.error(errorMessage);
56
- throw new Error(errorMessage);
57
- }
58
-
59
52
  // 获取当前配置
60
53
  const currentConfig = this.app.storeManager.get(
61
54
  'networkProxy',
62
55
  defaultProxySettings,
63
56
  ) as NetworkProxySettings;
64
57
 
65
- // 检查是否有变化
66
- if (isEqual(currentConfig, config)) {
58
+ // 合并配置并验证
59
+ const newConfig = merge({}, currentConfig, config);
60
+
61
+ const validation = ProxyConfigValidator.validate(newConfig);
62
+ if (!validation.isValid) {
63
+ const errorMessage = `Invalid proxy configuration: ${validation.errors.join(', ')}`;
64
+ logger.error(errorMessage);
65
+ throw new Error(errorMessage);
66
+ }
67
+
68
+ if (isEqual(currentConfig, newConfig)) {
67
69
  logger.debug('Proxy settings unchanged, skipping update');
68
70
  return;
69
71
  }
70
72
 
71
- // 合并配置
72
- const newConfig = merge({}, currentConfig, config);
73
-
74
73
  // 应用代理设置
75
74
  await ProxyDispatcherManager.applyProxySettings(newConfig);
76
75
 
@@ -92,7 +91,7 @@ export default class NetworkProxyCtr extends ControllerModule {
92
91
  /**
93
92
  * 测试代理连接
94
93
  */
95
- @ipcClientEvent('testProxyConnection')
94
+ @IpcMethod()
96
95
  async testProxyConnection(url: string): Promise<{ message?: string; success: boolean }> {
97
96
  try {
98
97
  const result = await ProxyConnectionTester.testConnection(url);
@@ -112,7 +111,7 @@ export default class NetworkProxyCtr extends ControllerModule {
112
111
  /**
113
112
  * 测试指定代理配置
114
113
  */
115
- @ipcClientEvent('testProxyConfig')
114
+ @IpcMethod()
116
115
  async testProxyConfig({
117
116
  config,
118
117
  testUrl,
@@ -7,11 +7,12 @@ import { macOS, windows } from 'electron-is';
7
7
 
8
8
  import { createLogger } from '@/utils/logger';
9
9
 
10
- import { ControllerModule, ipcClientEvent } from './index';
10
+ import { ControllerModule, IpcMethod } from './index';
11
11
 
12
12
  const logger = createLogger('controllers:NotificationCtr');
13
13
 
14
14
  export default class NotificationCtr extends ControllerModule {
15
+ static override readonly groupName = 'notification';
15
16
  /**
16
17
  * Set up desktop notifications after the application is ready
17
18
  */
@@ -51,7 +52,7 @@ export default class NotificationCtr extends ControllerModule {
51
52
  /**
52
53
  * Show system desktop notification (only when window is hidden)
53
54
  */
54
- @ipcClientEvent('showDesktopNotification')
55
+ @IpcMethod()
55
56
  async showDesktopNotification(
56
57
  params: ShowDesktopNotificationParams,
57
58
  ): Promise<DesktopNotificationResult> {
@@ -126,7 +127,7 @@ export default class NotificationCtr extends ControllerModule {
126
127
  /**
127
128
  * Check if the main window is hidden
128
129
  */
129
- @ipcClientEvent('isMainWindowHidden')
130
+ @IpcMethod()
130
131
  isMainWindowHidden(): boolean {
131
132
  try {
132
133
  const mainWindow = this.app.browserManager.getMainWindow();
@@ -7,7 +7,7 @@ import { URL } from 'node:url';
7
7
  import { OFFICIAL_CLOUD_SERVER } from '@/const/env';
8
8
  import { createLogger } from '@/utils/logger';
9
9
 
10
- import { ControllerModule, ipcClientEvent } from './index';
10
+ import { ControllerModule, IpcMethod } from './index';
11
11
 
12
12
  /**
13
13
  * Non-retryable OIDC error codes
@@ -39,6 +39,7 @@ const logger = createLogger('controllers:RemoteServerConfigCtr');
39
39
  * Used to manage custom remote LobeChat server configuration
40
40
  */
41
41
  export default class RemoteServerConfigCtr extends ControllerModule {
42
+ static override readonly groupName = 'remoteServer';
42
43
  /**
43
44
  * Key used to store encrypted tokens in electron-store.
44
45
  */
@@ -47,7 +48,7 @@ export default class RemoteServerConfigCtr extends ControllerModule {
47
48
  /**
48
49
  * Get remote server configuration
49
50
  */
50
- @ipcClientEvent('getRemoteServerConfig')
51
+ @IpcMethod()
51
52
  async getRemoteServerConfig() {
52
53
  logger.debug('Getting remote server configuration');
53
54
  const { storeManager } = this.app;
@@ -64,7 +65,7 @@ export default class RemoteServerConfigCtr extends ControllerModule {
64
65
  /**
65
66
  * Set remote server configuration
66
67
  */
67
- @ipcClientEvent('setRemoteServerConfig')
68
+ @IpcMethod()
68
69
  async setRemoteServerConfig(config: Partial<DataSyncConfig>) {
69
70
  logger.info(
70
71
  `Setting remote server storageMode: active=${config.active}, storageMode=${config.storageMode}, url=${config.remoteServerUrl}`,
@@ -81,7 +82,7 @@ export default class RemoteServerConfigCtr extends ControllerModule {
81
82
  /**
82
83
  * Clear remote server configuration
83
84
  */
84
- @ipcClientEvent('clearRemoteServerConfig')
85
+ @IpcMethod()
85
86
  async clearRemoteServerConfig() {
86
87
  logger.info('Clearing remote server configuration');
87
88
  const { storeManager } = this.app;
@@ -15,7 +15,7 @@ import { defaultProxySettings } from '@/const/store';
15
15
  import { createLogger } from '@/utils/logger';
16
16
 
17
17
  import RemoteServerConfigCtr from './RemoteServerConfigCtr';
18
- import { ControllerModule, ipcClientEvent } from './index';
18
+ import { ControllerModule, IpcMethod } from './index';
19
19
 
20
20
  // Create logger
21
21
  const logger = createLogger('controllers:RemoteServerSyncCtr');
@@ -25,6 +25,7 @@ const logger = createLogger('controllers:RemoteServerSyncCtr');
25
25
  * For handling data synchronization with remote servers via IPC.
26
26
  */
27
27
  export default class RemoteServerSyncCtr extends ControllerModule {
28
+ static override readonly groupName = 'remoteServerSync';
28
29
  /**
29
30
  * Cached instance of RemoteServerConfigCtr
30
31
  */
@@ -345,7 +346,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
345
346
  * Handles the 'proxy-trpc-request' IPC call from the renderer process.
346
347
  * This method should be invoked by the ipcMain.handle setup in your main process entry point.
347
348
  */
348
- @ipcClientEvent('proxyTRPCRequest')
349
+ @IpcMethod()
349
350
  public async proxyTRPCRequest(args: ProxyTRPCRequestParams): Promise<ProxyTRPCRequestResult> {
350
351
  logger.debug('Received proxyTRPCRequest IPC call:', {
351
352
  headers: args.headers,
@@ -11,7 +11,7 @@ import { randomUUID } from 'node:crypto';
11
11
 
12
12
  import { createLogger } from '@/utils/logger';
13
13
 
14
- import { ControllerModule, ipcClientEvent } from './index';
14
+ import { ControllerModule, IpcMethod } from './index';
15
15
 
16
16
  const logger = createLogger('controllers:ShellCommandCtr');
17
17
 
@@ -24,10 +24,11 @@ interface ShellProcess {
24
24
  }
25
25
 
26
26
  export default class ShellCommandCtr extends ControllerModule {
27
+ static override readonly groupName = 'shellCommand';
27
28
  // Shell process management
28
29
  private shellProcesses = new Map<string, ShellProcess>();
29
30
 
30
- @ipcClientEvent('runCommand')
31
+ @IpcMethod()
31
32
  async handleRunCommand({
32
33
  command,
33
34
  description,
@@ -153,7 +154,7 @@ export default class ShellCommandCtr extends ControllerModule {
153
154
  }
154
155
  }
155
156
 
156
- @ipcClientEvent('getCommandOutput')
157
+ @IpcMethod()
157
158
  async handleGetCommandOutput({
158
159
  filter,
159
160
  shell_id,
@@ -212,7 +213,7 @@ export default class ShellCommandCtr extends ControllerModule {
212
213
  };
213
214
  }
214
215
 
215
- @ipcClientEvent('killCommand')
216
+ @IpcMethod()
216
217
  async handleKillCommand({ shell_id }: KillCommandParams): Promise<KillCommandResult> {
217
218
  const logPrefix = `[killCommand: ${shell_id}]`;
218
219
  logger.debug(`${logPrefix} Attempting to kill shell`);
@@ -1,12 +1,13 @@
1
1
  import { ShortcutUpdateResult } from '@/core/ui/ShortcutManager';
2
2
 
3
- import { ControllerModule, ipcClientEvent } from '.';
3
+ import { ControllerModule, IpcMethod } from '.';
4
4
 
5
5
  export default class ShortcutController extends ControllerModule {
6
+ static override readonly groupName = 'shortcut';
6
7
  /**
7
8
  * Get all shortcut configurations
8
9
  */
9
- @ipcClientEvent('getShortcutsConfig')
10
+ @IpcMethod()
10
11
  getShortcutsConfig() {
11
12
  return this.app.shortcutManager.getShortcutsConfig();
12
13
  }
@@ -14,7 +15,7 @@ export default class ShortcutController extends ControllerModule {
14
15
  /**
15
16
  * Update a single shortcut configuration
16
17
  */
17
- @ipcClientEvent('updateShortcutConfig')
18
+ @IpcMethod()
18
19
  updateShortcutConfig({
19
20
  id,
20
21
  accelerator,