@lobehub/chat 0.35.1 → 0.36.1

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.
@@ -24,6 +24,9 @@ jobs:
24
24
  - name: Install deps
25
25
  run: pnpm install
26
26
 
27
+ - name: Lint
28
+ run: pnpm run type-check
29
+
27
30
  - name: Test
28
31
  run: pnpm run test
29
32
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,72 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.36.1](https://github.com/lobehub/lobe-chat/compare/v0.36.0...v0.36.1)
6
+
7
+ <sup>Released on **2023-08-03**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor zustand usage with v4.4.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor zustand usage with v4.4, closes [#52](https://github.com/lobehub/lobe-chat/issues/52) ([4c65aa7](https://github.com/lobehub/lobe-chat/commit/4c65aa7))
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.36.0](https://github.com/lobehub/lobe-chat/compare/v0.35.1...v0.36.0)
31
+
32
+ <sup>Released on **2023-08-03**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **misc**: 实现自定义历史消息数功能.
37
+
38
+ #### 🐛 Bug Fixes
39
+
40
+ - **misc**: Fix setting type.
41
+
42
+ #### 💄 Styles
43
+
44
+ - **misc**: Fix session item height.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### What's improved
52
+
53
+ - **misc**: 实现自定义历史消息数功能 ([7baa022](https://github.com/lobehub/lobe-chat/commit/7baa022))
54
+
55
+ #### What's fixed
56
+
57
+ - **misc**: Fix setting type ([57e415e](https://github.com/lobehub/lobe-chat/commit/57e415e))
58
+
59
+ #### Styles
60
+
61
+ - **misc**: Fix session item height ([6cd1de5](https://github.com/lobehub/lobe-chat/commit/6cd1de5))
62
+
63
+ </details>
64
+
65
+ <div align="right">
66
+
67
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
68
+
69
+ </div>
70
+
5
71
  ### [Version 0.35.1](https://github.com/lobehub/lobe-chat/compare/v0.35.0...v0.35.1)
6
72
 
7
73
  <sup>Released on **2023-07-31**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.35.1",
3
+ "version": "0.36.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -93,7 +93,7 @@
93
93
  "swr": "^2",
94
94
  "ts-md5": "^1",
95
95
  "uuid": "^9",
96
- "zustand": "^4",
96
+ "zustand": "^4.4",
97
97
  "zustand-utils": "^1"
98
98
  },
99
99
  "devDependencies": {
@@ -21,6 +21,7 @@ const SliderWithInput = memo<SliderWithInputProps>(
21
21
  controls,
22
22
  style,
23
23
  className,
24
+ disabled,
24
25
  ...props
25
26
  }) => {
26
27
  const handleOnchange = useCallback((value: number | null) => {
@@ -38,6 +39,7 @@ const SliderWithInput = memo<SliderWithInputProps>(
38
39
  >
39
40
  <Slider
40
41
  defaultValue={defaultValue}
42
+ disabled={disabled}
41
43
  max={max}
42
44
  min={min}
43
45
  onChange={handleOnchange}
@@ -50,6 +52,7 @@ const SliderWithInput = memo<SliderWithInputProps>(
50
52
  <InputNumber
51
53
  controls={size !== 'small' || controls}
52
54
  defaultValue={defaultValue}
55
+ disabled={disabled}
53
56
  max={max}
54
57
  min={min}
55
58
  onChange={handleOnchange}
@@ -11,10 +11,11 @@ import { FORM_STYLE } from '@/const/layoutTokens';
11
11
  import { AgentAction } from '@/store/session/slices/agentConfig';
12
12
  import { LanguageModel } from '@/types/llm';
13
13
  import type { LobeAgentConfig } from '@/types/session';
14
+ import { LobeAgentConfigKeys } from '@/types/session';
14
15
 
15
16
  type SettingItemGroup = ItemGroup & {
16
17
  children: {
17
- name?: keyof LobeAgentConfig | string[];
18
+ name?: LobeAgentConfigKeys | string | string[];
18
19
  }[];
19
20
  };
20
21
 
@@ -61,7 +61,10 @@ export default {
61
61
  title: '是否开启历史消息长度压缩阈值',
62
62
  },
63
63
  enableHistoryCount: {
64
- title: '是否开启携带的历史消息数限制',
64
+ alias: '不限制',
65
+ limited: '只包含 {{number}} 条会话消息',
66
+ title: '限制历史消息数',
67
+ unlimited: '不限历史消息数',
65
68
  },
66
69
  historyCount: {
67
70
  desc: '每次请求携带的历史消息数',
@@ -56,7 +56,7 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
56
56
  meta.backgroundColor,
57
57
  session?.updateAt,
58
58
  session.config.model,
59
- chatSelectors.currentChats(s).length,
59
+ chatSelectors.getChatsById(id)(s).length,
60
60
  s.removeSession,
61
61
  s.pinSession,
62
62
  ];
@@ -177,7 +177,6 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
177
177
  pin={pin}
178
178
  ref={ref}
179
179
  showAction={open || isHovering}
180
- style={{ color: theme.colorText }}
181
180
  title={title}
182
181
  />
183
182
  );
@@ -21,7 +21,6 @@ export const useStyles = createStyles(({ css, token }) => {
21
21
  `,
22
22
  container: css`
23
23
  position: relative;
24
- height: 72px;
25
24
  `,
26
25
 
27
26
  modalRoot: css`
@@ -1,8 +1,9 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
- import { Dropdown, Popconfirm, Popover } from 'antd';
3
- import { BrainCog, Eraser, Thermometer } from 'lucide-react';
2
+ import { Dropdown, Popover, Switch } from 'antd';
3
+ import { BrainCog, Thermometer, Timer, TimerOff } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
6
7
  import { shallow } from 'zustand/shallow';
7
8
 
8
9
  import SliderWithInput from '@/components/SliderWithInput';
@@ -11,16 +12,15 @@ import { LanguageModel } from '@/types/llm';
11
12
 
12
13
  const InputActions = memo(() => {
13
14
  const { t } = useTranslation('setting');
14
- const [clearMessage, updateAgentConfig] = useSessionStore(
15
- (s) => [s.clearMessage, s.updateAgentConfig],
16
- shallow,
17
- );
18
- const [model, temperature] = useSessionStore((s) => {
15
+
16
+ const [model, temperature, historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
19
17
  const config = agentSelectors.currentAgentConfig(s);
20
18
  return [
21
19
  config.model,
22
20
  config.params.temperature,
23
- // config.historyCount
21
+ config.historyCount,
22
+ !config.enableHistoryCount,
23
+ s.updateAgentConfig,
24
24
  ];
25
25
  }, shallow);
26
26
 
@@ -54,6 +54,7 @@ const InputActions = memo(() => {
54
54
  value={temperature}
55
55
  />
56
56
  }
57
+ placement={'top'}
57
58
  trigger={'click'}
58
59
  >
59
60
  <ActionIcon
@@ -62,36 +63,47 @@ const InputActions = memo(() => {
62
63
  title={t('settingModel.temperature.titleWithValue', { value: temperature })}
63
64
  />
64
65
  </Popover>
65
- {/*TODO 历史记录功能实现 */}
66
- {/*<Popover*/}
67
- {/* content={*/}
68
- {/* <SliderWithInput*/}
69
- {/* min={0}*/}
70
- {/* onChange={(v) => {*/}
71
- {/* updateAgentConfig({ historyCount: v });*/}
72
- {/* }}*/}
73
- {/* step={1}*/}
74
- {/* style={{ width: 120 }}*/}
75
- {/* value={historyCount}*/}
76
- {/* />*/}
77
- {/* }*/}
78
- {/* trigger={'click'}*/}
79
- {/*>*/}
80
- {/* <ActionIcon icon={Timer} title={t('settingChat.historyCount.title')} />*/}
81
- {/*</Popover>*/}
82
- <Popconfirm
83
- cancelText={t('cancel', { ns: 'common' })}
84
- okButtonProps={{ danger: true }}
85
- okText={t('ok', { ns: 'common' })}
86
- onConfirm={() => clearMessage()}
87
- title={t('confirmClearCurrentMessages', { ns: 'common' })}
66
+ <Popover
67
+ arrow={false}
68
+ content={
69
+ <Flexbox align={'center'} gap={16} horizontal>
70
+ <SliderWithInput
71
+ disabled={unlimited}
72
+ max={30}
73
+ min={1}
74
+ onChange={(v) => {
75
+ updateAgentConfig({ historyCount: v });
76
+ }}
77
+ step={1}
78
+ style={{ width: 160 }}
79
+ value={historyCount}
80
+ />
81
+ <Flexbox align={'center'} gap={4} horizontal>
82
+ <Switch
83
+ checked={unlimited}
84
+ onChange={(checked) => {
85
+ updateAgentConfig({ enableHistoryCount: !checked });
86
+ }}
87
+ size={'small'}
88
+ />
89
+ {t('settingChat.enableHistoryCount.alias')}
90
+ </Flexbox>
91
+ </Flexbox>
92
+ }
93
+ placement={'top'}
94
+ trigger={'click'}
88
95
  >
89
96
  <ActionIcon
90
- icon={Eraser}
97
+ icon={unlimited ? TimerOff : Timer}
91
98
  placement={'bottom'}
92
- title={t('clearCurrentMessages', { ns: 'common' })}
99
+ title={t(
100
+ unlimited
101
+ ? 'settingChat.enableHistoryCount.unlimited'
102
+ : 'settingChat.enableHistoryCount.limited',
103
+ { number: historyCount || 0 },
104
+ )}
93
105
  />
94
- </Popconfirm>
106
+ </Popover>
95
107
  </>
96
108
  );
97
109
  });
@@ -0,0 +1,31 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { Popconfirm } from 'antd';
3
+ import { Eraser } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { shallow } from 'zustand/shallow';
7
+
8
+ import { useSessionStore } from '@/store/session';
9
+
10
+ const ActionsRight = memo(() => {
11
+ const { t } = useTranslation('setting');
12
+ const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig], shallow);
13
+
14
+ return (
15
+ <Popconfirm
16
+ cancelText={t('cancel', { ns: 'common' })}
17
+ okButtonProps={{ danger: true }}
18
+ okText={t('ok', { ns: 'common' })}
19
+ onConfirm={() => clearMessage()}
20
+ title={t('confirmClearCurrentMessages', { ns: 'common' })}
21
+ >
22
+ <ActionIcon
23
+ icon={Eraser}
24
+ placement={'bottom'}
25
+ title={t('clearCurrentMessages', { ns: 'common' })}
26
+ />
27
+ </Popconfirm>
28
+ );
29
+ });
30
+
31
+ export default ActionsRight;
@@ -10,6 +10,7 @@ import { useSessionStore } from '@/store/session';
10
10
  import { useSettings } from '@/store/settings';
11
11
 
12
12
  import InputActions from './Action';
13
+ import ActionsRight from './ActionRight';
13
14
  import Token from './Token';
14
15
 
15
16
  const ChatInput = () => {
@@ -52,6 +53,7 @@ const ChatInput = () => {
52
53
  <Token input={text} />
53
54
  </>
54
55
  }
56
+ actionsRight={<ActionsRight />}
55
57
  expand={expand}
56
58
  footer={footer}
57
59
  minHeight={CHAT_TEXTAREA_HEIGHT}
@@ -20,7 +20,7 @@ import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../ThemeSwatches';
20
20
 
21
21
  type SettingItemGroup = ItemGroup & {
22
22
  children: {
23
- name?: ConfigKeys;
23
+ name?: ConfigKeys | string;
24
24
  }[];
25
25
  };
26
26
 
@@ -1,5 +1,6 @@
1
- import { create } from 'zustand';
1
+ import equal from 'fast-deep-equal';
2
2
  import { PersistOptions, devtools, persist } from 'zustand/middleware';
3
+ import { createWithEqualityFn } from 'zustand/traditional';
3
4
 
4
5
  import { isDev } from '@/utils/env';
5
6
 
@@ -22,13 +23,14 @@ const persistOptions: PersistOptions<SessionStore, SessionPersist> = {
22
23
  // version: Migration.targetVersion,
23
24
  };
24
25
 
25
- export const useSessionStore = create<SessionStore>()(
26
+ export const useSessionStore = createWithEqualityFn<SessionStore>()(
26
27
  persist(
27
28
  devtools(createStore, {
28
29
  name: LOBE_CHAT + (isDev ? '_DEV' : ''),
29
30
  }),
30
31
  persistOptions,
31
32
  ),
33
+ equal,
32
34
  );
33
35
 
34
36
  export * from './selectors';
@@ -5,6 +5,7 @@ import { LOADING_FLAT } from '@/const/message';
5
5
  import { fetchChatModel } from '@/services/chatModel';
6
6
  import { fetchPlugin } from '@/services/plugin';
7
7
  import { SessionStore, agentSelectors, chatSelectors, sessionSelectors } from '@/store/session';
8
+ import { getSlicedMessagesWithConfig } from '@/store/session/slices/chat/utils';
8
9
  import { ChatMessage, OpenAIFunctionCall } from '@/types/chatMessage';
9
10
  import { fetchSSE } from '@/utils/fetch';
10
11
  import { isFunctionMessage } from '@/utils/message';
@@ -123,12 +124,17 @@ export const chatMessage: StateCreator<
123
124
 
124
125
  const compiler = template(config.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });
125
126
 
126
- // 对 message 做统一预处理
127
+ // ========================== //
128
+ // 对 messages 做统一预处理 //
129
+ // ========================== //
127
130
 
128
- // 1. 替换 inputMessage 模板
131
+ // 1. 按参数设定截断长度
132
+ const slicedMessages = getSlicedMessagesWithConfig(messages, config);
133
+
134
+ // 2. 替换 inputMessage 模板
129
135
  const postMessages = !config.inputTemplate
130
- ? messages
131
- : messages.map((m) => {
136
+ ? slicedMessages
137
+ : slicedMessages.map((m) => {
132
138
  if (m.role === 'user') {
133
139
  try {
134
140
  return { ...m, content: compiler({ text: m.content }) };
@@ -141,12 +147,9 @@ export const chatMessage: StateCreator<
141
147
  return m;
142
148
  });
143
149
 
144
- // 2. TODO 按参数设定截断长度
145
-
146
150
  // 3. 添加 systemRole
147
- const { systemRole } = agentSelectors.currentAgentConfig(get());
148
- if (systemRole) {
149
- postMessages.unshift({ content: systemRole, role: 'system' } as ChatMessage);
151
+ if (config.systemRole) {
152
+ postMessages.unshift({ content: config.systemRole, role: 'system' } as ChatMessage);
150
153
  }
151
154
 
152
155
  const fetcher = () =>
@@ -1,29 +1,40 @@
1
1
  import { DEFAULT_USER_AVATAR } from '@/const/meta';
2
- import { agentSelectors } from '@/store/session';
3
2
  import { useSettings } from '@/store/settings';
4
3
  import { ChatMessage } from '@/types/chatMessage';
5
4
 
6
5
  import type { SessionStore } from '../../../store';
6
+ import { agentSelectors } from '../../agentConfig';
7
7
  import { sessionSelectors } from '../../session';
8
+ import { getSlicedMessagesWithConfig } from '../utils';
8
9
  import { organizeChats } from './utils';
9
10
 
10
- // 展示在聊天框中的消息
11
+ export const getChatsById =
12
+ (id: string) =>
13
+ (s: SessionStore): ChatMessage[] => {
14
+ const session = sessionSelectors.getSessionById(id)(s);
15
+
16
+ if (!session) return [];
17
+
18
+ return organizeChats(
19
+ session,
20
+ {
21
+ assistant: agentSelectors.currentAgentAvatar(s),
22
+ user: useSettings.getState().settings.avatar || DEFAULT_USER_AVATAR,
23
+ },
24
+ s.activeTopicId,
25
+ );
26
+ };
27
+
28
+ // 当前激活的消息列表
11
29
  export const currentChats = (s: SessionStore): ChatMessage[] => {
12
- const session = sessionSelectors.currentSession(s);
13
- if (!session) return [];
14
-
15
- return organizeChats(
16
- session,
17
- {
18
- assistant: agentSelectors.currentAgentAvatar(s),
19
- user: useSettings.getState().settings.avatar || DEFAULT_USER_AVATAR,
20
- },
21
- s.activeTopicId,
22
- );
30
+ if (!s.activeId) return [];
31
+
32
+ return getChatsById(s.activeId)(s);
23
33
  };
24
34
 
25
- export const systemRoleSel = (s: SessionStore): string => {
35
+ export const currentChatsWithHistoryConfig = (s: SessionStore): ChatMessage[] => {
36
+ const chats = currentChats(s);
26
37
  const config = agentSelectors.currentAgentConfig(s);
27
38
 
28
- return config.systemRole;
39
+ return getSlicedMessagesWithConfig(chats, config);
29
40
  };
@@ -1,10 +1,11 @@
1
- import { currentChats } from './chat';
1
+ import { currentChats, getChatsById } from './chat';
2
2
  import { chatsTokenCount, systemRoleTokenCount, totalTokenCount } from './token';
3
3
  import { currentTopics, getTopicMessages } from './topic';
4
4
 
5
5
  export const chatSelectors = {
6
6
  chatsTokenCount,
7
7
  currentChats,
8
+ getChatsById,
8
9
  systemRoleTokenCount,
9
10
  totalTokenCount,
10
11
  };
@@ -1,7 +1,15 @@
1
1
  import { encode } from 'gpt-tokenizer';
2
2
 
3
+ import { agentSelectors } from '@/store/session';
4
+
3
5
  import type { SessionStore } from '../../../store';
4
- import { currentChats, systemRoleSel } from './chat';
6
+ import { currentChatsWithHistoryConfig } from './chat';
7
+
8
+ export const systemRoleSel = (s: SessionStore): string => {
9
+ const config = agentSelectors.currentAgentConfig(s);
10
+
11
+ return config.systemRole;
12
+ };
5
13
 
6
14
  const systemRoleTokens = (s: SessionStore): number[] => {
7
15
  const systemRole = systemRoleSel(s);
@@ -10,7 +18,7 @@ const systemRoleTokens = (s: SessionStore): number[] => {
10
18
  };
11
19
 
12
20
  const chatsTokens = (s: SessionStore): number[] => {
13
- const chats = currentChats(s);
21
+ const chats = currentChatsWithHistoryConfig(s);
14
22
  return encode(chats.map((m) => m.content).join(''));
15
23
  };
16
24
 
@@ -0,0 +1,13 @@
1
+ import { ChatMessage } from '@/types/chatMessage';
2
+ import { LobeAgentConfig } from '@/types/session';
3
+
4
+ export const getSlicedMessagesWithConfig = (
5
+ messages: ChatMessage[],
6
+ config: LobeAgentConfig,
7
+ ): ChatMessage[] => {
8
+ // 如果没有开启历史消息数限制,或者限制为 0,则直接返回
9
+ if (!config.enableHistoryCount || !config.historyCount) return messages;
10
+
11
+ // 如果开启了,则返回尾部的N条消息
12
+ return messages.reverse().slice(0, config.historyCount).reverse();
13
+ };
@@ -1,5 +1,6 @@
1
- import { create } from 'zustand';
1
+ import equal from 'fast-deep-equal';
2
2
  import { type PersistOptions, devtools, persist } from 'zustand/middleware';
3
+ import { createWithEqualityFn } from 'zustand/traditional';
3
4
 
4
5
  import { isDev } from '@/utils/env';
5
6
 
@@ -12,13 +13,14 @@ const persistOptions: PersistOptions<SettingsStore> = {
12
13
  skipHydration: true,
13
14
  };
14
15
 
15
- export const useSettings = create<SettingsStore>()(
16
+ export const useSettings = createWithEqualityFn<SettingsStore>()(
16
17
  persist(
17
18
  devtools(createStore, {
18
19
  name: LOBE_SETTINGS + (isDev ? '_DEV' : ''),
19
20
  }),
20
21
  persistOptions,
21
22
  ),
23
+ equal,
22
24
  );
23
25
 
24
26
  export * from './selectors';
@@ -85,3 +85,7 @@ export interface LobeAgentSession extends LobeSessionBase {
85
85
  }
86
86
 
87
87
  export type LobeSessions = Record<string, LobeAgentSession>;
88
+
89
+ export type LobeAgentConfigKeys =
90
+ | keyof LobeAgentConfig
91
+ | ['params', keyof LobeAgentConfig['params']];