@lobehub/lobehub 2.0.0-next.163 → 2.0.0-next.165

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 (124) 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 +52 -0
  5. package/README.md +6 -6
  6. package/README.zh-CN.md +6 -6
  7. package/apps/desktop/Development.md +42 -46
  8. package/apps/desktop/README.md +37 -1
  9. package/apps/desktop/README.zh-CN.md +26 -1
  10. package/apps/desktop/electron.vite.config.ts +1 -0
  11. package/apps/desktop/src/main/controllers/AuthCtr.ts +4 -3
  12. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +33 -20
  13. package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +4 -2
  14. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +14 -13
  15. package/apps/desktop/src/main/controllers/MenuCtr.ts +5 -4
  16. package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +18 -19
  17. package/apps/desktop/src/main/controllers/NotificationCtr.ts +4 -3
  18. package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +5 -4
  19. package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +3 -2
  20. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +5 -4
  21. package/apps/desktop/src/main/controllers/ShortcutCtr.ts +4 -3
  22. package/apps/desktop/src/main/controllers/SystemCtr.ts +7 -37
  23. package/apps/desktop/src/main/controllers/SystemServerCtr.ts +38 -0
  24. package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -4
  25. package/apps/desktop/src/main/controllers/UpdaterCtr.ts +6 -5
  26. package/apps/desktop/src/main/controllers/UploadFileCtr.ts +3 -25
  27. package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +33 -0
  28. package/apps/desktop/src/main/controllers/__tests__/AuthCtr.test.ts +9 -1
  29. package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +29 -9
  30. package/apps/desktop/src/main/controllers/__tests__/DevtoolsCtr.test.ts +12 -3
  31. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +7 -0
  32. package/apps/desktop/src/main/controllers/__tests__/MenuCtr.test.ts +10 -0
  33. package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +10 -0
  34. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +8 -0
  35. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +8 -0
  36. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +1 -0
  37. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +10 -0
  38. package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +11 -0
  39. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +43 -73
  40. package/apps/desktop/src/main/controllers/__tests__/SystemServerCtr.test.ts +75 -0
  41. package/apps/desktop/src/main/controllers/__tests__/TrayMenuCtr.test.ts +24 -13
  42. package/apps/desktop/src/main/controllers/__tests__/UpdaterCtr.test.ts +13 -2
  43. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +29 -108
  44. package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +55 -0
  45. package/apps/desktop/src/main/controllers/_template.ts +2 -2
  46. package/apps/desktop/src/main/controllers/index.ts +5 -29
  47. package/apps/desktop/src/main/controllers/registry.ts +52 -0
  48. package/apps/desktop/src/main/core/App.ts +15 -47
  49. package/apps/desktop/src/main/core/__tests__/App.test.ts +5 -4
  50. package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +0 -5
  51. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +0 -50
  52. package/apps/desktop/src/main/exports.d.ts +8 -0
  53. package/apps/desktop/src/main/exports.ts +2 -0
  54. package/apps/desktop/src/main/global.d.ts +3 -0
  55. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +17 -8
  56. package/apps/desktop/src/main/package.json +10 -0
  57. package/apps/desktop/src/main/services/fileSrv.ts +1 -1
  58. package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +91 -0
  59. package/apps/desktop/src/main/utils/ipc/base.ts +170 -0
  60. package/apps/desktop/src/main/utils/ipc/index.ts +11 -0
  61. package/apps/desktop/src/main/utils/ipc/utility.ts +20 -0
  62. package/apps/desktop/src/preload/electronApi.ts +4 -1
  63. package/apps/desktop/src/preload/invoke.test.ts +13 -16
  64. package/apps/desktop/src/preload/invoke.ts +2 -5
  65. package/apps/desktop/src/preload/routeInterceptor.test.ts +13 -13
  66. package/apps/desktop/src/preload/routeInterceptor.ts +4 -4
  67. package/apps/desktop/tsconfig.json +15 -5
  68. package/changelog/v1.json +14 -0
  69. package/locales/ar/auth.json +3 -0
  70. package/locales/bg-BG/auth.json +3 -0
  71. package/locales/de-DE/auth.json +3 -0
  72. package/locales/en-US/auth.json +4 -1
  73. package/locales/es-ES/auth.json +3 -0
  74. package/locales/fa-IR/auth.json +3 -0
  75. package/locales/fr-FR/auth.json +3 -0
  76. package/locales/it-IT/auth.json +3 -0
  77. package/locales/ja-JP/auth.json +3 -0
  78. package/locales/ko-KR/auth.json +3 -0
  79. package/locales/nl-NL/auth.json +3 -0
  80. package/locales/pl-PL/auth.json +3 -0
  81. package/locales/pt-BR/auth.json +3 -0
  82. package/locales/ru-RU/auth.json +3 -0
  83. package/locales/tr-TR/auth.json +3 -0
  84. package/locales/vi-VN/auth.json +3 -0
  85. package/locales/zh-CN/auth.json +3 -0
  86. package/locales/zh-TW/auth.json +3 -0
  87. package/package.json +4 -3
  88. package/packages/electron-client-ipc/src/index.ts +1 -1
  89. package/packages/electron-client-ipc/src/ipc.test.ts +62 -0
  90. package/packages/electron-client-ipc/src/ipc.ts +63 -0
  91. package/packages/electron-client-ipc/src/streamInvoke.ts +7 -1
  92. package/packages/electron-client-ipc/src/types/dispatch.ts +1 -10
  93. package/packages/electron-client-ipc/vitest.config.mts +10 -0
  94. package/packages/electron-server-ipc/src/ipcClient.ts +1 -2
  95. package/packages/electron-server-ipc/src/ipcServer.ts +1 -2
  96. package/packages/electron-server-ipc/src/types/index.ts +1 -5
  97. package/pnpm-workspace.yaml +1 -1
  98. package/scripts/i18nWorkflow/const.ts +2 -2
  99. package/scripts/i18nWorkflow/i18nConfig.ts +7 -0
  100. package/scripts/i18nWorkflow/utils.ts +1 -1
  101. package/src/app/[variants]/(auth)/signup/[[...signup]]/BetterAuthSignUpForm.tsx +23 -0
  102. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/UserBanner.tsx +3 -9
  103. package/src/app/[variants]/(main)/discover/(detail)/provider/features/Sidebar/ActionButton/ProviderConfig.tsx +2 -2
  104. package/src/app/[variants]/(main)/profile/(home)/Client.tsx +206 -138
  105. package/src/features/User/PlanTag.tsx +4 -4
  106. package/src/locales/default/auth.ts +3 -0
  107. package/src/locales/default/setting.ts +1 -0
  108. package/src/server/modules/ElectronIPCClient/index.ts +59 -13
  109. package/src/services/electron/__tests__/devtools.test.ts +10 -6
  110. package/src/services/electron/autoUpdate.ts +5 -5
  111. package/src/services/electron/desktopNotification.ts +4 -7
  112. package/src/services/electron/devtools.ts +2 -2
  113. package/src/services/electron/file.ts +3 -2
  114. package/src/services/electron/localFileService.ts +17 -16
  115. package/src/services/electron/remoteServer.ts +7 -6
  116. package/src/services/electron/settings.ts +9 -11
  117. package/src/services/electron/system.ts +8 -6
  118. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +1 -1
  119. package/src/store/global/actions/general.ts +8 -10
  120. package/src/utils/electron/desktopRemoteRPCFetch.ts +3 -2
  121. package/src/utils/electron/ipc.ts +12 -0
  122. package/tsconfig.json +5 -0
  123. package/apps/desktop/src/main/types/ipcClientEvent.ts +0 -3
  124. 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,58 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.165](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.164...v2.0.0-next.165)
