@lobehub/chat 1.111.2 → 1.111.3
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 +27 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/models.json +21 -3
- package/locales/bg-BG/models.json +21 -3
- package/locales/de-DE/models.json +21 -3
- package/locales/en-US/models.json +21 -3
- package/locales/es-ES/models.json +21 -3
- package/locales/fa-IR/models.json +21 -3
- package/locales/fr-FR/models.json +21 -3
- package/locales/it-IT/models.json +21 -3
- package/locales/ja-JP/models.json +21 -3
- package/locales/ko-KR/models.json +21 -3
- package/locales/nl-NL/models.json +21 -3
- package/locales/pl-PL/models.json +21 -3
- package/locales/pt-BR/models.json +21 -3
- package/locales/ru-RU/models.json +21 -3
- package/locales/tr-TR/models.json +21 -3
- package/locales/vi-VN/models.json +21 -3
- package/locales/zh-CN/models.json +21 -3
- package/locales/zh-TW/models.json +21 -3
- package/package.json +1 -1
- package/packages/types/src/hotkey.ts +2 -0
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +3 -3
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.test.tsx +1 -0
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.tsx +11 -2
- package/src/app/[variants]/(main)/_layout/Desktop/SideBar/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +2 -2
- package/src/app/[variants]/(main)/chat/_layout/Desktop/SessionPanel.tsx +2 -2
- package/src/components/Thinking/index.tsx +53 -13
- package/src/const/hotkeys.ts +6 -0
- package/src/hooks/useHotkeys/chatScope.ts +2 -2
- package/src/hooks/useHotkeys/globalScope.ts +16 -4
- package/src/hooks/usePinnedAgentState.ts +21 -0
- package/src/hooks/useSwitchSession.ts +1 -1
- package/src/locales/default/hotkey.ts +4 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
import { ActionIcon, ActionIconProps } from '@lobehub/ui';
|
1
|
+
import { ActionIcon, ActionIconProps, Hotkey } from '@lobehub/ui';
|
2
2
|
import { Compass, FolderClosed, MessageSquare, Palette } from 'lucide-react';
|
3
3
|
import Link from 'next/link';
|
4
4
|
import { memo } from 'react';
|
@@ -9,6 +9,9 @@ import { useGlobalStore } from '@/store/global';
|
|
9
9
|
import { SidebarTabKey } from '@/store/global/initialState';
|
10
10
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
11
11
|
import { useSessionStore } from '@/store/session';
|
12
|
+
import { useUserStore } from '@/store/user';
|
13
|
+
import { settingsSelectors } from '@/store/user/selectors';
|
14
|
+
import { HotkeyEnum } from '@/types/hotkey';
|
12
15
|
|
13
16
|
const ICON_SIZE: ActionIconProps['size'] = {
|
14
17
|
blockSize: 40,
|
@@ -25,6 +28,7 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
|
|
25
28
|
const { t } = useTranslation('common');
|
26
29
|
const switchBackToChat = useGlobalStore((s) => s.switchBackToChat);
|
27
30
|
const { showMarket, enableKnowledgeBase } = useServerConfigStore(featureFlagsSelectors);
|
31
|
+
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.NavigateToChat));
|
28
32
|
|
29
33
|
const isChatActive = tab === SidebarTabKey.Chat && !isPinned;
|
30
34
|
const isFilesActive = tab === SidebarTabKey.Files;
|
@@ -51,7 +55,12 @@ const TopActions = memo<TopActionProps>(({ tab, isPinned }) => {
|
|
51
55
|
active={isChatActive}
|
52
56
|
icon={MessageSquare}
|
53
57
|
size={ICON_SIZE}
|
54
|
-
title={
|
58
|
+
title={
|
59
|
+
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
|
60
|
+
<span>{t('tab.chat')}</span>
|
61
|
+
<Hotkey inverseTheme keys={hotkey} />
|
62
|
+
</Flexbox>
|
63
|
+
}
|
55
64
|
tooltipProps={{ placement: 'right' }}
|
56
65
|
/>
|
57
66
|
</Link>
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
import { SideNav } from '@lobehub/ui';
|
4
4
|
import { useTheme } from 'antd-style';
|
5
|
-
import { parseAsBoolean, useQueryState } from 'nuqs';
|
6
5
|
import { Suspense, memo } from 'react';
|
7
6
|
|
8
7
|
import { isDesktop } from '@/const/version';
|
9
8
|
import { useActiveTabKey } from '@/hooks/useActiveTabKey';
|
9
|
+
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
10
10
|
import { useGlobalStore } from '@/store/global';
|
11
11
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
12
12
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
@@ -18,7 +18,7 @@ import PinList from './PinList';
|
|
18
18
|
import TopActions from './TopActions';
|
19
19
|
|
20
20
|
const Top = () => {
|
21
|
-
const [isPinned] =
|
21
|
+
const [isPinned] = usePinnedAgentState();
|
22
22
|
const sidebarKey = useActiveTabKey();
|
23
23
|
|
24
24
|
return <TopActions isPinned={isPinned} tab={sidebarKey} />;
|
@@ -3,13 +3,13 @@
|
|
3
3
|
import { Avatar } from '@lobehub/ui';
|
4
4
|
import { Skeleton } from 'antd';
|
5
5
|
import { createStyles } from 'antd-style';
|
6
|
-
import { parseAsBoolean, useQueryState } from 'nuqs';
|
7
6
|
import { Suspense, memo } from 'react';
|
8
7
|
import { useTranslation } from 'react-i18next';
|
9
8
|
import { Flexbox } from 'react-layout-kit';
|
10
9
|
|
11
10
|
import { useInitAgentConfig } from '@/hooks/useInitAgentConfig';
|
12
11
|
import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
|
12
|
+
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
13
13
|
import { useGlobalStore } from '@/store/global';
|
14
14
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
15
15
|
import { useSessionStore } from '@/store/session';
|
@@ -44,7 +44,7 @@ const Main = memo<{ className?: string }>(({ className }) => {
|
|
44
44
|
const { t } = useTranslation(['chat', 'hotkey']);
|
45
45
|
const { styles } = useStyles();
|
46
46
|
useInitAgentConfig();
|
47
|
-
const [isPinned] =
|
47
|
+
const [isPinned] = usePinnedAgentState();
|
48
48
|
|
49
49
|
const [init, isInbox, title, avatar, backgroundColor] = useSessionStore((s) => [
|
50
50
|
sessionSelectors.isSomeSessionActive(s),
|
@@ -3,11 +3,11 @@
|
|
3
3
|
import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } from '@lobehub/ui';
|
4
4
|
import { createStyles, useResponsive } from 'antd-style';
|
5
5
|
import isEqual from 'fast-deep-equal';
|
6
|
-
import { parseAsBoolean, useQueryState } from 'nuqs';
|
7
6
|
import { PropsWithChildren, memo, useEffect, useState } from 'react';
|
8
7
|
|
9
8
|
import { withSuspense } from '@/components/withSuspense';
|
10
9
|
import { FOLDER_WIDTH } from '@/const/layoutTokens';
|
10
|
+
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
11
11
|
import { useGlobalStore } from '@/store/global';
|
12
12
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
13
13
|
|
@@ -35,7 +35,7 @@ export const useStyles = createStyles(({ css, token }) => ({
|
|
35
35
|
const SessionPanel = memo<PropsWithChildren>(({ children }) => {
|
36
36
|
const { md = true } = useResponsive();
|
37
37
|
|
38
|
-
const [isPinned] =
|
38
|
+
const [isPinned] = usePinnedAgentState();
|
39
39
|
|
40
40
|
const { styles } = useStyles();
|
41
41
|
const [sessionsWidth, sessionExpandable, updatePreference] = useGlobalStore((s) => [
|
@@ -3,7 +3,7 @@ import { createStyles } from 'antd-style';
|
|
3
3
|
import { AnimatePresence, motion } from 'framer-motion';
|
4
4
|
import { AtomIcon, ChevronDown, ChevronRight } from 'lucide-react';
|
5
5
|
import { rgba } from 'polished';
|
6
|
-
import { CSSProperties, memo, useEffect, useState } from 'react';
|
6
|
+
import { CSSProperties, RefObject, memo, useEffect, useRef, useState } from 'react';
|
7
7
|
import { useTranslation } from 'react-i18next';
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
9
9
|
|
@@ -16,11 +16,19 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
16
16
|
color: ${token.colorTextTertiary};
|
17
17
|
transition: all 0.2s ${token.motionEaseOut};
|
18
18
|
`,
|
19
|
+
contentScroll: css`
|
20
|
+
scroll-behavior: auto;
|
21
|
+
|
22
|
+
overflow-y: auto;
|
23
|
+
overscroll-behavior: contain;
|
24
|
+
|
25
|
+
max-height: 40vh;
|
26
|
+
padding-block-end: 12px;
|
27
|
+
padding-inline: 12px;
|
28
|
+
`,
|
19
29
|
expand: css`
|
20
|
-
color: ${token.
|
21
|
-
background: ${token.colorFillTertiary};
|
30
|
+
color: ${token.colorTextTertiary};
|
22
31
|
`,
|
23
|
-
|
24
32
|
header: css`
|
25
33
|
padding-block: 4px;
|
26
34
|
padding-inline: 8px 4px;
|
@@ -34,7 +42,6 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
34
42
|
|
35
43
|
headerExpand: css`
|
36
44
|
color: ${token.colorTextSecondary};
|
37
|
-
background: ${token.colorFillQuaternary};
|
38
45
|
`,
|
39
46
|
shinyText: css`
|
40
47
|
color: ${rgba(token.colorText, 0.45)};
|
@@ -86,11 +93,39 @@ const Thinking = memo<ThinkingProps>((props) => {
|
|
86
93
|
const { styles, cx, theme } = useStyles();
|
87
94
|
|
88
95
|
const [showDetail, setShowDetail] = useState(false);
|
96
|
+
const contentRef = useRef<HTMLDivElement | null>(null);
|
89
97
|
|
90
98
|
useEffect(() => {
|
91
99
|
setShowDetail(!!thinking);
|
92
100
|
}, [thinking]);
|
93
101
|
|
102
|
+
// 当内容变更且正在思考时,如果用户接近底部则自动滚动到底部
|
103
|
+
useEffect(() => {
|
104
|
+
if (!thinking || !showDetail) return;
|
105
|
+
const container = contentRef.current;
|
106
|
+
if (!container) return;
|
107
|
+
|
108
|
+
// 仅当用户接近底部时才自动滚动,避免打断用户查看上方内容
|
109
|
+
const distanceToBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
|
110
|
+
const isNearBottom = distanceToBottom < 60;
|
111
|
+
|
112
|
+
if (isNearBottom) {
|
113
|
+
requestAnimationFrame(() => {
|
114
|
+
container.scrollTop = container.scrollHeight;
|
115
|
+
});
|
116
|
+
}
|
117
|
+
}, [content, thinking, showDetail]);
|
118
|
+
|
119
|
+
// 展开时滚动到底部,便于查看最新内容
|
120
|
+
useEffect(() => {
|
121
|
+
if (!showDetail) return;
|
122
|
+
const container = contentRef.current;
|
123
|
+
if (!container) return;
|
124
|
+
requestAnimationFrame(() => {
|
125
|
+
container.scrollTop = container.scrollHeight;
|
126
|
+
});
|
127
|
+
}, [showDetail]);
|
128
|
+
|
94
129
|
return (
|
95
130
|
<Flexbox
|
96
131
|
className={cx(styles.container, showDetail && styles.expand)}
|
@@ -145,7 +180,7 @@ const Thinking = memo<ThinkingProps>((props) => {
|
|
145
180
|
animate="open"
|
146
181
|
exit="collapsed"
|
147
182
|
initial="collapsed"
|
148
|
-
style={{ overflow: 'hidden'
|
183
|
+
style={{ overflow: 'hidden' }}
|
149
184
|
transition={{
|
150
185
|
duration: 0.2,
|
151
186
|
ease: [0.4, 0, 0.2, 1], // 使用 ease-out 缓动函数
|
@@ -155,13 +190,18 @@ const Thinking = memo<ThinkingProps>((props) => {
|
|
155
190
|
open: { opacity: 1, width: 'auto' },
|
156
191
|
}}
|
157
192
|
>
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
193
|
+
<div
|
194
|
+
className={styles.contentScroll}
|
195
|
+
ref={contentRef as unknown as RefObject<HTMLDivElement>}
|
196
|
+
>
|
197
|
+
{typeof content === 'string' ? (
|
198
|
+
<Markdown animated={thinkingAnimated} citations={citations} variant={'chat'}>
|
199
|
+
{content}
|
200
|
+
</Markdown>
|
201
|
+
) : (
|
202
|
+
content
|
203
|
+
)}
|
204
|
+
</div>
|
165
205
|
</motion.div>
|
166
206
|
)}
|
167
207
|
</AnimatePresence>
|
package/src/const/hotkeys.ts
CHANGED
@@ -28,6 +28,12 @@ export const HOTKEYS_REGISTRATION: HotkeyRegistration = [
|
|
28
28
|
nonEditable: true,
|
29
29
|
scopes: [HotkeyScopeEnum.Global],
|
30
30
|
},
|
31
|
+
{
|
32
|
+
group: HotkeyGroupEnum.Essential,
|
33
|
+
id: HotkeyEnum.NavigateToChat,
|
34
|
+
keys: combineKeys([KeyEnum.Ctrl, KeyEnum.Backquote]),
|
35
|
+
scopes: [HotkeyScopeEnum.Global],
|
36
|
+
},
|
31
37
|
{
|
32
38
|
group: HotkeyGroupEnum.Essential,
|
33
39
|
id: HotkeyEnum.ToggleZenMode,
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import isEqual from 'fast-deep-equal';
|
2
|
-
import { parseAsBoolean, useQueryState } from 'nuqs';
|
3
2
|
import { useEffect } from 'react';
|
4
3
|
import { useHotkeysContext } from 'react-hotkeys-hook';
|
5
4
|
|
@@ -13,6 +12,7 @@ import { useGlobalStore } from '@/store/global';
|
|
13
12
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
14
13
|
import { HotkeyEnum, HotkeyScopeEnum } from '@/types/hotkey';
|
15
14
|
|
15
|
+
import { usePinnedAgentState } from '../usePinnedAgentState';
|
16
16
|
import { useHotkeyById } from './useHotkeyById';
|
17
17
|
|
18
18
|
export const useSaveTopicHotkey = () => {
|
@@ -48,7 +48,7 @@ export const useRegenerateMessageHotkey = () => {
|
|
48
48
|
|
49
49
|
export const useToggleLeftPanelHotkey = () => {
|
50
50
|
const isZenMode = useGlobalStore((s) => s.status.zenMode);
|
51
|
-
const [isPinned] =
|
51
|
+
const [isPinned] = usePinnedAgentState();
|
52
52
|
const showSessionPanel = useGlobalStore(systemStatusSelectors.showSessionPanel);
|
53
53
|
const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
|
54
54
|
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import isEqual from 'fast-deep-equal';
|
2
|
-
import { parseAsBoolean, useQueryState } from 'nuqs';
|
3
2
|
import { useHotkeys } from 'react-hotkeys-hook';
|
4
3
|
|
4
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
5
|
+
import { usePinnedAgentState } from '@/hooks/usePinnedAgentState';
|
5
6
|
import { useSwitchSession } from '@/hooks/useSwitchSession';
|
6
7
|
import { useGlobalStore } from '@/store/global';
|
7
8
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
@@ -18,12 +19,11 @@ export const useSwitchAgentHotkey = () => {
|
|
18
19
|
const list = useSessionStore(sessionSelectors.pinnedSessions, isEqual);
|
19
20
|
const hotkey = useUserStore(settingsSelectors.getHotkeyById(HotkeyEnum.SwitchAgent));
|
20
21
|
const switchSession = useSwitchSession();
|
21
|
-
|
22
|
-
const [_, setPinned] = useQueryState('pinned', parseAsBoolean);
|
22
|
+
const [, { pinAgent }] = usePinnedAgentState();
|
23
23
|
|
24
24
|
const switchAgent = (id: string) => {
|
25
25
|
switchSession(id);
|
26
|
-
|
26
|
+
pinAgent();
|
27
27
|
};
|
28
28
|
|
29
29
|
const ref = useHotkeys(
|
@@ -49,6 +49,17 @@ export const useSwitchAgentHotkey = () => {
|
|
49
49
|
};
|
50
50
|
};
|
51
51
|
|
52
|
+
// 切换到会话标签(并聚焦到随便聊聊)
|
53
|
+
export const useNavigateToChatHotkey = () => {
|
54
|
+
const switchSession = useSwitchSession();
|
55
|
+
const [, { unpinAgent }] = usePinnedAgentState();
|
56
|
+
|
57
|
+
return useHotkeyById(HotkeyEnum.NavigateToChat, () => {
|
58
|
+
switchSession(INBOX_SESSION_ID);
|
59
|
+
unpinAgent();
|
60
|
+
});
|
61
|
+
};
|
62
|
+
|
52
63
|
export const useOpenHotkeyHelperHotkey = () => {
|
53
64
|
const [open, updateSystemStatus] = useGlobalStore((s) => [
|
54
65
|
s.status.showHotkeyHelper,
|
@@ -65,5 +76,6 @@ export const useOpenHotkeyHelperHotkey = () => {
|
|
65
76
|
export const useRegisterGlobalHotkeys = () => {
|
66
77
|
// 全局自动注册不需要 enableScope
|
67
78
|
useSwitchAgentHotkey();
|
79
|
+
useNavigateToChatHotkey();
|
68
80
|
useOpenHotkeyHelperHotkey();
|
69
81
|
};
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { parseAsBoolean, useQueryState } from 'nuqs';
|
2
|
+
import { useMemo } from 'react';
|
3
|
+
|
4
|
+
export const usePinnedAgentState = () => {
|
5
|
+
const [isPinned, setIsPinned] = useQueryState(
|
6
|
+
'pinned',
|
7
|
+
parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true }),
|
8
|
+
);
|
9
|
+
|
10
|
+
const actions = useMemo(
|
11
|
+
() => ({
|
12
|
+
pinAgent: () => setIsPinned(true),
|
13
|
+
setIsPinned,
|
14
|
+
togglePinAgent: () => setIsPinned((prev) => !prev),
|
15
|
+
unpinAgent: () => setIsPinned(false),
|
16
|
+
}),
|
17
|
+
[],
|
18
|
+
);
|
19
|
+
|
20
|
+
return [isPinned, actions] as const;
|
21
|
+
};
|