@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
@@ -36,13 +36,13 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
36
36
  1. **创建控制器 (Controller)**
37
37
  - 位置:`apps/desktop/src/main/controllers/`
38
38
  - 示例:创建 `NewFeatureCtr.ts`
39
- - 规范:按 `_template.ts` 模板格式实现
40
- - 注册:在 `apps/desktop/src/main/controllers/index.ts` 导出
39
+ - 需继承 `ControllerModule`,并设置 `static readonly groupName`(例如 `static override readonly groupName = 'newFeature';`)
40
+ - `_template.ts` 模板格式实现,并在 `apps/desktop/src/main/controllers/registry.ts` 的 `controllerIpcConstructors`(或 `controllerServerIpcConstructors`)中注册,保证类型推导与自动装配
41
41
 
42
42
  2. **定义 IPC 事件处理器**
43
- - 使用 `@ipcClientEvent('eventName')` 装饰器注册事件处理函数
44
- - 处理函数应接收前端传递的参数并返回结果
45
- - 处理可能的错误情况
43
+ - 使用 `@IpcMethod()` 装饰器暴露渲染进程可访问的通道,或使用 `@IpcServerMethod()` 声明仅供 Next.js 服务器调用的 IPC
44
+ - 通道名称基于 `groupName.methodName` 自动生成,不再手动拼接字符串
45
+ - 处理函数可通过 `getIpcContext()` 获取 `sender`、`event` 等上下文信息,并按照需要返回结构化结果
46
46
 
47
47
  3. **实现业务逻辑**
48
48
  - 可能需要调用 Electron API 或 Node.js 原生模块
@@ -60,15 +60,17 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
60
60
  1. **创建服务层**
61
61
  - 位置:`src/services/electron/`
62
62
  - 添加服务方法调用 IPC
63
- - 使用 `dispatch` 或 `invoke` 函数
63
+ - 使用 `ensureElectronIpc()` 生成的类型安全代理,避免手动拼通道名称
64
64
 
65
65
  ```typescript
66
66
  // src/services/electron/newFeatureService.ts
67
- import { dispatch } from '@lobechat/electron-client-ipc';
68
- import { NewFeatureParams } from 'types';
67
+ import { ensureElectronIpc } from '@/utils/electron/ipc';
68
+ import type { NewFeatureParams } from '@lobechat/electron-client-ipc';
69
+
70
+ const ipc = ensureElectronIpc();
69
71
 
70
72
  export const newFeatureService = async (params: NewFeatureParams) => {
71
- return dispatch('newFeatureEventName', params);
73
+ return ipc.newFeature.doSomething(params);
72
74
  };
73
75
  ```
74
76
 
@@ -118,36 +120,31 @@ LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架
118
120
 
