@lobehub/chat 1.75.5 → 1.76.0

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 (99) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/developer/database-schema.dbml +1 -0
  4. package/locales/ar/hotkey.json +46 -0
  5. package/locales/ar/setting.json +12 -0
  6. package/locales/bg-BG/hotkey.json +46 -0
  7. package/locales/bg-BG/setting.json +12 -0
  8. package/locales/de-DE/hotkey.json +46 -0
  9. package/locales/de-DE/setting.json +12 -0
  10. package/locales/en-US/hotkey.json +46 -0
  11. package/locales/en-US/setting.json +12 -0
  12. package/locales/es-ES/hotkey.json +46 -0
  13. package/locales/es-ES/setting.json +12 -0
  14. package/locales/fa-IR/hotkey.json +46 -0
  15. package/locales/fa-IR/setting.json +12 -0
  16. package/locales/fr-FR/hotkey.json +46 -0
  17. package/locales/fr-FR/setting.json +12 -0
  18. package/locales/it-IT/hotkey.json +46 -0
  19. package/locales/it-IT/setting.json +12 -0
  20. package/locales/ja-JP/hotkey.json +46 -0
  21. package/locales/ja-JP/setting.json +12 -0
  22. package/locales/ko-KR/hotkey.json +46 -0
  23. package/locales/ko-KR/setting.json +12 -0
  24. package/locales/nl-NL/hotkey.json +46 -0
  25. package/locales/nl-NL/setting.json +12 -0
  26. package/locales/pl-PL/hotkey.json +46 -0
  27. package/locales/pl-PL/setting.json +12 -0
  28. package/locales/pt-BR/hotkey.json +46 -0
  29. package/locales/pt-BR/setting.json +12 -0
  30. package/locales/ru-RU/hotkey.json +46 -0
  31. package/locales/ru-RU/setting.json +12 -0
  32. package/locales/tr-TR/hotkey.json +46 -0
  33. package/locales/tr-TR/setting.json +12 -0
  34. package/locales/vi-VN/hotkey.json +46 -0
  35. package/locales/vi-VN/setting.json +12 -0
  36. package/locales/zh-CN/hotkey.json +46 -0
  37. package/locales/zh-CN/setting.json +12 -0
  38. package/locales/zh-TW/hotkey.json +46 -0
  39. package/locales/zh-TW/setting.json +12 -0
  40. package/package.json +3 -3
  41. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/Category.tsx +1 -1
  42. package/src/app/[variants]/(main)/(mobile)/me/(home)/layout.tsx +3 -2
  43. package/src/app/[variants]/(main)/(mobile)/me/data/features/Category.tsx +1 -1
  44. package/src/app/[variants]/(main)/(mobile)/me/profile/features/Category.tsx +1 -1
  45. package/src/app/[variants]/(main)/(mobile)/me/settings/features/Category.tsx +1 -1
  46. package/src/app/[variants]/(main)/_layout/Desktop/RegisterHotkeys.tsx +11 -0
  47. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +6 -23
  48. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.test.tsx +2 -0
  49. package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +11 -4
  50. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/SendMore.tsx +6 -21
  51. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/ShortcutHint.tsx +13 -34
  52. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +1 -1
  53. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ZenModeToast/Toast.tsx +7 -4
  54. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +12 -8
  55. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +24 -30
  56. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/index.tsx +0 -2
  57. package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +12 -7
  58. package/src/app/[variants]/(main)/chat/@session/features/SessionSearchBar.tsx +5 -1
  59. package/src/app/[variants]/(main)/chat/_layout/Desktop/RegisterHotkeys.tsx +10 -0
  60. package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +5 -0
  61. package/src/app/[variants]/(main)/chat/_layout/Mobile.tsx +1 -1
  62. package/src/app/[variants]/(main)/discover/features/StoreSearchBar.tsx +5 -1
  63. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +31 -21
  64. package/src/app/[variants]/(main)/settings/hotkey/features/HotkeySetting.tsx +80 -0
  65. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +9 -0
  66. package/src/app/[variants]/(main)/settings/hotkey/page.tsx +15 -0
  67. package/src/app/[variants]/layout.tsx +16 -13
  68. package/src/const/hotkeys.ts +80 -10
  69. package/src/const/settings/hotkey.ts +10 -0
  70. package/src/const/settings/index.ts +3 -0
  71. package/src/database/client/migrations.json +46 -32
  72. package/src/database/migrations/0019_add_hotkey_user_settings.sql +2 -0
  73. package/src/database/migrations/meta/0019_snapshot.json +4218 -0
  74. package/src/database/migrations/meta/_journal.json +7 -0
  75. package/src/database/schemas/user.ts +1 -0
  76. package/src/database/server/models/user.ts +2 -0
  77. package/src/features/ChatInput/Desktop/InputArea/index.tsx +8 -0
  78. package/src/features/ChatInput/Desktop/index.tsx +0 -1
  79. package/src/features/ChatInput/Topic/index.tsx +10 -15
  80. package/src/features/FileManager/Header/FilesSearchBar.tsx +6 -2
  81. package/src/features/HotkeyHelperPanel/HotkeyContent.tsx +62 -0
  82. package/src/features/HotkeyHelperPanel/index.tsx +59 -0
  83. package/src/hooks/useHotkeys/chatScope.ts +105 -0
  84. package/src/hooks/useHotkeys/globalScope.ts +69 -0
  85. package/src/hooks/useHotkeys/index.ts +2 -0
  86. package/src/hooks/useHotkeys/useHotkeyById.test.ts +194 -0
  87. package/src/hooks/useHotkeys/useHotkeyById.ts +57 -0
  88. package/src/locales/default/hotkey.ts +50 -0
  89. package/src/locales/default/index.ts +2 -0
  90. package/src/locales/default/setting.ts +12 -0
  91. package/src/store/global/initialState.ts +3 -0
  92. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +79 -0
  93. package/src/store/user/slices/settings/selectors/settings.test.ts +131 -0
  94. package/src/store/user/slices/settings/selectors/settings.ts +6 -0
  95. package/src/types/hotkey.ts +59 -0
  96. package/src/types/user/settings/hotkey.ts +3 -0
  97. package/src/types/user/settings/index.ts +3 -0
  98. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/HotKeys.tsx +0 -44
  99. package/src/components/HotKeys/index.tsx +0 -77
