♻️ refactor(desktop): consolidate global shortcuts (LOBE-7181) (#13880)

* ♻️ refactor(desktop): consolidate global shortcuts and remove default showApp hotkey

- Add desktopGlobalShortcuts.ts as single source for Electron + renderer defaults
- Wire ShortcutManager and store to DEFAULT_ELECTRON_DESKTOP_SHORTCUTS
- Use DesktopHotkeyId for @shortcut; drop local shortcuts barrel
- Stop re-exporting DESKTOP_HOTKEYS_REGISTRATION from hotkeys

Fixes LOBE-7181

Made-with: Cursor

*  feat(desktop): introduce new stubs for business constants and types

- Added `@lobechat/business-const` and `@lobechat/types` packages to support workspace dependency resolution.
- Updated `package.json` and `pnpm-workspace.yaml` to include new stubs.
- Refactored imports in `index.ts` to utilize the new constants structure.
- Enhanced `desktopGlobalShortcuts.ts` with improved type definitions for hotkeys.

This change streamlines the management of constants and types across the desktop application.

Signed-off-by: Innei <tukon479@gmail.com>

* ♻️ refactor(hotkeys): consolidate desktop global shortcut definitions (LOBE-7181)

Made-with: Cursor

*  feat(session, user): replace direct type imports with constants

- Updated session.ts to use constants for session types instead of direct imports from @lobechat/types.
- Updated user.ts to use a constant for the default topic display mode, enhancing consistency and maintainability.

This change improves code clarity and reduces dependencies on external type definitions.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2026-04-17 00:32:05 +08:00 committed by GitHub
parent 35558cbea1
commit d2197f4c30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 260 additions and 236 deletions

View file

@ -1,8 +1,11 @@
packages:
- '../../packages/const'
- '../../packages/electron-server-ipc'
- '../../packages/electron-client-ipc'
- '../../packages/file-loaders'
- '../../packages/desktop-bridge'
- '../../packages/device-gateway-client'
- '../../packages/local-file-shell'
- './stubs/business-const'
- './stubs/types'
- '.'

View file

@ -1,11 +1,11 @@
/**
* Application settings storage related constants
*/
import { DEFAULT_ELECTRON_DESKTOP_SHORTCUTS } from '@lobechat/const/desktopGlobalShortcuts';
import type { NetworkProxySettings } from '@lobechat/electron-client-ipc';
import { appStorageDir } from '@/const/dir';
import { UPDATE_CHANNEL } from '@/modules/updater/configs';
import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
import type { ElectronMainStore } from '@/types/store';
/**
@ -35,7 +35,7 @@ export const STORE_DEFAULTS: ElectronMainStore = {
gatewayUrl: 'https://device-gateway.lobehub.com',
locale: 'auto',
networkProxy: defaultProxySettings,
shortcuts: DEFAULT_SHORTCUTS_CONFIG,
shortcuts: DEFAULT_ELECTRON_DESKTOP_SHORTCUTS,
storagePath: appStorageDir,
themeMode: 'system',
updateChannel: UPDATE_CHANNEL,

View file

@ -1,6 +1,7 @@
import type { DesktopHotkeyId } from '@lobechat/const/desktopGlobalShortcuts';
import type { App } from '@/core/App';
import { IoCContainer } from '@/core/infrastructure/IoCContainer';
import type { ShortcutActionType } from '@/shortcuts';
import { IpcService } from '@/utils/ipc';
const shortcutDecorator = (name: string) => (target: any, methodName: string, descriptor?: any) => {
@ -15,7 +16,7 @@ const shortcutDecorator = (name: string) => (target: any, methodName: string, de
/**
* shortcut inject decorator
*/
export const shortcut = (method: ShortcutActionType) => shortcutDecorator(method);
export const shortcut = (method: DesktopHotkeyId) => shortcutDecorator(method);
const protocolDecorator =
(urlType: string, action: string) => (target: any, methodName: string, descriptor?: any) => {

View file

@ -1,6 +1,6 @@
import { DEFAULT_ELECTRON_DESKTOP_SHORTCUTS } from '@lobechat/const/desktopGlobalShortcuts';
import { globalShortcut } from 'electron';
import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
import { createLogger } from '@/utils/logger';
import type { App } from '../App';
@ -77,8 +77,8 @@ export class ShortcutManager {
try {
logger.debug(`Updating shortcut ${id} to ${accelerator}`);
// 1. Check if ID is valid
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
// 1. Check if ID is valid (value may be empty string when disabled by default)
if (!(id in DEFAULT_ELECTRON_DESKTOP_SHORTCUTS)) {
logger.error(`Invalid shortcut ID: ${id}`);
return { errorType: 'INVALID_ID', success: false };
}
@ -231,15 +231,15 @@ export class ShortcutManager {
// If no configuration, use default configuration
if (!config || Object.keys(config).length === 0) {
logger.debug('No shortcuts config found, using defaults');
this.shortcutsConfig = { ...DEFAULT_SHORTCUTS_CONFIG };
this.shortcutsConfig = { ...DEFAULT_ELECTRON_DESKTOP_SHORTCUTS };
this.saveShortcutsConfig();
} else {
// Filter out invalid shortcuts that are not in DEFAULT_SHORTCUTS_CONFIG
// Filter out invalid shortcuts that are not in DEFAULT_ELECTRON_DESKTOP_SHORTCUTS
const filteredConfig: Record<string, string> = {};
let hasInvalidKeys = false;
Object.entries(config).forEach(([id, accelerator]) => {
if (DEFAULT_SHORTCUTS_CONFIG[id]) {
if (id in DEFAULT_ELECTRON_DESKTOP_SHORTCUTS) {
filteredConfig[id] = accelerator;
} else {
hasInvalidKeys = true;
@ -248,7 +248,7 @@ export class ShortcutManager {
});
// Ensure all default shortcuts are present
Object.entries(DEFAULT_SHORTCUTS_CONFIG).forEach(([id, defaultAccelerator]) => {
Object.entries(DEFAULT_ELECTRON_DESKTOP_SHORTCUTS).forEach(([id, defaultAccelerator]) => {
if (!(id in filteredConfig)) {
filteredConfig[id] = defaultAccelerator;
logger.debug(`Adding missing default shortcut: ${id} = ${defaultAccelerator}`);
@ -267,7 +267,7 @@ export class ShortcutManager {
logger.debug('Loaded shortcuts config:', this.shortcutsConfig);
} catch (error) {
logger.error('Error loading shortcuts config:', error);
this.shortcutsConfig = { ...DEFAULT_SHORTCUTS_CONFIG };
this.shortcutsConfig = { ...DEFAULT_ELECTRON_DESKTOP_SHORTCUTS };
this.saveShortcutsConfig();
}
}
@ -295,9 +295,9 @@ export class ShortcutManager {
Object.entries(this.shortcutsConfig).forEach(([id, accelerator]) => {
logger.debug(`Registering shortcut '${id}' with ${accelerator}`);
// Only register shortcuts that exist in DEFAULT_SHORTCUTS_CONFIG
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
logger.debug(`Skipping shortcut '${id}' - not found in DEFAULT_SHORTCUTS_CONFIG`);
// Only register shortcuts that exist in DEFAULT_ELECTRON_DESKTOP_SHORTCUTS
if (!(id in DEFAULT_ELECTRON_DESKTOP_SHORTCUTS)) {
logger.debug(`Skipping shortcut '${id}' - not found in DEFAULT_ELECTRON_DESKTOP_SHORTCUTS`);
return;
}

View file

@ -1,8 +1,7 @@
import { DEFAULT_ELECTRON_DESKTOP_SHORTCUTS } from '@lobechat/const/desktopGlobalShortcuts';
import { globalShortcut } from 'electron';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
import type { App } from '../../App';
import { ShortcutManager } from '../ShortcutManager';
@ -26,10 +25,10 @@ vi.mock('@/utils/logger', () => ({
}),
}));
// Mock DEFAULT_SHORTCUTS_CONFIG
vi.mock('@/shortcuts', () => ({
DEFAULT_SHORTCUTS_CONFIG: {
showApp: 'Control+E',
// Mock desktop global shortcut defaults
vi.mock('@lobechat/const/desktopGlobalShortcuts', () => ({
DEFAULT_ELECTRON_DESKTOP_SHORTCUTS: {
showApp: '',
openSettings: 'CommandOrControl+,',
},
}));
@ -115,7 +114,7 @@ describe('ShortcutManager', () => {
expect(mockStoreManager.get).toHaveBeenCalledWith('shortcuts');
expect(globalShortcut.unregisterAll).toHaveBeenCalled();
expect(globalShortcut.register).toHaveBeenCalledWith('Control+E', expect.any(Function));
expect(globalShortcut.register).not.toHaveBeenCalledWith('Control+E', expect.any(Function));
expect(globalShortcut.register).toHaveBeenCalledWith(
'CommandOrControl+,',
expect.any(Function),
@ -145,7 +144,7 @@ describe('ShortcutManager', () => {
shortcutManager.initialize();
const config = shortcutManager.getShortcutsConfig();
expect(config).toEqual(DEFAULT_SHORTCUTS_CONFIG);
expect(config).toEqual(DEFAULT_ELECTRON_DESKTOP_SHORTCUTS);
});
});
@ -346,8 +345,11 @@ describe('ShortcutManager', () => {
shortcutManager['loadShortcutsConfig']();
expect(shortcutManager['shortcutsConfig']).toEqual(DEFAULT_SHORTCUTS_CONFIG);
expect(mockStoreManager.set).toHaveBeenCalledWith('shortcuts', DEFAULT_SHORTCUTS_CONFIG);
expect(shortcutManager['shortcutsConfig']).toEqual(DEFAULT_ELECTRON_DESKTOP_SHORTCUTS);
expect(mockStoreManager.set).toHaveBeenCalledWith(
'shortcuts',
DEFAULT_ELECTRON_DESKTOP_SHORTCUTS,
);
});
it('should use defaults when config is empty', () => {
@ -355,7 +357,7 @@ describe('ShortcutManager', () => {
shortcutManager['loadShortcutsConfig']();
expect(shortcutManager['shortcutsConfig']).toEqual(DEFAULT_SHORTCUTS_CONFIG);
expect(shortcutManager['shortcutsConfig']).toEqual(DEFAULT_ELECTRON_DESKTOP_SHORTCUTS);
});
it('should filter invalid keys from stored config', () => {
@ -413,8 +415,11 @@ describe('ShortcutManager', () => {
shortcutManager['loadShortcutsConfig']();
expect(shortcutManager['shortcutsConfig']).toEqual(DEFAULT_SHORTCUTS_CONFIG);
expect(mockStoreManager.set).toHaveBeenCalledWith('shortcuts', DEFAULT_SHORTCUTS_CONFIG);
expect(shortcutManager['shortcutsConfig']).toEqual(DEFAULT_ELECTRON_DESKTOP_SHORTCUTS);
expect(mockStoreManager.set).toHaveBeenCalledWith(
'shortcuts',
DEFAULT_ELECTRON_DESKTOP_SHORTCUTS,
);
});
});
@ -458,7 +463,7 @@ describe('ShortcutManager', () => {
expect(globalShortcut.register).toHaveBeenCalledWith('Ctrl+P', expect.any(Function));
});
it('should skip shortcuts not in DEFAULT_SHORTCUTS_CONFIG', () => {
it('should skip shortcuts not defined in default electron desktop shortcuts', () => {
shortcutManager['shortcutsConfig'] = {
showApp: 'Alt+E',
invalidKey: 'Ctrl+I',

View file

@ -1,20 +0,0 @@
/**
* Shortcut action type enum
*/
export const ShortcutActionEnum = {
openSettings: 'openSettings',
/**
* Show/hide main window
*/
showApp: 'showApp',
} as const;
export type ShortcutActionType = (typeof ShortcutActionEnum)[keyof typeof ShortcutActionEnum];
/**
* Default shortcut configuration
*/
export const DEFAULT_SHORTCUTS_CONFIG: Record<ShortcutActionType, string> = {
[ShortcutActionEnum.showApp]: 'Control+E',
[ShortcutActionEnum.openSettings]: 'CommandOrControl+,',
};

View file

@ -1 +0,0 @@
export * from './config';

View file

@ -0,0 +1,9 @@
{
"name": "@lobechat/business-const",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts"
},
"main": "./src/index.ts"
}

View file

@ -0,0 +1,6 @@
export const BRANDING_LOGO_URL = '';
export const BRANDING_NAME = 'LobeHub';
export const DEFAULT_EMBEDDING_PROVIDER = 'openai';
export const DEFAULT_MINI_PROVIDER = 'openai';
export const DEFAULT_PROVIDER = 'openai';
export const ORG_NAME = 'LobeHub';

View file

@ -0,0 +1,9 @@
{
"name": "@lobechat/types",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts"
},
"main": "./src/index.ts"
}

View file

@ -0,0 +1,8 @@
/**
* Desktop isolated workspace stub.
*
* `@lobechat/types` is only consumed via `import type` in desktop code; those
* specifiers are erased at build time, so this package needs no runtime
* exports.
*/
export {};

View file

@ -4,7 +4,9 @@
"private": true,
"exports": {
".": "./src/index.ts",
"./currency": "./src/currency.ts"
"./currency": "./src/currency.ts",
"./desktopGlobalShortcuts": "./src/desktopGlobalShortcuts.ts",
"./hotkeys": "./src/hotkeys.ts"
},
"main": "./src/index.ts",
"dependencies": {

View file

@ -1,6 +1,4 @@
import type { DesktopHotkeyConfig } from '@lobechat/types';
import { DESKTOP_HOTKEYS_REGISTRATION } from './hotkeys';
import { DESKTOP_HOTKEYS_REGISTRATION, type DesktopHotkeyConfig } from './desktopGlobalShortcuts';
export const DESKTOP_USER_ID = 'DEFAULT_DESKTOP_USER';

View file

@ -0,0 +1,59 @@
const combineKeys = (keys: string[]) => keys.join('+');
export const DesktopHotkeyEnum = {
OpenSettings: 'openSettings',
ShowApp: 'showApp',
} as const;
export type DesktopHotkeyId = (typeof DesktopHotkeyEnum)[keyof typeof DesktopHotkeyEnum];
export interface DesktopHotkeyItem {
id: DesktopHotkeyId;
keys: string;
nonEditable?: boolean;
}
export type DesktopHotkeyConfig = Record<DesktopHotkeyId, string>;
interface DesktopGlobalShortcutDefault {
/** Electron `globalShortcut` accelerator; empty string means unregistered. */
electronAccelerator: string;
id: DesktopHotkeyId;
nonEditable?: boolean;
/** React-hotkey style string for renderer (HotkeyInput, merge defaults). */
uiKeys: string;
}
/**
* Single source of truth for desktop (Electron) global shortcut defaults.
* Main process reads `electronAccelerator`; renderer uses `uiKeys` and metadata.
*/
export const DESKTOP_GLOBAL_SHORTCUT_DEFAULTS = [
{
electronAccelerator: '',
id: DesktopHotkeyEnum.ShowApp,
uiKeys: '',
},
{
electronAccelerator: 'CommandOrControl+,',
id: DesktopHotkeyEnum.OpenSettings,
nonEditable: true,
uiKeys: combineKeys(['mod', 'comma']),
},
] as const satisfies readonly DesktopGlobalShortcutDefault[];
export const DESKTOP_HOTKEYS_REGISTRATION: DesktopHotkeyItem[] =
DESKTOP_GLOBAL_SHORTCUT_DEFAULTS.map((item): DesktopHotkeyItem => {
const base: DesktopHotkeyItem = {
id: item.id,
keys: item.uiKeys,
};
return 'nonEditable' in item && item.nonEditable ? { ...base, nonEditable: true } : base;
});
export const DEFAULT_ELECTRON_DESKTOP_SHORTCUTS: DesktopHotkeyConfig =
DESKTOP_GLOBAL_SHORTCUT_DEFAULTS.reduce<DesktopHotkeyConfig>((acc, item) => {
acc[item.id] = item.electronAccelerator;
return acc;
}, {} as DesktopHotkeyConfig);

View file

@ -1,14 +1,87 @@
import type { DesktopHotkeyItem, HotkeyItem } from '@lobechat/types';
import {
DesktopHotkeyEnum,
HotkeyEnum,
HotkeyGroupEnum,
HotkeyScopeEnum,
KeyEnum,
} from '@lobechat/types';
const combineKeys = (keys: string[]) => keys.join('+');
export const KeyEnum = {
Alt: 'alt',
Backquote: 'backquote',
Backslash: 'backslash',
Backspace: 'backspace',
BracketLeft: 'bracketleft',
BracketRight: 'bracketright',
Comma: 'comma',
Ctrl: 'ctrl',
Down: 'down',
Enter: 'enter',
Equal: 'equal',
Esc: 'esc',
Left: 'left',
LeftClick: 'left-click',
LeftDoubleClick: 'left-double-click',
Meta: 'meta',
MiddleClick: 'middle-click',
Minus: 'minus',
Mod: 'mod',
Number: '1-9',
Period: 'period',
Plus: 'equal',
QuestionMark: 'slash',
Quote: 'quote',
Right: 'right',
RightClick: 'right-click',
RightDoubleClick: 'right-double-click',
Semicolon: 'semicolon',
Shift: 'shift',
Slash: 'slash',
Space: 'space',
Tab: 'tab',
Up: 'up',
Zero: '0',
} as const;
export const HotkeyEnum = {
AddUserMessage: 'addUserMessage',
ClearCurrentMessages: 'clearCurrentMessages',
CommandPalette: 'commandPalette',
DeleteAndRegenerateMessage: 'deleteAndRegenerateMessage',
DeleteLastMessage: 'deleteLastMessage',
EditMessage: 'editMessage',
NavigateToChat: 'navigateToChat',
OpenChatSettings: 'openChatSettings',
OpenHotkeyHelper: 'openHotkeyHelper',
RegenerateMessage: 'regenerateMessage',
SaveDocument: 'saveDocument',
SaveTopic: 'saveTopic',
Search: 'search',
ShowApp: 'showApp',
SwitchAgent: 'switchAgent',
ToggleLeftPanel: 'toggleLeftPanel',
ToggleRightPanel: 'toggleRightPanel',
ToggleZenMode: 'toggleZenMode',
} as const;
export const HotkeyGroupEnum = {
Conversation: 'conversation',
Essential: 'essential',
} as const;
export const HotkeyScopeEnum = {
Chat: 'chat',
Files: 'files',
Global: 'global',
Image: 'image',
} as const;
export type HotkeyId = (typeof HotkeyEnum)[keyof typeof HotkeyEnum];
export type HotkeyGroupId = (typeof HotkeyGroupEnum)[keyof typeof HotkeyGroupEnum];
export type HotkeyScopeId = (typeof HotkeyScopeEnum)[keyof typeof HotkeyScopeEnum];
export interface HotkeyItem {
group: HotkeyGroupId;
id: HotkeyId;
keys: string;
nonEditable?: boolean;
scopes?: HotkeyScopeId[];
}
export type HotkeyRegistration = HotkeyItem[];
// mod is the command key on Mac, alt is the ctrl key on Windows
@ -120,18 +193,3 @@ export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
scopes: [HotkeyScopeEnum.Files],
},
];
type DesktopHotkeyRegistration = DesktopHotkeyItem[];
// Desktop hotkey configuration
export const DESKTOP_HOTKEYS_REGISTRATION: DesktopHotkeyRegistration = [
{
id: DesktopHotkeyEnum.ShowApp,
keys: combineKeys([KeyEnum.Ctrl, 'e']),
},
{
id: DesktopHotkeyEnum.OpenSettings,
keys: combineKeys([KeyEnum.Mod, KeyEnum.Comma]),
nonEditable: true,
},
];

View file

@ -1,6 +1,7 @@
export * from './bot';
export * from './currency';
export * from './desktop';
export * from './desktopGlobalShortcuts';
export * from './discover';
export * from './editor';
export * from './file';

View file

@ -1,5 +1,4 @@
import type { LobeAgentSession, LobeGroupSession } from '@lobechat/types';
import { LobeSessionType } from '@lobechat/types';
import { DEFAULT_AGENT_META, DEFAULT_INBOX_AVATAR } from './meta';
import { DEFAULT_AGENT_CONFIG } from './settings';
@ -9,13 +8,16 @@ export const INBOX_SESSION_ID = 'inbox';
export const WELCOME_GUIDE_CHAT_ID = 'welcome';
const DEFAULT_AGENT_SESSION_TYPE = 'agent' as LobeAgentSession['type'];
const DEFAULT_GROUP_SESSION_TYPE = 'group' as LobeGroupSession['type'];
export const DEFAULT_AGENT_LOBE_SESSION: LobeAgentSession = {
config: DEFAULT_AGENT_CONFIG,
createdAt: new Date(),
id: '',
meta: DEFAULT_AGENT_META,
model: DEFAULT_AGENT_CONFIG.model,
type: LobeSessionType.Agent,
type: DEFAULT_AGENT_SESSION_TYPE,
updatedAt: new Date(),
};
@ -24,7 +26,7 @@ export const DEFAULT_GROUP_LOBE_SESSION: LobeGroupSession = {
id: '',
members: [],
meta: DEFAULT_AGENT_META,
type: LobeSessionType.Group,
type: DEFAULT_GROUP_SESSION_TYPE,
updatedAt: new Date(),
};

View file

@ -1,6 +1,6 @@
import type { UserHotkeyConfig } from '@lobechat/types';
import { type HotkeyId, HOTKEYS_REGISTRATION } from '../hotkeys';
import { HOTKEYS_REGISTRATION } from '../hotkeys';
type UserHotkeyConfig = Record<HotkeyId, string>;
export const DEFAULT_HOTKEY_CONFIG: UserHotkeyConfig = HOTKEYS_REGISTRATION.reduce(
(acc: UserHotkeyConfig, item) => {

View file

@ -1,5 +1,4 @@
import type { UserPreference } from '@lobechat/types';
import { TopicDisplayMode } from '@lobechat/types';
/**
* Current onboarding flow version.
@ -8,6 +7,10 @@ import { TopicDisplayMode } from '@lobechat/types';
*/
export const CURRENT_ONBOARDING_VERSION = 1;
const DEFAULT_TOPIC_DISPLAY_MODE = 'byUpdatedTime' as NonNullable<
UserPreference['topicDisplayMode']
>;
export const DEFAULT_PREFERENCE: UserPreference = {
guide: {
moveSettingsToAvatar: true,
@ -17,6 +20,6 @@ export const DEFAULT_PREFERENCE: UserPreference = {
enableAgentWorkingPanel: false,
enableInputMarkdown: true,
},
topicDisplayMode: TopicDisplayMode.ByUpdatedTime,
topicDisplayMode: DEFAULT_TOPIC_DISPLAY_MODE,
useCmdEnterToSend: false,
};

View file

@ -5,6 +5,7 @@
"type": "module",
"main": "./src/index.ts",
"dependencies": {
"@lobechat/const": "workspace:*",
"@lobechat/python-interpreter": "workspace:*",
"@lobechat/web-crawler": "workspace:*",
"@lobehub/market-sdk": "0.32.2",

View file

@ -1,131 +1,11 @@
export const KeyEnum = {
Alt: 'alt',
Backquote: 'backquote',
// `
Backslash: 'backslash',
// \
Backspace: 'backspace',
BracketLeft: 'bracketleft',
// [
BracketRight: 'bracketright',
// ]
Comma: 'comma',
// ,
Ctrl: 'ctrl',
Down: 'down',
Enter: 'enter',
Equal: 'equal',
// =
Esc: 'esc',
Left: 'left',
LeftClick: 'left-click',
LeftDoubleClick: 'left-double-click',
Meta: 'meta',
// Command on Mac, Win on Win
MiddleClick: 'middle-click',
Minus: 'minus',
// -
Mod: 'mod',
import type { HotkeyId } from '@lobechat/const/hotkeys';
Number: '1-9',
// Command on Mac, Ctrl on Win
Period: 'period',
// .
Plus: 'equal',
// +
QuestionMark: 'slash',
// ?
Quote: 'quote',
// '
Right: 'right',
RightClick: 'right-click',
RightDoubleClick: 'right-double-click',
Semicolon: 'semicolon',
// ;
Shift: 'shift',
Slash: 'slash',
// /
Space: 'space',
Tab: 'tab',
Up: 'up',
Zero: '0',
} as const;
export const HotkeyEnum = {
AddUserMessage: 'addUserMessage',
ClearCurrentMessages: 'clearCurrentMessages',
CommandPalette: 'commandPalette',
DeleteAndRegenerateMessage: 'deleteAndRegenerateMessage',
DeleteLastMessage: 'deleteLastMessage',
EditMessage: 'editMessage',
NavigateToChat: 'navigateToChat',
OpenChatSettings: 'openChatSettings',
OpenHotkeyHelper: 'openHotkeyHelper',
RegenerateMessage: 'regenerateMessage',
SaveDocument: 'saveDocument',
SaveTopic: 'saveTopic',
Search: 'search',
ShowApp: 'showApp',
SwitchAgent: 'switchAgent',
ToggleLeftPanel: 'toggleLeftPanel',
ToggleRightPanel: 'toggleRightPanel',
ToggleZenMode: 'toggleZenMode',
} as const;
export const HotkeyGroupEnum = {
Conversation: 'conversation',
Essential: 'essential',
} as const;
export const HotkeyScopeEnum = {
Chat: 'chat',
Files: 'files',
// Default globally registered hotkey scope
// https://react-hotkeys-hook.vercel.app/docs/documentation/hotkeys-provider
Global: 'global',
Image: 'image',
} as const;
export type HotkeyId = (typeof HotkeyEnum)[keyof typeof HotkeyEnum];
export type HotkeyGroupId = (typeof HotkeyGroupEnum)[keyof typeof HotkeyGroupEnum];
export type HotkeyScopeId = (typeof HotkeyScopeEnum)[keyof typeof HotkeyScopeEnum];
export interface HotkeyItem {
// Hotkey grouping for display purposes
group: HotkeyGroupId;
id: HotkeyId;
keys: string;
// Whether the hotkey is non-editable
nonEditable?: boolean;
// Hotkey scope
scopes?: HotkeyScopeId[];
}
// ================== Desktop ================== //
export const DesktopHotkeyEnum = {
OpenSettings: 'openSettings',
ShowApp: 'showApp',
};
export type DesktopHotkeyId = (typeof DesktopHotkeyEnum)[keyof typeof DesktopHotkeyEnum];
export interface DesktopHotkeyItem {
id: DesktopHotkeyId;
keys: string;
// Whether the hotkey is non-editable
nonEditable?: boolean;
}
export type DesktopHotkeyConfig = Record<DesktopHotkeyId, string>;
export type {
DesktopHotkeyConfig,
DesktopHotkeyId,
DesktopHotkeyItem,
} from '@lobechat/const/desktopGlobalShortcuts';
export type { HotkeyGroupId, HotkeyId, HotkeyItem, HotkeyScopeId } from '@lobechat/const/hotkeys';
export type HotkeyI18nTranslations = Record<
HotkeyId,

View file

@ -1,3 +1,4 @@
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { Popconfirm } from 'antd';
import { Eraser } from 'lucide-react';
import { memo, useCallback, useState } from 'react';
@ -8,7 +9,6 @@ import { useChatStore } from '@/store/chat';
import { useFileStore } from '@/store/file';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
import Action from '../components/Action';

View file

@ -1,3 +1,4 @@
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { ActionIcon, Flexbox, Hotkey } from '@lobehub/ui';
import { Popconfirm } from 'antd';
import { LucideGalleryVerticalEnd, LucideMessageSquarePlus } from 'lucide-react';
@ -9,7 +10,6 @@ import { useActionSWR } from '@/libs/swr';
import { useChatStore } from '@/store/chat';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
const SaveTopic = memo(() => {
const { t } = useTranslation('chat');

View file

@ -1,5 +1,5 @@
import { KeyEnum } from '@lobechat/types';
import { combineKeys,Flexbox, Hotkey } from '@lobehub/ui';
import { KeyEnum } from '@lobechat/const/hotkeys';
import { combineKeys, Flexbox, Hotkey } from '@lobehub/ui';
import { memo } from 'react';
import { Trans, useTranslation } from 'react-i18next';

View file

@ -1,6 +1,6 @@
import { isDesktop } from '@lobechat/const';
import { HotkeyEnum, KeyEnum } from '@lobechat/const/hotkeys';
import { chainInputCompletion } from '@lobechat/prompts';
import { HotkeyEnum, KeyEnum } from '@lobechat/types';
import { isCommandPressed, merge } from '@lobechat/utils';
import { INSERT_MENTION_COMMAND, ReactAutoCompletePlugin, ReactMathPlugin } from '@lobehub/editor';
import { Editor, FloatMenu, useEditorState } from '@lobehub/editor/react';

View file

@ -1,11 +1,11 @@
import { combineKeys,Flexbox, Hotkey, Text } from '@lobehub/ui';
import { KeyEnum } from '@lobechat/const/hotkeys';
import { combineKeys, Flexbox, Hotkey, Text } from '@lobehub/ui';
import { cssVar } from 'antd-style';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useUserStore } from '@/store/user';
import { preferenceSelectors } from '@/store/user/selectors';
import { KeyEnum } from '@/types/hotkey';
const ShortcutHint = memo(() => {
const { t } = useTranslation('chat');

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyGroupEnum } from '@lobechat/const/hotkeys';
import { Grid, Icon, Modal, Segmented } from '@lobehub/ui';
import { MessageSquare, Settings2 } from 'lucide-react';
import { memo, useState } from 'react';
@ -7,7 +8,6 @@ import { useTranslation } from 'react-i18next';
import { useGlobalStore } from '@/store/global';
import { type HotkeyGroupId } from '@/types/hotkey';
import { HotkeyGroupEnum } from '@/types/hotkey';
import HotkeyContent from './HotkeyContent';

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { type ActionIconProps } from '@lobehub/ui';
import { ActionIcon } from '@lobehub/ui';
import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
@ -12,7 +13,6 @@ import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
export const TOGGLE_BUTTON_ID = 'toggle_left_panel_button';

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { type ActionIconProps } from '@lobehub/ui';
import { ActionIcon } from '@lobehub/ui';
import { PanelRightClose, PanelRightOpen } from 'lucide-react';
@ -12,7 +13,6 @@ import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
export const TOGGLE_BUTTON_ID = 'toggle_right_panel_button';

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { Flexbox, Hotkey } from '@lobehub/ui';
import { createStaticStyles } from 'antd-style';
import { useEffect, useState } from 'react';
@ -7,7 +8,6 @@ import { useTranslation } from 'react-i18next';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
const styles = createStaticStyles(({ css, cssVar }) => ({
closeButton: css`

View file

@ -1,3 +1,4 @@
import { HotkeyEnum, HotkeyScopeEnum } from '@lobechat/const/hotkeys';
import { useEffect } from 'react';
import { useHotkeysContext } from 'react-hotkeys-hook';
@ -6,7 +7,6 @@ import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
import { useActionSWR } from '@/libs/swr';
import { useChatStore } from '@/store/chat';
import { useGlobalStore } from '@/store/global';
import { HotkeyEnum, HotkeyScopeEnum } from '@/types/hotkey';
import { useHotkeyById } from './useHotkeyById';

View file

@ -1,4 +1,4 @@
import { HotkeyEnum, HotkeyScopeEnum } from '@lobechat/types';
import { HotkeyEnum, HotkeyScopeEnum } from '@lobechat/const/hotkeys';
import { useEffect } from 'react';
import { useHotkeysContext } from 'react-hotkeys-hook';

View file

@ -1,5 +1,5 @@
import { INBOX_SESSION_ID } from '@lobechat/const';
import { HotkeyEnum } from '@lobechat/types';
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { useLocation } from 'react-router-dom';
import { useNavigateToAgent } from '@/hooks/useNavigateToAgent';

View file

@ -1,9 +1,9 @@
import { HotkeyEnum, HotkeyScopeEnum } from '@lobechat/const/hotkeys';
import { useEffect } from 'react';
import { useHotkeysContext } from 'react-hotkeys-hook';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { HotkeyEnum, HotkeyScopeEnum } from '@/types/hotkey';
import { useHotkeyById } from './useHotkeyById';

View file

@ -1,9 +1,9 @@
import { HotkeyEnum, HotkeyScopeEnum } from '@lobechat/const/hotkeys';
import { renderHook } from '@testing-library/react';
import { uniq } from 'es-toolkit/compat';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { HOTKEYS_REGISTRATION } from '@/const/hotkeys';
import { HotkeyEnum, HotkeyScopeEnum } from '@/types/hotkey';
import { useHotkeyById } from './useHotkeyById';

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyScopeEnum } from '@lobechat/const/hotkeys';
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
import { Flexbox } from '@lobehub/ui';
import { cx } from 'antd-style';
@ -24,7 +25,6 @@ import CmdkLazy from '@/layout/GlobalProvider/CmdkLazy';
import dynamic from '@/libs/next/dynamic';
import { DndContextWrapper } from '@/routes/(main)/resource/features/DndContextWrapper';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
import { HotkeyScopeEnum } from '@/types/hotkey';
import DesktopHome from '../home';
import DesktopHomeLayout from '../home/_layout';

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum, KeyEnum } from '@lobechat/const/hotkeys';
import { type MenuProps } from '@lobehub/ui';
import { Flexbox, Hotkey, Icon } from '@lobehub/ui';
import { BotMessageSquare, LucideCheck, MessageSquarePlus } from 'lucide-react';
@ -10,7 +11,6 @@ import { useConversationStore, useConversationStoreApi } from '@/features/Conver
import { useAddUserMessageHotkey } from '@/hooks/useHotkeys';
import { useUserStore } from '@/store/user';
import { preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum, KeyEnum } from '@/types/hotkey';
/**
* useSendMenuItems hook for ConversationStore

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum, KeyEnum } from '@lobechat/const/hotkeys';
import { type MenuProps } from '@lobehub/ui';
import { Flexbox, Hotkey, Icon } from '@lobehub/ui';
import { BotMessageSquare, LucideCheck, MessageSquarePlus } from 'lucide-react';
@ -9,7 +10,6 @@ import { Trans, useTranslation } from 'react-i18next';
import { useConversationStore, useConversationStoreApi } from '@/features/Conversation';
import { useUserStore } from '@/store/user';
import { preferenceSelectors, settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum, KeyEnum } from '@/types/hotkey';
/**
* useSendMenuItems hook for ConversationStore

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyGroupEnum } from '@lobechat/const/hotkeys';
import { type FormGroupItemType } from '@lobehub/ui';
import { Form, HotkeyInput, Icon, Skeleton } from '@lobehub/ui';
import isEqual from 'fast-deep-equal';
@ -13,7 +14,6 @@ import hotkeyMeta from '@/locales/default/hotkey';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { type HotkeyItem } from '@/types/hotkey';
import { HotkeyGroupEnum } from '@/types/hotkey';
const HotkeySetting = memo(() => {
const { t } = useTranslation(['setting', 'hotkey']);

View file

@ -8,7 +8,7 @@ import { Loader2Icon } from 'lucide-react';
import { memo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DESKTOP_HOTKEYS_REGISTRATION } from '@/const/hotkeys';
import { DESKTOP_HOTKEYS_REGISTRATION } from '@/const/desktopGlobalShortcuts';
import { FORM_STYLE } from '@/const/layoutTokens';
import hotkeyMeta from '@/locales/default/hotkey';
import { useElectronStore } from '@/store/electron';

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyGroupEnum } from '@lobechat/const/hotkeys';
import { type FormGroupItemType } from '@lobehub/ui';
import { Form, HotkeyInput, Icon, Skeleton } from '@lobehub/ui';
import isEqual from 'fast-deep-equal';
@ -13,7 +14,6 @@ import hotkeyMeta from '@/locales/default/hotkey';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { type HotkeyItem } from '@/types/hotkey';
import { HotkeyGroupEnum } from '@/types/hotkey';
const HotkeySetting = memo(() => {
const { t } = useTranslation(['setting', 'hotkey']);

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { SearchBar } from '@lobehub/ui';
import { type ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@ -8,7 +9,6 @@ import { useTranslation } from 'react-i18next';
import { useSessionStore } from '@/store/session';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
const SessionSearchBar = memo<{ mobile?: boolean }>(({ mobile }) => {
const { t } = useTranslation('chat');

View file

@ -1,5 +1,6 @@
'use client';
import { HotkeyEnum } from '@lobechat/const/hotkeys';
import { ActionIcon } from '@lobehub/ui';
import { AlignJustify } from 'lucide-react';
import { memo } from 'react';
@ -11,7 +12,6 @@ import dynamic from '@/libs/next/dynamic';
import { useSessionStore } from '@/store/session';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
const AgentSettingsEditor = dynamic(() => import('@/routes/(main)/agent/profile'), {
ssr: false,