@lobehub/chat 1.103.2 → 1.104.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.
- package/CHANGELOG.md +25 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +1 -1
- package/apps/desktop/src/main/controllers/ShortcutCtr.ts +9 -1
- package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +1 -5
- package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +14 -11
- package/apps/desktop/src/main/core/ui/ShortcutManager.ts +71 -5
- package/apps/desktop/src/main/shortcuts/config.ts +4 -2
- package/changelog/v1.json +9 -0
- package/locales/ar/hotkey.json +10 -4
- package/locales/ar/setting.json +12 -1
- package/locales/bg-BG/hotkey.json +10 -4
- package/locales/bg-BG/setting.json +12 -1
- package/locales/de-DE/hotkey.json +10 -4
- package/locales/de-DE/setting.json +12 -1
- package/locales/en-US/hotkey.json +10 -4
- package/locales/en-US/setting.json +12 -1
- package/locales/es-ES/hotkey.json +10 -4
- package/locales/es-ES/setting.json +12 -1
- package/locales/fa-IR/hotkey.json +10 -4
- package/locales/fa-IR/setting.json +12 -1
- package/locales/fr-FR/hotkey.json +10 -4
- package/locales/fr-FR/setting.json +12 -1
- package/locales/it-IT/hotkey.json +10 -4
- package/locales/it-IT/setting.json +12 -1
- package/locales/ja-JP/hotkey.json +10 -4
- package/locales/ja-JP/setting.json +12 -1
- package/locales/ko-KR/hotkey.json +10 -4
- package/locales/ko-KR/setting.json +12 -1
- package/locales/nl-NL/hotkey.json +10 -4
- package/locales/nl-NL/setting.json +12 -1
- package/locales/pl-PL/hotkey.json +10 -4
- package/locales/pl-PL/setting.json +12 -1
- package/locales/pt-BR/hotkey.json +10 -4
- package/locales/pt-BR/setting.json +12 -1
- package/locales/ru-RU/hotkey.json +10 -4
- package/locales/ru-RU/setting.json +12 -1
- package/locales/tr-TR/hotkey.json +10 -4
- package/locales/tr-TR/setting.json +12 -1
- package/locales/vi-VN/hotkey.json +10 -4
- package/locales/vi-VN/setting.json +12 -1
- package/locales/zh-CN/hotkey.json +10 -4
- package/locales/zh-CN/setting.json +12 -1
- package/locales/zh-TW/hotkey.json +10 -4
- package/locales/zh-TW/setting.json +12 -1
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/shortcut.ts +3 -1
- package/packages/electron-client-ipc/src/types/shortcut.ts +11 -0
- package/src/app/[variants]/(main)/settings/hotkey/features/Conversation.tsx +3 -11
- package/src/app/[variants]/(main)/settings/hotkey/features/Desktop.tsx +92 -0
- package/src/app/[variants]/(main)/settings/hotkey/features/Essential.tsx +3 -11
- package/src/app/[variants]/(main)/settings/hotkey/page.tsx +3 -0
- package/src/const/desktop.ts +9 -0
- package/src/const/hotkeys.ts +20 -16
- package/src/features/User/UserPanel/useMenu.tsx +2 -2
- package/src/locales/default/hotkey.ts +13 -5
- package/src/locales/default/setting.ts +11 -0
- package/src/services/electron/settings.ts +19 -1
- package/src/store/electron/actions/settings.ts +42 -1
- package/src/store/electron/initialState.ts +9 -1
- package/src/store/electron/selectors/__tests__/desktopState.test.ts +6 -17
- package/src/store/electron/selectors/hotkey.ts +11 -0
- package/src/store/electron/selectors/index.ts +1 -0
- package/src/types/hotkey.ts +18 -4
@@ -45,14 +45,25 @@
|
|
45
45
|
},
|
46
46
|
"hotkey": {
|
47
47
|
"conflicts": "與現有快捷鍵衝突",
|
48
|
+
"errors": {
|
49
|
+
"CONFLICT": "快速鍵衝突:該快速鍵已被其他功能佔用",
|
50
|
+
"INVALID_FORMAT": "快速鍵格式無效:請使用正確的格式(如 CommandOrControl+E)",
|
51
|
+
"INVALID_ID": "無效的快速鍵ID",
|
52
|
+
"NO_MODIFIER": "快速鍵必須包含修飾鍵(Ctrl、Alt、Shift等)",
|
53
|
+
"SYSTEM_OCCUPIED": "快速鍵已被系統或其他應用程式佔用",
|
54
|
+
"UNKNOWN": "更新失敗:未知錯誤"
|
55
|
+
},
|
48
56
|
"group": {
|
49
57
|
"conversation": "對話",
|
58
|
+
"desktop": "桌面端",
|
50
59
|
"essential": "基本"
|
51
60
|
},
|
52
61
|
"invalidCombination": "快捷鍵需要至少包含一個修飾鍵 (Ctrl, Alt, Shift) 和一個常規鍵",
|
53
62
|
"record": "按下按鍵以錄製快捷鍵",
|
54
63
|
"reset": "重置為預設快捷鍵",
|
55
|
-
"title": "快速鍵"
|
64
|
+
"title": "快速鍵",
|
65
|
+
"updateError": "快速鍵更新失敗:網路或系統錯誤",
|
66
|
+
"updateSuccess": "快速鍵更新成功"
|
56
67
|
},
|
57
68
|
"llm": {
|
58
69
|
"aesGcm": "您的金鑰與代理地址等將使用 <1>AES-GCM</1> 加密演算法進行加密",
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.104.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",
|
@@ -1,4 +1,6 @@
|
|
1
|
+
import { ShortcutUpdateResult } from '../types';
|
2
|
+
|
1
3
|
export interface ShortcutDispatchEvents {
|
2
4
|
getShortcutsConfig: () => Record<string, string>;
|
3
|
-
updateShortcutConfig: (
|
5
|
+
updateShortcutConfig: (params: { accelerator: string; id: string }) => ShortcutUpdateResult;
|
4
6
|
}
|
@@ -9,3 +9,14 @@ export interface ShortcutConfig {
|
|
9
9
|
id: string;
|
10
10
|
}
|
11
11
|
export type ShortcutActionType = Record<string, any>;
|
12
|
+
|
13
|
+
export interface ShortcutUpdateResult {
|
14
|
+
errorType?:
|
15
|
+
| 'INVALID_ID'
|
16
|
+
| 'INVALID_FORMAT'
|
17
|
+
| 'NO_MODIFIER'
|
18
|
+
| 'CONFLICT'
|
19
|
+
| 'SYSTEM_OCCUPIED'
|
20
|
+
| 'UNKNOWN';
|
21
|
+
success: boolean;
|
22
|
+
}
|
@@ -9,19 +9,11 @@ import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
10
10
|
import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
|
11
11
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
12
|
-
import { isDesktop } from '@/const/version';
|
13
12
|
import hotkeyMeta from '@/locales/default/hotkey';
|
14
13
|
import { useUserStore } from '@/store/user';
|
15
14
|
import { settingsSelectors } from '@/store/user/selectors';
|
16
15
|
import { HotkeyGroupEnum, HotkeyItem } from '@/types/hotkey';
|
17
16
|
|
18
|
-
const filterByDesktop = (item: HotkeyItem) => {
|
19
|
-
if (isDesktop) return true;
|
20
|
-
|
21
|
-
// is not desktop, filter out desktop only items
|
22
|
-
if (!isDesktop) return !item.isDesktop;
|
23
|
-
};
|
24
|
-
|
25
17
|
const HotkeySetting = memo(() => {
|
26
18
|
const { t } = useTranslation(['setting', 'hotkey']);
|
27
19
|
const [form] = Form.useForm();
|
@@ -61,9 +53,9 @@ const HotkeySetting = memo(() => {
|
|
61
53
|
};
|
62
54
|
|
63
55
|
const conversation: FormGroupItemType = {
|
64
|
-
children: HOTKEYS_REGISTRATION.filter(
|
65
|
-
|
66
|
-
|
56
|
+
children: HOTKEYS_REGISTRATION.filter(
|
57
|
+
(item) => item.group === HotkeyGroupEnum.Conversation,
|
58
|
+
).map((item) => mapHotkeyItem(item)),
|
67
59
|
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
68
60
|
title: t('hotkey.group.conversation'),
|
69
61
|
};
|
@@ -0,0 +1,92 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Form, type FormGroupItemType, HotkeyInput, Icon } from '@lobehub/ui';
|
4
|
+
import { App, Skeleton } from 'antd';
|
5
|
+
import isEqual from 'fast-deep-equal';
|
6
|
+
import { Loader2Icon } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
8
|
+
import { useTranslation } from 'react-i18next';
|
9
|
+
|
10
|
+
import { DESKTOP_HOTKEYS_REGISTRATION } from '@/const/hotkeys';
|
11
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
12
|
+
import hotkeyMeta from '@/locales/default/hotkey';
|
13
|
+
import { useElectronStore } from '@/store/electron';
|
14
|
+
import { desktopHotkeysSelectors } from '@/store/electron/selectors';
|
15
|
+
import { DesktopHotkeyItem } from '@/types/hotkey';
|
16
|
+
|
17
|
+
const HotkeySetting = memo(() => {
|
18
|
+
const { t } = useTranslation(['setting', 'hotkey']);
|
19
|
+
const [form] = Form.useForm();
|
20
|
+
const { message } = App.useApp();
|
21
|
+
|
22
|
+
const hotkeys = useElectronStore(desktopHotkeysSelectors.hotkeys, isEqual);
|
23
|
+
const [isHotkeysInit, updateDesktopHotkey, useFetchDesktopHotkeys] = useElectronStore((s) => [
|
24
|
+
desktopHotkeysSelectors.isHotkeysInit(s),
|
25
|
+
s.updateDesktopHotkey,
|
26
|
+
s.useFetchDesktopHotkeys,
|
27
|
+
]);
|
28
|
+
|
29
|
+
useFetchDesktopHotkeys();
|
30
|
+
|
31
|
+
const [loading, setLoading] = useState(false);
|
32
|
+
|
33
|
+
if (!isHotkeysInit) return <Skeleton active paragraph={{ rows: 5 }} title={false} />;
|
34
|
+
|
35
|
+
const mapHotkeyItem = (item: DesktopHotkeyItem) => ({
|
36
|
+
children: (
|
37
|
+
<HotkeyInput
|
38
|
+
disabled={item.nonEditable}
|
39
|
+
onChange={async (value) => {
|
40
|
+
setLoading(true);
|
41
|
+
try {
|
42
|
+
const result = await updateDesktopHotkey(item.id, value);
|
43
|
+
console.log(result);
|
44
|
+
if (result.success) {
|
45
|
+
message.success(t('hotkey.updateSuccess', { ns: 'setting' }));
|
46
|
+
} else {
|
47
|
+
// 根据错误类型显示相应的错误消息
|
48
|
+
|
49
|
+
message.error(t(`hotkey.errors.${result.errorType}` as any, { ns: 'setting' }));
|
50
|
+
}
|
51
|
+
} catch {
|
52
|
+
message.error(t('hotkey.updateError', { ns: 'setting' }));
|
53
|
+
} finally {
|
54
|
+
setLoading(false);
|
55
|
+
}
|
56
|
+
}}
|
57
|
+
placeholder={t('hotkey.record')}
|
58
|
+
resetValue={item.keys}
|
59
|
+
texts={{
|
60
|
+
conflicts: t('hotkey.conflicts'),
|
61
|
+
invalidCombination: t('hotkey.invalidCombination'),
|
62
|
+
reset: t('hotkey.reset'),
|
63
|
+
}}
|
64
|
+
value={hotkeys[item.id]}
|
65
|
+
/>
|
66
|
+
),
|
67
|
+
desc: hotkeyMeta.desktop[item.id]?.desc
|
68
|
+
? t(`desktop.${item.id}.desc`, { ns: 'hotkey' })
|
69
|
+
: undefined,
|
70
|
+
label: t(`desktop.${item.id}.title`, { ns: 'hotkey' }),
|
71
|
+
name: item.id,
|
72
|
+
});
|
73
|
+
|
74
|
+
const desktop: FormGroupItemType = {
|
75
|
+
children: DESKTOP_HOTKEYS_REGISTRATION.map((item) => mapHotkeyItem(item)),
|
76
|
+
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
77
|
+
title: t('hotkey.group.desktop'),
|
78
|
+
};
|
79
|
+
|
80
|
+
return (
|
81
|
+
<Form
|
82
|
+
form={form}
|
83
|
+
initialValues={hotkeys}
|
84
|
+
items={[desktop]}
|
85
|
+
itemsType={'group'}
|
86
|
+
variant={'borderless'}
|
87
|
+
{...FORM_STYLE}
|
88
|
+
/>
|
89
|
+
);
|
90
|
+
});
|
91
|
+
|
92
|
+
export default HotkeySetting;
|
@@ -9,19 +9,11 @@ import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
10
10
|
import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
|
11
11
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
12
|
-
import { isDesktop } from '@/const/version';
|
13
12
|
import hotkeyMeta from '@/locales/default/hotkey';
|
14
13
|
import { useUserStore } from '@/store/user';
|
15
14
|
import { settingsSelectors } from '@/store/user/selectors';
|
16
15
|
import { HotkeyGroupEnum, HotkeyItem } from '@/types/hotkey';
|
17
16
|
|
18
|
-
const filterByDesktop = (item: HotkeyItem) => {
|
19
|
-
if (isDesktop) return true;
|
20
|
-
|
21
|
-
// is not desktop, filter out desktop only items
|
22
|
-
if (!isDesktop) return !item.isDesktop;
|
23
|
-
};
|
24
|
-
|
25
17
|
const HotkeySetting = memo(() => {
|
26
18
|
const { t } = useTranslation(['setting', 'hotkey']);
|
27
19
|
const [form] = Form.useForm();
|
@@ -61,9 +53,9 @@ const HotkeySetting = memo(() => {
|
|
61
53
|
};
|
62
54
|
|
63
55
|
const essential: FormGroupItemType = {
|
64
|
-
children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Essential)
|
65
|
-
|
66
|
-
|
56
|
+
children: HOTKEYS_REGISTRATION.filter((item) => item.group === HotkeyGroupEnum.Essential).map(
|
57
|
+
(item) => mapHotkeyItem(item),
|
58
|
+
),
|
67
59
|
extra: loading && <Icon icon={Loader2Icon} size={16} spin style={{ opacity: 0.5 }} />,
|
68
60
|
title: t('hotkey.group.essential'),
|
69
61
|
};
|
@@ -1,9 +1,11 @@
|
|
1
|
+
import { isDesktop } from '@/const/version';
|
1
2
|
import { metadataModule } from '@/server/metadata';
|
2
3
|
import { translation } from '@/server/translation';
|
3
4
|
import { DynamicLayoutProps } from '@/types/next';
|
4
5
|
import { RouteVariants } from '@/utils/server/routeVariants';
|
5
6
|
|
6
7
|
import Conversation from './features/Conversation';
|
8
|
+
import Desktop from './features/Desktop';
|
7
9
|
import Essential from './features/Essential';
|
8
10
|
|
9
11
|
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
@@ -19,6 +21,7 @@ export const generateMetadata = async (props: DynamicLayoutProps) => {
|
|
19
21
|
const Page = () => {
|
20
22
|
return (
|
21
23
|
<>
|
24
|
+
{isDesktop && <Desktop />}
|
22
25
|
<Essential />
|
23
26
|
<Conversation />
|
24
27
|
</>
|
package/src/const/desktop.ts
CHANGED
@@ -1 +1,10 @@
|
|
1
|
+
import { DESKTOP_HOTKEYS_REGISTRATION } from '@/const/hotkeys';
|
2
|
+
import { DesktopHotkeyConfig } from '@/types/hotkey';
|
3
|
+
|
1
4
|
export const DESKTOP_USER_ID = 'DEFAULT_DESKTOP_USER';
|
5
|
+
|
6
|
+
export const DEFAULT_DESKTOP_HOTKEY_CONFIG: DesktopHotkeyConfig =
|
7
|
+
DESKTOP_HOTKEYS_REGISTRATION.reduce((acc: DesktopHotkeyConfig, item) => {
|
8
|
+
acc[item.id] = item.keys;
|
9
|
+
return acc;
|
10
|
+
}, {} as DesktopHotkeyConfig);
|
package/src/const/hotkeys.ts
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
import {
|
2
|
+
DesktopHotkeyEnum,
|
3
|
+
DesktopHotkeyItem,
|
2
4
|
HotkeyEnum,
|
3
5
|
HotkeyGroupEnum,
|
4
|
-
|
6
|
+
HotkeyItem,
|
5
7
|
HotkeyScopeEnum,
|
6
8
|
KeyEnum,
|
7
9
|
} from '@/types/hotkey';
|
8
10
|
|
9
11
|
const combineKeys = (keys: string[]) => keys.join('+');
|
10
12
|
|
13
|
+
export type HotkeyRegistration = HotkeyItem[];
|
14
|
+
|
11
15
|
// mod 在 Mac 上是 command 键,alt 在 Win 上是 ctrl 键
|
12
16
|
export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
|
13
17
|
// basic
|
14
|
-
{
|
15
|
-
group: HotkeyGroupEnum.Essential,
|
16
|
-
id: HotkeyEnum.ShowApp,
|
17
|
-
keys: combineKeys([KeyEnum.Mod, 'e']),
|
18
|
-
nonEditable: true,
|
19
|
-
scopes: [HotkeyScopeEnum.Global],
|
20
|
-
},
|
21
18
|
{
|
22
19
|
group: HotkeyGroupEnum.Essential,
|
23
20
|
id: HotkeyEnum.Search,
|
@@ -55,14 +52,6 @@ export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
|
|
55
52
|
keys: combineKeys([KeyEnum.Ctrl, KeyEnum.Shift, KeyEnum.QuestionMark]),
|
56
53
|
scopes: [HotkeyScopeEnum.Global],
|
57
54
|
},
|
58
|
-
{
|
59
|
-
group: HotkeyGroupEnum.Essential,
|
60
|
-
id: HotkeyEnum.OpenSettings,
|
61
|
-
isDesktop: true,
|
62
|
-
keys: combineKeys([KeyEnum.Mod, KeyEnum.Comma]),
|
63
|
-
nonEditable: true,
|
64
|
-
scopes: [HotkeyScopeEnum.Global],
|
65
|
-
},
|
66
55
|
// Chat
|
67
56
|
{
|
68
57
|
group: HotkeyGroupEnum.Conversation,
|
@@ -102,3 +91,18 @@ export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
|
|
102
91
|
scopes: [HotkeyScopeEnum.Chat],
|
103
92
|
},
|
104
93
|
];
|
94
|
+
|
95
|
+
type DesktopHotkeyRegistration = DesktopHotkeyItem[];
|
96
|
+
|
97
|
+
// 桌面端快捷键配置
|
98
|
+
export const DESKTOP_HOTKEYS_REGISTRATION: DesktopHotkeyRegistration = [
|
99
|
+
{
|
100
|
+
id: DesktopHotkeyEnum.ShowApp,
|
101
|
+
keys: combineKeys([KeyEnum.Ctrl, 'e']),
|
102
|
+
},
|
103
|
+
{
|
104
|
+
id: DesktopHotkeyEnum.OpenSettings,
|
105
|
+
keys: combineKeys([KeyEnum.Mod, KeyEnum.Comma]),
|
106
|
+
nonEditable: true,
|
107
|
+
},
|
108
|
+
];
|
@@ -23,7 +23,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
23
23
|
import type { MenuProps } from '@/components/Menu';
|
24
24
|
import { enableAuth } from '@/const/auth';
|
25
25
|
import { BRANDING_EMAIL, LOBE_CHAT_CLOUD, SOCIAL_URL } from '@/const/branding';
|
26
|
-
import {
|
26
|
+
import { DEFAULT_DESKTOP_HOTKEY_CONFIG } from '@/const/desktop';
|
27
27
|
import {
|
28
28
|
CHANGELOG,
|
29
29
|
DOCUMENTS_REFER_URL,
|
@@ -85,7 +85,7 @@ export const useMenu = () => {
|
|
85
85
|
{
|
86
86
|
extra: isDesktop ? (
|
87
87
|
<div>
|
88
|
-
<Hotkey keys={
|
88
|
+
<Hotkey keys={DEFAULT_DESKTOP_HOTKEY_CONFIG.openSettings} />
|
89
89
|
</div>
|
90
90
|
) : undefined,
|
91
91
|
icon: <Icon icon={Settings2} />,
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import { HotkeyI18nTranslations } from '@/types/hotkey';
|
2
2
|
|
3
|
-
const hotkey: HotkeyI18nTranslations
|
3
|
+
const hotkey: HotkeyI18nTranslations & {
|
4
|
+
desktop: Record<string, { desc?: string; title: string }>;
|
5
|
+
} = {
|
4
6
|
addUserMessage: {
|
5
7
|
desc: '将当前输入内容添加为用户消息,但不触发生成',
|
6
8
|
title: '添加一条用户消息',
|
@@ -9,6 +11,16 @@ const hotkey: HotkeyI18nTranslations = {
|
|
9
11
|
desc: '清空当前会话的消息和上传的文件',
|
10
12
|
title: '清空会话消息',
|
11
13
|
},
|
14
|
+
desktop: {
|
15
|
+
openSettings: {
|
16
|
+
desc: '打开应用设置页面',
|
17
|
+
title: '应用设置',
|
18
|
+
},
|
19
|
+
showApp: {
|
20
|
+
desc: '全局快捷键显示或隐藏主窗口',
|
21
|
+
title: '显示/隐藏主窗口',
|
22
|
+
},
|
23
|
+
},
|
12
24
|
editMessage: {
|
13
25
|
desc: '通过按住 Alt 并双击消息进入编辑模式',
|
14
26
|
title: '编辑消息',
|
@@ -21,10 +33,6 @@ const hotkey: HotkeyI18nTranslations = {
|
|
21
33
|
desc: '查看所有快捷键的使用说明',
|
22
34
|
title: '打开快捷键帮助',
|
23
35
|
},
|
24
|
-
openSettings: {
|
25
|
-
desc: '打开应用设置页面',
|
26
|
-
title: '应用设置',
|
27
|
-
},
|
28
36
|
regenerateMessage: {
|
29
37
|
desc: '重新生成最后一条消息',
|
30
38
|
title: '重新生成消息',
|
@@ -45,14 +45,25 @@ export default {
|
|
45
45
|
},
|
46
46
|
hotkey: {
|
47
47
|
conflicts: '与现有快捷键冲突',
|
48
|
+
errors: {
|
49
|
+
CONFLICT: '快捷键冲突:该快捷键已被其他功能占用',
|
50
|
+
INVALID_FORMAT: '快捷键格式无效:请使用正确的格式(如 CommandOrControl+E)',
|
51
|
+
INVALID_ID: '无效的快捷键ID',
|
52
|
+
NO_MODIFIER: '快捷键必须包含修饰键(Ctrl、Alt、Shift等)',
|
53
|
+
SYSTEM_OCCUPIED: '快捷键已被系统或其他应用程序占用',
|
54
|
+
UNKNOWN: '更新失败:未知错误',
|
55
|
+
},
|
48
56
|
group: {
|
49
57
|
conversation: '会话',
|
58
|
+
desktop: '桌面端',
|
50
59
|
essential: '基础',
|
51
60
|
},
|
52
61
|
invalidCombination: '快捷键需要至少包含一个修饰键 (Ctrl, Alt, Shift) 和一个常规键',
|
53
62
|
record: '按下按键以录制快捷键',
|
54
63
|
reset: '重置为默认快捷键',
|
55
64
|
title: '快捷键',
|
65
|
+
updateError: '快捷键更新失败:网络或系统错误',
|
66
|
+
updateSuccess: '快捷键更新成功',
|
56
67
|
},
|
57
68
|
llm: {
|
58
69
|
aesGcm: '您的秘钥与代理地址等将使用 <1>AES-GCM</1> 加密算法进行加密',
|
@@ -1,4 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
NetworkProxySettings,
|
3
|
+
ShortcutUpdateResult,
|
4
|
+
dispatch,
|
5
|
+
} from '@lobechat/electron-client-ipc';
|
2
6
|
|
3
7
|
class DesktopSettingsService {
|
4
8
|
/**
|
@@ -15,6 +19,20 @@ class DesktopSettingsService {
|
|
15
19
|
return dispatch('setProxySettings', data);
|
16
20
|
};
|
17
21
|
|
22
|
+
/**
|
23
|
+
* 获取桌面热键配置
|
24
|
+
*/
|
25
|
+
getDesktopHotkeys = async () => {
|
26
|
+
return dispatch('getShortcutsConfig');
|
27
|
+
};
|
28
|
+
|
29
|
+
/**
|
30
|
+
* 更新桌面热键配置
|
31
|
+
*/
|
32
|
+
updateDesktopHotkey = async (id: string, accelerator: string): Promise<ShortcutUpdateResult> => {
|
33
|
+
return dispatch('updateShortcutConfig', { accelerator, id });
|
34
|
+
};
|
35
|
+
|
18
36
|
/**
|
19
37
|
* 测试代理连接
|
20
38
|
*/
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
|
1
|
+
import { NetworkProxySettings, ShortcutUpdateResult } from '@lobechat/electron-client-ipc';
|
2
2
|
import isEqual from 'fast-deep-equal';
|
3
3
|
import useSWR, { SWRResponse, mutate } from 'swr';
|
4
4
|
import type { StateCreator } from 'zustand/vanilla';
|
@@ -11,12 +11,16 @@ import type { ElectronStore } from '../store';
|
|
11
11
|
* 设置操作
|
12
12
|
*/
|
13
13
|
export interface ElectronSettingsAction {
|
14
|
+
refreshDesktopHotkeys: () => Promise<void>;
|
14
15
|
refreshProxySettings: () => Promise<void>;
|
15
16
|
setProxySettings: (params: Partial<NetworkProxySettings>) => Promise<void>;
|
17
|
+
updateDesktopHotkey: (id: string, accelerator: string) => Promise<ShortcutUpdateResult>;
|
18
|
+
useFetchDesktopHotkeys: () => SWRResponse;
|
16
19
|
useGetProxySettings: () => SWRResponse;
|
17
20
|
}
|
18
21
|
|
19
22
|
const ELECTRON_PROXY_SETTINGS_KEY = 'electron:getProxySettings';
|
23
|
+
const ELECTRON_DESKTOP_HOTKEYS_KEY = 'electron:getDesktopHotkeys';
|
20
24
|
|
21
25
|
export const settingsSlice: StateCreator<
|
22
26
|
ElectronStore,
|
@@ -24,6 +28,10 @@ export const settingsSlice: StateCreator<
|
|
24
28
|
[],
|
25
29
|
ElectronSettingsAction
|
26
30
|
> = (set, get) => ({
|
31
|
+
refreshDesktopHotkeys: async () => {
|
32
|
+
await mutate(ELECTRON_DESKTOP_HOTKEYS_KEY);
|
33
|
+
},
|
34
|
+
|
27
35
|
refreshProxySettings: async () => {
|
28
36
|
await mutate(ELECTRON_PROXY_SETTINGS_KEY);
|
29
37
|
},
|
@@ -40,6 +48,39 @@ export const settingsSlice: StateCreator<
|
|
40
48
|
}
|
41
49
|
},
|
42
50
|
|
51
|
+
updateDesktopHotkey: async (id, accelerator) => {
|
52
|
+
try {
|
53
|
+
// 更新热键配置
|
54
|
+
const result = await desktopSettingsService.updateDesktopHotkey(id, accelerator);
|
55
|
+
|
56
|
+
// 如果更新成功,刷新状态
|
57
|
+
if (result.success) {
|
58
|
+
await get().refreshDesktopHotkeys();
|
59
|
+
}
|
60
|
+
|
61
|
+
return result;
|
62
|
+
} catch (error) {
|
63
|
+
console.error('桌面热键更新失败:', error);
|
64
|
+
return {
|
65
|
+
errorType: 'UNKNOWN' as const,
|
66
|
+
success: false,
|
67
|
+
};
|
68
|
+
}
|
69
|
+
},
|
70
|
+
|
71
|
+
useFetchDesktopHotkeys: () =>
|
72
|
+
useSWR<Record<string, string>>(
|
73
|
+
ELECTRON_DESKTOP_HOTKEYS_KEY,
|
74
|
+
async () => desktopSettingsService.getDesktopHotkeys(),
|
75
|
+
{
|
76
|
+
onSuccess: (data) => {
|
77
|
+
if (!isEqual(data, get().desktopHotkeys)) {
|
78
|
+
set({ desktopHotkeys: data, isDesktopHotkeysInit: true });
|
79
|
+
}
|
80
|
+
},
|
81
|
+
},
|
82
|
+
),
|
83
|
+
|
43
84
|
useGetProxySettings: () =>
|
44
85
|
useSWR<NetworkProxySettings>(
|
45
86
|
ELECTRON_PROXY_SETTINGS_KEY,
|
@@ -1,4 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
DataSyncConfig,
|
3
|
+
ElectronAppState,
|
4
|
+
NetworkProxySettings,
|
5
|
+
} from '@lobechat/electron-client-ipc';
|
2
6
|
|
3
7
|
export type RemoteServerError = 'CONFIG_ERROR' | 'AUTH_ERROR' | 'DISCONNECT_ERROR';
|
4
8
|
|
@@ -14,8 +18,10 @@ export const defaultProxySettings: NetworkProxySettings = {
|
|
14
18
|
export interface ElectronState {
|
15
19
|
appState: ElectronAppState;
|
16
20
|
dataSyncConfig: DataSyncConfig;
|
21
|
+
desktopHotkeys: Record<string, string>;
|
17
22
|
isAppStateInit?: boolean;
|
18
23
|
isConnectingServer?: boolean;
|
24
|
+
isDesktopHotkeysInit: boolean;
|
19
25
|
isInitRemoteServerConfig: boolean;
|
20
26
|
isSyncActive?: boolean;
|
21
27
|
proxySettings: NetworkProxySettings;
|
@@ -25,8 +31,10 @@ export interface ElectronState {
|
|
25
31
|
export const initialState: ElectronState = {
|
26
32
|
appState: {},
|
27
33
|
dataSyncConfig: { storageMode: 'local' },
|
34
|
+
desktopHotkeys: {},
|
28
35
|
isAppStateInit: false,
|
29
36
|
isConnectingServer: false,
|
37
|
+
isDesktopHotkeysInit: false,
|
30
38
|
isInitRemoteServerConfig: false,
|
31
39
|
isSyncActive: false,
|
32
40
|
proxySettings: defaultProxySettings,
|
@@ -1,14 +1,14 @@
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
2
2
|
|
3
|
-
import { ElectronState, defaultProxySettings } from '@/store/electron/initialState';
|
3
|
+
import { ElectronState, defaultProxySettings, initialState } from '@/store/electron/initialState';
|
4
|
+
import { merge } from '@/utils/merge';
|
4
5
|
|
5
6
|
import { desktopStateSelectors } from '../desktopState';
|
6
7
|
|
7
8
|
describe('desktopStateSelectors', () => {
|
8
9
|
describe('usePath', () => {
|
9
10
|
it('should return userPath from appState', () => {
|
10
|
-
const state: ElectronState = {
|
11
|
-
isAppStateInit: false,
|
11
|
+
const state: ElectronState = merge(initialState, {
|
12
12
|
appState: {
|
13
13
|
userPath: {
|
14
14
|
desktop: '/test/desktop',
|
@@ -21,12 +21,7 @@ describe('desktopStateSelectors', () => {
|
|
21
21
|
videos: '/test/videos',
|
22
22
|
},
|
23
23
|
},
|
24
|
-
|
25
|
-
storageMode: 'local',
|
26
|
-
},
|
27
|
-
isInitRemoteServerConfig: false,
|
28
|
-
proxySettings: defaultProxySettings,
|
29
|
-
};
|
24
|
+
});
|
30
25
|
|
31
26
|
expect(desktopStateSelectors.usePath(state)).toEqual({
|
32
27
|
desktop: '/test/desktop',
|
@@ -41,15 +36,9 @@ describe('desktopStateSelectors', () => {
|
|
41
36
|
});
|
42
37
|
|
43
38
|
it('should handle undefined userPath', () => {
|
44
|
-
const state: ElectronState = {
|
39
|
+
const state: ElectronState = merge(initialState, {
|
45
40
|
appState: {},
|
46
|
-
|
47
|
-
dataSyncConfig: {
|
48
|
-
storageMode: 'local',
|
49
|
-
},
|
50
|
-
isInitRemoteServerConfig: false,
|
51
|
-
proxySettings: defaultProxySettings,
|
52
|
-
};
|
41
|
+
});
|
53
42
|
|
54
43
|
expect(desktopStateSelectors.usePath(state)).toBeUndefined();
|
55
44
|
});
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { DEFAULT_DESKTOP_HOTKEY_CONFIG } from '@/const/desktop';
|
2
|
+
import { ElectronState } from '@/store/electron/initialState';
|
3
|
+
import { merge } from '@/utils/merge';
|
4
|
+
|
5
|
+
const hotkeys = (s: ElectronState) => merge(DEFAULT_DESKTOP_HOTKEY_CONFIG, s.desktopHotkeys);
|
6
|
+
const isHotkeysInit = (s: ElectronState) => s.isDesktopHotkeysInit;
|
7
|
+
|
8
|
+
export const desktopHotkeysSelectors = {
|
9
|
+
hotkeys,
|
10
|
+
isHotkeysInit,
|
11
|
+
};
|
package/src/types/hotkey.ts
CHANGED
@@ -62,7 +62,6 @@ export const HotkeyEnum = {
|
|
62
62
|
EditMessage: 'editMessage',
|
63
63
|
OpenChatSettings: 'openChatSettings',
|
64
64
|
OpenHotkeyHelper: 'openHotkeyHelper',
|
65
|
-
OpenSettings: 'openSettings',
|
66
65
|
RegenerateMessage: 'regenerateMessage',
|
67
66
|
SaveTopic: 'saveTopic',
|
68
67
|
Search: 'search',
|
@@ -96,8 +95,6 @@ export interface HotkeyItem {
|
|
96
95
|
// 快捷键分组用于展示
|
97
96
|
group: HotkeyGroupId;
|
98
97
|
id: HotkeyId;
|
99
|
-
isDesktop?: boolean;
|
100
|
-
// 是否是桌面端专用的快捷键
|
101
98
|
keys: string;
|
102
99
|
// 是否为不可编辑的快捷键
|
103
100
|
nonEditable?: boolean;
|
@@ -105,7 +102,24 @@ export interface HotkeyItem {
|
|
105
102
|
scopes?: HotkeyScopeId[];
|
106
103
|
}
|
107
104
|
|
108
|
-
|
105
|
+
// ================== Desktop ================== //
|
106
|
+
|
107
|
+
export const DesktopHotkeyEnum = {
|
108
|
+
OpenSettings: 'openSettings',
|
109
|
+
ShowApp: 'showApp',
|
110
|
+
};
|
111
|
+
|
112
|
+
export type DesktopHotkeyId = (typeof DesktopHotkeyEnum)[keyof typeof DesktopHotkeyEnum];
|
113
|
+
|
114
|
+
export interface DesktopHotkeyItem {
|
115
|
+
id: DesktopHotkeyId;
|
116
|
+
|
117
|
+
keys: string;
|
118
|
+
// 是否为不可编辑的快捷键
|
119
|
+
nonEditable?: boolean;
|
120
|
+
}
|
121
|
+
|
122
|
+
export type DesktopHotkeyConfig = Record<DesktopHotkeyId, string>;
|
109
123
|
|
110
124
|
export type HotkeyI18nTranslations = Record<
|
111
125
|
HotkeyId,
|