@@ -0,0 +1,46 @@
1
+ {
2
+ "addUserMessage": {
3
+ "desc": "將當前輸入內容添加為使用者消息,但不觸發生成",
4
+ "title": "添加一條使用者消息"
5
+ },
6
+ "editMessage": {
7
+ "desc": "通過按住 Alt 並雙擊消息進入編輯模式",
8
+ "title": "編輯消息"
9
+ },
10
+ "openChatSettings": {
11
+ "desc": "查看和修改當前會話的設定",
12
+ "title": "打開會話設定"
13
+ },
14
+ "openHotkeyHelper": {
15
+ "desc": "查看所有快捷鍵的使用說明",
16
+ "title": "打開快捷鍵幫助"
17
+ },
18
+ "regenerateMessage": {
19
+ "desc": "重新生成最後一條消息",
20
+ "title": "重新生成消息"
21
+ },
22
+ "saveTopic": {
23
+ "desc": "保存當前話題並打開新話題",
24
+ "title": "開啟新話題"
25
+ },
26
+ "search": {
27
+ "desc": "喚起當前頁面主要搜尋框",
28
+ "title": "搜尋"
29
+ },
30
+ "switchAgent": {
31
+ "desc": "通過按住 Ctrl 加數字 0~9 切換固定在側邊欄的助手",
32
+ "title": "快捷切換助手"
33
+ },
34
+ "toggleLeftPanel": {
35
+ "desc": "顯示或隱藏左側助手面板",
36
+ "title": "顯示/隱藏助手面板"
37
+ },
38
+ "toggleRightPanel": {
39
+ "desc": "顯示或隱藏右側話題面板",
40
+ "title": "顯示/隱藏話題面板"
41
+ },
42
+ "toggleZenMode": {
43
+ "desc": "專注模式下,只顯示當前會話,隱藏其他 UI",
44
+ "title": "切換專注模式"
45
+ }
46
+ }
@@ -42,6 +42,17 @@
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": {
@@ -425,6 +436,7 @@
425
436
  "agent": "默認助手",