119
121
  ```typescript
120
122
  // apps/desktop/src/main/controllers/NotificationCtr.ts
121
- import { BrowserWindow, Notification } from 'electron';
122
- import { ipcClientEvent } from 'electron-client-ipc';
123
-
124
- interface ShowNotificationParams {
125
- title: string;
126
- body: string;
127
- }
123
+ import { Notification } from 'electron';
124
+ import { ControllerModule, IpcMethod } from '@/controllers';
125
+ import type {
126
+ DesktopNotificationResult,
127
+ ShowDesktopNotificationParams,
128
+ } from '@lobechat/electron-client-ipc';
129
+
130
+ export default class NotificationCtr extends ControllerModule {
131
+ static override readonly groupName = 'notification';
132
+
133
+ @IpcMethod()
134
+ async showDesktopNotification(
135
+ params: ShowDesktopNotificationParams,
136
+ ): Promise<DesktopNotificationResult> {
137
+ if (!Notification.isSupported()) {
138
+ return { error: 'Notifications not supported', success: false };
139
+ }
128
140
 
129
- export class NotificationCtr {
130
- @ipcClientEvent('showNotification')
131
- async handleShowNotification({ title, body }: ShowNotificationParams) {
132
141
  try {
133
- if (!Notification.isSupported()) {
134
- return { success: false, error: 'Notifications not supported' };
135
- }
136
-
137
- const notification = new Notification({
138
- title,
139
- body,
140
- });
141
-
142
+ const notification = new Notification({ body: params.body, title: params.title });
142
143
  notification.show();
143
-
144
144
  return { success: true };
145
145
  } catch (error) {
146
- console.error('Failed to show notification:', error);
147
- return {
148
- success: false,
149
- error: error instanceof Error ? error.message : 'Unknown error'
150
- };
146
+ console.error('[NotificationCtr] Failed to show notification:', error);
147
+ return { error: error instanceof Error ? error.message : 'Unknown error', success: false };
151
148
  }
152
149
  }
153
150
  }
@@ -51,15 +51,15 @@ alwaysApply: false
51
51
  * 导入在步骤 2 中定义的 IPC 参数类型。
52
52
  * 添加一个新的 `async` 方法,方法名通常与 Action 名称对应 (例如: `renameLocalFile`)。
53
53
  * 方法接收 `params` (符合 IPC 参数类型)。
54
- * 使用从 `@lobechat/electron-client-ipc` 导入的 `dispatch` (或 `invoke`) 函数,调用与 Manifest 中 `name` 字段匹配的 IPC 事件名称,并将 `params` 传递过去。
54
+ * 通过 `ensureElectronIpc()` 获取 IPC 代理 (`const ipc = ensureElectronIpc();`),调用与 Manifest 中 `name` 字段匹配的链式方法,并将 `params` 传递过去。
55
55
  * 定义方法的返回类型,通常是 `Promise<{ success: boolean; error?: string }>`,与后端 Controller 返回的结构一致。
56
56
 
57
57
  5. **实现后端逻辑 (Controller / IPC Handler):**
58
58
  * **文件:** `apps/desktop/src/main/controllers/[ToolName]Ctr.ts` (例如: `apps/desktop/src/main/controllers/LocalFileCtr.ts`)
59
59
  * **操作:**
60
- * 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ipcClientEvent`, 参数类型等)。
60
+ * 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ControllerModule`, `IpcMethod`/`IpcServerMethod`、参数类型等)。
61
61
  * 添加一个新的 `async` 方法,方法名通常以 `handle` 开头 (例如: `handleRenameFile`)。
62
- * 使用 `@ipcClientEvent('yourApiName')` 装饰器将此方法注册为对应 IPC 事件的处理器,确保 `'yourApiName'` 与 Manifest 中的 `name` Service 层调用的事件名称一致。
62
+ * 使用 `@IpcMethod()` 或 `@IpcServerMethod()` 装饰器将此方法注册为对应 IPC 事件的处理器,确保方法名与 Manifest 中的 `name` 以及 Service 层的链式调用一致。
63
63
  * 方法的参数应解构自 Service 层传递过来的对象,类型与步骤 2 中定义的 IPC 参数类型匹配。
64
64
  * 实现核心业务逻辑:
65
65
  * 进行必要的输入验证。
@@ -149,50 +149,52 @@ export const createMainWindow = () => {
149
149
 
150
150
  1. **在主进程中注册 IPC 处理器**
151
151
  ```typescript