6
+
7
+ <sup>Released on **2025-12-09**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **electron-main**: Client ipc decorate.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **electron-main**: Client ipc decorate, closes [#10679](https://github.com/lobehub/lobe-chat/issues/10679) ([f74befa](https://github.com/lobehub/lobe-chat/commit/f74befa))
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.164](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.163...v2.0.0-next.164)
31
+
32
+ <sup>Released on **2025-12-08**</sup>
33
+
34
+ #### 💄 Styles
35
+
36
+ - **profile**: Add mobile responsive layout and signup improvements.
37
+ - **misc**: Update link handling in PlanTag component to use react-router-dom.
38
+
39
+ <br/>
40
+
41
+ <details>
42
+ <summary><kbd>Improvements and Fixes</kbd></summary>
43
+
44
+ #### Styles
45
+
46
+ - **profile**: Add mobile responsive layout and signup improvements, closes [#10669](https://github.com/lobehub/lobe-chat/issues/10669) ([1afd471](https://github.com/lobehub/lobe-chat/commit/1afd471))
47
+ - **misc**: Update link handling in PlanTag component to use react-router-dom, closes [#10673](https://github.com/lobehub/lobe-chat/issues/10673) ([3aceeb6](https://github.com/lobehub/lobe-chat/commit/3aceeb6))
48
+
49
+ </details>
50
+
51
+ <div align="right">
52
+
53
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
54
+
55
+ </div>
56
+
5
57
  ## [Version 2.0.0-next.163](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.162...v2.0.0-next.163)
6
58
 
7
59
  <sup>Released on **2025-12-06**</sup>
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. **事件广播**:
@@ -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