@lobehub/chat 0.118.10 → 0.119.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/locales/de-DE/chat.json +10 -0
  3. package/locales/de-DE/common.json +1 -0
  4. package/locales/de-DE/error.json +4 -4
  5. package/locales/en-US/chat.json +10 -0
  6. package/locales/en-US/common.json +1 -0
  7. package/locales/en-US/error.json +1 -1
  8. package/locales/es-ES/chat.json +10 -0
  9. package/locales/es-ES/common.json +1 -0
  10. package/locales/fr-FR/chat.json +10 -0
  11. package/locales/fr-FR/common.json +1 -0
  12. package/locales/fr-FR/error.json +1 -2
  13. package/locales/it-IT/chat.json +10 -0
  14. package/locales/it-IT/common.json +1 -0
  15. package/locales/it-IT/error.json +5 -5
  16. package/locales/ja-JP/chat.json +10 -0
  17. package/locales/ja-JP/common.json +1 -0
  18. package/locales/ko-KR/chat.json +10 -0
  19. package/locales/ko-KR/common.json +1 -0
  20. package/locales/ko-KR/error.json +4 -4
  21. package/locales/nl-NL/chat.json +10 -0
  22. package/locales/nl-NL/common.json +1 -0
  23. package/locales/pl-PL/chat.json +10 -0
  24. package/locales/pl-PL/common.json +1 -0
  25. package/locales/pl-PL/error.json +2 -3
  26. package/locales/pt-BR/chat.json +10 -0
  27. package/locales/pt-BR/common.json +1 -0
  28. package/locales/ru-RU/chat.json +10 -0
  29. package/locales/ru-RU/common.json +1 -0
  30. package/locales/ru-RU/error.json +3 -3
  31. package/locales/tr-TR/chat.json +10 -0
  32. package/locales/tr-TR/common.json +1 -0
  33. package/locales/vi-VN/chat.json +10 -0
  34. package/locales/vi-VN/common.json +1 -0
  35. package/locales/vi-VN/error.json +1 -1
  36. package/locales/zh-CN/chat.json +10 -0
  37. package/locales/zh-CN/common.json +1 -0
  38. package/locales/zh-TW/chat.json +10 -0
  39. package/locales/zh-TW/common.json +1 -0
  40. package/package.json +2 -2
  41. package/src/app/chat/(desktop)/features/Conversation.tsx +17 -0
  42. package/src/app/chat/(desktop)/features/HotKeys.tsx +44 -0
  43. package/src/app/chat/(desktop)/index.tsx +2 -3
  44. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +1 -1
  45. package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +24 -3
  46. package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +43 -2
  47. package/src/database/models/__tests__/message.test.ts +48 -0
  48. package/src/database/models/__tests__/topic.test.ts +135 -0
  49. package/src/database/models/message.ts +35 -0
  50. package/src/database/models/session.ts +11 -1
  51. package/src/database/models/topic.ts +34 -2
  52. package/src/features/ChatInput/ActionBar/Clear.tsx +0 -6
  53. package/src/features/Conversation/Actions/Assistant.tsx +16 -5
  54. package/src/features/Conversation/Actions/Error.tsx +2 -2
  55. package/src/features/Conversation/Actions/Fallback.tsx +4 -3
  56. package/src/features/Conversation/Actions/Function.tsx +3 -3
  57. package/src/features/Conversation/Actions/User.tsx +3 -3
  58. package/src/features/Conversation/Actions/customAction.ts +2 -4
  59. package/src/features/Conversation/Actions/index.ts +50 -45
  60. package/src/features/Conversation/Error/InvalidAccess.tsx +1 -1
  61. package/src/features/Conversation/Error/OpenAPIKey.tsx +1 -1
  62. package/src/features/Conversation/Error/OpenAiBizError.tsx +1 -1
  63. package/src/features/Conversation/Error/Plugin/PluginError.tsx +1 -1
  64. package/src/features/Conversation/Error/Plugin/PluginSettings.tsx +1 -1
  65. package/src/features/Conversation/Error/index.tsx +2 -2
  66. package/src/features/Conversation/Extras/Assistant.tsx +32 -29
  67. package/src/features/Conversation/Extras/User.tsx +2 -1
  68. package/src/features/Conversation/Extras/index.ts +4 -4
  69. package/src/features/Conversation/Messages/index.ts +3 -3
  70. package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +58 -0
  71. package/src/features/Conversation/components/ChatItem/index.tsx +126 -0
  72. package/src/features/Conversation/components/ChatList/HistoryDivider.tsx +6 -3
  73. package/src/features/Conversation/components/ChatList/index.tsx +35 -79
  74. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +43 -31
  75. package/src/features/Conversation/hooks/useInitConversation.ts +4 -1
  76. package/src/features/Conversation/index.tsx +4 -3
  77. package/src/features/Conversation/types/index.tsx +35 -0
  78. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +7 -2
  79. package/src/locales/default/chat.ts +10 -1
  80. package/src/locales/default/common.ts +1 -0
  81. package/src/services/config.ts +22 -0
  82. package/src/services/session.ts +6 -0
  83. package/src/services/topic.ts +4 -0
  84. package/src/store/chat/slices/message/selectors.ts +10 -0
  85. package/src/store/chat/slices/topic/action.ts +22 -0
  86. package/src/store/global/slices/common/action.ts +2 -0
  87. package/src/store/session/slices/session/action.test.ts +136 -0
  88. package/src/store/session/slices/session/action.ts +26 -6
  89. package/src/types/topic.ts +1 -0
  90. package/src/features/Conversation/components/ChatList/ActionsBar.tsx +0 -27
  91. package/src/features/Conversation/components/ChatList/Item.tsx +0 -236
  92. package/src/features/Conversation/container/index.tsx +0 -83
