@lobehub/chat 1.75.5 → 1.76.1

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 (123) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +3 -2
  3. package/Dockerfile.database +3 -1
  4. package/Dockerfile.pglite +3 -1
  5. package/changelog/v1.json +18 -0
  6. package/docs/developer/database-schema.dbml +1 -0
  7. package/locales/ar/hotkey.json +46 -0
  8. package/locales/ar/models.json +3 -0
  9. package/locales/ar/setting.json +12 -0
  10. package/locales/bg-BG/hotkey.json +46 -0
  11. package/locales/bg-BG/models.json +3 -0
  12. package/locales/bg-BG/setting.json +12 -0
  13. package/locales/de-DE/hotkey.json +46 -0
  14. package/locales/de-DE/models.json +3 -0
  15. package/locales/de-DE/setting.json +12 -0
  16. package/locales/en-US/hotkey.json +46 -0
  17. package/locales/en-US/models.json +3 -0
  18. package/locales/en-US/setting.json +12 -0
  19. package/locales/es-ES/hotkey.json +46 -0
  20. package/locales/es-ES/models.json +3 -0
  21. package/locales/es-ES/setting.json +12 -0
  22. package/locales/fa-IR/hotkey.json +46 -0
  23. package/locales/fa-IR/models.json +3 -0
  24. package/locales/fa-IR/setting.json +12 -0
  25. package/locales/fr-FR/hotkey.json +46 -0
  26. package/locales/fr-FR/models.json +3 -0
  27. package/locales/fr-FR/setting.json +12 -0
  28. package/locales/it-IT/hotkey.json +46 -0
  29. package/locales/it-IT/models.json +3 -0
  30. package/locales/it-IT/setting.json +12 -0
  31. package/locales/ja-JP/hotkey.json +46 -0
  32. package/locales/ja-JP/models.json +3 -0
  33. package/locales/ja-JP/setting.json +12 -0
  34. package/locales/ko-KR/hotkey.json +46 -0
  35. package/locales/ko-KR/models.json +3 -0
  36. package/locales/ko-KR/setting.json +12 -0
  37. package/locales/nl-NL/hotkey.json +46 -0
  38. package/locales/nl-NL/models.json +3 -0
  39. package/locales/nl-NL/setting.json +12 -0
  40. package/locales/pl-PL/hotkey.json +46 -0
  41. package/locales/pl-PL/models.json +3 -0
  42. package/locales/pl-PL/setting.json +12 -0
  43. package/locales/pt-BR/hotkey.json +46 -0
  44. package/locales/pt-BR/models.json +3 -0
  45. package/locales/pt-BR/setting.json +12 -0
  46. package/locales/ru-RU/hotkey.json +46 -0
  47. package/locales/ru-RU/models.json +3 -0
  48. package/locales/ru-RU/setting.json +12 -0
  49. package/locales/tr-TR/hotkey.json +46 -0
  50. package/locales/tr-TR/models.json +3 -0
  51. package/locales/tr-TR/setting.json +12 -0
  52. package/locales/vi-VN/hotkey.json +46 -0
  53. package/locales/vi-VN/models.json +3 -0
  54. package/locales/vi-VN/setting.json +12 -0
  55. package/locales/zh-CN/hotkey.json +46 -0
  56. package/locales/zh-CN/models.json +3 -0
  57. package/locales/zh-CN/setting.json +12 -0
  58. package/locales/zh-TW/hotkey.json +46 -0
  59. package/locales/zh-TW/models.json +3 -0
  60. package/locales/zh-TW/setting.json +12 -0
  61. package/package.json +3 -3
  62. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/Category.tsx +1 -1
  63. package/src/app/[variants]/(main)/(mobile)/me/(home)/layout.tsx +3 -2
  64. package/src/app/[variants]/(main)/(mobile)/me/data/features/Category.tsx +1 -1
  65. package/src/app/[variants]/(main)/(mobile)/me/profile/features/Category.tsx +1 -1
  66. package/src/app/[variants]/(main)/(mobile)/me/settings/features/Category.tsx +1 -1
  67. package/src/app/[variants]/(main)/_layout/Desktop/RegisterHotkeys.tsx +11 -0
  68. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +6 -23
  69. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.test.tsx +2 -0
  70. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +11 -4
  71. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/SendMore.tsx +6 -21
  72. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/ShortcutHint.tsx +13 -34
  73. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +1 -1
  74. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ZenModeToast/Toast.tsx +7 -4
  75. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +12 -8
  76. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +24 -30
  77. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/index.tsx +0 -2
  78. package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +12 -7
  79. package/src/app/[variants]/(main)/chat/@session/features/SessionSearchBar.tsx +5 -1
  80. package/src/app/[variants]/(main)/chat/_layout/Desktop/RegisterHotkeys.tsx +10 -0
  81. package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +5 -0
  82. package/src/app/[variants]/(main)/chat/_layout/Mobile.tsx +1 -1
  83. package/src/app/[variants]/(main)/discover/features/StoreSearchBar.tsx +5 -1
  84. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +31 -21
  85. package/src/app/[variants]/(main)/settings/hotkey/features/HotkeySetting.tsx +80 -0
  86. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +9 -0
  87. package/src/app/[variants]/(main)/settings/hotkey/page.tsx +15 -0
  88. package/src/app/[variants]/(main)/settings/tts/features/const.tsx +4 -0
  89. package/src/app/[variants]/layout.tsx +16 -13
  90. package/src/config/aiModels/openai.ts +10 -0
  91. package/src/const/hotkeys.ts +80 -10
  92. package/src/const/settings/hotkey.ts +10 -0
  93. package/src/const/settings/index.ts +3 -0
  94. package/src/database/client/migrations.json +46 -32
  95. package/src/database/migrations/0019_add_hotkey_user_settings.sql +2 -0
  96. package/src/database/migrations/meta/0019_snapshot.json +4218 -0
  97. package/src/database/migrations/meta/_journal.json +7 -0
  98. package/src/database/schemas/user.ts +1 -0
  99. package/src/database/server/models/user.ts +2 -0
  100. package/src/features/ChatInput/Desktop/InputArea/index.tsx +8 -0
  101. package/src/features/ChatInput/Desktop/index.tsx +0 -1
  102. package/src/features/ChatInput/Topic/index.tsx +10 -15
  103. package/src/features/FileManager/Header/FilesSearchBar.tsx +6 -2
  104. package/src/features/HotkeyHelperPanel/HotkeyContent.tsx +62 -0
  105. package/src/features/HotkeyHelperPanel/index.tsx +59 -0
  106. package/src/hooks/useHotkeys/chatScope.ts +105 -0
  107. package/src/hooks/useHotkeys/globalScope.ts +69 -0
  108. package/src/hooks/useHotkeys/index.ts +2 -0
  109. package/src/hooks/useHotkeys/useHotkeyById.test.ts +194 -0
  110. package/src/hooks/useHotkeys/useHotkeyById.ts +57 -0
  111. package/src/locales/default/hotkey.ts +50 -0
  112. package/src/locales/default/index.ts +2 -0
  113. package/src/locales/default/setting.ts +12 -0
  114. package/src/store/global/initialState.ts +3 -0
  115. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +79 -0
  116. package/src/store/user/slices/settings/selectors/settings.test.ts +131 -0
  117. package/src/store/user/slices/settings/selectors/settings.ts +6 -0
  118. package/src/types/hotkey.ts +59 -0
  119. package/src/types/user/settings/hotkey.ts +3 -0
  120. package/src/types/user/settings/index.ts +3 -0
  121. package/src/types/user/settings/tts.ts +1 -1
  122. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/HotKeys.tsx +0 -44
  123. package/src/components/HotKeys/index.tsx +0 -77