426
437
  "common": "通用設置",
427
438
  "experiment": "實驗",
439
+ "hotkey": "快速鍵",
428
440
  "llm": "語言模型",
429
441
  "provider": "AI 服務商",
430
442
  "sync": "雲端同步",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.75.5",
3
+ "version": "1.76.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -135,7 +135,7 @@
135
135
  "@lobehub/chat-plugins-gateway": "^1.9.0",
136
136
  "@lobehub/icons": "^1.73.1",
137
137
  "@lobehub/tts": "^1.28.0",
138
- "@lobehub/ui": "^1.165.8",
138
+ "@lobehub/ui": "^1.169.2",
139
139
  "@neondatabase/serverless": "^0.10.4",
140
140
  "@next/third-parties": "15.2.3",
141
141
  "@react-spring/web": "^9.7.5",
@@ -192,7 +192,7 @@
192
192
  "next-mdx-remote": "^5.0.0",
193
193
  "nextjs-toploader": "^3.7.15",
194
194
  "numeral": "^2.0.6",
195
- "nuqs": "^1.20.0",
195
+ "nuqs": "^2.4.1",
196
196
  "officeparser": "^5.1.1",
197
197
  "ollama": "^0.5.11",
198
198
  "openai": "^4.77.3",
@@ -9,7 +9,7 @@ import { useCategory } from './useCategory';
9
9
  const Category = memo(() => {
10
10
  const items = useCategory();
11
11
 
12
- return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
12
+ return items?.map(({ key, ...item }, index) => <Cell key={key || index} {...item} />);
13
13
  });
14
14
 
15
15
  export default Category;
@@ -1,5 +1,6 @@
1
- import { PropsWithChildren } from 'react';
1
+ import { PropsWithChildren, Suspense } from 'react';
2
2
 
3
+ import Loading from '@/components/Loading/BrandTextLoading';
3
4
  import MobileContentLayout from '@/components/server/MobileNavLayout';
4
5
  import InitClientDB from '@/features/InitClientDB';
5
6
 
@@ -8,7 +9,7 @@ import Header from './features/Header';
8
9
  const Layout = ({ children }: PropsWithChildren) => {
9
10
  return (
10
11
  <MobileContentLayout header={<Header />} withNav>
11
- {children}
12
+ <Suspense fallback={<Loading />}>{children}</Suspense>
12
13
  <InitClientDB />
13
14
  </MobileContentLayout>
14
15
  );
@@ -42,7 +42,7 @@ const Category = memo(() => {
42
42
  },
43
43
  ];
44
44
 
45
- return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
45
+ return items?.map(({ key, ...item }, index) => <Cell key={key || index} {...item} />);
46
46
  });
47
47
 
48
48
  export default Category;
@@ -56,7 +56,7 @@ const Category = memo(() => {
56
56
  },
57
57
  ].filter(Boolean) as CellProps[];
58
58
 
59
- return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
59
+ return items?.map(({ key, ...item }, index) => <Cell key={key || index} {...item} />);
60
60
  });
61
61
 
62
62
  export default Category;
@@ -9,7 +9,7 @@ import { useCategory } from './useCategory';
9
9
  const Category = memo(() => {
10
10
  const items = useCategory();
11
11
 
12
- return items?.map((item, index) => <Cell {...item} key={item.key || index} />);
12
+ return items?.map(({ key, ...item }, index) => <Cell key={key || index} {...item} />);
13
13
  });
14
14
 