@@ -168,6 +168,41 @@ class _MessageModel extends BaseModel {
168
168
  return this.table.where('sessionId').equals(sessionId).toArray();
169
169
  }
170
170
 
171
+ queryByTopicId = async (topicId: string) => {
172
+ const dbMessages = await this.table.where('topicId').equals(topicId).toArray();
173
+
174
+ return dbMessages.map((message) => this.mapToChatMessage(message));
175
+ };
176
+
177
+ async duplicateMessages(messages: ChatMessage[]): Promise<ChatMessage[]> {
178
+ const duplicatedMessages = await this.createDuplicateMessages(messages);
179
+ // 批量添加复制后的消息到数据库
180
+ await this.batchCreate(duplicatedMessages);
181
+ return duplicatedMessages;
182
+ }
183
+
184
+ async createDuplicateMessages(messages: ChatMessage[]): Promise<ChatMessage[]> {
185
+ // 创建一个映射来存储原始消息ID和复制消息ID之间的关系
186
+ const idMapping = new Map<string, string>();
187
+
188
+ // 首先复制所有消息,并为每个复制的消息生成新的ID
189
+ const duplicatedMessages = messages.map((originalMessage) => {
190
+ const newId = nanoid();
191
+ idMapping.set(originalMessage.id, newId);
192
+
193
+ return { ...originalMessage, id: newId };
194
+ });
195
+
196
+ // 更新 parentId 为复制后的新ID
197
+ for (const duplicatedMessage of duplicatedMessages) {
198
+ if (duplicatedMessage.parentId && idMapping.has(duplicatedMessage.parentId)) {
199
+ duplicatedMessage.parentId = idMapping.get(duplicatedMessage.parentId);
200
+ }
201
+ }
202
+
203
+ return duplicatedMessages;
204
+ }
205
+
171
206
  private mapChatMessageToDBMessage(message: ChatMessage): DB_Message {
172
207
  const { extra, ...messageData } = message;
173
208
 
@@ -2,6 +2,7 @@ import { DeepPartial } from 'utility-types';
2
2
 
3
3
  import { DEFAULT_AGENT_LOBE_SESSION } from '@/const/session';
4
4
  import { BaseModel } from '@/database/core';
5
+ import { DBModel } from '@/database/core/types/db';
5
6
  import { DB_Session, DB_SessionSchema } from '@/database/schemas/session';
6
7
  import { LobeAgentConfig } from '@/types/agent';
7
8
  import { LobeAgentSession, LobeSessions, SessionGroupKey } from '@/types/session';
@@ -79,7 +80,7 @@ class _SessionModel extends BaseModel {
79
80
  return this.table.clear();
80
81
  }
81
82
 
82
- async findById(id: string) {
83
+ async findById(id: string): Promise<DBModel<DB_Session>> {
83
84
  return this.table.get(id);
84
85
  }
85
86
 
@@ -155,6 +156,15 @@ class _SessionModel extends BaseModel {
155
156
  console.timeEnd('queryByKeyword');
156
157
  return data;
157
158
  }
159
+
160
+ async duplicate(id: string, newTitle?: string) {
161
+ const session = await this.findById(id);
162
+ if (!session) return;
163
+
164
+ const newSession = merge(session, { meta: { title: newTitle } });
165
+
166
+ return this._add(newSession, uuid());
167
+ }
158
168
  }
159
169
 
160
170
  export const SessionModel = new _SessionModel();
@@ -1,4 +1,6 @@
1
1
  import { BaseModel } from '@/database/core';
2
+ import { DBModel } from '@/database/core/types/db';
3
+ import { MessageModel } from '@/database/models/message';
2
4
  import { DB_Topic, DB_TopicSchema } from '@/database/schemas/topic';
3
5
  import { ChatTopic } from '@/types/topic';
4
6
  import { nanoid } from '@/utils/uuid';
@@ -53,14 +55,14 @@ class _TopicModel extends BaseModel {
53
55
  // handle pageSize
54
56
  const pagedTopics = sortedTopics.slice(offset, offset + pageSize);
55
57
 
56
- return pagedTopics.map((i) => ({ ...i, favorite: !!i.favorite }));
58
+ return pagedTopics.map((i) => this.mapToChatTopic(i));
57
59
  }
58
60
 
59
61
  async findBySessionId(sessionId: string) {
60
62
  return this.table.where({ sessionId }).toArray();
61
63
  }
62
64
 
63
- async findById(id: string) {
65
+ async findById(id: string): Promise<DBModel<DB_Topic>> {
64
66
  return this.table.get(id);
65
67
  }
66
68
 
@@ -198,6 +200,36 @@ class _TopicModel extends BaseModel {
198
200
  console.timeEnd('queryTopicsByKeyword');
199
201
  return uniqueTopics.map((i) => ({ ...i, favorite: !!i.favorite }));
200
202
  }
203
+
204
+ async duplicateTopic(topicId: string, newTitle?: string) {
205
+ return this.db.transaction('rw', this.db.topics, this.db.messages, async () => {
206
+ // Step 1: get DB_Topic
207
+ const topic = await this.findById(topicId);
208
+
209
+ if (!topic) {
210
+ throw new Error(`Topic with id ${topicId} not found`);
211
+ }
212
+
213
+ // Step 3: 查询与 `topic` 关联的 `messages`
214
+ const originalMessages = await MessageModel.queryByTopicId(topicId);
215
+
216
+ const duplicateMessages = await MessageModel.duplicateMessages(originalMessages);
217
+
218
+ const { id } = await this.create({
219
+ ...this.mapToChatTopic(topic),
220
+ messages: duplicateMessages.map((m) => m.id),
221
+ sessionId: topic.sessionId!,
222
+ title: newTitle || topic.title,
223
+ });
224
+
225
+ return id;
226
+ });
227
+ }
228
+
229
+ private mapToChatTopic = (dbTopic: DBModel<DB_Topic>): ChatTopic => ({
230
+ ...dbTopic,
231
+ favorite: !!dbTopic.favorite,
232
+ });
201
233
  }
202
234
 
203
235
  export const TopicModel = new _TopicModel();
@@ -2,7 +2,6 @@ import { ActionIcon } from '@lobehub/ui';
2
2
  import { Popconfirm } from 'antd';
3
3
  import { Eraser } from 'lucide-react';
4
4
  import { memo, useCallback } from 'react';
5
- import { useHotkeys } from 'react-hotkeys-hook';
6
5
  import { useTranslation } from 'react-i18next';
7
6
 
8
7
  import HotKeys from '@/components/HotKeys';
@@ -21,11 +20,6 @@ const Clear = memo(() => {
21
20
  clearImageList();
22
21
  }, []);
23
22
 
24
- useHotkeys(hotkeys, resetConversation, {
25
- enableOnFormTags: true,
26
- preventDefault: true,
27
- });
28
-
29
23
  return (
30
24
  <Popconfirm
31
25
  cancelText={t('cancel', { ns: 'common' })}
@@ -1,21 +1,32 @@
1
1
  import { ActionIconGroup } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
 
4
- import { RenderAction } from '../components/ChatList';
5
4
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
5
+ import { RenderAction } from '../types';
6
6
  import { ErrorActionsBar } from './Error';
7
7
  import { useCustomActions } from './customAction';
8
8
 
9
- export const AssistantActionsBar: RenderAction = memo(({ text, id, onActionClick, error }) => {
10
- const { regenerate, edit, copy, divider, del } = useChatListActionsBar(text);
9
+ export const AssistantActionsBar: RenderAction = memo(({ id, onActionClick, error }) => {
10
+ const { regenerate, edit, delAndRegenerate, copy, divider, del } = useChatListActionsBar();
11
11
  const { translate, tts } = useCustomActions();
12
+
12
13
  if (id === 'default') return;
13
14
 
14
- if (error) return <ErrorActionsBar onActionClick={onActionClick} text={text} />;
15
+ if (error) return <ErrorActionsBar onActionClick={onActionClick} />;
15
16
 
16
17
  return (
17
18
  <ActionIconGroup
18
- dropdownMenu={[edit, copy, regenerate, divider, tts, translate, divider, del]}
19
+ dropdownMenu={[
20
+ edit,
21
+ copy,
22
+ regenerate,
23
+ divider,
24
+ tts,
25
+ translate,
26
+ divider,
27
+ delAndRegenerate,
28
+ del,
29
+ ]}
19
30
  items={[regenerate, copy]}
20
31
  onActionClick={onActionClick}
21
32
  type="ghost"
@@ -4,8 +4,8 @@ import { memo } from 'react';
4
4
 
5
5
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
6
6
 
7
- export const ErrorActionsBar = memo<ActionsBarProps>(({ text, onActionClick }) => {
8
- const { regenerate, del } = useChatListActionsBar(text);
7
+ export const ErrorActionsBar = memo<ActionsBarProps>(({ onActionClick }) => {
8
+ const { regenerate, del } = useChatListActionsBar();
9
9
 
10
10
  return <ActionIconGroup items={[regenerate, del]} onActionClick={onActionClick} type="ghost" />;
11
11
  });
@@ -1,11 +1,12 @@
1
1
  import { ActionIconGroup } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
 
4
- import { RenderAction } from '../components/ChatList';
5
4
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
5
+ import { RenderAction } from '../types';
6
+
7
+ export const DefaultActionsBar: RenderAction = memo(({ onActionClick }) => {
8
+ const { del } = useChatListActionsBar();
6
9
 
7
- export const DefaultActionsBar: RenderAction = memo(({ text, onActionClick }) => {
8
- const { del } = useChatListActionsBar(text);
9
10
  return (
10
11
  <ActionIconGroup dropdownMenu={[del]} items={[]} onActionClick={onActionClick} type="ghost" />
11
12
  );
@@ -1,11 +1,11 @@
1
1
  import { ActionIconGroup } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
 
4
- import { RenderAction } from '../components/ChatList';
5
4
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
5
+ import { RenderAction } from '../types';
6
6
 
7
- export const FunctionActionsBar: RenderAction = memo(({ text, onActionClick }) => {
8
- const { regenerate, divider, del } = useChatListActionsBar(text);
7
+ export const FunctionActionsBar: RenderAction = memo(({ onActionClick }) => {
8
+ const { regenerate, divider, del } = useChatListActionsBar();
9
9
  return (
10
10
  <ActionIconGroup
11
11
  dropdownMenu={[regenerate, divider, del]}
@@ -1,12 +1,12 @@
1
1
  import { ActionIconGroup } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
 
4
- import { RenderAction } from '../components/ChatList';
5
4
  import { useChatListActionsBar } from '../hooks/useChatListActionsBar';
5
+ import { RenderAction } from '../types';
6
6
  import { useCustomActions } from './customAction';
7
7
 
8
- export const UserActionsBar: RenderAction = memo(({ text, onActionClick }) => {
9
- const { regenerate, edit, copy, divider, del } = useChatListActionsBar(text);
8
+ export const UserActionsBar: RenderAction = memo(({ onActionClick }) => {
9
+ const { regenerate, edit, copy, divider, del } = useChatListActionsBar();
10
10
  const { translate, tts } = useCustomActions();
11
11
 
12
12
  return (
@@ -1,5 +1,6 @@
1
1
  import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
2
2
  import { LanguagesIcon, Play } from 'lucide-react';
3
+ import { useMemo } from 'react';
3
4
  import { useTranslation } from 'react-i18next';
4
5
 
5
6
  import { localeOptions } from '@/locales/resources';
@@ -23,8 +24,5 @@ export const useCustomActions = () => {
23
24
  label: t('tts.action'),
24
25
  } as ActionIconGroupItems;
25
26
 
26
- return {
27
- translate,
28
- tts,
29
- };
27
+ return useMemo(() => ({ translate, tts }), []);
30
28
  };
@@ -1,67 +1,72 @@
1
+ import { copyToClipboard } from '@lobehub/ui';
2
+ import { App } from 'antd';
3
+ import { useCallback } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
1
6
  import { useChatStore } from '@/store/chat';
7
+ import { LLMRoleType } from '@/types/llm';
2
8
 
3
- import { ChatListProps } from '../components/ChatList';
9
+ import { OnActionsClick, RenderAction } from '../types';
4
10
  import { AssistantActionsBar } from './Assistant';
5
11
  import { DefaultActionsBar } from './Fallback';
6
12
  import { FunctionActionsBar } from './Function';
7
13
  import { UserActionsBar } from './User';
8
14
 
9
- export const renderActions: ChatListProps['renderActions'] = {
15
+ export const renderActions: Record<LLMRoleType, RenderAction> = {
10
16
  assistant: AssistantActionsBar,
11
17
  function: FunctionActionsBar,
12
18
  system: DefaultActionsBar,
13
19
  user: UserActionsBar,
14
20
  };
15
21
 
16
- interface ActionsClick {
17
- onClick: () => void;
18
- trigger: boolean;
19
- }
20
-
21
- export const useActionsClick = (): ChatListProps['onActionsClick'] => {
22
+ export const useActionsClick = (): OnActionsClick => {
23
+ const { t } = useTranslation('common');
22
24
  const [deleteMessage, resendMessage, translateMessage, ttsMessage] = useChatStore((s) => [
23
25
  s.deleteMessage,
24
26
  s.resendMessage,
25
27
  s.translateMessage,
26
28
  s.ttsMessage,
27
29
  ]);
30
+ const { message } = App.useApp();
31
+
32
+ return useCallback<OnActionsClick>(async (action, { id, content, error }) => {
33
+ switch (action.key) {
34
+ case 'copy': {
35
+ await copyToClipboard(content);
36
+ message.success(t('copySuccess', { defaultValue: 'Copy Success' }));
37
+ break;
38
+ }
39
+
40
+ case 'del': {
41
+ deleteMessage(id);
42
+ break;
43
+ }
44
+
45
+ case 'regenerate': {
46
+ resendMessage(id);
47
+ // if this message is an error message, we need to delete it
48
+ if (error) deleteMessage(id);
49
+ break;
50
+ }
51
+
52
+ case 'delAndRegenerate': {
53
+ resendMessage(id);
54
+ deleteMessage(id);
55
+ break;
56
+ }
28
57
 
29
- return (action, { id, error }) => {
30
- const actionsClick: ActionsClick[] = [
31
- {
32
- onClick: () => {
33
- deleteMessage(id);
34
- },
35
- trigger: action.key === 'del',
36
- },
37
- {
38
- onClick: () => {
39
- resendMessage(id);
40
- // if this message is an error message, we need to delete it
41
- if (error) deleteMessage(id);
42
- },
43
- trigger: action.key === 'regenerate',
44
- },
45
- {
46
- onClick: () => {
47
- ttsMessage(id);
48
- },
49
- trigger: action.key === 'tts',
50
- },
51
- {
52
- onClick: () => {
53
- /**
54
- * @description Click the menu item with translate item, the result is:
55
- * @key 'en-US'
56
- * @keyPath ['en-US','translate']
57
- */
58
- const lang = action.keyPath[0];
59
- translateMessage(id, lang);
60
- },
61
- trigger: action.keyPath.at(-1) === 'translate',
62
- },
63
- ];
58
+ case 'tts': {
59
+ ttsMessage(id);
60
+ break;
61
+ }
62
+ }
64
63
 
65
- actionsClick.find((item) => item.trigger)?.onClick();
66
- };
64
+ if (action.keyPath.at(-1) === 'translate') {
65
+ // click the menu item with translate item, the result is:
66
+ // key: 'en-US'
67
+ // keyPath: ['en-US','translate']
68
+ const lang = action.keyPath[0];
69
+ translateMessage(id, lang);
70
+ }
71
+ }, []);
67
72
  };
@@ -8,7 +8,7 @@ import { Flexbox } from 'react-layout-kit';
8
8
  import { useChatStore } from '@/store/chat';
9
9
  import { useGlobalStore } from '@/store/global';
10
10
 
11
- import { RenderErrorMessage } from '../components/ChatList';
11
+ import { RenderErrorMessage } from '../types';
12
12
  import APIKeyForm from './ApiKeyForm';
13
13
  import { ErrorActionContainer, FormAction } from './style';
14
14
 
@@ -1,6 +1,6 @@
1
1
  import { memo } from 'react';
2
2
 
3
- import { RenderErrorMessage } from '../components/ChatList';
3
+ import { RenderErrorMessage } from '../types';
4
4
  import APIKeyForm from './ApiKeyForm';
5
5
  import { ErrorActionContainer } from './style';
6
6
 
@@ -2,7 +2,7 @@ import { Highlighter } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
- import { RenderErrorMessage } from '../components/ChatList';
5
+ import { RenderErrorMessage } from '../types';
6
6
  import OpenAPIKey from './OpenAPIKey';
7
7
 
8
8
  interface OpenAIError {
@@ -2,7 +2,7 @@ import { Highlighter } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
- import { RenderErrorMessage } from '../../components/ChatList';
5
+ import { RenderErrorMessage } from '../../types';
6
6
 
7
7
  interface OpenAIError {
8
8
  code: 'invalid_api_key' | string;
@@ -11,7 +11,7 @@ import { useChatStore } from '@/store/chat';
11
11
  import { pluginHelpers, useToolStore } from '@/store/tool';
12
12
  import { pluginSelectors } from '@/store/tool/selectors';
13
13
 
14
- import { RenderErrorMessage } from '../../components/ChatList';
14
+ import { RenderErrorMessage } from '../../types';
15
15
  import { ErrorActionContainer, useStyles } from '../style';
16
16
 
17
17
  const PluginSettings: RenderErrorMessage['Render'] = memo(({ id, plugin }) => {
@@ -2,14 +2,14 @@ import { PluginErrorType } from '@lobehub/chat-plugin-sdk';
2
2
 
3
3
  import { ChatErrorType } from '@/types/fetch';
4
4
 
5
- import { ChatListProps } from '../components/ChatList';
5
+ import { RenderErrorMessage } from '../types';
6
6
  import InvalidAccess from './InvalidAccess';
7
7
  import OpenAPIKey from './OpenAPIKey';
8
8
  import OpenAiBizError from './OpenAiBizError';
9
9
  import PluginError from './Plugin/PluginError';
10
10
  import PluginSettings from './Plugin/PluginSettings';
11
11
 
12
- export const renderErrorMessages: ChatListProps['renderErrorMessages'] = {
12
+ export const renderErrorMessages: Record<string, RenderErrorMessage> = {
13
13
  [PluginErrorType.PluginMarketIndexNotFound]: {
14
14
  Render: PluginError,
15
15
  },
@@ -8,41 +8,44 @@ import { useSessionStore } from '@/store/session';
8
8
  import { agentSelectors } from '@/store/session/selectors';
9
9
  import { ChatMessage } from '@/types/message';
10
10
 
11
+ import { RenderMessageExtra } from '../types';
11
12
  import ExtraContainer from './ExtraContainer';
12
13
  import TTS from './TTS';
13
14
  import Translate from './Translate';
14
15
 
15
- export const AssistantMessageExtra = memo<ChatMessage>(({ extra, id, content }) => {
16
- const model = useSessionStore(agentSelectors.currentAgentModel);
17
- const loading = useChatStore((s) => s.chatLoadingId === id);
16
+ export const AssistantMessageExtra: RenderMessageExtra = memo<ChatMessage>(
17
+ ({ extra, id, content }) => {
18
+ const model = useSessionStore(agentSelectors.currentAgentModel);
19
+ const loading = useChatStore((s) => s.chatLoadingId === id);
18
20
 
19
- const showModelTag = extra?.fromModel && model !== extra?.fromModel;
20
- const showTranslate = !!extra?.translate;
21
- const showTTS = !!extra?.tts;
21
+ const showModelTag = extra?.fromModel && model !== extra?.fromModel;
22
+ const showTranslate = !!extra?.translate;
23
+ const showTTS = !!extra?.tts;
22
24
 
23
- const showExtra = showModelTag || showTranslate || showTTS;
25
+ const showExtra = showModelTag || showTranslate || showTTS;
24
26
 
25
- if (!showExtra) return;
27
+ if (!showExtra) return;
26
28
 
27
- return (
28
- <Flexbox gap={8} style={{ marginTop: 8 }}>
29
- {showModelTag && (
30
- <div>
31
- <Tag icon={<SiOpenai size={'1em'} />}>{extra?.fromModel as string}</Tag>
32
- </div>
33
- )}
34
- <>
35
- {extra?.tts && (
36
- <ExtraContainer>
37
- <TTS content={content} id={id} loading={loading} {...extra?.tts} />
38
- </ExtraContainer>
29
+ return (
30
+ <Flexbox gap={8} style={{ marginTop: 8 }}>
31
+ {showModelTag && (
32
+ <div>
33
+ <Tag icon={<SiOpenai size={'1em'} />}>{extra?.fromModel as string}</Tag>
34
+ </div>
39
35
  )}
40
- {extra?.translate && (
41
- <ExtraContainer>
42
- <Translate id={id} loading={loading} {...extra?.translate} />
43
- </ExtraContainer>
44
- )}
45
- </>
46
- </Flexbox>
47
- );
48
- });
36
+ <>
37
+ {extra?.tts && (
38
+ <ExtraContainer>
39
+ <TTS content={content} id={id} loading={loading} {...extra?.tts} />
40
+ </ExtraContainer>
41
+ )}
42
+ {extra?.translate && (
43
+ <ExtraContainer>
44
+ <Translate id={id} loading={loading} {...extra?.translate} />
45
+ </ExtraContainer>
46
+ )}
47
+ </>
48
+ </Flexbox>
49
+ );
50
+ },
51
+ );
@@ -3,11 +3,12 @@ import { memo } from 'react';
3
3
  import { useChatStore } from '@/store/chat';
4
4
  import { ChatMessage } from '@/types/message';
5
5
 
6
+ import { RenderMessageExtra } from '../types';
6
7
  import ExtraContainer from './ExtraContainer';
7
8
  import TTS from './TTS';
8
9
  import Translate from './Translate';
9
10
 
10
- export const UserMessageExtra = memo<ChatMessage>(({ extra, id, content }) => {
11
+ export const UserMessageExtra: RenderMessageExtra = memo<ChatMessage>(({ extra, id, content }) => {
11
12
  const loading = useChatStore((s) => s.chatLoadingId === id);
12
13
 
13
14
  const showTranslate = !!extra?.translate;
@@ -1,8 +1,8 @@
1
- import { ChatListProps, RenderMessageExtra } from '../components/ChatList';
1
+ import { RenderMessageExtra } from '../types';
2
2
  import { AssistantMessageExtra } from './Assistant';
3
3
  import { UserMessageExtra } from './User';
4
4
 
5
- export const renderMessagesExtra: ChatListProps['renderMessagesExtra'] = {
6
- assistant: AssistantMessageExtra as unknown as RenderMessageExtra,
7
- user: UserMessageExtra as unknown as RenderMessageExtra,
5
+ export const renderMessagesExtra: Record<string, RenderMessageExtra> = {
6
+ assistant: AssistantMessageExtra,
7
+ user: UserMessageExtra,
8
8
  };
@@ -6,20 +6,20 @@ import { useSessionStore } from '@/store/session';
6
6
  import { sessionSelectors } from '@/store/session/selectors';
7
7
  import { pathString } from '@/utils/url';
8
8
 
9
- import { ChatListProps } from '../components/ChatList';
9
+ import { OnAvatarsClick, RenderMessage } from '../types';
10
10
  import { AssistantMessage } from './Assistant';
11
11
  import { DefaultMessage } from './Default';
12
12
  import { FunctionMessage } from './Function';
13
13
  import { UserMessage } from './User';
14
14
 
15
- export const renderMessages: ChatListProps['renderMessages'] = {
15
+ export const renderMessages: Record<string, RenderMessage> = {
16
16
  assistant: AssistantMessage,
17
17
  default: DefaultMessage,
18
18
  function: FunctionMessage,
19
19
  user: UserMessage,
20
20
  };
21
21
 
22
- export const useAvatarsClick = (): ChatListProps['onAvatarsClick'] => {
22
+ export const useAvatarsClick = (): OnAvatarsClick => {
23
23
  const [isInbox] = useSessionStore((s) => [sessionSelectors.isInboxSession(s)]);
24
24
  const [toggleSystemRole] = useGlobalStore((s) => [s.toggleSystemRole]);
25
25
  const { mobile } = useResponsive();
@@ -0,0 +1,58 @@
1
+ import { ActionEvent, ActionIconGroup, type ActionIconGroupProps } from '@lobehub/ui';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo, useCallback } from 'react';
4
+
5
+ import { useChatStore } from '@/store/chat';
6
+ import { chatSelectors } from '@/store/chat/selectors';
7
+ import { useSessionStore } from '@/store/session';
8
+ import { agentSelectors } from '@/store/session/selectors';
9
+
10
+ import { renderActions, useActionsClick } from '../../Actions';
11
+ import { useChatListActionsBar } from '../../hooks/useChatListActionsBar';
12
+
13
+ export type ActionsBarProps = ActionIconGroupProps;
14
+
15
+ const ActionsBar = memo<ActionsBarProps>((props) => {
16
+ const { regenerate, edit, copy, divider, del } = useChatListActionsBar();
17
+ return (
18
+ <ActionIconGroup
19
+ dropdownMenu={[edit, copy, regenerate, divider, del]}
20
+ items={[regenerate, edit]}
21
+ type="ghost"
22
+ {...props}
23
+ />
24
+ );
25
+ });
26
+
27
+ interface ActionsProps {
28
+ index: number;
29
+ setEditing: (edit: boolean) => void;
30
+ }
31
+ const Actions = memo<ActionsProps>(({ index, setEditing }) => {
32
+ const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
33
+
34
+ const item = useChatStore(
35
+ (s) => chatSelectors.currentChatsWithGuideMessage(meta)(s)[index],
36
+ isEqual,
37
+ );
38
+ const onActionsClick = useActionsClick();
39
+
40
+ const handleActionClick = useCallback(
41
+ async (action: ActionEvent) => {
42
+ switch (action.key) {
43
+ case 'edit': {
44
+ setEditing(true);
45
+ }
46
+ }
47
+
48
+ onActionsClick(action, item);
49
+ },
50
+ [item],
51
+ );
52
+
53
+ const RenderFunction = renderActions[item?.role] ?? ActionsBar;
54
+
55
+ return <RenderFunction {...item} onActionClick={handleActionClick} />;
56
+ });
57
+
58
+ export default Actions;