@lobehub/lobehub 2.0.0-next.353 → 2.0.0-next.355
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.
- package/.agents/skills/add-provider-doc/SKILL.md +90 -0
- package/.agents/skills/add-setting-env/SKILL.md +106 -0
- package/.agents/skills/debug/SKILL.md +66 -0
- package/.agents/skills/desktop/SKILL.md +78 -0
- package/.agents/skills/desktop/references/feature-implementation.md +99 -0
- package/.agents/skills/desktop/references/local-tools.md +133 -0
- package/.agents/skills/desktop/references/menu-config.md +103 -0
- package/.agents/skills/desktop/references/window-management.md +143 -0
- package/.agents/skills/drizzle/SKILL.md +129 -0
- package/.agents/skills/drizzle/references/db-migrations.md +50 -0
- package/.agents/skills/hotkey/SKILL.md +90 -0
- package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
- package/.agents/skills/linear/SKILL.md +51 -0
- package/.agents/skills/microcopy/SKILL.md +83 -0
- package/.agents/skills/modal/SKILL.md +102 -0
- package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
- package/.agents/skills/react/SKILL.md +73 -0
- package/.agents/skills/react/references/layout-kit.md +100 -0
- package/.agents/skills/recent-data/SKILL.md +108 -0
- package/.agents/skills/testing/SKILL.md +89 -0
- package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
- package/.agents/skills/testing/references/db-model-test.md +124 -0
- package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
- package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
- package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
- package/.agents/skills/typescript/SKILL.md +52 -0
- package/.agents/skills/zustand/SKILL.md +78 -0
- package/.agents/skills/zustand/references/action-patterns.md +125 -0
- package/.agents/skills/zustand/references/slice-organization.md +125 -0
- package/AGENTS.md +42 -55
- package/CHANGELOG.md +60 -0
- package/CLAUDE.md +57 -46
- package/GEMINI.md +47 -39
- package/changelog/v1.json +18 -0
- package/locales/en-US/plugin.json +3 -0
- package/locales/zh-CN/plugin.json +3 -0
- package/package.json +1 -1
- package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +3 -11
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +0 -13
- package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +0 -25
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +3 -3
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +4 -0
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/InboxItem.tsx +19 -29
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/List.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +1 -1
- package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
- package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
- package/src/libs/pdfjs/index.tsx +25 -0
- package/src/locales/default/plugin.ts +3 -0
- package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +0 -25
- package/src/services/chat/chat.test.ts +19 -19
- package/src/services/chat/index.ts +8 -3
- package/src/services/chat/mecha/agentConfigResolver.test.ts +72 -55
- package/src/services/chat/mecha/agentConfigResolver.ts +28 -4
- package/src/services/chat/mecha/contextEngineering.test.ts +21 -14
- package/src/services/chat/mecha/contextEngineering.ts +12 -0
- package/src/services/chat/types.ts +7 -1
- package/src/store/chat/agents/createAgentExecutors.ts +15 -4
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +1 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
- package/src/store/test-coverage.md +5 -5
- package/.cursor/rules/add-provider-doc.mdc +0 -183
- package/.cursor/rules/add-setting-env.mdc +0 -175
- package/.cursor/rules/cursor-rules.mdc +0 -28
- package/.cursor/rules/db-migrations.mdc +0 -46
- package/.cursor/rules/debug-usage.mdc +0 -86
- package/.cursor/rules/desktop-controller-tests.mdc +0 -189
- package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
- package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
- package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
- package/.cursor/rules/desktop-window-management.mdc +0 -301
- package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
- package/.cursor/rules/hotkey.mdc +0 -162
- package/.cursor/rules/linear.mdc +0 -53
- package/.cursor/rules/microcopy-cn.mdc +0 -158
- package/.cursor/rules/microcopy-en.mdc +0 -148
- package/.cursor/rules/modal-imperative.mdc +0 -162
- package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
- package/.cursor/rules/project-introduce.mdc +0 -36
- package/.cursor/rules/react.mdc +0 -169
- package/.cursor/rules/recent-data-usage.mdc +0 -139
- package/.cursor/rules/rules-index.mdc +0 -44
- package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
- package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
- package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
- package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
- package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
- package/.cursor/rules/typescript.mdc +0 -55
- package/.cursor/rules/zustand-action-patterns.mdc +0 -328
- package/.cursor/rules/zustand-slice-organization.mdc +0 -308
- package/src/libs/pdfjs/pdf.worker.ts +0 -1
- package/src/libs/pdfjs/worker.ts +0 -12
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
- /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.
|
package/.cursor/rules/hotkey.mdc
DELETED
|
@@ -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
|
-
通过这些步骤,您可以确保新添加的快捷键功能稳定、可靠且用户友好。
|