mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-15 04:00:09 +00:00
💄 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:
@@ -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) => [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -27,6 +27,6 @@ export const useSwitchSession = () => {
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
[mobile],
|
||||
[mobile, pathname],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,6 +25,10 @@ const hotkey: HotkeyI18nTranslations & {
|
||||
desc: '通过按住 Alt 并双击消息进入编辑模式',
|
||||
title: '编辑消息',
|
||||
},
|
||||
navigateToChat: {
|
||||
desc: '切换至会话标签并进入随便聊聊',
|
||||
title: '切换至默认会话',
|
||||
},
|
||||
openChatSettings: {
|
||||
desc: '查看和修改当前会话的设置',
|
||||
title: '打开会话设置',
|
||||
|
||||
Reference in New Issue
Block a user