@lobehub/chat 0.90.3 → 0.91.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 CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.91.0](https://github.com/lobehub/lobe-chat/compare/v0.90.3...v0.91.0)
6
+
7
+ <sup>Released on **2023-10-17**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Add hotkeys.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Add hotkeys, closes [#286](https://github.com/lobehub/lobe-chat/issues/286) ([041054d](https://github.com/lobehub/lobe-chat/commit/041054d))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 0.90.3](https://github.com/lobehub/lobe-chat/compare/v0.90.2...v0.90.3)
6
31
 
7
32
  <sup>Released on **2023-10-17**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.90.3",
3
+ "version": "0.91.0",
4
4
  "description": "An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -3,8 +3,11 @@ import { Popconfirm } from 'antd';
3
3
  import { useResponsive } from 'antd-style';
4
4
  import { Eraser } from 'lucide-react';
5
5
  import { memo } from 'react';
6
+ import { useHotkeys } from 'react-hotkeys-hook';
6
7
  import { useTranslation } from 'react-i18next';
7
8
 
9
+ import HotKeys from '@/components/HotKeys';
10
+ import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
8
11
  import { useSessionStore } from '@/store/session';
9
12
 
10
13
  import SaveTopic from '../Footer/SaveTopic';
@@ -13,6 +16,12 @@ const ActionsRight = memo(() => {
13
16
  const { t } = useTranslation('setting');
14
17
  const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
15
18
  const { mobile } = useResponsive();
19
+ const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
20
+
21
+ useHotkeys(hotkeys, clearMessage, {
22
+ preventDefault: true,
23
+ });
24
+
16
25
  return (
17
26
  <>
18
27
  {mobile && <SaveTopic />}
@@ -20,14 +29,16 @@ const ActionsRight = memo(() => {
20
29
  cancelText={t('cancel', { ns: 'common' })}
21
30
  okButtonProps={{ danger: true }}
22
31
  okText={t('ok', { ns: 'common' })}
23
- onConfirm={() => clearMessage()}
32
+ onConfirm={clearMessage}
24
33
  placement={'topRight'}
25
34
  title={t('confirmClearCurrentMessages', { ns: 'common' })}
26
35
  >
27
36
  <ActionIcon
28
37
  icon={Eraser}
29
38
  placement={'bottom'}
30
- title={t('clearCurrentMessages', { ns: 'common' })}
39
+ title={
40
+ (<HotKeys desc={t('clearCurrentMessages', { ns: 'common' })} keys={hotkeys} />) as any
41
+ }
31
42
  />
32
43
  </Popconfirm>
33
44
  </>
@@ -3,8 +3,11 @@ import { Button } from 'antd';
3
3
  import { useResponsive } from 'antd-style';
4
4
  import { LucideGalleryVerticalEnd } from 'lucide-react';
5
5
  import { memo } from 'react';
6
+ import { useHotkeys } from 'react-hotkeys-hook';
6
7
  import { useTranslation } from 'react-i18next';
7
8
 
9
+ import HotKeys from '@/components/HotKeys';
10
+ import { PREFIX_KEY, SAVE_TOPIC_KEY } from '@/const/hotkeys';
8
11
  import { useSessionStore } from '@/store/session';
9
12
 
10
13
  const SaveTopic = memo(() => {
@@ -12,13 +15,19 @@ const SaveTopic = memo(() => {
12
15
  const [hasTopic, saveToTopic] = useSessionStore((s) => [!!s.activeTopicId, s.saveToTopic]);
13
16
  const { mobile } = useResponsive();
14
17
 
18
+ const hotkeys = [PREFIX_KEY, SAVE_TOPIC_KEY].join('+');
19
+
20
+ useHotkeys(hotkeys, saveToTopic, {
21
+ preventDefault: true,
22
+ });
23
+
15
24
  if (hasTopic) return undefined;
16
25
 
17
26
  const Render = mobile ? ActionIcon : Button;
18
27
  const icon: any = mobile ? LucideGalleryVerticalEnd : <Icon icon={LucideGalleryVerticalEnd} />;
19
28
 
20
29
  return (
21
- <Tooltip title={t('topic.saveCurrentMessages')}>
30
+ <Tooltip title={<HotKeys desc={t('topic.saveCurrentMessages')} keys={hotkeys} />}>
22
31
  <Render aria-label={t('topic.saveCurrentMessages')} icon={icon} onClick={saveToTopic} />
23
32
  </Tooltip>
24
33
  );
@@ -1,8 +1,10 @@
1
1
  import { ChatList } from '@lobehub/ui';
2
2
  import isEqual from 'fast-deep-equal';
3
3
  import { memo } from 'react';
4
+ import { useHotkeys } from 'react-hotkeys-hook';
4
5
  import { useTranslation } from 'react-i18next';
5
6
 
7
+ import { PREFIX_KEY, REGENERATE_KEY } from '@/const/hotkeys';
6
8
  import { useSessionChatInit, useSessionStore } from '@/store/session';
7
9
  import { agentSelectors, chatSelectors } from '@/store/session/selectors';
8
10
 
@@ -36,6 +38,21 @@ const List = memo(() => {
36
38
  ];
37
39
  });
38
40
 
41
+ const hotkeys = [PREFIX_KEY, REGENERATE_KEY].join('+');
42
+
43
+ useHotkeys(
44
+ hotkeys,
45
+ () => {
46
+ console.log(111);
47
+ const lastMessage = data.at(-1);
48
+ if (!lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system') return;
49
+ resendMessage(lastMessage.id);
50
+ },
51
+ {
52
+ preventDefault: true,
53
+ },
54
+ );
55
+
39
56
  if (!init) return <SkeletonList />;
40
57
 
41
58
  return (
@@ -5,15 +5,13 @@ import { memo } from 'react';
5
5
  import PageTitle from '@/components/PageTitle';
6
6
  import { useSessionStore } from '@/store/session';
7
7
  import { agentSelectors } from '@/store/session/selectors';
8
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
9
8
 
10
9
  const Title = memo(() => {
11
10
  const [avatar, title] = useSessionStore((s) => [
12
11
  agentSelectors.currentAgentAvatar(s),
13
12
  agentSelectors.currentAgentTitle(s),
14
13
  ]);
15
- const pageTitle = genSiteHeadTitle([avatar, title].filter(Boolean).join(' '));
16
14
 
17
- return <PageTitle title={pageTitle} />;
15
+ return <PageTitle title={[avatar, title].filter(Boolean).join(' ')} />;
18
16
  });
19
17
  export default Title;
@@ -13,7 +13,7 @@ import { useStyles } from './style';
13
13
 
14
14
  const SystemRole = memo(() => {
15
15
  const [openModal, setOpenModal] = useState(false);
16
-
16
+ const [editing, setEditing] = useState(false);
17
17
  const { styles } = useStyles();
18
18
  const [systemRole, updateAgentConfig] = useSessionStore((s) => [
19
19
  agentSelectors.currentAgentSystemRole(s),
@@ -35,7 +35,16 @@ const SystemRole = memo(() => {
35
35
  }
36
36
  title={t('settingAgent.prompt.title', { ns: 'setting' })}
37
37
  />
38
- <Flexbox className={styles.promptBox} height={200}>
38
+ <Flexbox
39
+ className={styles.promptBox}
40
+ height={200}
41
+ onDoubleClick={(e) => {
42
+ if (e.altKey) {
43
+ setOpenModal(true);
44
+ setEditing(true);
45
+ }
46
+ }}
47
+ >
39
48
  {!init ? (
40
49
  <Skeleton
41
50
  active
@@ -47,9 +56,11 @@ const SystemRole = memo(() => {
47
56
  <>
48
57
  <EditableMessage
49
58
  classNames={{ markdown: styles.prompt }}
59
+ editing={editing}
50
60
  onChange={(e) => {
51
61
  updateAgentConfig({ systemRole: e });
52
62
  }}
63
+ onEditingChange={setEditing}
53
64
  onOpenChange={setOpenModal}
54
65
  openModal={openModal}
55
66
  placeholder={`${t('settingAgent.prompt.placeholder', { ns: 'setting' })}...`}
@@ -83,7 +83,16 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav }) => {
83
83
  );
84
84
 
85
85
  return (
86
- <Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
86
+ <Flexbox
87
+ align={'center'}
88
+ gap={8}
89
+ horizontal
90
+ justify={'space-between'}
91
+ onDoubleClick={(e) => {
92
+ if (!id) return;
93
+ if (e.altKey) toggleEditing(true);
94
+ }}
95
+ >
87
96
  <ActionIcon
88
97
  color={fav ? theme.colorWarning : undefined}
89
98
  fill={fav ? theme.colorWarning : 'transparent'}
@@ -8,7 +8,6 @@ import PageTitle from '@/components/PageTitle';
8
8
  import AgentSetting from '@/features/AgentSetting';
9
9
  import { useSessionStore } from '@/store/session';
10
10
  import { agentSelectors } from '@/store/session/selectors';
11
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
12
11
 
13
12
  const EditPage = memo(() => {
14
13
  const { t } = useTranslation('setting');
@@ -20,11 +19,9 @@ const EditPage = memo(() => {
20
19
  agentSelectors.currentAgentTitle(s),
21
20
  ]);
22
21
 
23
- const pageTitle = genSiteHeadTitle(t('header.sessionWithName', { name: title }));
24
-
25
22
  return (
26
23
  <>
27
- <PageTitle title={pageTitle} />
24
+ <PageTitle title={t('header.sessionWithName', { name: title })} />
28
25
  <AgentSetting
29
26
  config={config}
30
27
  meta={meta}
@@ -5,13 +5,11 @@ import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import PageTitle from '@/components/PageTitle';
7
7
  import { useMarketStore } from '@/store/market';
8
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
9
8
 
10
9
  import AgentSearchBar from './features/AgentSearchBar';
11
10
 
12
11
  const Market = memo(() => {
13
12
  const { t } = useTranslation('common');
14
- const pageTitle = genSiteHeadTitle(t('tab.market'));
15
13
 
16
14
  useEffect(() => {
17
15
  // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
@@ -20,7 +18,7 @@ const Market = memo(() => {
20
18
 
21
19
  return (
22
20
  <>
23
- <PageTitle title={pageTitle} />
21
+ <PageTitle title={t('tab.market')} />
24
22
  <AgentSearchBar />
25
23
  </>
26
24
  );
@@ -1,10 +1,8 @@
1
1
  import { Metadata } from 'next';
2
2
 
3
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
4
-
5
3
  import pkg from '../../package.json';
6
4
 
7
- const title = genSiteHeadTitle();
5
+ const title = 'LobeChat';
8
6
  const { description, homepage } = pkg;
9
7
  const metadata: Metadata = {
10
8
  appleWebApp: {
@@ -6,17 +6,15 @@ import { useTranslation } from 'react-i18next';
6
6
  import PageTitle from '@/components/PageTitle';
7
7
  import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
8
8
  import { SettingsTabs } from '@/store/global/initialState';
9
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
10
9
 
11
10
  import Agent from './Agent';
12
11
 
13
12
  export default memo(() => {
14
13
  useSwitchSideBarOnInit(SettingsTabs.Agent);
15
14
  const { t } = useTranslation('setting');
16
- const pageTitle = genSiteHeadTitle(t('tab.agent'));
17
15
  return (
18
16
  <>
19
- <PageTitle title={pageTitle} />
17
+ <PageTitle title={t('tab.agent')} />
20
18
  <Agent />
21
19
  </>
22
20
  );
@@ -7,7 +7,6 @@ import PageTitle from '@/components/PageTitle';
7
7
  import { CURRENT_VERSION } from '@/const/version';
8
8
  import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
9
9
  import { SettingsTabs } from '@/store/global/initialState';
10
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
11
10
 
12
11
  import Footer from '../features/Footer';
13
12
  import Common from './Common';
@@ -15,11 +14,10 @@ import Common from './Common';
15
14
  export default memo(() => {
16
15
  useSwitchSideBarOnInit(SettingsTabs.Common);
17
16
  const { t } = useTranslation('setting');
18
- const pageTitle = genSiteHeadTitle(t('tab.common'));
19
17
 
20
18
  return (
21
19
  <>
22
- <PageTitle title={pageTitle} />
20
+ <PageTitle title={t('tab.common')} />
23
21
  <Common />
24
22
  <Footer>LobeChat v{CURRENT_VERSION}</Footer>
25
23
  </>
@@ -8,17 +8,15 @@ import Footer from '@/app/settings/features/Footer';
8
8
  import PageTitle from '@/components/PageTitle';
9
9
  import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
10
10
  import { SettingsTabs } from '@/store/global/initialState';
11
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
12
11
 
13
12
  import LLM from './LLM';
14
13
 
15
14
  export default memo(() => {
16
15
  useSwitchSideBarOnInit(SettingsTabs.LLM);
17
16
  const { t } = useTranslation('setting');
18
- const pageTitle = genSiteHeadTitle(t('tab.llm'));
19
17
  return (
20
18
  <>
21
- <PageTitle title={pageTitle} />
19
+ <PageTitle title={t('tab.llm')} />
22
20
  <LLM />
23
21
  <Footer>
24
22
  <Trans i18nKey="llm.waitingForMore" ns={'setting'}>
@@ -0,0 +1,57 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { Fragment, memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ const useStyles = createStyles(
6
+ ({ css, token }) => css`
7
+ font-size: 12px;
8
+
9
+ span {
10
+ font-weight: 600;
11
+ opacity: 0.5;
12
+ }
13
+
14
+ kbd {
15
+ padding: 3px 5px;
16
+
17
+ line-height: 1;
18
+ color: ${token.colorTextSecondary};
19
+
20
+ background: ${token.colorBgContainer};
21
+ border: 1px solid ${token.colorBorderSecondary};
22
+ border-bottom-color: ${token.colorBorder};
23
+ border-radius: ${token.borderRadius}px;
24
+ box-shadow: inset 0 -1px 0 ${token.colorBorder};
25
+ }
26
+ `,
27
+ );
28
+
29
+ export interface HotKeysProps {
30
+ desc?: string;
31
+ keys: string;
32
+ }
33
+
34
+ const HotKeys = memo<HotKeysProps>(({ keys, desc }) => {
35
+ const { styles } = useStyles();
36
+ const keysGroup = keys.split('+').filter(Boolean);
37
+ const content = (
38
+ <Flexbox align={'center'} className={styles} gap={2} horizontal>
39
+ {keysGroup.map((key, index) => (
40
+ <Fragment key={index}>
41
+ <kbd>{key.toUpperCase()}</kbd>
42
+ {index + 1 < keysGroup.length && <span>+</span>}
43
+ </Fragment>
44
+ ))}
45
+ </Flexbox>
46
+ );
47
+
48
+ if (!desc) return content;
49
+ return (
50
+ <Flexbox align={'center'} gap={8} horizontal>
51
+ {content}
52
+ {desc}
53
+ </Flexbox>
54
+ );
55
+ });
56
+
57
+ export default HotKeys;
@@ -2,7 +2,7 @@ import { memo, useEffect } from 'react';
2
2
 
3
3
  const PageTitle = memo<{ title: string }>(({ title }) => {
4
4
  useEffect(() => {
5
- document.title = title;
5
+ document.title = title ? `${title} · LobeChat` : 'LobeChat';
6
6
  }, [title]);
7
7
 
8
8
  return null;
@@ -0,0 +1,4 @@
1
+ export const PREFIX_KEY = 'alt';
2
+ export const SAVE_TOPIC_KEY = 'n';
3
+ export const CLEAN_MESSAGE_KEY = 'backspace';
4
+ export const REGENERATE_KEY = 'r';
@@ -1 +0,0 @@
1
- export const genSiteHeadTitle = (title?: string) => (title ? `${title} · LobeChat` : 'LobeChat');