@lobehub/chat 0.44.0 → 0.44.2

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,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.44.2](https://github.com/lobehub/lobe-chat/compare/v0.44.1...v0.44.2)
6
+
7
+ <sup>Released on **2023-08-13**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: 修正重新生成时切分历史消息的逻辑.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: 修正重新生成时切分历史消息的逻辑, closes [#50](https://github.com/lobehub/lobe-chat/issues/50) ([de5141f](https://github.com/lobehub/lobe-chat/commit/de5141f))
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
+
30
+ ### [Version 0.44.1](https://github.com/lobehub/lobe-chat/compare/v0.44.0...v0.44.1)
31
+
32
+ <sup>Released on **2023-08-12**</sup>
33
+
34
+ <br/>
35
+
36
+ <details>
37
+ <summary><kbd>Improvements and Fixes</kbd></summary>
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ## [Version 0.44.0](https://github.com/lobehub/lobe-chat/compare/v0.43.0...v0.44.0)
6
48
 
7
49
  <sup>Released on **2023-08-12**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.44.0",
3
+ "version": "0.44.2",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -3,7 +3,7 @@ import { App, ConfigProvider } from 'antd';
3
3
  import { useThemeMode } from 'antd-style';
4
4
  import 'antd/dist/reset.css';
5
5
  import Zh_CN from 'antd/locale/zh_CN';
6
- import { PropsWithChildren, useCallback, useEffect } from 'react';
6
+ import { PropsWithChildren, memo, useCallback, useEffect } from 'react';
7
7
 
8
8
  import { useGlobalStore } from '@/store/global';
9
9
  import { useSessionStore } from '@/store/session';
@@ -12,7 +12,7 @@ import { GlobalStyle } from '@/styles';
12
12
  import i18n from '../locales';
13
13
  import { useStyles } from './style';
14
14
 
15
- const Layout = ({ children }: PropsWithChildren) => {
15
+ const Layout = memo<PropsWithChildren>(({ children }) => {
16
16
  const { styles } = useStyles();
17
17
 
18
18
  useEffect(() => {
@@ -25,9 +25,9 @@ const Layout = ({ children }: PropsWithChildren) => {
25
25
  <App className={styles.bg}>{children}</App>
26
26
  </ConfigProvider>
27
27
  );
28
- };
28
+ });
29
29
 
30
- export default ({ children }: PropsWithChildren) => {
30
+ export default memo(({ children }: PropsWithChildren) => {
31
31
  const themeMode = useGlobalStore((s) => s.settings.themeMode);
32
32
  const [primaryColor, neutralColor] = useGlobalStore((s) => [
33
33
  s.settings.primaryColor,
@@ -54,4 +54,4 @@ export default ({ children }: PropsWithChildren) => {
54
54
  <Layout>{children}</Layout>
55
55
  </ThemeProvider>
56
56
  );
57
- };
57
+ });
@@ -1,4 +1,5 @@
1
1
  import { Avatar, List } from '@lobehub/ui';
2
+ import Link from 'next/link';
2
3
  import { memo } from 'react';
3
4
  import { useTranslation } from 'react-i18next';
4
5
 
@@ -8,15 +9,16 @@ import { useSessionStore } from '@/store/session';
8
9
 
9
10
  const Inbox = memo(() => {
10
11
  const { t } = useTranslation('common');
11
- const [activeId, switchInbox] = useSessionStore((s) => [s.activeId, s.switchInbox]);
12
+ const [activeId] = useSessionStore((s) => [s.activeId]);
12
13
 
13
14
  return (
14
- <List.Item
15
- active={activeId === INBOX_SESSION_ID}
16
- avatar={<Avatar avatar={DEFAULT_INBOX_AVATAR} size={46} style={{ padding: 3 }} />}
17
- onClick={switchInbox}
18
- title={t('inbox.title')}
19
- />
15
+ <Link href={'/chat'}>
16
+ <List.Item
17
+ active={activeId === INBOX_SESSION_ID}
18
+ avatar={<Avatar avatar={DEFAULT_INBOX_AVATAR} size={46} style={{ padding: 3 }} />}
19
+ title={t('inbox.title')}
20
+ />
21
+ </Link>
20
22
  );
21
23
  });
22
24
 
@@ -0,0 +1,112 @@
1
+ import { ActionIcon, Icon } from '@lobehub/ui';
2
+ import { App, Dropdown, type MenuProps } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import { FolderOutput, MoreVertical, Pin, PinOff, Trash } from 'lucide-react';
5
+ import { memo, useMemo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
9
+ import { sessionSelectors, useSessionStore } from '@/store/session';
10
+
11
+ const useStyles = createStyles(({ css }) => ({
12
+ modalRoot: css`
13
+ z-index: 2000;
14
+ `,
15
+ }));
16
+
17
+ interface ActionProps {
18
+ id: string;
19
+ setOpen: (open: boolean) => void;
20
+ }
21
+
22
+ const Actions = memo<ActionProps>(({ id, setOpen }) => {
23
+ const { t } = useTranslation('common');
24
+
25
+ const { styles } = useStyles();
26
+
27
+ const [pin, removeSession, pinSession] = useSessionStore((s) => {
28
+ const session = sessionSelectors.getSessionById(id)(s);
29
+ return [session.pinned, s.removeSession, s.pinSession];
30
+ });
31
+
32
+ const { modal } = App.useApp();
33
+
34
+ const items: MenuProps['items'] = useMemo(
35
+ () => [
36
+ {
37
+ icon: <Icon icon={pin ? PinOff : Pin} />,
38
+ key: 'pin',
39
+ label: t(pin ? 'pinOff' : 'pin'),
40
+ onClick: () => {
41
+ pinSession(id, !pin);
42
+ },
43
+ },
44
+ {
45
+ children: [
46
+ {
47
+ key: 'agent',
48
+ label: <div>{t('exportType.agent')}</div>,
49
+ onClick: () => {
50
+ exportSingleAgent(id);
51
+ },
52
+ },
53
+ {
54
+ key: 'agentWithMessage',
55
+ label: <div>{t('exportType.agentWithMessage')}</div>,
56
+ onClick: () => {
57
+ exportSingleSession(id);
58
+ },
59
+ },
60
+ ],
61
+ icon: <Icon icon={FolderOutput} />,
62
+ key: 'export',
63
+ label: t('export'),
64
+ },
65
+ {
66
+ danger: true,
67
+ icon: <Icon icon={Trash} />,
68
+ key: 'delete',
69
+ label: t('delete'),
70
+ onClick: ({ domEvent }) => {
71
+ domEvent.stopPropagation();
72
+
73
+ modal.confirm({
74
+ centered: true,
75
+ okButtonProps: { danger: true },
76
+ okText: t('ok'),
77
+ onOk: () => {
78
+ removeSession(id);
79
+ },
80
+ rootClassName: styles.modalRoot,
81
+ title: t('confirmRemoveSessionItemAlert'),
82
+ });
83
+ },
84
+ },
85
+ ],
86
+ [id, pin],
87
+ );
88
+
89
+ return (
90
+ <Dropdown
91
+ arrow={false}
92
+ menu={{
93
+ items,
94
+ onClick: ({ domEvent }) => {
95
+ domEvent.stopPropagation();
96
+ },
97
+ }}
98
+ onOpenChange={setOpen}
99
+ trigger={['click']}
100
+ >
101
+ <ActionIcon
102
+ icon={MoreVertical}
103
+ size={{
104
+ blockSize: 28,
105
+ fontSize: 16,
106
+ }}
107
+ />
108
+ </Dropdown>
109
+ );
110
+ });
111
+
112
+ export default Actions;
@@ -0,0 +1,115 @@
1
+ import { Avatar, List } from '@lobehub/ui';
2
+ import { useHover } from 'ahooks';
3
+ import { Tag } from 'antd';
4
+ import { createStyles } from 'antd-style';
5
+ import { memo, useMemo, useRef, useState } from 'react';
6
+ import { Flexbox } from 'react-layout-kit';
7
+ import { shallow } from 'zustand/shallow';
8
+
9
+ import { useGlobalStore } from '@/store/global';
10
+ import { agentSelectors, sessionSelectors, useSessionStore } from '@/store/session';
11
+
12
+ import Actions from './Actions';
13
+
14
+ const { Item } = List;
15
+
16
+ const useStyles = createStyles(({ css }) => {
17
+ return {
18
+ container: css`
19
+ position: relative;
20
+ `,
21
+
22
+ modalRoot: css`
23
+ z-index: 2000;
24
+ `,
25
+ };
26
+ });
27
+
28
+ interface SessionItemProps {
29
+ id: string;
30
+ }
31
+
32
+ const SessionItem = memo<SessionItemProps>(({ id }) => {
33
+ const [open, setOpen] = useState(false);
34
+ const ref = useRef(null);
35
+ const isHovering = useHover(ref);
36
+
37
+ const { styles } = useStyles();
38
+ const [defaultModel] = useGlobalStore((s) => [s.settings.defaultAgent.config?.model]);
39
+
40
+ const [
41
+ active,
42
+ loading,
43
+ pin,
44
+ title,
45
+ description,
46
+ systemRole,
47
+ avatar,
48
+ avatarBackground,
49
+ updateAt,
50
+ model,
51
+ ] = useSessionStore((s) => {
52
+ const session = sessionSelectors.getSessionById(id)(s);
53
+ const meta = session.meta;
54
+ const systemRole = session.config.systemRole;
55
+
56
+ return [
57
+ s.activeId === id,
58
+ s.autocompleteLoading.title && id === s.activeId,
59
+ session.pinned,
60
+ agentSelectors.getTitle(meta),
61
+ agentSelectors.getDescription(meta),
62
+ systemRole,
63
+ agentSelectors.getAvatar(meta),
64
+ meta.backgroundColor,
65
+ session?.updateAt,
66
+ session.config.model,
67
+ ];
68
+ });
69
+
70
+ const showModel = model !== defaultModel;
71
+
72
+ const actions = useMemo(() => <Actions id={id} setOpen={setOpen} />, [id]);
73
+
74
+ const avatarRender = useMemo(
75
+ () => (
76
+ <Avatar
77
+ animation={isHovering}
78
+ avatar={avatar}
79
+ background={avatarBackground}
80
+ shape="circle"
81
+ size={46}
82
+ />
83
+ ),
84
+ [isHovering, avatar, avatarBackground],
85
+ );
86
+
87
+ const addon = useMemo(
88
+ () =>
89
+ !showModel ? undefined : (
90
+ <Flexbox gap={4} horizontal style={{ flexWrap: 'wrap' }}>
91
+ {showModel && <Tag bordered={false}>{model}</Tag>}
92
+ </Flexbox>
93
+ ),
94
+ [showModel, model],
95
+ );
96
+
97
+ return (
98
+ <Item
99
+ actions={actions}
100
+ active={active}
101
+ addon={addon}
102
+ avatar={avatarRender}
103
+ className={styles.container}
104
+ date={updateAt}
105
+ description={description || systemRole}
106
+ loading={loading}
107
+ pin={pin}
108
+ ref={ref}
109
+ showAction={open || isHovering}
110
+ title={title}
111
+ />
112
+ );
113
+ }, shallow);
114
+
115
+ export default SessionItem;
@@ -25,7 +25,7 @@ const useStyles = createStyles(({ css }) => ({
25
25
 
26
26
  // 从 3~10 随机取一个整数
27
27
 
28
- const SkeletonItem = () => {
28
+ const SkeletonList = () => {
29
29
  const { styles } = useStyles();
30
30
 
31
31
  const list = Array.from({ length: 4 }).fill('');
@@ -44,4 +44,4 @@ const SkeletonItem = () => {
44
44
  </Flexbox>
45
45
  );
46
46
  };
47
- export default SkeletonItem;
47
+ export default SkeletonList;
@@ -5,22 +5,21 @@ import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { sessionSelectors, useSessionHydrated, useSessionStore } from '@/store/session';
7
7
 
8
- import SessionItem from './SessionItem';
9
- import SkeletonItem from './SkeletonItem';
8
+ import SessionItem from './Item';
9
+ import SkeletonList from './SkeletonList';
10
10
 
11
11
  const SessionList = memo(() => {
12
12
  const list = useSessionStore(sessionSelectors.sessionList, isEqual);
13
- const [activeId, loading] = useSessionStore((s) => [s.activeId, s.autocompleteLoading.title]);
14
13
 
15
14
  const isInit = useSessionHydrated();
16
15
 
17
16
  return !isInit ? (
18
- <SkeletonItem />
17
+ <SkeletonList />
19
18
  ) : (
20
19
  <Flexbox>
21
20
  {list.map(({ id }) => (
22
21
  <Link href={`/chat/${id}`} key={id}>
23
- <SessionItem active={activeId === id} id={id} loading={loading && id === activeId} />
22
+ <SessionItem id={id} />
24
23
  </Link>
25
24
  ))}
26
25
  </Flexbox>
@@ -223,22 +223,34 @@ export const chatMessage: StateCreator<
223
223
  const chats = chatSelectors.currentChats(get());
224
224
 
225
225
  const currentIndex = chats.findIndex((c) => c.id === messageId);
226
+ const currentMessage = chats[currentIndex];
227
+ let contextMessages: ChatMessage[] = [];
228
+
229
+ switch (currentMessage.role) {
230
+ case 'function':
231
+ case 'user': {
232
+ contextMessages = chats.slice(0, currentIndex + 1);
233
+ break;
234
+ }
235
+ case 'assistant': {
236
+ // 消息是 AI 发出的因此需要找到它的 user 消息
237
+ const userId = currentMessage.parentId;
238
+ const userIndex = chats.findIndex((c) => c.id === userId);
239
+ // 如果消息没有 parentId,那么同 user/function 模式
240
+ contextMessages = chats.slice(0, userIndex < 0 ? currentIndex + 1 : userIndex + 1);
241
+ break;
242
+ }
243
+ }
226
244
 
227
- const histories = chats
228
- .slice(0, currentIndex + 1)
229
- // 如果点击重新发送的 message 其 role 是 assistant 或者 function,那么需要移除
230
- // 如果点击重新发送的 message 其 role 是 user,则不需要移除
231
- .filter((c) => !(c.role === 'assistant' && c.id === messageId));
232
-
233
- if (histories.length <= 0) return;
245
+ if (contextMessages.length <= 0) return;
234
246
 
235
247
  const { realFetchAIResponse } = get();
236
248
 
237
- const latestMsg = histories.filter((s) => s.role === 'user').at(-1);
249
+ const latestMsg = contextMessages.filter((s) => s.role === 'user').at(-1);
238
250
 
239
251
  if (!latestMsg) return;
240
252
 
241
- await realFetchAIResponse(histories, latestMsg.id);
253
+ await realFetchAIResponse(contextMessages, latestMsg.id);
242
254
  },
243
255
 
244
256
  sendMessage: async (message) => {
@@ -29,6 +29,7 @@ export interface SessionAction {
29
29
  * @param payload - 聊天记录
30
30
  */
31
31
  dispatchSession: (payload: SessionDispatch) => void;
32
+ importInbox: (inbox: LobeAgentSession) => void;
32
33
  /**
33
34
  * 导入会话
34
35
  * @param sessions
@@ -52,7 +53,6 @@ export interface SessionAction {
52
53
  * @returns void
53
54
  */
54
55
  removeSession: (sessionId: string) => void;
55
- switchInbox: () => void;
56
56
 
57
57
  /**
58
58
  * @title 切换会话
@@ -114,7 +114,8 @@ export const createSessionSlice: StateCreator<
114
114
  );
115
115
  }
116
116
  },
117
-
117
+ // TODO:暂时先不实现导入 inbox 的功能
118
+ importInbox: () => {},
118
119
  importSessions: (importSessions) => {
119
120
  const { sessions } = get();
120
121
  set(
@@ -147,13 +148,6 @@ export const createSessionSlice: StateCreator<
147
148
  }
148
149
  },
149
150
 
150
- switchInbox: () => {
151
- if (get().activeId === INBOX_SESSION_ID) return;
152
- get().activeSession(INBOX_SESSION_ID);
153
-
154
- Router.push('/chat');
155
- },
156
-
157
151
  switchSession: async (sessionId) => {
158
152
  if (get().activeId === sessionId) return;
159
153
 
@@ -1,189 +0,0 @@
1
- import { ActionIcon, Avatar, Icon, List } from '@lobehub/ui';
2
- import { useHover } from 'ahooks';
3
- import { App, Dropdown, type MenuProps, Tag } from 'antd';
4
- import { createStyles } from 'antd-style';
5
- import { FolderOutput, MoreVertical, Pin, PinOff, Trash } from 'lucide-react';
6
- import { memo, useMemo, useRef, useState } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
- import { Flexbox } from 'react-layout-kit';
9
- import { shallow } from 'zustand/shallow';
10
-
11
- import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
12
- import { useGlobalStore } from '@/store/global';
13
- import { agentSelectors, chatSelectors, sessionSelectors, useSessionStore } from '@/store/session';
14
-
15
- const { Item } = List;
16
-
17
- const useStyles = createStyles(({ css }) => {
18
- return {
19
- container: css`
20
- position: relative;
21
- `,
22
-
23
- modalRoot: css`
24
- z-index: 2000;
25
- `,
26
- };
27
- });
28
-
29
- interface SessionItemProps {
30
- active?: boolean;
31
- id: string;
32
- loading?: boolean;
33
- }
34
-
35
- const SessionItem = memo<SessionItemProps>(({ id, active = true, loading }) => {
36
- const [open, setOpen] = useState(false);
37
- const ref = useRef(null);
38
- const isHovering = useHover(ref);
39
-
40
- const { t } = useTranslation('common');
41
-
42
- const { styles } = useStyles();
43
- const [defaultModel] = useGlobalStore((s) => [s.settings.defaultAgent.config?.model]);
44
-
45
- const [
46
- pin,
47
- title,
48
- description,
49
- systemRole,
50
- avatar,
51
- avatarBackground,
52
- updateAt,
53
- model,
54
- chatLength,
55
- removeSession,
56
- pinSession,
57
- ] = useSessionStore((s) => {
58
- const session = sessionSelectors.getSessionById(id)(s);
59
- const meta = session.meta;
60
- const systemRole = session.config.systemRole;
61
- return [
62
- session.pinned,
63
- agentSelectors.getTitle(meta),
64
- agentSelectors.getDescription(meta),
65
- systemRole,
66
- agentSelectors.getAvatar(meta),
67
- meta.backgroundColor,
68
- session?.updateAt,
69
- session.config.model,
70
- chatSelectors.getChatsById(id)(s).length,
71
- s.removeSession,
72
- s.pinSession,
73
- ];
74
- });
75
-
76
- const { modal } = App.useApp();
77
- const items: MenuProps['items'] = useMemo(
78
- () => [
79
- {
80
- icon: <Icon icon={pin ? PinOff : Pin} />,
81
- key: 'pin',
82
- label: t(pin ? 'pinOff' : 'pin'),
83
- onClick: () => {
84
- pinSession(id, !pin);
85
- },
86
- },
87
- {
88
- children: [
89
- {
90
- key: 'agent',
91
- label: <div>{t('exportType.agent')}</div>,
92
- onClick: () => {
93
- exportSingleAgent(id);
94
- },
95
- },
96
- {
97
- key: 'agentWithMessage',
98
- label: <div>{t('exportType.agentWithMessage')}</div>,
99
- onClick: () => {
100
- exportSingleSession(id);
101
- },
102
- },
103
- ],
104
- icon: <Icon icon={FolderOutput} />,
105
- key: 'export',
106
- label: t('export'),
107
- },
108
- {
109
- danger: true,
110
- icon: <Icon icon={Trash} />,
111
- key: 'delete',
112
- label: t('delete'),
113
- onClick: ({ domEvent }) => {
114
- domEvent.stopPropagation();
115
-
116
- modal.confirm({
117
- centered: true,
118
- okButtonProps: { danger: true },
119
- okText: t('ok'),
120
- onOk: () => {
121
- removeSession(id);
122
- },
123
- rootClassName: styles.modalRoot,
124
- title: t('confirmRemoveSessionItemAlert'),
125
- });
126
- },
127
- },
128
- ],
129
- [id, pin],
130
- );
131
-
132
- const showModel = model !== defaultModel;
133
- const showChatLength = chatLength > 0;
134
-
135
- return (
136
- <Item
137
- actions={
138
- <Dropdown
139
- arrow={false}
140
- menu={{
141
- items,
142
- onClick: ({ domEvent }) => {
143
- domEvent.stopPropagation();
144
- },
145
- }}
146
- onOpenChange={setOpen}
147
- open={open}
148
- trigger={['click']}
149
- >
150
- <ActionIcon
151
- icon={MoreVertical}
152
- size={{
153
- blockSize: 28,
154
- fontSize: 16,
155
- }}
156
- />
157
- </Dropdown>
158
- }
159
- active={active}
160
- addon={
161
- !(showModel || showChatLength) ? undefined : (
162
- <Flexbox gap={4} horizontal style={{ flexWrap: 'wrap' }}>
163
- {showModel && <Tag bordered={false}>{model}</Tag>}
164
- </Flexbox>
165
- )
166
- }
167
- avatar={
168
- <Avatar
169
- animation={isHovering}
170
- avatar={avatar}
171
- background={avatarBackground}
172
- shape="circle"
173
- size={46}
174
- title={title}
175
- />
176
- }
177
- className={styles.container}
178
- date={updateAt}
179
- description={description || systemRole}
180
- loading={loading}
181
- pin={pin}
182
- ref={ref}
183
- showAction={open || isHovering}
184
- title={title}
185
- />
186
- );
187
- }, shallow);
188
-
189
- export default SessionItem;