@@ -0,0 +1,57 @@
1
+ import { uniq } from 'lodash-es';
2
+ import { DependencyList } from 'react';
3
+ import { type HotkeyCallback, type Options, useHotkeys } from 'react-hotkeys-hook';
4
+
5
+ import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
6
+ import { useServerConfigStore } from '@/store/serverConfig';
7
+ import { useUserStore } from '@/store/user';
8
+ import { settingsSelectors } from '@/store/user/selectors';
9
+ import { HotkeyId } from '@/types/hotkey';
10
+ import { isDev } from '@/utils/env';
11
+
12
+ type OptionsOrDependencyArray = Options | DependencyList;
13
+
14
+ export const useHotkeyById = (
15
+ hotkeyId: HotkeyId,
16
+ callback: HotkeyCallback,
17
+ options?: OptionsOrDependencyArray,
18
+ dependencies?: OptionsOrDependencyArray,
19
+ ) => {
20
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(hotkeyId));
21
+ const mobile = useServerConfigStore((s) => s.isMobile);
22
+
23
+ const _options: Options | undefined = !Array.isArray(options)
24
+ ? (options as Options)
25
+ : !Array.isArray(dependencies)
26
+ ? (dependencies as Options)
27
+ : undefined;
28
+
29
+ const _deps: DependencyList | undefined = Array.isArray(options)
30
+ ? options
31
+ : Array.isArray(dependencies)
32
+ ? dependencies
33
+ : undefined;
34
+
35
+ const item = HOTKEYS_REGISTRATION.find((item) => item.id === hotkeyId);
36
+
37
+ const ref = useHotkeys(
38
+ hotkey,
39
+ (...props) => {
40
+ if (isDev) console.log('[Hotkey]', hotkeyId);
41
+ return callback(...props);
42
+ },
43
+ {
44
+ enableOnFormTags: true,
45
+ preventDefault: true,
46
+ ..._options,
47
+ enabled: !mobile && _options?.enabled,
48
+ scopes: uniq([hotkeyId, ...(item?.scopes || []), ...(_options?.scopes || [])]),
49
+ },
50
+ _deps,
51
+ );
52
+
53
+ return {
54
+ id: hotkeyId,
55
+ ref,
56
+ };
57
+ };
@@ -0,0 +1,50 @@
1
+ import { HotkeyI18nTranslations } from '@/types/hotkey';
2
+
3
+ const hotkey: HotkeyI18nTranslations = {
4
+ addUserMessage: {
5
+ desc: '将当前输入内容添加为用户消息,但不触发生成',
6
+ title: '添加一条用户消息',
7
+ },
8
+ editMessage: {
9
+ desc: '通过按住 Alt 并双击消息进入编辑模式',
10
+ title: '编辑消息',
11
+ },
12
+ openChatSettings: {
13
+ desc: '查看和修改当前会话的设置',
14
+ title: '打开会话设置',
15
+ },
16
+ openHotkeyHelper: {
17
+ desc: '查看所有快捷键的使用说明',
18
+ title: '打开快捷键帮助',
19
+ },
20
+ regenerateMessage: {
21
+ desc: '重新生成最后一条消息',
22
+ title: '重新生成消息',
23
+ },
24
+ saveTopic: {
25
+ desc: '保存当前话题并打开新话题',
26
+ title: '开启新话题',
27
+ },
28
+ search: {
29
+ desc: '唤起当前页面主要搜索框',
30
+ title: '搜索',
31
+ },
32
+ switchAgent: {
33
+ desc: '通过按住 Ctrl 加数字 0~9 切换固定在侧边栏的助手',
34
+ title: '快捷切换助手',
35
+ },
36
+ toggleLeftPanel: {
37
+ desc: '显示或隐藏左侧助手面板',
38
+ title: '显示/隐藏助手面板',
39
+ },
40
+ toggleRightPanel: {
41
+ desc: '显示或隐藏右侧话题面板',
42
+ title: '显示/隐藏话题面板',
43
+ },
44
+ toggleZenMode: {
45
+ desc: '专注模式下,只显示当前会话,隐藏其他 UI',
46
+ title: '切换专注模式',
47
+ },
48
+ };
49
+
50
+ export default hotkey;
@@ -7,6 +7,7 @@ import components from './components';
7
7
  import discover from './discover';