152
- // BrowserWindowsCtr.ts
153
- @ipcClientEvent('minimizeWindow')
154
- handleMinimizeWindow() {
155
- const focusedWindow = BrowserWindow.getFocusedWindow();
156
- if (focusedWindow) {
157
- focusedWindow.minimize();
152
+ // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
153
+ import { BrowserWindow } from 'electron';
154
+ import { ControllerModule, IpcMethod } from '@/controllers';
155
+
156
+ export default class BrowserWindowsCtr extends ControllerModule {
157
+ static override readonly groupName = 'windows';
158
+
159
+ @IpcMethod()
160
+ minimizeWindow() {
161
+ const focusedWindow = BrowserWindow.getFocusedWindow();
162
+ focusedWindow?.minimize();
163
+ return { success: true };
158
164
  }
159
- return { success: true };
160
- }
161
165
 
162
- @ipcClientEvent('maximizeWindow')
163
- handleMaximizeWindow() {
164
- const focusedWindow = BrowserWindow.getFocusedWindow();
165
- if (focusedWindow) {
166
- if (focusedWindow.isMaximized()) {
167
- focusedWindow.restore();
168
- } else {
169
- focusedWindow.maximize();
170
- }
166
+ @IpcMethod()
167
+ maximizeWindow() {
168
+ const focusedWindow = BrowserWindow.getFocusedWindow();
169
+ if (focusedWindow?.isMaximized()) focusedWindow.restore();
170
+ else focusedWindow?.maximize();
171
+ return { success: true };
171
172
  }
172
- return { success: true };
173
- }
174
173
 
175
- @ipcClientEvent('closeWindow')
176
- handleCloseWindow() {
177
- const focusedWindow = BrowserWindow.getFocusedWindow();
178
- if (focusedWindow) {
179
- focusedWindow.close();
174
+ @IpcMethod()
175
+ closeWindow() {
176
+ BrowserWindow.getFocusedWindow()?.close();
177
+ return { success: true };
180
178
  }
181
- return { success: true };
182
179
  }
183
180
  ```
181
+ - `@IpcMethod()` 根据控制器的 `groupName` 自动将方法映射为 `windows.minimizeWindow` 形式的通道名称。
182
+ - 控制器需继承 `ControllerModule`,并在 `controllers/registry.ts` 中通过 `controllerIpcConstructors` 注册,便于类型生成。
184
183
 
185
184
  2. **在渲染进程中调用**
186
185
  ```typescript
187
186
  // src/services/electron/windowService.ts
188
- import { dispatch } from '@lobechat/electron-client-ipc';
187
+ import { ensureElectronIpc } from '@/utils/electron/ipc';
188
+
189
+ const ipc = ensureElectronIpc();
189
190
 
190
191
  export const windowService = {
191
- minimize: () => dispatch('minimizeWindow'),
192
- maximize: () => dispatch('maximizeWindow'),
193
- close: () => dispatch('closeWindow'),
192
+ minimize: () => ipc.windows.minimizeWindow(),
193
+ maximize: () => ipc.windows.maximizeWindow(),
194
+ close: () => ipc.windows.closeWindow(),
194
195
  };
195
196
  ```
197
+ - `ensureElectronIpc()` 会基于 `DesktopIpcServices` 运行时生成 Proxy,并通过 `window.electronAPI.invoke` 与主进程通信;不再直接使用 `dispatch`。
196
198
 
197
199
  ### 5. 自定义窗口控制 (无边框窗口)
198
200
 
@@ -252,45 +254,33 @@ export const createMainWindow = () => {
252
254
 
253
255
  ```typescript
254
256
  // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
257
+ import type { OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
258
+ import { ControllerModule, IpcMethod } from '@/controllers';
259
+
260
+ export default class BrowserWindowsCtr extends ControllerModule {
261
+ static override readonly groupName = 'windows';
262
+
263
+ @IpcMethod()
264
+ async openSettingsWindow(options?: string | OpenSettingsWindowOptions) {
265
+ const normalizedOptions =
266
+ typeof options === 'string' || options === undefined
267
+ ? { tab: typeof options === 'string' ? options : undefined }
268
+ : options;
269
+
270
+ const mainWindow = this.app.browserManager.getMainWindow();
271
+ const query = new URLSearchParams();
272
+ if (normalizedOptions.tab) query.set('active', normalizedOptions.tab);
273
+ if (normalizedOptions.searchParams) {
274
+ for (const [key, value] of Object.entries(normalizedOptions.searchParams)) {
275
+ if (value) query.set(key, value);
276
+ }
277
+ }
278
+
279
+ const fullPath = `/settings${query.size ? `?${query.toString()}` : ''}`;
280
+ await mainWindow.loadUrl(fullPath);
281
+ mainWindow.show();
255
282
 
256
- @ipcClientEvent('openSettings')
257
- handleOpenSettings() {
258
- // 检查设置窗口是否已经存在
259
- if (this.settingsWindow && !this.settingsWindow.isDestroyed()) {
260
- // 如果窗口已存在,将其置于前台
261
- this.settingsWindow.focus();
262
283
  return { success: true };
263
284
  }
264
-
265
- // 创建新窗口
266
- this.settingsWindow = new BrowserWindow({
267
- width: 800,
268
- height: 600,
269
- title: 'Settings',
270
- parent: this.mainWindow, // 设置父窗口,使其成为模态窗口
271
- modal: true,
272
- webPreferences: {
273
- preload: path.join(__dirname, '../preload/index.js'),
274
- contextIsolation: true,
275
- nodeIntegration: false,
276
- },
277
- });
278
-
279
- // 加载设置页面
280
- if (isDev) {
281
- this.settingsWindow.loadURL('http://localhost:3000/settings');
282
- } else {
283
- this.settingsWindow.loadFile(
284
- path.join(__dirname, '../../renderer/index.html'),
285
- { hash: 'settings' }
286
- );
287
- }
288
-
289
- // 监听窗口关闭事件
290
- this.settingsWindow.on('closed', () => {
291
- this.settingsWindow = null;
292
- });
293
-
294
- return { success: true };
295
285
  }
296
286
  ```
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.166](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.165...v2.0.0-next.166)
6
+
7
+ <sup>Released on **2025-12-09**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **Dockerfile**: Electron main typing pkg.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **Dockerfile**: Electron main typing pkg, closes [#10693](https://github.com/lobehub/lobe-chat/issues/10693) ([f3357b0](https://github.com/lobehub/lobe-chat/commit/f3357b0))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.165](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.164...v2.0.0-next.165)
31
+
32
+ <sup>Released on **2025-12-09**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **electron-main**: Client ipc decorate.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **electron-main**: Client ipc decorate, closes [#10679](https://github.com/lobehub/lobe-chat/issues/10679) ([f74befa](https://github.com/lobehub/lobe-chat/commit/f74befa))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.164](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.163...v2.0.0-next.164)
6
56
 
7
57
  <sup>Released on **2025-12-08**</sup>
package/Dockerfile CHANGED
@@ -8,29 +8,24 @@ ARG USE_CN_MIRROR
8
8
 
9
9
  ENV DEBIAN_FRONTEND="noninteractive"
10
10
 
11
- RUN \
12
- # If you want to build docker in China, build with --build-arg USE_CN_MIRROR=true
13
- if [ "${USE_CN_MIRROR:-false}" = "true" ]; then \
14
- sed -i "s/deb.debian.org/mirrors.ustc.edu.cn/g" "/etc/apt/sources.list.d/debian.sources"; \
15
- fi \
16
- # Add required package
17
- && apt update \
18
- && apt install ca-certificates proxychains-ng -qy \
19
- # Prepare required package to distroless
20
- && mkdir -p /distroless/bin /distroless/etc /distroless/etc/ssl/certs /distroless/lib \
21
- # Copy proxychains to distroless
22
- && cp /usr/lib/$(arch)-linux-gnu/libproxychains.so.4 /distroless/lib/libproxychains.so.4 \
23
- && cp /usr/lib/$(arch)-linux-gnu/libdl.so.2 /distroless/lib/libdl.so.2 \
24
- && cp /usr/bin/proxychains4 /distroless/bin/proxychains \
25
- && cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf \
26
- # Copy node to distroless
27
- && cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6 \
28
- && cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1 \
29
- && cp /usr/local/bin/node /distroless/bin/node \
30
- # Copy CA certificates to distroless
31
- && cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt \
32
- # Cleanup temp files
33
- && rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*
11
+ RUN <<'EOF'
12
+ set -e
13
+ if [ "${USE_CN_MIRROR:-false}" = "true" ]; then
14
+ sed -i "s/deb.debian.org/mirrors.ustc.edu.cn/g" "/etc/apt/sources.list.d/debian.sources"
15
+ fi
16
+ apt update
17
+ apt install ca-certificates proxychains-ng -qy
18
+ mkdir -p /distroless/bin /distroless/etc /distroless/etc/ssl/certs /distroless/lib
19
+ cp /usr/lib/$(arch)-linux-gnu/libproxychains.so.4 /distroless/lib/libproxychains.so.4
20
+ cp /usr/lib/$(arch)-linux-gnu/libdl.so.2 /distroless/lib/libdl.so.2
21
+ cp /usr/bin/proxychains4 /distroless/bin/proxychains
22
+ cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf
23
+ cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6
24
+ cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1
25
+ cp /usr/local/bin/node /distroless/bin/node
26
+ cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt
27
+ rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*
28
+ EOF
34
29
 
35
30
  ## Builder image, install all the dependencies and build the app
36
31
  FROM base AS builder
@@ -86,29 +81,26 @@ WORKDIR /app
86
81
  COPY package.json pnpm-workspace.yaml ./
87
82
  COPY .npmrc ./
88
83
  COPY packages ./packages
89
-
90
- RUN \
91
- # If you want to build docker in China, build with --build-arg USE_CN_MIRROR=true
92
- if [ "${USE_CN_MIRROR:-false}" = "true" ]; then \
93
- export SENTRYCLI_CDNURL="https://npmmirror.com/mirrors/sentry-cli"; \
94
- npm config set registry "https://registry.npmmirror.com/"; \
95
- echo 'canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas' >> .npmrc; \
96
- fi \
97
- # Set the registry for corepack
98
- && export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\/$//') \
99
- # Update corepack to latest (nodejs/corepack#612)
100
- && npm i -g corepack@latest \
101
- # Enable corepack
102
- && corepack enable \
103
- # Use pnpm for corepack
104
- && corepack use $(sed -n 's/.*"packageManager": "\(.*\)".*/\1/p' package.json) \
105
- # Install the dependencies
106
- && pnpm i \
107
- # Add db migration dependencies
108
- && mkdir -p /deps \
109
- && cd /deps \
110
- && pnpm init \
111
- && pnpm add pg drizzle-orm
84
+ # bring in desktop workspace manifest so pnpm can resolve it
85
+ COPY apps/desktop/src/main/package.json ./apps/desktop/src/main/package.json
86
+
87
+ RUN <<'EOF'
88
+ set -e
89
+ if [ "${USE_CN_MIRROR:-false}" = "true" ]; then
90
+ export SENTRYCLI_CDNURL="https://npmmirror.com/mirrors/sentry-cli"
91
+ npm config set registry "https://registry.npmmirror.com/"
92
+ echo 'canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas' >> .npmrc
93
+ fi
94
+ export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\/$//')
95
+ npm i -g corepack@latest
96
+ corepack enable
97
+ corepack use $(sed -n 's/.*"packageManager": "\(.*\)".*/\1/p' package.json)
98
+ pnpm i
99
+ mkdir -p /deps
100
+ cd /deps
101
+ pnpm init
102
+ pnpm add pg drizzle-orm
103
+ EOF
112
104
 
113
105
  COPY . .
114
106
 
@@ -137,12 +129,12 @@ COPY --from=builder /deps/node_modules/drizzle-orm /app/node_modules/drizzle-orm
137
129
  # Copy server launcher
138
130
  COPY --from=builder /app/scripts/serverLauncher/startServer.js /app/startServer.js
139
131
 
140
- RUN \
141
- # Add nextjs:nodejs to run the app
142
- addgroup -S -g 1001 nodejs \
143
- && adduser -D -G nodejs -H -S -h /app -u 1001 nextjs \
144
- # Set permission for nextjs:nodejs
145
- && chown -R nextjs:nodejs /app /etc/proxychains4.conf
132
+ RUN <<'EOF'
133
+ set -e
134
+ addgroup -S -g 1001 nodejs
135
+ adduser -D -G nodejs -H -S -h /app -u 1001 nextjs
136
+ chown -R nextjs:nodejs /app /etc/proxychains4.conf
137
+ EOF
146
138
 
147
139
  ## Production image, copy all the files and run next
148
140
  FROM scratch
package/README.md CHANGED
@@ -345,12 +345,12 @@ In addition, these plugins are not limited to news aggregation, but can also ext
345
345
 
346
346
  <!-- PLUGIN LIST -->
347
347
 
348
- | Recent Submits | Description |
349
- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
350
- | [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-11-28**</sup> | Analyze stocks and get comprehensive real-time investment data and analytics.<br/>`stock` |
351
- | [SEO](https://lobechat.com/discover/plugin/SEO)<br/><sup>By **orrenprunckun** on **2025-11-14**</sup> | Enter any URL and keyword and get an On-Page SEO analysis & insights!<br/>`seo` |
352
- | [Shopping tools](https://lobechat.com/discover/plugin/ShoppingTools)<br/><sup>By **shoppingtools** on **2025-10-27**</sup> | Search for products on eBay & AliExpress, find eBay events & coupons. Get prompt examples.<br/>`shopping` `e-bay` `ali-express` `coupons` |
353
- | [Web](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | Smart web search that reads and analyzes pages to deliver comprehensive answers from Google results.<br/>`web` `search` |
348
+ | Recent Submits | Description |
349
+ | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
350
+ | [AladinBooks](https://lobechat.com/discover/plugin/AladinSearchBooks)<br/><sup>By **azurewebsites** on **2025-12-08**</sup> | Search for books on Aladin.<br/>`book` `search` |
351
+ | [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-11-28**</sup> | Analyze stocks and get comprehensive real-time investment data and analytics.<br/>`stock` |
352
+ | [SEO](https://lobechat.com/discover/plugin/SEO)<br/><sup>By **orrenprunckun** on **2025-11-14**</sup> | Enter any URL and keyword and get an On-Page SEO analysis & insights!<br/>`seo` |
353
+ | [Shopping tools](https://lobechat.com/discover/plugin/ShoppingTools)<br/><sup>By **shoppingtools** on **2025-10-27**</sup> | Search for products on eBay & AliExpress, find eBay events & coupons. Get prompt examples.<br/>`shopping` `e-bay` `ali-express` `coupons` |
354
354
 
355
355
  > 📊 Total plugins: [<kbd>**41**</kbd>](https://lobechat.com/discover/plugins)
356
356
 
package/README.zh-CN.md CHANGED
@@ -338,12 +338,12 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
338
338
 
339
339
  <!-- PLUGIN LIST -->
340
340
 
341
- | 最近新增 | 描述 |
342
- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
343
- | [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-11-28**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
344
- | [SEO](https://lobechat.com/discover/plugin/SEO)<br/><sup>By **orrenprunckun** on **2025-11-14**</sup> | 输入任何 URL 和关键词,获取页面 SEO 分析和见解!<br/>`seo` |
345
- | [购物工具](https://lobechat.com/discover/plugin/ShoppingTools)<br/><sup>By **shoppingtools** on **2025-10-27**</sup> | eBay AliExpress 上搜索产品,查找 eBay 活动和优惠券。获取快速示例。<br/>`购物` `e-bay` `ali-express` `优惠券` |
346
- | [网页](https://lobechat.com/discover/plugin/web)<br/><sup>By **Proghit** on **2025-01-24**</sup> | 智能网页搜索,读取和分析页面,以提供来自 Google 结果的全面答案。<br/>`网页` `搜索` |
341
+ | 最近新增 | 描述 |
342
+ | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
343
+ | [AladinBooks](https://lobechat.com/discover/plugin/AladinSearchBooks)<br/><sup>By **azurewebsites** on **2025-12-08**</sup> | 在阿拉丁上搜索书籍。<br/>`书籍` `搜索` |
344
+ | [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2025-11-28**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
345
+ | [SEO](https://lobechat.com/discover/plugin/SEO)<br/><sup>By **orrenprunckun** on **2025-11-14**</sup> | 输入任何 URL 和关键词,获取页面 SEO 分析和见解!<br/>`seo` |
346
+ | [购物工具](https://lobechat.com/discover/plugin/ShoppingTools)<br/><sup>By **shoppingtools** on **2025-10-27**</sup> | eBay 和 AliExpress 上搜索产品,查找 eBay 活动和优惠券。获取快速示例。<br/>`购物` `e-bay` `ali-express` `优惠券` |
347
347
 
348
348
  > 📊 Total plugins: [<kbd>**41**</kbd>](https://lobechat.com/discover/plugins)
349
349
 
@@ -156,24 +156,26 @@ apps/desktop/src/main/
156
156
  - 事件广播:向渲染进程通知授权状态变化
157
157
 
158
158
  ```typescript
159
- // 认证流程示例
160
- @ipcClientEvent('requestAuthorization')
161
- async requestAuthorization(config: DataSyncConfig) {
162
- // 生成状态参数防止 CSRF 攻击
163
- this.authRequestState = crypto.randomBytes(16).toString('hex');
164
-
165
- // 构建授权 URL
166
- const authUrl = new URL('/oidc/auth', remoteUrl);
167
- authUrl.search = querystring.stringify({
168
- client_id: 'lobe-chat',
169
- response_type: 'code',
170
- redirect_uri: `${protocolPrefix}://auth/callback`,
171
- scope: 'openid profile',
172
- state: this.authRequestState,
173
- });
174
-
175
- // 在默认浏览器中打开授权 URL
176
- await shell.openExternal(authUrl.toString());
159
+ import { ControllerModule, IpcMethod } from '@/controllers';
160
+
161
+ export default class AuthCtr extends ControllerModule {
162
+ static override groupName = 'auth';
163
+
164
+ @IpcMethod()
165
+ async requestAuthorization(config: DataSyncConfig) {
166
+ this.authRequestState = crypto.randomBytes(16).toString('hex');
167
+
168
+ const authUrl = new URL('/oidc/auth', remoteUrl);
169
+ authUrl.search = querystring.stringify({
170
+ client_id: 'lobe-chat',
171
+ redirect_uri: `${protocolPrefix}://auth/callback`,
172
+ response_type: 'code',
173
+ scope: 'openid profile',
174
+ state: this.authRequestState,
175
+ });
176
+
177
+ await shell.openExternal(authUrl.toString());
178
+ }
177
179
  }
178
180
  ```
179
181
 
@@ -267,20 +269,27 @@ export class ShortcutManager {
267
269
  - 注入 App 实例
268
270
 
269
271
  ```typescript
270
- // 控制器基类和装饰器
272
+ import { ControllerModule, IpcMethod, IpcServerMethod } from '@/controllers'
273
+
271
274
  export class ControllerModule implements IControllerModule {
272
275
  constructor(public app: App) {
273
- this.app = app;
276
+ this.app = app
274
277
  }
275
278
  }
276
279
 
277
- // IPC 客户端事件装饰器
278
- export const ipcClientEvent = (method: keyof ClientDispatchEvents) =>
279
- ipcDecorator(method, 'client');
280
+ export class BrowserWindowsCtr extends ControllerModule {
281
+ static override readonly groupName = 'windows' // must be readonly
282
+
283
+ @IpcMethod()
284
+ openSettingsWindow(params?: OpenSettingsWindowOptions) {
285
+ // ...
286
+ }
280
287
 
281
- // IPC 服务器事件装饰器
282
- export const ipcServerEvent = (method: keyof ServerDispatchEvents) =>
283
- ipcDecorator(method, 'server');
288
+ @IpcServerMethod()
289
+ handleServerCommand(payload: any) {
290
+ // ...
291
+ }
292
+ }
284
293
  ```
285
294
 
286
295
  2. **IoC 容器**:
@@ -346,26 +355,13 @@ makeSureDirExist(storagePath);
346
355
  - 自动映射控制器方法到 IPC 事件
347
356
 
348
357
  ```typescript
349
- // IPC 事件初始化
350
- private initializeIPCEvents() {
351
- // 注册客户端事件处理程序
352
- this.ipcClientEventMap.forEach((eventInfo, key) => {
353
- ipcMain.handle(key, async (e, ...data) => {
354
- return await eventInfo.controller[eventInfo.methodName](...data);
355
- });
356
- });
357
-
358
- // 注册服务器事件处理程序
359
- const ipcServerEvents = {} as ElectronIPCEventHandler;
360
- this.ipcServerEventMap.forEach((eventInfo, key) => {
361
- ipcServerEvents[key] = async (payload) => {
362
- return await eventInfo.controller[eventInfo.methodName](payload);
363
- };
364
- });
365
-
366
- // 创建 IPC 服务器
367
- this.ipcServer = new ElectronIPCServer(name, ipcServerEvents);
368
- }
358
+ import { ensureElectronIpc } from '@/utils/electron/ipc';
359
+
360
+ // 渲染进程中使用 type-safe proxy 调用主进程方法
361
+ const ipc = ensureElectronIpc();
362
+
363
+ await ipc.localSystem.readLocalFile({ path });
364
+ await ipc.system.updateLocale('en-US');
369
365
  ```
370
366
 
371
367
  2. **事件广播**: