@lobehub/lobehub 2.0.0-next.352 → 2.0.0-next.354

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/.agents/skills/add-provider-doc/SKILL.md +90 -0
  2. package/.agents/skills/add-setting-env/SKILL.md +106 -0
  3. package/.agents/skills/debug/SKILL.md +66 -0
  4. package/.agents/skills/desktop/SKILL.md +78 -0
  5. package/.agents/skills/desktop/references/feature-implementation.md +99 -0
  6. package/.agents/skills/desktop/references/local-tools.md +133 -0
  7. package/.agents/skills/desktop/references/menu-config.md +103 -0
  8. package/.agents/skills/desktop/references/window-management.md +143 -0
  9. package/.agents/skills/drizzle/SKILL.md +129 -0
  10. package/.agents/skills/drizzle/references/db-migrations.md +50 -0
  11. package/.agents/skills/hotkey/SKILL.md +90 -0
  12. package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
  13. package/.agents/skills/linear/SKILL.md +51 -0
  14. package/.agents/skills/microcopy/SKILL.md +83 -0
  15. package/.agents/skills/modal/SKILL.md +102 -0
  16. package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
  17. package/.agents/skills/react/SKILL.md +73 -0
  18. package/.agents/skills/react/references/layout-kit.md +100 -0
  19. package/.agents/skills/recent-data/SKILL.md +108 -0
  20. package/.agents/skills/testing/SKILL.md +89 -0
  21. package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
  22. package/.agents/skills/testing/references/db-model-test.md +124 -0
  23. package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
  24. package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
  25. package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
  26. package/.agents/skills/typescript/SKILL.md +52 -0
  27. package/.agents/skills/zustand/SKILL.md +78 -0
  28. package/.agents/skills/zustand/references/action-patterns.md +125 -0
  29. package/.agents/skills/zustand/references/slice-organization.md +125 -0
  30. package/AGENTS.md +42 -55
  31. package/CHANGELOG.md +58 -0
  32. package/CLAUDE.md +57 -46
  33. package/GEMINI.md +47 -39
  34. package/changelog/v1.json +14 -0
  35. package/docs/development/database-schema.dbml +5 -0
  36. package/package.json +1 -1
  37. package/packages/database/migrations/0071_add_async_task_extend.sql +5 -0
  38. package/packages/database/migrations/meta/0071_snapshot.json +10720 -0
  39. package/packages/database/migrations/meta/_journal.json +7 -0
  40. package/packages/database/src/schemas/asyncTask.ts +12 -2
  41. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
  42. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
  43. package/src/libs/pdfjs/index.tsx +25 -0
  44. package/src/store/test-coverage.md +5 -5
  45. package/.cursor/rules/add-provider-doc.mdc +0 -183
  46. package/.cursor/rules/add-setting-env.mdc +0 -175
  47. package/.cursor/rules/cursor-rules.mdc +0 -28
  48. package/.cursor/rules/db-migrations.mdc +0 -46
  49. package/.cursor/rules/debug-usage.mdc +0 -86
  50. package/.cursor/rules/desktop-controller-tests.mdc +0 -189
  51. package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
  52. package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
  53. package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
  54. package/.cursor/rules/desktop-window-management.mdc +0 -301
  55. package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
  56. package/.cursor/rules/hotkey.mdc +0 -162
  57. package/.cursor/rules/linear.mdc +0 -53
  58. package/.cursor/rules/microcopy-cn.mdc +0 -158
  59. package/.cursor/rules/microcopy-en.mdc +0 -148
  60. package/.cursor/rules/modal-imperative.mdc +0 -162
  61. package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
  62. package/.cursor/rules/project-introduce.mdc +0 -36
  63. package/.cursor/rules/react.mdc +0 -169
  64. package/.cursor/rules/recent-data-usage.mdc +0 -139
  65. package/.cursor/rules/rules-index.mdc +0 -44
  66. package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
  67. package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
  68. package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
  69. package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
  70. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
  71. package/.cursor/rules/typescript.mdc +0 -55
  72. package/.cursor/rules/zustand-action-patterns.mdc +0 -328
  73. package/.cursor/rules/zustand-slice-organization.mdc +0 -308
  74. package/src/libs/pdfjs/pdf.worker.ts +0 -1
  75. package/src/libs/pdfjs/worker.ts +0 -12
  76. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
  77. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
  78. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
  79. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
  80. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
  81. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
  82. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
  83. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
  84. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
  85. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
  86. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
  87. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
  88. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
  89. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
  90. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
  91. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
  92. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
  93. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
  94. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
  95. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
  96. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
  97. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
  98. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
  99. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
  100. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
  101. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
  102. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
  103. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
  104. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
  105. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
  106. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
  107. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
  108. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
  109. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
  110. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
  111. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
  112. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
  113. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
  114. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
  115. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
  116. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
  117. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
  118. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
  119. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
  120. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
  121. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
  122. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
  123. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
  124. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
@@ -1,301 +0,0 @@
1
- ---
2
- description:
3
- globs:
4
- alwaysApply: false
5
- ---
6
-
7
- # 桌面端窗口管理指南
8
-
9
- ## 窗口管理概述
10
-
11
- LobeChat 桌面应用使用 Electron 的 `BrowserWindow` 管理应用窗口。主要的窗口管理功能包括:
12
-
13
- 1. **窗口创建和配置**
14
- 2. **窗口状态管理**(大小、位置、最大化等)
15
- 3. **多窗口协调**
16
- 4. **窗口事件处理**
17
-
18
- ## 相关文件结构
19
-
20
- ```plaintext
21
- apps/desktop/src/main/
22
- ├── appBrowsers.ts # 窗口管理的核心文件
23
- ├── controllers/
24
- │ └── BrowserWindowsCtr.ts # 窗口控制器
25
- └── modules/
26
- └── browserWindowManager.ts # 窗口管理模块
27
- ```
28
-
29
- ## 窗口管理流程
30
-
31
- ### 1. 窗口创建
32
-
33
- 在 `appBrowsers.ts` 或 `BrowserWindowsCtr.ts` 中定义窗口创建逻辑:
34
-
35
- ```typescript
36
- export const createMainWindow = () => {
37
- const mainWindow = new BrowserWindow({
38
- width: 1200,
39
- height: 800,
40
- minWidth: 600,
41
- minHeight: 400,
42
- webPreferences: {
43
- preload: path.join(__dirname, '../preload/index.js'),
44
- contextIsolation: true,
45
- nodeIntegration: false,
46
- },
47
- // 其他窗口配置项...
48
- });
49
-
50
- // 加载应用内容
51
- if (isDev) {
52
- mainWindow.loadURL('http://localhost:3000');
53
- mainWindow.webContents.openDevTools();
54
- } else {
55
- mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
56
- }
57
-
58
- return mainWindow;
59
- };
60
- ```
61
-
62
- ### 2. 窗口状态管理
63
-
64
- 实现窗口状态持久化保存和恢复:
65
-
66
- 1. **保存窗口状态**
67
-
68
- ```typescript
69
- const saveWindowState = (window: BrowserWindow) => {
70
- if (!window.isMinimized() && !window.isMaximized()) {
71
- const position = window.getPosition();
72
- const size = window.getSize();
73
-
74
- settings.set('windowState', {
75
- x: position[0],
76
- y: position[1],
77
- width: size[0],
78
- height: size[1],
79
- });
80
- }
81
- };
82
- ```
83
-
84
- 2. **恢复窗口状态**
85
-
86
- ```typescript
87
- const restoreWindowState = (window: BrowserWindow) => {
88
- const savedState = settings.get('windowState');
89
-
90
- if (savedState) {
91
- window.setBounds({
92
- x: savedState.x,
93
- y: savedState.y,
94
- width: savedState.width,
95
- height: savedState.height,
96
- });
97
- }
98
- };
99
- ```
100
-
101
- 3. **监听窗口事件**
102
-
103
- ```typescript
104
- window.on('close', () => saveWindowState(window));
105
- window.on('moved', () => saveWindowState(window));
106
- window.on('resized', () => saveWindowState(window));
107
- ```
108
-
109
- ### 3. 实现多窗口管理
110
-
111
- 对于需要多窗口支持的功能:
112
-
113
- 1. **跟踪窗口**
114
-
115
- ```typescript
116
- export class WindowManager {
117
- private windows: Map<string, BrowserWindow> = new Map();
118
-
119
- createWindow(id: string, options: BrowserWindowConstructorOptions) {
120
- const window = new BrowserWindow(options);
121
- this.windows.set(id, window);
122
-
123
- window.on('closed', () => {
124
- this.windows.delete(id);
125
- });
126
-
127
- return window;
128
- }
129
-
130
- getWindow(id: string) {
131
- return this.windows.get(id);
132
- }
133
-
134
- getAllWindows() {
135
- return Array.from(this.windows.values());
136
- }
137
- }
138
- ```
139
-
140
- 2. **窗口间通信**
141
-
142
- ```typescript
143
- // 从一个窗口向另一个窗口发送消息
144
- sendMessageToWindow(targetWindowId, channel, data) {
145
- const targetWindow = this.getWindow(targetWindowId);
146
- if (targetWindow) {
147
- targetWindow.webContents.send(channel, data);
148
- }
149
- }
150
- ```
151
-
152
- ### 4. 窗口与渲染进程通信
153
-
154
- 通过 IPC 实现窗口操作:
155
-
156
- 1. **在主进程中注册 IPC 处理器**
157
-
158
- ```typescript
159
- // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
160
- import { BrowserWindow } from 'electron';
161
-
162
- import { ControllerModule, IpcMethod } from '@/controllers';
163
-
164
- export default class BrowserWindowsCtr extends ControllerModule {
165
- static override readonly groupName = 'windows';
166
-
167
- @IpcMethod()
168
- minimizeWindow() {
169
- const focusedWindow = BrowserWindow.getFocusedWindow();
170
- focusedWindow?.minimize();
171
- return { success: true };
172
- }
173
-
174
- @IpcMethod()
175
- maximizeWindow() {
176
- const focusedWindow = BrowserWindow.getFocusedWindow();
177
- if (focusedWindow?.isMaximized()) focusedWindow.restore();
178
- else focusedWindow?.maximize();
179
- return { success: true };
180
- }
181
-
182
- @IpcMethod()
183
- closeWindow() {
184
- BrowserWindow.getFocusedWindow()?.close();
185
- return { success: true };
186
- }
187
- }
188
- ```
189
-
190
- - `@IpcMethod()` 根据控制器的 `groupName` 自动将方法映射为 `windows.minimizeWindow` 形式的通道名称。
191
- - 控制器需继承 `ControllerModule`,并在 `controllers/registry.ts` 中通过 `controllerIpcConstructors` 注册,便于类型生成。
192
-
193
- 2. **在渲染进程中调用**
194
-
195
- ```typescript
196
- // src/services/electron/windowService.ts
197
- import { ensureElectronIpc } from '@/utils/electron/ipc';
198
-
199
- const ipc = ensureElectronIpc();
200
-
201
- export const windowService = {
202
- minimize: () => ipc.windows.minimizeWindow(),
203
- maximize: () => ipc.windows.maximizeWindow(),
204
- close: () => ipc.windows.closeWindow(),
205
- };
206
- ```
207
-
208
- - `ensureElectronIpc()` 会基于 `DesktopIpcServices` 运行时生成 Proxy,并通过 `window.electronAPI.invoke` 与主进程通信;不再直接使用 `dispatch`。
209
-
210
- ### 5. 自定义窗口控制 (无边框窗口)
211
-
212
- 对于自定义窗口标题栏:
213
-
214
- 1. **创建无边框窗口**
215
-
216
- ```typescript
217
- const window = new BrowserWindow({
218
- frame: false,
219
- titleBarStyle: 'hidden',
220
- // 其他选项...
221
- });
222
- ```
223
-
224
- 2. **在渲染进程中实现拖拽区域**
225
-
226
- ```css
227
- /* CSS */
228
- .titlebar {
229
- -webkit-app-region: drag;
230
- }
231
-
232
- .titlebar-button {
233
- -webkit-app-region: no-drag;
234
- }
235
- ```
236
-
237
- ## 最佳实践
238
-
239
- 1. **性能考虑**
240
- - 避免创建过多窗口
241
- - 使用 `show: false` 创建窗口,在内容加载完成后再显示,避免白屏
242
-
243
- 2. **安全性**
244
- - 始终设置适当的 `webPreferences` 确保安全
245
-
246
- ```typescript
247
- webPreferences: {
248
- preload: path.join(__dirname, '../preload/index.js'),
249
- contextIsolation: true,
250
- nodeIntegration: false,
251
- sandbox: true,
252
- }
253
- ```
254
-
255
- 3. **跨平台兼容性**
256
- - 考虑不同操作系统的窗口行为差异
257
- - 使用 `process.platform` 为不同平台提供特定实现
258
-
259
- 4. **崩溃恢复**
260
- - 监听 `webContents.on('crashed')` 事件处理崩溃
261
- - 提供崩溃恢复选项
262
-
263
- 5. **内存管理**
264
- - 确保窗口关闭时清理所有相关资源
265
- - 使用 `window.on('closed')` 而不是 `window.on('close')` 进行最终清理
266
-
267
- ## 示例:创建设置窗口
268
-
269
- ```typescript
270
- // apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
271
- import type { OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
272
-
273
- import { ControllerModule, IpcMethod } from '@/controllers';
274
-
275
- export default class BrowserWindowsCtr extends ControllerModule {
276
- static override readonly groupName = 'windows';
277
-
278
- @IpcMethod()
279
- async openSettingsWindow(options?: string | OpenSettingsWindowOptions) {
280
- const normalizedOptions =
281
- typeof options === 'string' || options === undefined
282
- ? { tab: typeof options === 'string' ? options : undefined }
283
- : options;
284
-
285
- const mainWindow = this.app.browserManager.getMainWindow();
286
- const query = new URLSearchParams();
287
- if (normalizedOptions.tab) query.set('active', normalizedOptions.tab);
288
- if (normalizedOptions.searchParams) {
289
- for (const [key, value] of Object.entries(normalizedOptions.searchParams)) {
290
- if (value) query.set(key, value);
291
- }
292
- }
293
-
294
- const fullPath = `/settings${query.size ? `?${query.toString()}` : ''}`;
295
- await mainWindow.loadUrl(fullPath);
296
- mainWindow.show();
297
-
298
- return { success: true };
299
- }
300
- }
301
- ```
@@ -1,218 +0,0 @@
1
- ---
2
- description:
3
- globs: src/database/schemas/*
4
- alwaysApply: false
5
- ---
6
-
7
- # Drizzle ORM Schema Style Guide for lobe-chat
8
-
9
- This document outlines the conventions and best practices for defining PostgreSQL Drizzle ORM schemas within the lobe-chat project.
10
-
11
- ## Configuration
12
-
13
- - Drizzle configuration is managed in `drizzle.config.ts`
14
- - Schema files are located in the src/database/schemas/ directory
15
- - Migration files are output to `src/database/migrations/`
16
- - The project uses `postgresql` dialect with `strict: true`
17
-
18
- ## Helper Functions
19
-
20
- Commonly used column definitions, especially for timestamps, are centralized in `src/database/schemas/_helpers.ts`:
21
-
22
- - `timestamptz(name: string)`: Creates a timestamp column with timezone
23
- - `createdAt()`, `updatedAt()`, `accessedAt()`: Helper functions for standard timestamp columns
24
- - `timestamps`: An object `{ createdAt, updatedAt, accessedAt }` for easy inclusion in table definitions
25
-
26
- ## Naming Conventions
27
-
28
- - **Table Names**: Use plural snake_case (e.g., `users`, `agents`, `session_groups`)
29
- - **Column Names**: Use snake_case (e.g., `user_id`, `created_at`, `background_color`)
30
-
31
- ## Column Definitions
32
-
33
- ### Primary Keys (PKs)
34
-
35
- - Typically `text('id')` (or `varchar('id')` for some OIDC tables)
36
- - Often use `.$defaultFn(() => idGenerator('table_name'))` for automatic ID generation with meaningful prefixes
37
- - **ID Prefix Purpose**: Makes it easy for users and developers to distinguish different entity types at a glance
38
- - For internal/system tables that users don't need to see, can use `uuid` or auto-increment keys
39
- - Composite PKs are defined using `primaryKey({ columns: [t.colA, t.colB] })`
40
-
41
- ### Foreign Keys (FKs)
42
-
43
- - Defined using `.references(() => otherTable.id, { onDelete: 'cascade' | 'set null' | 'no action' })`
44
- - FK columns are usually named `related_table_singular_name_id` (e.g., `user_id` references `users.id`)
45
- - Most tables include a `user_id` column referencing `users.id` with `onDelete: 'cascade'`
46
-
47
- ### Timestamps
48
-
49
- - Consistently use the `...timestamps` spread from `_helpers.ts` for `created_at`, `updated_at`, and `accessed_at` columns
50
-
51
- ### Default Values
52
-
53
- - `.$defaultFn(() => expression)` for dynamic defaults (e.g., `idGenerator()`, `randomSlug()`)
54
- - `.default(staticValue)` for static defaults (e.g., `boolean('enabled').default(true)`)
55
-
56
- ### Indexes
57
-
58
- - Defined in the table's second argument: `pgTable('name', {...columns}, (t) => ({ indexName: indexType().on(...) }))`
59
- - Use `uniqueIndex()` for unique constraints and `index()` for non-unique indexes
60
- - Naming pattern: `table_name_column(s)_idx` or `table_name_column(s)_unique`
61
- - Many tables feature a `clientId: text('client_id')` column, often part of a composite unique index with `user_id`
62
-
63
- ### Data Types
64
-
65
- - Common types: `text`, `varchar`, `jsonb`, `boolean`, `integer`, `uuid`, `pgTable`
66
- - For `jsonb` fields, specify the TypeScript type using `.$type<MyType>()` for better type safety
67
-
68
- ## Zod Schemas & Type Inference
69
-
70
- - Utilize `drizzle-zod` to generate Zod schemas for validation:
71
- - `createInsertSchema(tableName)`
72
- - `createSelectSchema(tableName)` (less common)
73
- - Export inferred types: `export type NewEntity = typeof tableName.$inferInsert;` and `export type EntityItem = typeof tableName.$inferSelect;`
74
-
75
- ## Relations
76
-
77
- - Table relationships are defined centrally in `src/database/schemas/relations.ts` using the `relations()` utility from `drizzle-orm`
78
-
79
- ## Code Style & Structure
80
-
81
- - **File Organization**: Each main database entity typically has its own schema file (e.g., `user.ts`, `agent.ts`)
82
- - All schemas are re-exported from `src/database/schemas/index.ts`
83
- - **ESLint**: Files often start with `/* eslint-disable sort-keys-fix/sort-keys-fix */`
84
- - **Comments**: Use JSDoc-style comments to explain the purpose of tables and complex columns, fields that are self-explanatory do not require jsdoc explanations, such as id, user_id, etc.
85
-
86
- ## Example Pattern
87
-
88
- ```typescript
89
- // From src/database/schemas/agent.ts
90
- export const agents = pgTable(
91
- 'agents',
92
- {
93
- id: text('id')
94
- .primaryKey()
95
- .$defaultFn(() => idGenerator('agents'))
96
- .notNull(),
97
- slug: varchar('slug', { length: 100 })
98
- .$defaultFn(() => randomSlug(4))
99
- .unique(),
100
- userId: text('user_id')
101
- .references(() => users.id, { onDelete: 'cascade' })
102
- .notNull(),
103
- clientId: text('client_id'),
104
- chatConfig: jsonb('chat_config').$type<LobeAgentChatConfig>(),
105
- ...timestamps,
106
- },
107
- // return array instead of object, the object style is deprecated
108
- (t) => [uniqueIndex('client_id_user_id_unique').on(t.clientId, t.userId)],
109
- );
110
-
111
- export const insertAgentSchema = createInsertSchema(agents);
112
- export type NewAgent = typeof agents.$inferInsert;
113
- export type AgentItem = typeof agents.$inferSelect;
114
- ```
115
-
116
- ## Common Patterns
117
-
118
- ### 1. userId + clientId Pattern (Legacy)
119
-
120
- Some existing tables include both fields for different purposes:
121
-
122
- ```typescript
123
- // Example from agents table (legacy pattern)
124
- userId: text('user_id')
125
- .references(() => users.id, { onDelete: 'cascade' })
126
- .notNull(),
127
- clientId: text('client_id'),
128
-
129
- // Usually with a composite unique index
130
- clientIdUnique: uniqueIndex('agents_client_id_user_id_unique').on(t.clientId, t.userId),
131
- ```
132
-
133
- - **`userId`**: Server-side user association, ensures data belongs to specific user
134
- - **`clientId`**: Unique key for import/export operations, supports data migration between instances
135
- - **Current Status**: New tables should NOT include `clientId` unless specifically needed for import/export functionality
136
- - **Note**: This pattern is being phased out for new features to simplify the schema
137
-
138
- ### 2. Junction Tables (Many-to-Many Relationships)
139
-
140
- Use composite primary keys for relationship tables:
141
-
142
- ```typescript
143
- // Example: agents_knowledge_bases (from agent.ts)
144
- export const agentsKnowledgeBases = pgTable(
145
- 'agents_knowledge_bases',
146
- {
147
- agentId: text('agent_id')
148
- .references(() => agents.id, { onDelete: 'cascade' })
149
- .notNull(),
150
- knowledgeBaseId: text('knowledge_base_id')
151
- .references(() => knowledgeBases.id, { onDelete: 'cascade' })
152
- .notNull(),
153
- userId: text('user_id')
154
- .references(() => users.id, { onDelete: 'cascade' })
155
- .notNull(),
156
- enabled: boolean('enabled').default(true),
157
- ...timestamps,
158
- },
159
- (t) => [primaryKey({ columns: [t.agentId, t.knowledgeBaseId] })],
160
- );
161
- ```
162
-
163
- **Pattern**: `{entity1}Id` + `{entity2}Id` as composite PK, plus `userId` for ownership
164
-
165
- ### 3. OIDC Tables Special Patterns
166
-
167
- OIDC tables use `varchar` IDs instead of `text` with custom generators:
168
-
169
- ```typescript
170
- // Example from oidc.ts
171
- export const oidcAuthorizationCodes = pgTable('oidc_authorization_codes', {
172
- id: varchar('id', { length: 255 }).primaryKey(), // varchar not text
173
- data: jsonb('data').notNull(),
174
- expiresAt: timestamptz('expires_at').notNull(),
175
- // ... other fields
176
- });
177
- ```
178
-
179
- **Reason**: OIDC standards expect specific ID formats and lengths
180
-
181
- ### 4. File Processing with Async Tasks
182
-
183
- File-related tables reference async task IDs for background processing:
184
-
185
- ```typescript
186
- // Example from files table
187
- export const files = pgTable('files', {
188
- // ... other fields
189
- chunkTaskId: uuid('chunk_task_id').references(() => asyncTasks.id, { onDelete: 'set null' }),
190
- embeddingTaskId: uuid('embedding_task_id').references(() => asyncTasks.id, {
191
- onDelete: 'set null',
192
- }),
193
- // ...
194
- });
195
- ```
196
-
197
- **Purpose**:
198
-
199
- - Track file chunking progress (breaking files into smaller pieces)
200
- - Track embedding generation progress (converting text to vectors)
201
- - Allow querying task status and handling failures
202
-
203
- ### 5. Slug Pattern (Legacy)
204
-
205
- Some entities include auto-generated slugs - this is legacy code:
206
-
207
- ```typescript
208
- slug: varchar('slug', { length: 100 })
209
- .$defaultFn(() => randomSlug(4))
210
- .unique(),
211
-
212
- // Often with composite unique constraint
213
- slugUserIdUnique: uniqueIndex('slug_user_id_unique').on(t.slug, t.userId),
214
- ```
215
-
216
- **Current usage**: Only used to identify default agents/sessions (legacy pattern) **Future refactor**: Will likely be replaced with `isDefault: boolean()` field **Note**: Avoid using slugs for new features - prefer explicit boolean flags for status tracking
217
-
218
- By following these guidelines, maintain consistency, type safety, and maintainability across database schema definitions.
@@ -1,162 +0,0 @@
1
- ---
2
- alwaysApply: false
3
- ---
4
-
5
- # 如何添加新的快捷键:开发者指南
6
-
7
- 本指南将带您一步步地向 LobeChat 添加一个新的快捷键功能。我们将通过一个完整示例,演示从定义到实现的整个过程。
8
-
9
- ## 示例场景
10
-
11
- 假设我们要添加一个新的快捷键功能:**快速清空聊天记录**,快捷键为 `Mod+Shift+Backspace`。
12
-
13
- ## 步骤 1:更新快捷键常量定义
14
-
15
- 首先,在 `src/types/hotkey.ts` 中更新 `HotkeyEnum`:
16
-
17
- ```typescript
18
- export const HotkeyEnum = {
19
- // 已有的快捷键...
20
- AddUserMessage: 'addUserMessage',
21
- EditMessage: 'editMessage',
22
-
23
- // 新增快捷键
24
- ClearChat: 'clearChat', // 添加这一行
25
-
26
- // 其他已有快捷键...
27
- } as const;
28
- ```
29
-
30
- ## 步骤 2:注册默认快捷键
31
-
32
- 在 `src/const/hotkeys.ts` 中添加快捷键的默认配置:
33
-
34
- ```typescript
35
- import { KeyMapEnum as Key, combineKeys } from '@lobehub/ui';
36
-
37
- // ...现有代码
38
-
39
- export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
40
- // 现有的快捷键配置...
41
-
42
- // 添加新的快捷键配置
43
- {
44
- group: HotkeyGroupEnum.Conversation, // 归类到会话操作组
45
- id: HotkeyEnum.ClearChat,
46
- keys: combineKeys([Key.Mod, Key.Shift, Key.Backspace]),
47
- scopes: [HotkeyScopeEnum.Chat], // 在聊天作用域下生效
48
- },
49
-
50
- // 其他现有快捷键...
51
- ];
52
- ```
53
-
54
- ## 步骤 3:添加国际化翻译
55
-
56
- 在 `src/locales/default/hotkey.ts` 中添加对应的文本描述:
57
-
58
- ```typescript
59
- import { HotkeyI18nTranslations } from '@/types/hotkey';
60
-
61
- const hotkey: HotkeyI18nTranslations = {
62
- // 现有翻译...
63
-
64
- // 添加新快捷键的翻译
65
- clearChat: {
66
- desc: '清空当前会话的所有消息记录',
67
- title: '清空聊天记录',
68
- },
69
-
70
- // 其他现有翻译...
71
- };
72
-
73
- export default hotkey;
74
- ```
75
-
76
- 如需支持其他语言,还需要在相应的语言文件中添加对应翻译。
77
-
78
- ## 步骤 4:创建并注册快捷键 Hook
79
-
80
- 在 `src/hooks/useHotkeys/chatScope.ts` 中添加新的 Hook:
81
-
82
- ```typescript
83
- export const useClearChatHotkey = () => {
84
- const clearMessages = useChatStore((s) => s.clearMessages);
85
- const { t } = useTranslation();
86
-
87
- return useHotkeyById(HotkeyEnum.ClearChat, showConfirm);
88
- };
89
-
90
- // 注册聚合
91
-
92
- export const useRegisterChatHotkeys = () => {
93
- const { enableScope, disableScope } = useHotkeysContext();
94
-
95
- useOpenChatSettingsHotkey();
96
- // ...其他快捷键
97
- useClearChatHotkey();
98
-
99
- useEffect(() => {
100
- enableScope(HotkeyScopeEnum.Chat);
101
- return () => disableScope(HotkeyScopeEnum.Chat);
102
- }, []);
103
-
104
- return null;
105
- };
106
- ```
107
-
108
- ## 步骤 5:给相应 UI 元素添加 Tooltip 提示(可选)
109
-
110
- 如果有对应的 UI 按钮,可以添加快捷键提示:
111
-
112
- ```tsx
113
- import { DeleteOutlined } from '@ant-design/icons';
114
- import { Tooltip } from '@lobehub/ui';
115
- import { Button } from 'antd';
116
- import { useTranslation } from 'react-i18next';
117
-
118
- import { useUserStore } from '@/store/user';
119
- import { settingsSelectors } from '@/store/user/selectors';
120
- import { HotkeyEnum } from '@/types/hotkey';
121
-
122
- const ClearChatButton = () => {
123
- const { t } = useTranslation(['hotkey', 'chat']);
124
- const clearChatHotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ClearChat));
125
-
126
- // 获取清空聊天的方法
127
- const clearMessages = useChatStore((s) => s.clearMessages);
128
-
129
- return (
130
- <Tooltip hotkey={clearChatHotkey} title={t('clearChat.title', { ns: 'hotkey' })}>
131
- <Button icon={<DeleteOutlined />} onClick={clearMessages} />
132
- </Tooltip>
133
- );
134
- };
135
- ```
136
-
137
- ## 步骤 6:测试新快捷键
138
-
139
- 1. 启动开发服务器
140
- 2. 打开聊天页面
141
- 3. 按下设置的快捷键组合(`Cmd+Shift+Backspace` 或 `Ctrl+Shift+Backspace`)
142
- 4. 确认功能正常工作
143
- 5. 检查快捷键设置面板中是否正确显示了新快捷键
144
-
145
- ## 最佳实践
146
-
147
- 1. **作用域考虑**:根据功能决定快捷键应属于全局作用域还是聊天作用域
148
- 2. **分组合理**:将快捷键放在合适的功能组中(System/Layout/Conversation)
149
- 3. **冲突检查**:确保新快捷键不会与现有系统、浏览器或应用快捷键冲突
150
- 4. **平台适配**:使用 `Key.Mod` 而非硬编码 `Ctrl` 或 `Cmd`,以适配不同平台
151
- 5. **提供清晰描述**:为快捷键添加明确的标题和描述,帮助用户理解功能
152
-
153
- 按照以上步骤,您可以轻松地向系统添加新的快捷键功能,提升用户体验。如有特殊需求,如桌面专属快捷键,可以通过 `isDesktop` 标记进行区分处理。
154
-
155
- ## 常见问题排查
156
-
157
- - **快捷键未生效**:检查作用域是否正确,以及是否在 RegisterHotkeys 中调用了对应的 hook
158
- - **快捷键设置面板未显示**:确认在 HOTKEYS_REGISTRATION 中正确配置了快捷键
159
- - **快捷键冲突**:在 HotkeyInput 组件中可以检测到冲突,用户会看到警告
160
- - **功能在某些页面失效**:确认是否注册在了正确的作用域,以及相关页面是否激活了该作用域
161
-
162
- 通过这些步骤,您可以确保新添加的快捷键功能稳定、可靠且用户友好。