15
15
  export default Category;
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+
3
+ import { useRegisterGlobalHotkeys } from '@/hooks/useHotkeys';
4
+
5
+ const RegisterHotkeys = () => {
6
+ useRegisterGlobalHotkeys();
7
+
8
+ return null;
9
+ };
10
+
11
+ export default RegisterHotkeys;
@@ -3,14 +3,15 @@ import { Divider } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import isEqual from 'fast-deep-equal';
5
5
  import { parseAsBoolean, useQueryState } from 'nuqs';
6
- import { useHotkeys } from 'react-hotkeys-hook';
7
6
  import { Flexbox } from 'react-layout-kit';
8
7
 
9
- import HotKeys from '@/components/HotKeys';
10
8
  import { useSwitchSession } from '@/hooks/useSwitchSession';
11
9
  import { useSessionStore } from '@/store/session';
12
10
  import { sessionHelpers } from '@/store/session/helpers';
13
11
  import { sessionSelectors } from '@/store/session/selectors';
12
+ import { useUserStore } from '@/store/user';
13
+ import { settingsSelectors } from '@/store/user/selectors';
14
+ import { HotkeyEnum, KeyEnum } from '@/types/hotkey';
14
15
 
15
16
  const useStyles = createStyles(({ css, token }) => ({
16
17
  avatar: css`
@@ -52,7 +53,7 @@ const PinList = () => {
52
53
  const list = useSessionStore(sessionSelectors.pinnedSessions, isEqual);
53
54
  const [activeId] = useSessionStore((s) => [s.activeId]);
54
55
  const switchSession = useSwitchSession();
55
-
56
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.SwitchAgent));
56
57
  const hasList = list.length > 0;
57
58
  const [isPinned, setPinned] = useQueryState('pinned', parseAsBoolean);
58
59
 
@@ -61,20 +62,6 @@ const PinList = () => {
61
62
  setPinned(true);
62
63
  };
63
64
 
64
- useHotkeys(
65
- list.slice(0, 9).map((e, i) => `ctrl+${i + 1}`),
66
- (keyboardEvent, hotkeysEvent) => {
67
- if (!hotkeysEvent.keys?.[0]) return;
68
-
69
- const index = parseInt(hotkeysEvent.keys?.[0]) - 1;
70
- const item = list[index];
71
- if (!item) return;
72
-
73
- switchAgent(item.id);
74
- },
75
- { enableOnFormTags: true, preventDefault: true },
76
- );
77
-
78
65
  return (
79
66
  hasList && (
80
67
  <>
@@ -83,13 +70,9 @@ const PinList = () => {
83
70
  {list.slice(0, 9).map((item, index) => (
84
71
  <Flexbox key={item.id} style={{ position: 'relative' }}>
85
72
  <Tooltip
73
+ hotkey={hotkey.replaceAll(KeyEnum.Number, String(index + 1))}
86
74
  placement={'right'}
87
- title={
88
- <Flexbox gap={8} horizontal>
89
- {sessionHelpers.getTitle(item.meta)}
90
- <HotKeys inverseTheme keys={`ctrl+${index + 1}`} />
91
- </Flexbox>
92
- }
75
+ title={sessionHelpers.getTitle(item.meta)}
93
76
  >
94
77
  <Flexbox
95
78
  className={cx(
@@ -36,6 +36,8 @@ vi.mock('next/link', () => ({
36
36
 
37
37
  vi.mock('@lobehub/ui', () => ({
38
38
  ActionIcon: vi.fn(({ title }) => <div>{title}</div>),
39
+ combineKeys: vi.fn((keys) => keys.join('+')),
40
+ KeyMapEnum: { Alt: 'alt', Ctrl: 'ctrl', Shift: 'shift' },
39
41
  }));
40
42
 
41
43
  vi.mock('react-i18next', () => ({
@@ -2,13 +2,17 @@
2
2
 
3
3
  import { useTheme } from 'antd-style';
4
4
  import dynamic from 'next/dynamic';
5
- import { PropsWithChildren, memo } from 'react';
5
+ import { PropsWithChildren, Suspense, memo } from 'react';
6
+ import { HotkeysProvider } from 'react-hotkeys-hook';
6
7
  import { Flexbox } from 'react-layout-kit';
7
8
 
8
9
  import { BANNER_HEIGHT } from '@/features/AlertBanner/CloudBanner';
10
+ import HotkeyHelperPanel from '@/features/HotkeyHelperPanel';
9
11
  import { usePlatform } from '@/hooks/usePlatform';
10
12
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
13
+ import { HotkeyScopeEnum } from '@/types/hotkey';
11
14
 
15
+ import RegisterHotkeys from './RegisterHotkeys';
12
16
  import SideBar from './SideBar';
13
17
 
14
18
  const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
@@ -16,11 +20,10 @@ const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
16
20
  const Layout = memo<PropsWithChildren>(({ children }) => {
17
21
  const { isPWA } = usePlatform();
18
22
  const theme = useTheme();
19
-
20
23
  const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
21
24
 
22
25
  return (
23
- <>
26
+ <HotkeysProvider initiallyActiveScopes={[HotkeyScopeEnum.Global]}>
24
27
  {showCloudPromotion && <CloudBanner />}
25
28
  <Flexbox
26
29
  height={showCloudPromotion ? `calc(100% - ${BANNER_HEIGHT}px)` : '100%'}
@@ -34,7 +37,11 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
34
37
  <SideBar />
35
38
  {children}
36
39
  </Flexbox>
37
- </>
40
+ <HotkeyHelperPanel />
41
+ <Suspense>
42
+ <RegisterHotkeys />
43
+ </Suspense>
44
+ </HotkeysProvider>
38
45
  );
39
46
  });
40
47
 
@@ -1,18 +1,16 @@
1
- import { Icon } from '@lobehub/ui';
1
+ import { Hotkey, Icon } from '@lobehub/ui';
2
2
  import { Button, Dropdown } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import { BotMessageSquare, LucideCheck, LucideChevronDown, MessageSquarePlus } from 'lucide-react';
5
5
  import { memo } from 'react';
6
- import { useHotkeys } from 'react-hotkeys-hook';
7
6
  import { useTranslation } from 'react-i18next';
8
7
  import { Flexbox } from 'react-layout-kit';
9
8
 
10
- import HotKeys from '@/components/HotKeys';
11
- import { ALT_KEY } from '@/const/hotkeys';
12
9
  import { useSendMessage } from '@/features/ChatInput/useSend';
13
10
  import { useChatStore } from '@/store/chat';
14
11
  import { useUserStore } from '@/store/user';
15
- import { preferenceSelectors } from '@/store/user/selectors';
12
+ import { preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
13
+ import { HotkeyEnum } from '@/types/hotkey';
16
14
 
17
15
  const useStyles = createStyles(({ css, prefixCls }) => {
18
16
  return {
@@ -31,7 +29,7 @@ interface SendMoreProps {
31
29
 
32
30
  const SendMore = memo<SendMoreProps>(({ disabled, isMac }) => {
33
31
  const { t } = useTranslation('chat');
34
-
32
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.AddUserMessage));
35
33
  const { styles } = useStyles();
36
34
 
37
35
  const [useCmdEnterToSend, updatePreference] = useUserStore((s) => [
@@ -42,19 +40,6 @@ const SendMore = memo<SendMoreProps>(({ disabled, isMac }) => {
42
40
 
43
41
  const { send: sendMessage } = useSendMessage();
44
42
 
45
- const hotKey = [ALT_KEY, 'enter'].join('+');
46
- useHotkeys(
47
- hotKey,
48
- (keyboardEvent, hotkeysEvent) => {
49
- console.log(keyboardEvent, hotkeysEvent);
50
- sendMessage({ onlyAddUserMessage: true });
51
- },
52
- {
53
- enableOnFormTags: true,
54
- preventDefault: true,
55
- },
56
- );
57
-
58
43
  return (
59
44
  <Dropdown
60
45
  disabled={disabled}
@@ -91,9 +76,9 @@ const SendMore = memo<SendMoreProps>(({ disabled, isMac }) => {
91
76
  icon: <Icon icon={MessageSquarePlus} />,
92
77
  key: 'addUser',
93
78
  label: (
94
- <Flexbox gap={24} horizontal>
79
+ <Flexbox align={'center'} gap={24} horizontal>
95
80
  {t('input.addUser')}
96
- <HotKeys keys={hotKey} />
81
+ <Hotkey keys={hotkey} />
97
82
  </Flexbox>
98
83
  ),
99
84
  onClick: () => {
@@ -1,58 +1,37 @@
1
- import { Icon } from '@lobehub/ui';
2
- import { Skeleton } from 'antd';
1
+ import { Hotkey, combineKeys } from '@lobehub/ui';
3
2
  import { useTheme } from 'antd-style';
4
- import { ChevronUp, CornerDownLeft, LucideCommand } from 'lucide-react';
5
- import { memo, useEffect, useState } from 'react';
3
+ import { memo } from 'react';
6
4
  import { useTranslation } from 'react-i18next';
7
- import { Center, Flexbox } from 'react-layout-kit';
5
+ import { Flexbox } from 'react-layout-kit';
8
6
 
9
7
  import { useUserStore } from '@/store/user';
10
8
  import { preferenceSelectors } from '@/store/user/selectors';
11
- import { isMacOS } from '@/utils/platform';
9
+ import { KeyEnum } from '@/types/hotkey';
12
10
 
13
11
  const ShortcutHint = memo(() => {
14
12
  const { t } = useTranslation('chat');
15
13
  const theme = useTheme();
16
14
  const useCmdEnterToSend = useUserStore(preferenceSelectors.useCmdEnterToSend);
17
- const [isMac, setIsMac] = useState<boolean>();
18
15
 
19
- useEffect(() => {
20
- setIsMac(isMacOS());
21
- }, []);
16
+ const sendShortcut = useCmdEnterToSend
17
+ ? combineKeys([KeyEnum.Mod, KeyEnum.Enter])
18
+ : KeyEnum.Enter;
22
19
 
23
- const cmdEnter = (
24
- <Flexbox gap={2} horizontal>
25
- {typeof isMac === 'boolean' ? (
26
- <Icon icon={isMac ? LucideCommand : ChevronUp} />
27
- ) : (
28
- <Skeleton.Node active style={{ height: '100%', width: 12 }}>
29
- {' '}
30
- </Skeleton.Node>
31
- )}
32
- <Icon icon={CornerDownLeft} />
33
- </Flexbox>
34
- );
35
-
36
- const enter = (
37
- <Center>
38
- <Icon icon={CornerDownLeft} />
39
- </Center>
40
- );
41
-
42
- const sendShortcut = useCmdEnterToSend ? cmdEnter : enter;
43
-
44
- const wrapperShortcut = useCmdEnterToSend ? enter : cmdEnter;
20
+ const wrapperShortcut = useCmdEnterToSend
21
+ ? KeyEnum.Enter
22
+ : combineKeys([KeyEnum.Mod, KeyEnum.Enter]);
45
23
 
46
24
  return (
47
25
  <Flexbox
26
+ align={'center'}
48
27
  gap={4}
49
28
  horizontal
50
29
  style={{ color: theme.colorTextDescription, fontSize: 12, marginRight: 12 }}
51
30
  >
52
- {sendShortcut}
31
+ <Hotkey keys={sendShortcut} style={{ color: 'inherit' }} variant={'pure'} />
53
32
  <span>{t('input.send')}</span>
54
33
  <span>/</span>
55
- {wrapperShortcut}
34
+ <Hotkey keys={wrapperShortcut} style={{ color: 'inherit' }} variant={'pure'} />
56
35
  <span>{t('input.warp')}</span>
57
36
  </Flexbox>
58
37
  );
@@ -69,7 +69,7 @@ const Footer = memo<FooterProps>(({ onExpandChange, expand }) => {
69
69
 
70
70
  return (
71
71
  <>
72
- <Suspense fallback={null}>
72
+ <Suspense>
73
73
  <MessageFromUrl />
74
74
  </Suspense>
75
75
  <Flexbox
@@ -1,12 +1,14 @@
1
1
  'use client';
2
2
 
3
+ import { Hotkey } from '@lobehub/ui';
3
4
  import { createStyles } from 'antd-style';
4
5
  import { useEffect, useState } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
7
  import { Flexbox } from 'react-layout-kit';
7
8
 
8
- import HotKeys from '@/components/HotKeys';
9
- import { HOTKEYS } from '@/const/hotkeys';
9
+ import { useUserStore } from '@/store/user';
10
+ import { settingsSelectors } from '@/store/user/selectors';
11
+ import { HotkeyEnum } from '@/types/hotkey';
10
12
 
11
13
  const useStyles = createStyles(({ css, token }) => ({
12
14
  closeButton: css`
@@ -62,6 +64,7 @@ const Toast = () => {
62
64
  const { t } = useTranslation('chat');
63
65
  const { styles } = useStyles();
64
66
  const [isVisible, setIsVisible] = useState(true);
67
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ToggleZenMode));
65
68
 
66
69
  useEffect(() => {
67
70
  const timer = setTimeout(() => {
@@ -76,8 +79,8 @@ const Toast = () => {
76
79
  return (
77
80
  <div className={styles.container}>
78
81
  <div className={styles.toast}>
79
- <Flexbox className={styles.text} gap={12} horizontal>
80
- {t('zenMode')} <HotKeys inverseTheme keys={HOTKEYS.zenMode} />
82
+ <Flexbox align={'center'} className={styles.text} gap={8} horizontal>
83
+ {t('zenMode')} <Hotkey inverseTheme keys={hotkey} />
81
84
  </Flexbox>
82
85
  </div>
83
86
  </div>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ActionIcon } from '@lobehub/ui';
3
+ import { ActionIcon, Tooltip } from '@lobehub/ui';
4
4
  import { PanelRightClose, PanelRightOpen } from 'lucide-react';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
@@ -10,13 +10,16 @@ import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
10
10
  import { useGlobalStore } from '@/store/global';
11
11
  import { systemStatusSelectors } from '@/store/global/selectors';
12
12
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
13
+ import { useUserStore } from '@/store/user';
14
+ import { settingsSelectors } from '@/store/user/selectors';
15
+ import { HotkeyEnum } from '@/types/hotkey';
13
16
 
14
17
  import SettingButton from '../../../features/SettingButton';
15
18
  import ShareButton from '../../../features/ShareButton';
16
19
 
17
20
  const HeaderAction = memo(() => {
18
21
  const { t } = useTranslation('chat');
19
-
22
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ToggleRightPanel));
20
23
  const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
21
24
  systemStatusSelectors.showChatSideBar(s),
22
25
  s.toggleChatSideBar,
@@ -27,12 +30,13 @@ const HeaderAction = memo(() => {
27
30
  return (
28
31
  <Flexbox gap={4} horizontal>
29
32
  <ShareButton />
30
- <ActionIcon
31
- icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
32
- onClick={() => toggleConfig()}
33
- size={DESKTOP_HEADER_ICON_SIZE}
34
- title={t('roleAndArchive')}
35
- />
33
+ <Tooltip hotkey={hotkey} title={t('toggleRightPanel.title', { ns: 'hotkey' })}>
34
+ <ActionIcon
35
+ icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
36
+ onClick={() => toggleConfig()}
37
+ size={DESKTOP_HEADER_ICON_SIZE}
38
+ />
39
+ </Tooltip>
36
40
  {isAgentEditable && <SettingButton />}
37
41
  </Flexbox>
38
42
  );
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ActionIcon, Avatar } from '@lobehub/ui';
3
+ import { ActionIcon, Avatar, Tooltip } from '@lobehub/ui';
4
4
  import { Skeleton } from 'antd';
5
5
  import { createStyles } from 'antd-style';
6
6
  import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
@@ -16,6 +16,9 @@ import { useGlobalStore } from '@/store/global';
16
16
  import { systemStatusSelectors } from '@/store/global/selectors';
17
17
  import { useSessionStore } from '@/store/session';
18
18
  import { sessionMetaSelectors, sessionSelectors } from '@/store/session/selectors';
19
+ import { useUserStore } from '@/store/user';
20
+ import { settingsSelectors } from '@/store/user/selectors';
21
+ import { HotkeyEnum } from '@/types/hotkey';
19
22
 
20
23
  import Tags from './Tags';
21
24
 
@@ -42,7 +45,9 @@ const useStyles = createStyles(({ css }) => ({
42
45
  }));
43
46
 
44
47
  const Main = memo(() => {
45
- const { t } = useTranslation('chat');
48
+ const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.ToggleLeftPanel));
49
+
50
+ const { t } = useTranslation(['chat', 'hotkey']);
46
51
  const { styles } = useStyles();
47
52
  useInitAgentConfig();
48
53
  const [isPinned] = useQueryState('pinned', parseAsBoolean);
@@ -61,23 +66,25 @@ const Main = memo(() => {
61
66
  const showSessionPanel = useGlobalStore(systemStatusSelectors.showSessionPanel);
62
67
  const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
63
68
 
69
+ const ToggleAction = (
70
+ <Tooltip hotkey={hotkey} title={t('toggleLeftPanel.title', { ns: 'hotkey' })}>
71
+ <ActionIcon
72
+ icon={showSessionPanel ? PanelLeftClose : PanelLeftOpen}
73
+ onClick={() => {
74
+ updateSystemStatus({
75
+ sessionsWidth: showSessionPanel ? 0 : 320,
76
+ showSessionPanel: !showSessionPanel,
77
+ });
78
+ }}
79
+ size={DESKTOP_HEADER_ICON_SIZE}
80
+ />
81
+ </Tooltip>
82
+ );
83
+
64
84
  if (!init)
65
85
  return (
66
86
  <Flexbox align={'center'} gap={8} horizontal>
67
- {!isPinned && (
68
- <ActionIcon
69
- aria-label={t('agents')}
70
- icon={showSessionPanel ? PanelLeftClose : PanelLeftOpen}
71
- onClick={() => {
72
- updateSystemStatus({
73
- sessionsWidth: showSessionPanel ? 0 : 320,
74
- showSessionPanel: !showSessionPanel,
75
- });
76
- }}
77
- size={DESKTOP_HEADER_ICON_SIZE}
78
- title={t('agents')}
79
- />
80
- )}
87
+ {!isPinned && ToggleAction}
81
88
  <Skeleton
82
89
  active
83
90
  avatar={{ shape: 'circle', size: 28 }}
@@ -89,20 +96,7 @@ const Main = memo(() => {
89
96
 
90
97
  return (
91
98
  <Flexbox align={'center'} gap={4} horizontal>
92
- {!isPinned && (
93
- <ActionIcon
94
- aria-label={t('agents')}
95
- icon={showSessionPanel ? PanelLeftClose : PanelLeftOpen}
96
- onClick={() => {
97
- updateSystemStatus({
98
- sessionsWidth: showSessionPanel ? 0 : 320,
99
- showSessionPanel: !showSessionPanel,
100
- });
101
- }}
102
- size={DESKTOP_HEADER_ICON_SIZE}
103
- title={t('agents')}
104
- />
105
- )}
99
+ {!isPinned && ToggleAction}
106
100
  <Avatar
107
101
  avatar={avatar}
108
102
  background={backgroundColor}
@@ -5,7 +5,6 @@ import BrandTextLoading from '@/components/Loading/BrandTextLoading';
5
5
 
6
6
  import { LayoutProps } from '../type';
7
7
  import ChatHeader from './ChatHeader';
8
- import HotKeys from './HotKeys';
9
8
  import Portal from './Portal';
10
9
  import TopicPanel from './TopicPanel';
11
10
 
@@ -32,7 +31,6 @@ const Layout = ({ children, topic, conversation, portal }: LayoutProps) => {
32
31
  </Portal>
33
32
  <TopicPanel>{topic}</TopicPanel>
34
33
  </Flexbox>
35
- <HotKeys />
36
34
  </>
37
35
  );
38
36
  };