@lobehub/chat 0.12.0 → 0.12.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.12.1](https://github.com/lobehub/lobe-chat/compare/v0.12.0...v0.12.1)
6
+
7
+ <sup>Released on **2023-07-23**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: 修正 message parentId 不正确的问题.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: 修正 message parentId 不正确的问题 ([f86852a](https://github.com/lobehub/lobe-chat/commit/f86852a))
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.12.0](https://github.com/lobehub/lobe-chat/compare/v0.11.0...v0.12.0)
6
31
 
7
32
  <sup>Released on **2023-07-23**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -0,0 +1,24 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { Popconfirm } from 'antd';
3
+ import { Eraser, Languages } 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 InputActions = memo(() => {
11
+ const { t } = useTranslation();
12
+ const [clearMessage] = useSessionStore((s) => [s.clearMessage], shallow);
13
+
14
+ return (
15
+ <>
16
+ <ActionIcon icon={Languages} />
17
+ <Popconfirm onConfirm={() => clearMessage()} title={t('confirmClearCurrentMessages')}>
18
+ <ActionIcon icon={Eraser} title={t('clearCurrentMessages')} />
19
+ </Popconfirm>
20
+ </>
21
+ );
22
+ });
23
+
24
+ export default InputActions;
@@ -0,0 +1,32 @@
1
+ import { TokenTag, Tooltip } from '@lobehub/ui';
2
+ import { encode } from 'gpt-tokenizer';
3
+ import { memo, useMemo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { shallow } from 'zustand/shallow';
6
+
7
+ import { ModelTokens } from '@/const/modelTokens';
8
+ import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
9
+
10
+ const Token = memo<{ input: string }>(({ input }) => {
11
+ const { t } = useTranslation();
12
+
13
+ const inputTokenCount = useMemo(() => encode(input).length, [input]);
14
+
15
+ const [totalToken, systemRoleToken, chatsToken, model] = useSessionStore(
16
+ (s) => [
17
+ chatSelectors.totalTokenCount(s),
18
+ chatSelectors.systemRoleTokenCount(s),
19
+ chatSelectors.chatsTokenCount(s),
20
+ agentSelectors.currentAgentModel(s),
21
+ ],
22
+ shallow,
23
+ );
24
+
25
+ return (
26
+ <Tooltip title={t('tokenDetail', { chatsToken, systemRoleToken })}>
27
+ <TokenTag maxValue={ModelTokens[model]} value={totalToken + inputTokenCount} />
28
+ </Tooltip>
29
+ );
30
+ });
31
+
32
+ export default Token;
@@ -0,0 +1,62 @@
1
+ import { ChatInputArea, DraggablePanel, Icon } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { Archive } from 'lucide-react';
4
+ import { memo, useMemo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { shallow } from 'zustand/shallow';
7
+
8
+ import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
9
+ import { useSessionStore } from '@/store/session';
10
+ import { useSettings } from '@/store/settings';
11
+
12
+ import InputActions from './Action';
13
+ import Token from './Token';
14
+
15
+ const ChatInput = () => {
16
+ const { t } = useTranslation();
17
+ const [expand, setExpand] = useState<boolean>(false);
18
+ const [text, setText] = useState('');
19
+
20
+ const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
21
+ const [sendMessage] = useSessionStore((s) => [s.createOrSendMsg], shallow);
22
+
23
+ const footer = useMemo(
24
+ () => <Button icon={<Icon icon={Archive} title={t('archiveCurrentMessages')} />} />,
25
+ [],
26
+ );
27
+
28
+ return (
29
+ <DraggablePanel
30
+ expandable={false}
31
+ fullscreen={expand}
32
+ headerHeight={HEADER_HEIGHT}
33
+ minHeight={CHAT_TEXTAREA_HEIGHT}
34
+ onSizeChange={(_, size) => {
35
+ if (!size) return;
36
+ useSettings.setState({
37
+ inputHeight: typeof size.height === 'string' ? Number.parseInt(size.height) : size.height,
38
+ });
39
+ }}
40
+ placement="bottom"
41
+ size={{ height: inputHeight, width: '100%' }}
42
+ style={{ zIndex: 10 }}
43
+ >
44
+ <ChatInputArea
45
+ actions={
46
+ <>
47
+ <InputActions />
48
+ <Token input={text} />
49
+ </>
50
+ }
51
+ expand={expand}
52
+ footer={footer}
53
+ minHeight={CHAT_TEXTAREA_HEIGHT}
54
+ onExpandChange={setExpand}
55
+ onInputChange={setText}
56
+ onSend={sendMessage}
57
+ />
58
+ </DraggablePanel>
59
+ );
60
+ };
61
+
62
+ export default memo(ChatInput);
@@ -2,9 +2,9 @@ import { memo } from 'react';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
4
  import HeaderSpacing from '@/components/HeaderSpacing';
5
- import ChatInput from '@/pages/chat/[id]/Conversation/Input';
6
5
 
7
6
  import ChatList from './ChatList';
7
+ import ChatInput from './Input';
8
8
 
9
9
  const Conversation = () => {
10
10
  return (
@@ -50,7 +50,7 @@ export interface ChatAction {
50
50
  * @param messages - 聊天消息数组
51
51
  * @param parentId - 父消息 ID,可选
52
52
  */
53
- realFetchAIResponse: (messages: ChatMessage[], parentId?: string) => Promise<void>;
53
+ realFetchAIResponse: (messages: ChatMessage[], parentId: string) => Promise<void>;
54
54
  /**
55
55
  * 重新发送消息
56
56
  * @param id - 消息 ID
@@ -116,7 +116,7 @@ export const createChatSlice: StateCreator<
116
116
  set({ editingMessageId: messageId });
117
117
  },
118
118
 
119
- realFetchAIResponse: async (messages: ChatMessage[], parentId?: string) => {
119
+ realFetchAIResponse: async (messages, userMessageId) => {
120
120
  const { dispatchMessage, generateMessage } = get();
121
121
 
122
122
  // 添加 systemRole
@@ -128,12 +128,11 @@ export const createChatSlice: StateCreator<
128
128
  // 再添加一个空的信息用于放置 ai 响应,注意顺序不能反
129
129
  // 因为如果顺序反了,messages 中将包含新增的 ai message
130
130
  const assistantId = nanoid();
131
- const userId = parentId ?? nanoid();
132
131
 
133
132
  dispatchMessage({
134
133
  id: assistantId,
135
134
  message: LOADING_FLAT,
136
- parentId: userId,
135
+ parentId: userMessageId,
137
136
  role: 'assistant',
138
137
  type: 'addMessage',
139
138
  });
@@ -209,7 +208,7 @@ export const createChatSlice: StateCreator<
209
208
  // 先拿到当前的 messages
210
209
  const messages = chatSelectors.currentChats(get());
211
210
 
212
- await realFetchAIResponse(messages);
211
+ await realFetchAIResponse(messages, userId);
213
212
 
214
213
  const chats = chatSelectors.currentChats(get());
215
214
  if (chats.length >= 4) {
@@ -5,57 +5,17 @@ import { ChatMessage } from '@/types/chatMessage';
5
5
  import type { SessionStore } from '../../../store';
6
6
  import { DEFAULT_AVATAR } from '../../agentConfig';
7
7
  import { sessionSelectors } from '../../session';
8
+ import { organizeChats } from './utils';
8
9
 
9
10
  // 展示在聊天框中的消息
10
11
  export const currentChats = (s: SessionStore): ChatMessage[] => {
11
12
  const session = sessionSelectors.currentSession(s);
12
13
  if (!session) return [];
13
14
 
14
- const basic = Object.values<ChatMessage>(session.chats)
15
- // 首先按照时间顺序排序,越早的在越前面
16
- .sort((pre, next) => pre.createAt - next.createAt)
17
- // 过滤掉已归档的消息,归档消息不应该出现在聊天框中
18
- .filter((m) => !m.archive)
19
- // 映射头像关系
20
- .map((m) => {
21
- return {
22
- ...m,
23
- meta:
24
- m.role === 'assistant'
25
- ? {
26
- avatar: agentSelectors.currentAgentAvatar(s),
27
- title: session.meta.title,
28
- }
29
- : m.role === 'user'
30
- ? {
31
- avatar: useSettings.getState().settings.avatar || DEFAULT_AVATAR,
32
- }
33
- : m.meta,
34
- };
35
- });
36
-
37
- const finalList: ChatMessage[] = [];
38
-
39
- const addItem = (item: ChatMessage) => {
40
- const isExist = finalList.findIndex((i) => item.id === i.id) > -1;
41
- if (!isExist) {
42
- finalList.push(item);
43
- }
44
- };
45
-
46
- // 基于添加逻辑进行重排序
47
- for (const item of basic) {
48
- // 先判存在与否,不存在就加入
49
- addItem(item);
50
-
51
- for (const another of basic) {
52
- if (another.parentId === item.id) {
53
- addItem(another);
54
- }
55
- }
56
- }
57
-
58
- return finalList;
15
+ return organizeChats(session, {
16
+ assistant: agentSelectors.currentAgentAvatar(s),
17
+ user: useSettings.getState().settings.avatar || DEFAULT_AVATAR,
18
+ });
59
19
  };
60
20
 
61
21
  export const systemRoleSel = (s: SessionStore): string => {
@@ -0,0 +1,264 @@
1
+ import { beforeEach } from 'vitest';
2
+
3
+ import { LobeAgentSession } from '@/types/session';
4
+
5
+ import { organizeChats } from './utils';
6
+
7
+ let session: LobeAgentSession;
8
+
9
+ beforeEach(() => {
10
+ session = {
11
+ chats: {
12
+ '1': {
13
+ id: '1',
14
+ createAt: 1639440000000,
15
+ updateAt: 1639440000000,
16
+ meta: {},
17
+ content: 'Message 1',
18
+ role: 'assistant',
19
+ },
20
+ '2': {
21
+ id: '2',
22
+ createAt: 1639450000000,
23
+ updateAt: 1639450000000,
24
+ meta: {},
25
+ content: 'Message 2',
26
+ role: 'user',
27
+ },
28
+ '3': {
29
+ id: '3',
30
+ createAt: 1639460000000,
31
+ updateAt: 1639460000000,
32
+ meta: {},
33
+ content: 'Message 3',
34
+ role: 'assistant',
35
+ },
36
+ },
37
+ config: {
38
+ model: 'gpt-4',
39
+ params: {
40
+ temperature: 0.6,
41
+ },
42
+ systemRole: '',
43
+ },
44
+ type: 'agent',
45
+ createAt: 1690110700808,
46
+ id: 'abc',
47
+ meta: {},
48
+ updateAt: 1690110700808,
49
+ } as LobeAgentSession;
50
+ });
51
+
52
+ describe('organizeChats', () => {
53
+ it('should return an array of chat messages', () => {
54
+ const result = organizeChats(session, { assistant: '', user: '' });
55
+ expect(Array.isArray(result)).toBe(true);
56
+ expect(result.length).toBe(3);
57
+ expect(result[0].id).toBe('1');
58
+ expect(result[1].id).toBe('2');
59
+ expect(result[2].id).toBe('3');
60
+ });
61
+
62
+ it('should sort chat messages in ascending order of createAt', () => {
63
+ const result = organizeChats(session, { assistant: '', user: '' });
64
+ expect(result[0].id).toBe('1');
65
+ expect(result[1].id).toBe('2');
66
+ expect(result[2].id).toBe('3');
67
+ });
68
+
69
+ it('should filter out archived messages', () => {
70
+ session.chats['2'].archive = true;
71
+ const result = organizeChats(session, { assistant: '', user: '' });
72
+ expect(result.length).toBe(2);
73
+ expect(result[0].id).toBe('1');
74
+ expect(result[1].id).toBe('3');
75
+ });
76
+
77
+ it('should map avatars correctly', () => {
78
+ const avatar = 'https://example.com/avatar.png';
79
+ const settings = {
80
+ avatar: 'https://example.com/user-avatar.png',
81
+ };
82
+
83
+ const result = organizeChats(session, {
84
+ assistant: avatar,
85
+ user: settings.avatar,
86
+ });
87
+
88
+ expect(result[0].meta.avatar).toBe(avatar);
89
+ expect(result[1].meta.avatar).toBe(settings.avatar);
90
+ expect(result[2].meta.avatar).toBe(avatar);
91
+ });
92
+
93
+ it('should reorder messages based on parent-child relationship', () => {
94
+ session.chats['2'].parentId = '1';
95
+ session.chats['3'].parentId = '2';
96
+ const result = organizeChats(session, { assistant: '', user: '' });
97
+ expect(result.length).toBe(3);
98
+ expect(result[0].id).toBe('1');
99
+ expect(result[1].id).toBe('2');
100
+ expect(result[2].id).toBe('3');
101
+ });
102
+
103
+ it('should remove duplicate messages', () => {
104
+ session.chats['2'].parentId = '1';
105
+ session.chats['3'].parentId = '2';
106
+ session.chats['3'].id = '2';
107
+ const result = organizeChats(session, { assistant: '', user: '' });
108
+ expect(result.length).toBe(2);
109
+ expect(result[0].id).toBe('1');
110
+ expect(result[1].id).toBe('2');
111
+ });
112
+
113
+ it('should return an empty array for empty session', () => {
114
+ const emptySession = {
115
+ chats: {},
116
+ config: {},
117
+ type: 'agent',
118
+ } as LobeAgentSession;
119
+ const result = organizeChats(emptySession, { assistant: '', user: '' });
120
+ expect(Array.isArray(result)).toBe(true);
121
+ expect(result.length).toBe(0);
122
+ });
123
+
124
+ it('should handle large number of chat messages', () => {
125
+ const largeSession = {
126
+ chats: {},
127
+ config: {},
128
+ type: 'agent',
129
+ meta: {},
130
+ } as LobeAgentSession;
131
+
132
+ const numMessages = 1000;
133
+
134
+ for (let i = 1; i <= numMessages; i++) {
135
+ largeSession.chats[i.toString()] = {
136
+ id: i.toString(),
137
+ createAt: i,
138
+ updateAt: i,
139
+ meta: {},
140
+ content: `Message ${i}`,
141
+ role: 'assistant',
142
+ };
143
+ }
144
+
145
+ const result = organizeChats(largeSession, { assistant: '', user: '' });
146
+ expect(result.length).toBe(numMessages);
147
+ expect(result[0].id).toBe('1');
148
+ expect(result[numMessages - 1].id).toBe(numMessages.toString());
149
+ });
150
+
151
+ it('按照预期进行排序', () => {
152
+ const realSession = {
153
+ chats: {
154
+ 'Ftei28dF': {
155
+ content: '鲁迅为何暴打周树人',
156
+ createAt: 1690111354731,
157
+ id: 'Ftei28dF',
158
+ meta: {},
159
+ role: 'user',
160
+ updateAt: 1690111354731,
161
+ },
162
+ '9bQW9hTs': {
163
+ content:
164
+ '这是一种误解。鲁迅和周树人是同一人,指的都是现代文学家周樟寿。"鲁迅"是他的笔名,"周树人"则是他的原名。所以,鲁迅并没有暴打周树人,这只是一种说法上的误解。',
165
+ createAt: 1690111354734,
166
+ id: '9bQW9hTs',
167
+ meta: {},
168
+ parentId: 'Ftei28dF',
169
+ role: 'assistant',
170
+ updateAt: 1690111361514,
171
+ extra: {
172
+ fromModel: 'gpt-4',
173
+ },
174
+ },
175
+ 'HRQGSszU': {
176
+ content:
177
+ '这个问题基于一个误解。实际上,鲁迅和周树人是同一个人,这是中国现代文学的开山鼻祖周树人的笔名。他的本名是周树人,鲁迅则是他在30岁时开始使用的笔名。因此,鲁迅不能暴打周树人,因为他们是同一个人。',
178
+ createAt: 1690111364344,
179
+ id: 'HRQGSszU',
180
+ meta: {},
181
+ parentId: 'Ftei28dF',
182
+ role: 'assistant',
183
+ updateAt: 1690111369519,
184
+ extra: {
185
+ fromModel: 'gpt-4',
186
+ },
187
+ },
188
+ '981qr9n0': {
189
+ content:
190
+ '这是一个误解。鲁迅和周树人是同一个人,是中国现代文学的奠基人。鲁迅是他的笔名,周树人是他的本名。所以,鲁迅不可能暴打周树人。这种说法可能是源于一些误解或误传。',
191
+ createAt: 1690111375456,
192
+ id: '981qr9n0',
193
+ meta: {},
194
+ parentId: 'Ftei28dF',
195
+ role: 'assistant',
196
+ updateAt: 1690111381458,
197
+ extra: {
198
+ fromModel: 'gpt-4',
199
+ },
200
+ },
201
+ 'ddd': {
202
+ content: '鲁迅是谁',
203
+ createAt: 1690211354731,
204
+ id: 'ddd',
205
+ meta: {},
206
+ role: 'user',
207
+ updateAt: 1690211354731,
208
+ },
209
+ },
210
+ config: {
211
+ model: 'gpt-4',
212
+ params: {
213
+ temperature: 0.6,
214
+ },
215
+ systemRole: '',
216
+ },
217
+ createAt: 1690110700808,
218
+ id: '1515e861-0c64-49a3-bb85-2b24d65a19d6',
219
+ meta: {},
220
+ type: 'agent',
221
+ updateAt: 1690110700808,
222
+ } as LobeAgentSession;
223
+
224
+ const result = organizeChats(realSession, { assistant: '', user: '' });
225
+
226
+ expect(
227
+ result.map((i) => ({ id: i.id, content: i.content, role: i.role, createAt: i.createAt })),
228
+ ).toEqual([
229
+ {
230
+ content: '鲁迅为何暴打周树人',
231
+ createAt: 1690111354731,
232
+ id: 'Ftei28dF',
233
+ role: 'user',
234
+ },
235
+ {
236
+ content:
237
+ '这是一种误解。鲁迅和周树人是同一人,指的都是现代文学家周樟寿。"鲁迅"是他的笔名,"周树人"则是他的原名。所以,鲁迅并没有暴打周树人,这只是一种说法上的误解。',
238
+ createAt: 1690111354734,
239
+ id: '9bQW9hTs',
240
+ role: 'assistant',
241
+ },
242
+ {
243
+ content:
244
+ '这个问题基于一个误解。实际上,鲁迅和周树人是同一个人,这是中国现代文学的开山鼻祖周树人的笔名。他的本名是周树人,鲁迅则是他在30岁时开始使用的笔名。因此,鲁迅不能暴打周树人,因为他们是同一个人。',
245
+ createAt: 1690111364344,
246
+ id: 'HRQGSszU',
247
+ role: 'assistant',
248
+ },
249
+ {
250
+ content:
251
+ '这是一个误解。鲁迅和周树人是同一个人,是中国现代文学的奠基人。鲁迅是他的笔名,周树人是他的本名。所以,鲁迅不可能暴打周树人。这种说法可能是源于一些误解或误传。',
252
+ createAt: 1690111375456,
253
+ id: '981qr9n0',
254
+ role: 'assistant',
255
+ },
256
+ {
257
+ content: '鲁迅是谁',
258
+ createAt: 1690211354731,
259
+ id: 'ddd',
260
+ role: 'user',
261
+ },
262
+ ]);
263
+ });
264
+ });
@@ -0,0 +1,53 @@
1
+ import { ChatMessage } from '@/types/chatMessage';
2
+ import { LobeAgentSession } from '@/types/session';
3
+
4
+ export const organizeChats = (
5
+ session: LobeAgentSession,
6
+ avatar: { assistant: string; user: string },
7
+ ) => {
8
+ const basic = Object.values<ChatMessage>(session.chats)
9
+ // 首先按照时间顺序排序,越早的在越前面
10
+ .sort((pre, next) => pre.createAt - next.createAt)
11
+ // 过滤掉已归档的消息,归档消息不应该出现在聊天框中
12
+ .filter((m) => !m.archive)
13
+ // 映射头像关系
14
+ .map((m) => {
15
+ return {
16
+ ...m,
17
+ meta:
18
+ m.role === 'assistant'
19
+ ? {
20
+ avatar: avatar.assistant,
21
+ title: session.meta.title,
22
+ }
23
+ : m.role === 'user'
24
+ ? {
25
+ avatar: avatar.user,
26
+ }
27
+ : m.meta,
28
+ };
29
+ });
30
+
31
+ const finalList: ChatMessage[] = [];
32
+
33
+ const addItem = (item: ChatMessage) => {
34
+ const isExist = finalList.findIndex((i) => item.id === i.id) > -1;
35
+ if (!isExist) {
36
+ finalList.push(item);
37
+ }
38
+ };
39
+
40
+ // 基于添加逻辑进行重排序
41
+ for (const item of basic) {
42
+ // 先判存在与否,不存在就加入
43
+ addItem(item);
44
+
45
+ for (const another of basic) {
46
+ if (another.parentId === item.id) {
47
+ addItem(another);
48
+ }
49
+ }
50
+ }
51
+
52
+ return finalList;
53
+ };
@@ -1,73 +0,0 @@
1
- import { ActionIcon, ChatInputArea, DraggablePanel, Icon, TokenTag, Tooltip } from '@lobehub/ui';
2
- import { Button, Popconfirm } from 'antd';
3
- import { encode } from 'gpt-tokenizer';
4
- import { Archive, Eraser, Languages } from 'lucide-react';
5
- import { memo, useMemo, useState } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
- import { shallow } from 'zustand/shallow';
8
-
9
- import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
10
- import { ModelTokens } from '@/const/modelTokens';
11
- import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
12
- import { useSettings } from '@/store/settings';
13
-
14
- const ChatInput = () => {
15
- const { t } = useTranslation();
16
- const [expand, setExpand] = useState<boolean>(false);
17
- const [text, setText] = useState('');
18
- const inputTokenCount = useMemo(() => encode(text).length, [text]);
19
-
20
- const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
21
- const [totalToken, systemRoleToken, chatsToken, model, sendMessage, clearMessage] =
22
- useSessionStore(
23
- (s) => [
24
- chatSelectors.totalTokenCount(s),
25
- chatSelectors.systemRoleTokenCount(s),
26
- chatSelectors.chatsTokenCount(s),
27
- agentSelectors.currentAgentModel(s),
28
- s.createOrSendMsg,
29
- s.clearMessage,
30
- ],
31
- shallow,
32
- );
33
-
34
- return (
35
- <DraggablePanel
36
- expandable={false}
37
- fullscreen={expand}
38
- headerHeight={HEADER_HEIGHT}
39
- minHeight={CHAT_TEXTAREA_HEIGHT}
40
- onSizeChange={(_, size) => {
41
- if (!size) return;
42
- useSettings.setState({
43
- inputHeight: typeof size.height === 'string' ? Number.parseInt(size.height) : size.height,
44
- });
45
- }}
46
- placement="bottom"
47
- size={{ height: inputHeight, width: '100%' }}
48
- style={{ zIndex: 10 }}
49
- >
50
- <ChatInputArea
51
- actions={
52
- <>
53
- <ActionIcon icon={Languages} />
54
- <Popconfirm onConfirm={() => clearMessage()} title={t('confirmClearCurrentMessages')}>
55
- <ActionIcon icon={Eraser} title={t('clearCurrentMessages')} />
56
- </Popconfirm>
57
- <Tooltip title={t('tokenDetail', { chatsToken, systemRoleToken })}>
58
- <TokenTag maxValue={ModelTokens[model]} value={totalToken + inputTokenCount} />
59
- </Tooltip>
60
- </>
61
- }
62
- expand={expand}
63
- footer={<Button icon={<Icon icon={Archive} title={t('archiveCurrentMessages')} />} />}
64
- minHeight={CHAT_TEXTAREA_HEIGHT}
65
- onExpandChange={setExpand}
66
- onInputChange={setText}
67
- onSend={sendMessage}
68
- />
69
- </DraggablePanel>
70
- );
71
- };
72
-
73
- export default memo(ChatInput);