💄 style: Support session switch shortcut key (#8626)

*  feat: Support session switch shortcut key

Duplicate of https://github.com/lobehub/lobe-chat/pull/8366

* chore: update
This commit is contained in:
𝑾𝒖𝒙𝒉
2025-08-09 12:43:28 +08:00
committed by GitHub
parent acec55f605
commit efc7eaf537
13 changed files with 73 additions and 18 deletions
+2
View File
@@ -54,12 +54,14 @@ export const KeyEnum = {
Space: 'space',
Tab: 'tab',
Up: 'up',
Zero: '0',
} as const;
export const HotkeyEnum = {
AddUserMessage: 'addUserMessage',
ClearCurrentMessages: 'clearCurrentMessages',
EditMessage: 'editMessage',
NavigateToChat: 'navigateToChat',
OpenChatSettings: 'openChatSettings',
OpenHotkeyHelper: 'openHotkeyHelper',
RegenerateMessage: 'regenerateMessage',
@@ -2,9 +2,9 @@ import { Avatar, Tooltip } from '@lobehub/ui';
import { Divider } from 'antd';
import { createStyles } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { Flexbox } from 'react-layout-kit';
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
import { useSwitchSession } from '@/hooks/useSwitchSession';
import { useSessionStore } from '@/store/session';
import { sessionHelpers } from '@/store/session/helpers';
@@ -71,11 +71,11 @@ const PinList = () => {
const switchSession = useSwitchSession();
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.SwitchAgent));
const hasList = list.length > 0;
const [isPinned, setPinned] = useQueryState('pinned', parseAsBoolean);
const [isPinned, { pinAgent }] = usePinnedAgentState();
const switchAgent = (id: string) => {
switchSession(id);
setPinned(true);
pinAgent();
};
return (
@@ -38,6 +38,7 @@ vi.mock('@lobehub/ui', () => ({
ActionIcon: vi.fn(({ title }) => <div>{title}</div>),
combineKeys: vi.fn((keys) => keys.join('+')),
KeyMapEnum: { Alt: 'alt', Ctrl: 'ctrl', Shift: 'shift' },
Hotkey: vi.fn(({ keys = [] }) => <div>{keys}</div>),
}));
vi.mock('react-i18next', () => ({
@@ -1,4 +1,4 @@
import { ActionIcon, ActionIconProps } from '@lobehub/ui';
import { ActionIcon, ActionIconProps, Hotkey } from '@lobehub/ui';
import { Compass, FolderClosed, MessageSquare, Palette } from 'lucide-react';
import Link from 'next/link';
import { memo } from 'react';
@@ -9,6 +9,9 @@ import { useGlobalStore } from '@/store/global';
import { SidebarTabKey } from '@/store/global/initialState';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
import { useSessionStore } from '@/store/session';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { HotkeyEnum } from '@/types/hotkey';
const ICON_SIZE: ActionIconProps['size'] = {
blockSize: 40,
@@ -25,6 +28,7 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
const { t } = useTranslation('common');
const switchBackToChat = useGlobalStore((s) => s.switchBackToChat);
const { showMarket, enableKnowledgeBase } = useServerConfigStore(featureFlagsSelectors);
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.NavigateToChat));
const isChatActive = tab === SidebarTabKey.Chat && !isPinned;
const isFilesActive = tab === SidebarTabKey.Files;
@@ -51,7 +55,12 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
active={isChatActive}
icon={MessageSquare}
size={ICON_SIZE}
title={t('tab.chat')}
title={
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
<span>{t('tab.chat')}</span>
<Hotkey inverseTheme keys={hotkey} />
</Flexbox>
}
tooltipProps={{ placement: 'right' }}
/>
</Link>
@@ -2,11 +2,11 @@
import { SideNav } from '@lobehub/ui';
import { useTheme } from 'antd-style';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { Suspense, memo } from 'react';
import { isDesktop } from '@/const/version';
import { useActiveTabKey } from '@/hooks/useActiveTabKey';
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
@@ -18,7 +18,7 @@ import PinList from './PinList';
import TopActions from './TopActions';
const Top = () => {
const [isPinned] = useQueryState('pinned', parseAsBoolean);
const [isPinned] = usePinnedAgentState();
const sidebarKey = useActiveTabKey();
return <TopActions isPinned={isPinned} tab={sidebarKey} />;
@@ -3,13 +3,13 @@
import { Avatar } from '@lobehub/ui';
import { Skeleton } from 'antd';
import { createStyles } from 'antd-style';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { Suspense, memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { useInitAgentConfig } from '@/hooks/useInitAgentConfig';
import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { useSessionStore } from '@/store/session';
@@ -44,7 +44,7 @@ const Main = memo<{ className?: string }>(({ className }) => {
const { t } = useTranslation(['chat', 'hotkey']);
const { styles } = useStyles();
useInitAgentConfig();
const [isPinned] = useQueryState('pinned', parseAsBoolean);
const [isPinned] = usePinnedAgentState();
const [init, isInbox, title, avatar, backgroundColor] = useSessionStore((s) => [
sessionSelectors.isSomeSessionActive(s),
@@ -3,11 +3,11 @@
import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } from '@lobehub/ui';
import { createStyles, useResponsive } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { PropsWithChildren, memo, useEffect, useState } from 'react';
import { withSuspense } from '@/components/withSuspense';
import { FOLDER_WIDTH } from '@/const/layoutTokens';
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
@@ -35,7 +35,7 @@ export const useStyles = createStyles(({ css, token }) => ({
const SessionPanel = memo<PropsWithChildren>(({ children }) => {
const { md = true } = useResponsive();
const [isPinned] = useQueryState('pinned', parseAsBoolean);
const [isPinned] = usePinnedAgentState();
const { styles } = useStyles();
const [sessionsWidth, sessionExpandable, updatePreference] = useGlobalStore((s) => [
+6
View File
@@ -28,6 +28,12 @@ export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
nonEditable: true,
scopes: [HotkeyScopeEnum.Global],
},
{
group: HotkeyGroupEnum.Essential,
id: HotkeyEnum.NavigateToChat,
keys: combineKeys([KeyEnum.Ctrl, KeyEnum.Backquote]),
scopes: [HotkeyScopeEnum.Global],
},
{
group: HotkeyGroupEnum.Essential,
id: HotkeyEnum.ToggleZenMode,
+2 -2
View File
@@ -1,5 +1,4 @@
import isEqual from 'fast-deep-equal';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { useEffect } from 'react';
import { useHotkeysContext } from 'react-hotkeys-hook';
@@ -13,6 +12,7 @@ import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { HotkeyEnum, HotkeyScopeEnum } from '@/types/hotkey';
import { usePinnedAgentState } from '../usePinnedAgentState';
import { useHotkeyById } from './useHotkeyById';
export const useSaveTopicHotkey = () => {
@@ -48,7 +48,7 @@ export const useRegenerateMessageHotkey = () => {
export const useToggleLeftPanelHotkey = () => {
const isZenMode = useGlobalStore((s) => s.status.zenMode);
const [isPinned] = useQueryState('pinned', parseAsBoolean);
const [isPinned] = usePinnedAgentState();
const showSessionPanel = useGlobalStore(systemStatusSelectors.showSessionPanel);
const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
+16 -4
View File
@@ -1,7 +1,8 @@
import isEqual from 'fast-deep-equal';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { useHotkeys } from 'react-hotkeys-hook';
import { INBOX_SESSION_ID } from '@/const/session';
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
import { useSwitchSession } from '@/hooks/useSwitchSession';
import { useGlobalStore } from '@/store/global';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
@@ -18,12 +19,11 @@ export const useSwitchAgentHotkey = () => {
const list = useSessionStore(sessionSelectors.pinnedSessions, isEqual);
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.SwitchAgent));
const switchSession = useSwitchSession();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, setPinned] = useQueryState('pinned', parseAsBoolean);
const [, { pinAgent }] = usePinnedAgentState();
const switchAgent = (id: string) => {
switchSession(id);
setPinned(true);
pinAgent();
};
const ref = useHotkeys(
@@ -49,6 +49,17 @@ export const useSwitchAgentHotkey = () => {
};
};
// 切换到会话标签(并聚焦到随便聊聊)
export const useNavigateToChatHotkey = () => {
const switchSession = useSwitchSession();
const [, { unpinAgent }] = usePinnedAgentState();
return useHotkeyById(HotkeyEnum.NavigateToChat, () => {
switchSession(INBOX_SESSION_ID);
unpinAgent();
});
};
export const useOpenHotkeyHelperHotkey = () => {
const [open, updateSystemStatus] = useGlobalStore((s) => [
s.status.showHotkeyHelper,
@@ -65,5 +76,6 @@ export const useOpenHotkeyHelperHotkey = () => {
export const useRegisterGlobalHotkeys = () => {
// 全局自动注册不需要 enableScope
useSwitchAgentHotkey();
useNavigateToChatHotkey();
useOpenHotkeyHelperHotkey();
};
+21
View File
@@ -0,0 +1,21 @@
import { parseAsBoolean, useQueryState } from 'nuqs';
import { useMemo } from 'react';
export const usePinnedAgentState = () => {
const [isPinned, setIsPinned] = useQueryState(
'pinned',
parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true }),
);
const actions = useMemo(
() => ({
pinAgent: () => setIsPinned(true),
setIsPinned,
togglePinAgent: () => setIsPinned((prev) => !prev),
unpinAgent: () => setIsPinned(false),
}),
[],
);
return [isPinned, actions] as const;
};
+1 -1
View File
@@ -27,6 +27,6 @@ export const useSwitchSession = () => {
}, 50);
}
},
[mobile],
[mobile, pathname],
);
};
+4
View File
@@ -25,6 +25,10 @@ const hotkey: HotkeyI18nTranslations & {
desc: '通过按住 Alt 并双击消息进入编辑模式',
title: '编辑消息',
},
navigateToChat: {
desc: '切换至会话标签并进入随便聊聊',
title: '切换至默认会话',
},
openChatSettings: {
desc: '查看和修改当前会话的设置',
title: '打开会话设置',