8
8
  import error from './error';
9
9
  import file from './file';
10
+ import hotkey from './hotkey';
10
11
  import knowledgeBase from './knowledgeBase';
11
12
  import metadata from './metadata';
12
13
  import migration from './migration';
@@ -32,6 +33,7 @@ const resources = {
32
33
  discover,
33
34
  error,
34
35
  file,
36
+ hotkey,
35
37
  knowledgeBase,
36
38
  metadata,
37
39
  migration,
@@ -42,6 +42,17 @@ export default {
42
42
  sessionWithName: '会话设置 · {{name}}',
43
43
  title: '设置',
44
44
  },
45
+ hotkey: {
46
+ conflicts: '与现有快捷键冲突',
47
+ group: {
48
+ conversation: '会话',
49
+ essential: '基础',
50
+ },
51
+ invalidCombination: '快捷键需要至少包含一个修饰键 (Ctrl, Alt, Shift) 和一个常规键',
52
+ record: '按下按键以录制快捷键',
53
+ reset: '重置为默认快捷键',
54
+ title: '快捷键',
55
+ },
45
56
  llm: {
46
57
  aesGcm: '您的秘钥与代理地址等将使用 <1>AES-GCM</1> 加密算法进行加密',
47
58
  apiKey: {
@@ -427,6 +438,7 @@ export default {
427
438
  'agent': '默认助手',
428
439
  'common': '通用设置',
429
440
  'experiment': '实验',
441
+ 'hotkey': '快捷键',
430
442
  'llm': '语言模型',
431
443
  'provider': 'AI 服务商',
432
444
  'sync': '云端同步',
@@ -27,6 +27,7 @@ export enum SettingsTabs {
27
27
  About = 'about',
28
28
  Agent = 'agent',
29
29
  Common = 'common',
30
+ Hotkey = 'hotkey',
30
31
  LLM = 'llm',
31
32
  Provider = 'provider',
32
33
  Sync = 'sync',
@@ -60,6 +61,7 @@ export interface SystemStatus {
60
61
  sessionsWidth: number;
61
62
  showChatSideBar?: boolean;
62
63
  showFilePanel?: boolean;
64
+ showHotkeyHelper?: boolean;
63
65
  showSessionPanel?: boolean;
64
66
  showSystemRole?: boolean;
65
67
  /**
@@ -104,6 +106,7 @@ export const INITIAL_STATUS = {
104
106
  sessionsWidth: 320,
105
107
  showChatSideBar: true,
106
108
  showFilePanel: true,
109
+ showHotkeyHelper: false,
107
110
  showSessionPanel: true,
108
111
  showSystemRole: false,
109
112
  themeMode: 'auto',
@@ -48,6 +48,38 @@ exports[`settingsSelectors > currentSettings > should merge DEFAULT_SETTINGS and
48
48
  }
49
49
  `;
50
50
 
51
+ exports[`settingsSelectors > currentSystemAgent > should merge DEFAULT_SYSTEM_AGENT_CONFIG and s.settings.systemAgent correctly 1`] = `
52
+ {
53
+ "agentMeta": {
54
+ "model": "gpt-4o-mini",
55
+ "provider": "openai",
56
+ },
57
+ "enableAutoReply": true,
58
+ "historyCompress": {
59
+ "model": "gpt-4o-mini",
60
+ "provider": "openai",
61
+ },
62
+ "queryRewrite": {
63
+ "enabled": true,
64
+ "model": "gpt-4o-mini",
65
+ "provider": "openai",
66
+ },
67
+ "replyMessage": "Custom auto reply",
68
+ "thread": {
69
+ "model": "gpt-4o-mini",
70
+ "provider": "openai",
71
+ },
72
+ "topic": {
73
+ "model": "gpt-4o-mini",
74
+ "provider": "openai",
75
+ },
76
+ "translation": {
77
+ "model": "gpt-4o-mini",
78
+ "provider": "openai",
79
+ },
80
+ }
81
+ `;
82
+
51
83
  exports[`settingsSelectors > currentTTS > should merge DEFAULT_TTS_CONFIG and s.settings.tts correctly 1`] = `
52
84
  {
53
85
  "openAI": {
@@ -110,9 +142,56 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
110
142
  }
111
143
  `;
112
144
 
145
+ exports[`settingsSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CONFIG and defaultAgent(s).config correctly 1`] = `
146
+ {
147
+ "chatConfig": {
148
+ "autoCreateTopicThreshold": 2,
149
+ "displayMode": "chat",
150
+ "enableAutoCreateTopic": true,
151
+ "enableCompressHistory": true,
152
+ "enableHistoryCount": true,
153
+ "enableReasoning": false,
154
+ "historyCount": 8,
155
+ "reasoningBudgetToken": 1024,
156
+ "searchFCModel": {
157
+ "model": "gpt-4o-mini",
158
+ "provider": "openai",
159
+ },
160
+ "searchMode": "off",
161
+ },
162
+ "model": "gpt-4",
163
+ "params": {
164
+ "frequency_penalty": 0,
165
+ "presence_penalty": 0,
166
+ "temperature": 0.7,
167
+ "top_p": 1,
168
+ },
169
+ "plugins": [],
170
+ "provider": "openai",
171
+ "systemRole": "custom role",
172
+ "tts": {
173
+ "showAllLocaleVoice": false,
174
+ "sttLocale": "auto",
175
+ "ttsService": "openai",
176
+ "voice": {
177
+ "openai": "alloy",
178
+ },
179
+ },
180
+ }
181
+ `;
182
+
113
183
  exports[`settingsSelectors > defaultAgentMeta > should merge DEFAULT_AGENT_META and defaultAgent(s).meta correctly 1`] = `
114
184
  {
115
185
  "avatar": "agent-avatar.jpg",
116
186
  "description": "Test agent",
117
187
  }
118
188
  `;
189
+
190
+ exports[`settingsSelectors > getHotkeyById > should return default hotkey if not defined in settings 1`] = `undefined`;
191
+
192
+ exports[`settingsSelectors > getHotkeyById > should return the hotkey config for a given id 1`] = `
193
+ {
194
+ "hotkey": "ctrl+shift+f",
195
+ "scope": "global",
196
+ }
197
+ `;
@@ -1,3 +1,6 @@
1
+ import { HotkeyId } from '@/types/hotkey';
2
+ import { GlobalLLMProviderKey } from '@/types/user/settings';
3
+
1
4
  import { UserStore } from '../../../store';
2
5
  import { settingsSelectors } from './settings';
3
6
 
@@ -153,4 +156,132 @@ describe('settingsSelectors', () => {
153
156
  expect(result).toBe(true);
154
157
  });
155
158
  });
159
+
160
+ describe('getProviderConfigById', () => {
161
+ it('should return the provider config for a given provider id', () => {
162
+ const providerConfig = {
163
+ OPENAI_API_KEY: 'test-key',
164
+ endpoint: 'https://test-endpoint.com',
165
+ };
166
+
167
+ const s = {
168
+ settings: {
169
+ languageModel: {
170
+ openAI: providerConfig,
171
+ },
172
+ },
173
+ } as unknown as UserStore;
174
+
175
+ const result = settingsSelectors.providerConfig('openAI')(s);
176
+
177
+ expect(result).toEqual(providerConfig);
178
+ });
179
+
180
+ it('should return undefined if provider does not exist', () => {
181
+ const s = {
182
+ settings: {
183
+ languageModel: {},
184
+ },
185
+ } as unknown as UserStore;
186
+
187
+ const result = settingsSelectors.providerConfig(
188
+ 'nonExistentProvider' as GlobalLLMProviderKey,
189
+ )(s);
190
+
191
+ expect(result).toBeUndefined();
192
+ });
193
+ });
194
+
195
+ describe('defaultAgentConfig', () => {
196
+ it('should merge DEFAULT_AGENT_CONFIG and defaultAgent(s).config correctly', () => {
197
+ const s = {
198
+ settings: {
199
+ defaultAgent: {
200
+ config: {
201
+ systemRole: 'custom role',
202
+ model: 'gpt-4',
203
+ params: {
204
+ temperature: 0.7,
205
+ },
206
+ },
207
+ },
208
+ },
209
+ } as unknown as UserStore;
210
+
211
+ const result = settingsSelectors.defaultAgentConfig(s);
212
+
213
+ expect(result).toMatchSnapshot();
214
+ });
215
+ });
216
+
217
+ describe('exportSettings', () => {
218
+ it('should return the current settings', () => {
219
+ const s = {
220
+ defaultSettings: {
221
+ fontSize: 16,
222
+ },
223
+ settings: {
224
+ fontSize: 14,
225
+ language: 'en-US',
226
+ },
227
+ } as unknown as UserStore;
228
+
229
+ const result = settingsSelectors.exportSettings(s);
230
+
231
+ expect(result).toEqual({
232
+ fontSize: 14,
233
+ language: 'en-US',
234
+ });
235
+ });
236
+ });
237
+
238
+ describe('currentSystemAgent', () => {
239
+ it('should merge DEFAULT_SYSTEM_AGENT_CONFIG and s.settings.systemAgent correctly', () => {
240
+ const s = {
241
+ settings: {
242
+ systemAgent: {
243
+ enableAutoReply: true,
244
+ replyMessage: 'Custom auto reply',
245
+ },
246
+ },
247
+ } as unknown as UserStore;
248
+
249
+ const result = settingsSelectors.currentSystemAgent(s);
250
+
251
+ expect(result).toMatchSnapshot();
252
+ });
253
+ });
254
+
255
+ describe('getHotkeyById', () => {
256
+ it('should return the hotkey config for a given id', () => {
257
+ const hotkeyConfig = {
258
+ hotkey: 'ctrl+shift+f',
259
+ scope: 'global',
260
+ };
261
+
262
+ const s = {
263
+ settings: {
264
+ hotkey: {
265
+ newChat: hotkeyConfig,
266
+ },
267
+ },
268
+ } as unknown as UserStore;
269
+
270
+ const result = settingsSelectors.getHotkeyById('newChat' as HotkeyId)(s);
271
+
272
+ expect(result).toMatchSnapshot();
273
+ });
274
+
275
+ it('should return default hotkey if not defined in settings', () => {
276
+ const s = {
277
+ settings: {
278
+ hotkey: {},
279
+ },
280
+ } as unknown as UserStore;
281
+
282
+ const result = settingsSelectors.getHotkeyById('newChat' as HotkeyId)(s);
283
+
284
+ expect(result).toMatchSnapshot();
285
+ });
286
+ });
156
287
  });
@@ -2,9 +2,11 @@ import { DEFAULT_AGENT_META } from '@/const/meta';
2
2
  import {
3
3
  DEFAULT_AGENT,
4
4
  DEFAULT_AGENT_CONFIG,
5
+ DEFAULT_HOTKEY_CONFIG,
5
6
  DEFAULT_SYSTEM_AGENT_CONFIG,
6
7
  DEFAULT_TTS_CONFIG,
7
8
  } from '@/const/settings';
9
+ import { HotkeyId } from '@/types/hotkey';
8
10
  import {
9
11
  GlobalLLMProviderKey,
10
12
  ProviderConfig,
@@ -38,6 +40,9 @@ const isDalleAutoGenerating = (s: UserStore) => currentSettings(s).tool?.dalle?.
38
40
  const currentSystemAgent = (s: UserStore) =>
39
41
  merge(DEFAULT_SYSTEM_AGENT_CONFIG, currentSettings(s).systemAgent);
40
42
 
43
+ const getHotkeyById = (id: HotkeyId) => (s: UserStore) =>
44
+ merge(DEFAULT_HOTKEY_CONFIG, currentSettings(s).hotkey)[id];
45
+
41
46
  export const settingsSelectors = {
42
47
  currentSettings,
43
48
  currentSystemAgent,
@@ -47,6 +52,7 @@ export const settingsSelectors = {
47
52
  defaultAgentConfig,
48
53
  defaultAgentMeta,
49
54
  exportSettings,
55
+ getHotkeyById,
50
56
  isDalleAutoGenerating,
51
57
  providerConfig: getProviderConfigById,
52
58
  };
@@ -0,0 +1,59 @@
1
+ import { KeyMapEnum } from '@lobehub/ui/es/Hotkey';
2
+
3
+ export const KeyEnum = {
4
+ ...KeyMapEnum,
5
+ Number: '1-9',
6
+ } as const;
7
+
8
+ export const HotkeyEnum = {
9
+ AddUserMessage: 'addUserMessage',
10
+ EditMessage: 'editMessage',
11
+ OpenChatSettings: 'openChatSettings',
12
+ OpenHotkeyHelper: 'openHotkeyHelper',
13
+ RegenerateMessage: 'regenerateMessage',
14
+ SaveTopic: 'saveTopic',
15
+ Search: 'search',
16
+ SwitchAgent: 'switchAgent',
17
+ ToggleLeftPanel: 'toggleLeftPanel',
18
+ ToggleRightPanel: 'toggleRightPanel',
19
+ ToggleZenMode: 'toggleZenMode',
20
+ } as const;
21
+
22
+ export const HotkeyGroupEnum = {
23
+ Conversation: 'conversation',
24
+ Essential: 'essential',
25
+ } as const;
26
+
27
+ export const HotkeyScopeEnum = {
28
+ Chat: 'chat',
29
+ // 默认全局注册的快捷键 scope
30
+ // https://react-hotkeys-hook.vercel.app/docs/documentation/hotkeys-provider
31
+ Global: 'global',
32
+ } as const;
33
+
34
+ export type HotkeyId = (typeof HotkeyEnum)[keyof typeof HotkeyEnum];
35
+ export type HotkeyGroupId = (typeof HotkeyGroupEnum)[keyof typeof HotkeyGroupEnum];
36
+ export type HotkeyScopeId = (typeof HotkeyScopeEnum)[keyof typeof HotkeyScopeEnum];
37
+
38
+ export interface HotkeyItem {
39
+ // 快捷键分组用于展示
40
+ group: HotkeyGroupId;
41
+ id: HotkeyId;
42
+ isDesktop?: boolean;
43
+ // 是否是桌面端专用的快捷键
44
+ keys: string;
45
+ // 是否为不可编辑的快捷键
46
+ nonEditable?: boolean;
47
+ // 快捷键作用域
48
+ scopes?: HotkeyScopeId[];
49
+ }
50
+
51
+ export type HotkeyRegistration = HotkeyItem[];
52
+
53
+ export type HotkeyI18nTranslations = Record<
54
+ HotkeyId,
55
+ {
56
+ desc?: string;
57
+ title: string;
58
+ }
59
+ >;
@@ -0,0 +1,3 @@
1
+ import { HotkeyId } from '@/types/hotkey';
2
+
3
+ export type UserHotkeyConfig = Record<HotkeyId, string>;
@@ -1,6 +1,7 @@
1
1
  import type { LobeAgentSettings } from '@/types/session';
2
2
 
3
3
  import { UserGeneralConfig } from './general';
4
+ import { UserHotkeyConfig } from './hotkey';
4
5
  import { UserKeyVaults } from './keyVaults';
5
6
  import { UserModelProviderConfig } from './modelProvider';
6
7
  import { UserSyncSettings } from './sync';
@@ -11,6 +12,7 @@ import { UserTTSConfig } from './tts';
11
12
  export type UserDefaultAgent = LobeAgentSettings;
12
13
 
13
14
  export * from './general';
15
+ export * from './hotkey';
14
16
  export * from './keyVaults';
15
17
  export * from './modelProvider';
16
18
  export * from './sync';
@@ -23,6 +25,7 @@ export * from './tts';
23
25
  export interface UserSettings {
24
26
  defaultAgent: UserDefaultAgent;
25
27
  general: UserGeneralConfig;
28
+ hotkey: UserHotkeyConfig;
26
29
  keyVaults: UserKeyVaults;
27
30
  languageModel: UserModelProviderConfig;
28
31
  sync?: UserSyncSettings;
@@ -3,7 +3,7 @@ export type STTServer = 'openai' | 'browser';
3
3
  export interface UserTTSConfig {
4
4
  openAI: {
5
5
  sttModel: 'whisper-1';
6
- ttsModel: 'tts-1' | 'tts-1-hd';
6
+ ttsModel: 'gpt-4o-mini-tts' | 'tts-1' | 'tts-1-hd';
7
7
  };
8
8
  sttAutoStop: boolean;
9
9
  sttServer: STTServer;
@@ -1,44 +0,0 @@
1
- 'use client';
2
-
3
- import isEqual from 'fast-deep-equal';
4
- import { useHotkeys } from 'react-hotkeys-hook';
5
-
6
- import { HOTKEYS } from '@/const/hotkeys';
7
- import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
8
- import { useChatStore } from '@/store/chat';
9
- import { chatSelectors } from '@/store/chat/selectors';
10
- import { useGlobalStore } from '@/store/global';
11
-
12
- const HotKeys = () => {
13
- const [regenerateMessage] = useChatStore((s) => [s.regenerateMessage]);
14
- const lastMessage = useChatStore(chatSelectors.latestMessage, isEqual);
15
-
16
- const toggleZenMode = useGlobalStore((s) => s.toggleZenMode);
17
- const openChatSettings = useOpenChatSettings();
18
-
19
- useHotkeys(HOTKEYS.zenMode, toggleZenMode, {
20
- enableOnFormTags: true,
21
- preventDefault: true,
22
- });
23
-
24
- useHotkeys(HOTKEYS.chatSettings, () => openChatSettings(), {
25
- enableOnFormTags: false,
26
- preventDefault: true,
27
- });
28
-
29
- useHotkeys(
30
- HOTKEYS.regenerate,
31
- () => {
32
- if (!lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system') return;
33
- regenerateMessage(lastMessage.id);
34
- },
35
- {
36
- enableOnFormTags: true,
37
- preventDefault: true,
38
- },
39
- );
40
-
41
- return null;
42
- };
43
-
44
- export default HotKeys;
@@ -1,77 +0,0 @@
1
- 'use client';
2
-
3
- import { Icon } from '@lobehub/ui';
4
- import { createStyles } from 'antd-style';
5
- import { isString } from 'lodash-es';
6
- import { Command, Delete, Option } from 'lucide-react';
7
- import { rgba } from 'polished';
8
- import { memo, useEffect, useState } from 'react';
9
- import { Flexbox } from 'react-layout-kit';
10
-
11
- import { ALT_KEY, CLEAN_MESSAGE_KEY, META_KEY } from '@/const/hotkeys';
12
- import { usePlatform } from '@/hooks/usePlatform';
13
-
14
- const useStyles = createStyles(
15
- ({ css, token }, inverseTheme: boolean) => css`
16
- font-size: 12px;
17
-
18
- kbd {
19
- min-width: 16px;
20
- height: 22px;
21
- padding-inline: 8px;
22
- border-radius: ${token.borderRadius}px;
23
-
24
- line-height: 22px;
25
- color: ${inverseTheme ? token.colorTextTertiary : token.colorTextSecondary};
26
- text-align: center;
27
-
28
- background: ${inverseTheme ? rgba(token.colorTextTertiary, 0.15) : token.colorFillTertiary};
29
- }
30
- `,
31
- );
32
-
33
- export interface HotKeysProps {
34
- desc?: string;
35
- inverseTheme?: boolean;
36
- keys: string;
37
- }
38
-
39
- const HotKeys = memo<HotKeysProps>(({ keys, desc, inverseTheme }) => {
40
- const { styles } = useStyles(inverseTheme);
41
- const [keysGroup, setKeysGroup] = useState(keys.split('+'));
42
- const visibility = typeof window === 'undefined' ? 'hidden' : 'visible';
43
- const { isApple } = usePlatform();
44
-
45
- useEffect(() => {
46
- const mapping: Record<string, any> = {
47
- [ALT_KEY]: isApple ? <Icon icon={Option} /> : 'alt',
48
- [CLEAN_MESSAGE_KEY]: isApple ? <Icon icon={Delete} /> : 'backspace',
49
- [META_KEY]: isApple ? <Icon icon={Command} /> : 'ctrl',
50
- };
51
- const newValue = keys
52
- .split('+')
53
- .filter(Boolean)
54
- .map((k) => mapping[k] ?? k);
55
- setKeysGroup(newValue);
56
- }, [keys]);
57
-
58
- const content = (
59
- <Flexbox align={'center'} className={styles} gap={4} horizontal>
60
- {keysGroup.map((key, index) => (
61
- <kbd key={index}>
62
- <span style={{ visibility }}>{isString(key) ? key.toUpperCase() : key}</span>
63
- </kbd>
64
- ))}
65
- </Flexbox>
66
- );
67
-
68
- if (!desc) return content;
69
- return (
70
- <Flexbox gap={16} horizontal>
71
- {desc}
72
- {content}
73
- </Flexbox>
74
- );
75
- });
76
-
77
- export default HotKeys;