@lobehub/chat 0.118.10 → 0.119.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/docs/Home.md +1 -1
  5. package/docs/_Sidebar.md +1 -1
  6. package/locales/de-DE/chat.json +10 -0
  7. package/locales/de-DE/common.json +1 -0
  8. package/locales/de-DE/error.json +4 -4
  9. package/locales/en-US/chat.json +10 -0
  10. package/locales/en-US/common.json +1 -0
  11. package/locales/en-US/error.json +1 -1
  12. package/locales/es-ES/chat.json +10 -0
  13. package/locales/es-ES/common.json +1 -0
  14. package/locales/fr-FR/chat.json +10 -0
  15. package/locales/fr-FR/common.json +1 -0
  16. package/locales/fr-FR/error.json +1 -2
  17. package/locales/it-IT/chat.json +10 -0
  18. package/locales/it-IT/common.json +1 -0
  19. package/locales/it-IT/error.json +5 -5
  20. package/locales/ja-JP/chat.json +10 -0
  21. package/locales/ja-JP/common.json +1 -0
  22. package/locales/ko-KR/chat.json +10 -0
  23. package/locales/ko-KR/common.json +1 -0
  24. package/locales/ko-KR/error.json +4 -4
  25. package/locales/nl-NL/chat.json +10 -0
  26. package/locales/nl-NL/common.json +1 -0
  27. package/locales/pl-PL/chat.json +10 -0
  28. package/locales/pl-PL/common.json +1 -0
  29. package/locales/pl-PL/error.json +2 -3
  30. package/locales/pt-BR/chat.json +10 -0
  31. package/locales/pt-BR/common.json +1 -0
  32. package/locales/ru-RU/chat.json +10 -0
  33. package/locales/ru-RU/common.json +1 -0
  34. package/locales/ru-RU/error.json +3 -3
  35. package/locales/tr-TR/chat.json +10 -0
  36. package/locales/tr-TR/common.json +1 -0
  37. package/locales/vi-VN/chat.json +10 -0
  38. package/locales/vi-VN/common.json +1 -0
  39. package/locales/vi-VN/error.json +1 -1
  40. package/locales/zh-CN/chat.json +10 -0
  41. package/locales/zh-CN/common.json +1 -0
  42. package/locales/zh-TW/chat.json +10 -0
  43. package/locales/zh-TW/common.json +1 -0
  44. package/package.json +6 -6
  45. package/src/app/chat/(desktop)/features/Conversation.tsx +17 -0
  46. package/src/app/chat/(desktop)/features/HotKeys.tsx +44 -0
  47. package/src/app/chat/(desktop)/index.tsx +2 -3
  48. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +1 -1
  49. package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +24 -3
  50. package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +43 -2
  51. package/src/database/models/__tests__/message.test.ts +48 -0
  52. package/src/database/models/__tests__/topic.test.ts +135 -0
  53. package/src/database/models/message.ts +35 -0
  54. package/src/database/models/session.ts +11 -1
  55. package/src/database/models/topic.ts +34 -2
  56. package/src/features/ChatInput/ActionBar/Clear.tsx +0 -6
  57. package/src/features/Conversation/Actions/Assistant.tsx +16 -5
  58. package/src/features/Conversation/Actions/Error.tsx +2 -2
  59. package/src/features/Conversation/Actions/Fallback.tsx +4 -3
  60. package/src/features/Conversation/Actions/Function.tsx +3 -3
  61. package/src/features/Conversation/Actions/User.tsx +3 -3
  62. package/src/features/Conversation/Actions/customAction.ts +2 -4
  63. package/src/features/Conversation/Actions/index.ts +50 -45
  64. package/src/features/Conversation/Error/InvalidAccess.tsx +1 -1
  65. package/src/features/Conversation/Error/OpenAPIKey.tsx +1 -1
  66. package/src/features/Conversation/Error/OpenAiBizError.tsx +1 -1
  67. package/src/features/Conversation/Error/Plugin/PluginError.tsx +1 -1
  68. package/src/features/Conversation/Error/Plugin/PluginSettings.tsx +1 -1
  69. package/src/features/Conversation/Error/index.tsx +2 -2
  70. package/src/features/Conversation/Extras/Assistant.tsx +32 -29
  71. package/src/features/Conversation/Extras/User.tsx +2 -1
  72. package/src/features/Conversation/Extras/index.ts +4 -4
  73. package/src/features/Conversation/Messages/index.ts +3 -3
  74. package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +58 -0
  75. package/src/features/Conversation/components/ChatItem/index.tsx +126 -0
  76. package/src/features/Conversation/components/ChatList/HistoryDivider.tsx +6 -3
  77. package/src/features/Conversation/components/ChatList/index.tsx +35 -79
  78. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +43 -31
  79. package/src/features/Conversation/hooks/useInitConversation.ts +4 -1
  80. package/src/features/Conversation/index.tsx +4 -3
  81. package/src/features/Conversation/types/index.tsx +35 -0
  82. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +7 -2
  83. package/src/locales/default/chat.ts +10 -1
  84. package/src/locales/default/common.ts +1 -0
  85. package/src/services/config.ts +22 -0
  86. package/src/services/session.ts +6 -0
  87. package/src/services/topic.ts +4 -0
  88. package/src/store/chat/slices/message/action.ts +89 -2
  89. package/src/store/chat/slices/message/selectors.ts +10 -0
  90. package/src/store/chat/slices/topic/action.ts +22 -0
  91. package/src/store/global/slices/common/action.ts +2 -0
  92. package/src/store/session/slices/session/action.test.ts +136 -0
  93. package/src/store/session/slices/session/action.ts +26 -6
  94. package/src/types/topic.ts +1 -0
  95. package/src/utils/fetch.test.ts +131 -0
  96. package/src/utils/fetch.ts +26 -10
  97. package/src/features/Conversation/components/ChatList/ActionsBar.tsx +0 -27
  98. package/src/features/Conversation/components/ChatList/Item.tsx +0 -236
  99. package/src/features/Conversation/container/index.tsx +0 -83
  100. /package/docs/Development/{Steup-Development.md → Setup-Development.md} +0 -0
  101. /package/docs/Development/{Steup-Development.zh-CN.md → Setup-Development.zh-CN.md} +0 -0
@@ -11,13 +11,13 @@ export const getMessageError = async (response: Response) => {
11
11
  const data = (await response.json()) as ErrorResponse;
12
12
  chatMessageError = {
13
13
  body: data.body,
14
- message: t(`response.${data.errorType}`),
14
+ message: t(`response.${data.errorType}` as any, { ns: 'error' }),
15
15
  type: data.errorType,
16
16
  };
17
17
  } catch {
18
18
  // 如果无法正常返回,说明是常规报错
19
19
  chatMessageError = {
20
- message: t(`response.${response.status}`),
20
+ message: t(`response.${response.status}` as any, { ns: 'error' }),
21
21
  type: response.status as ErrorType,
22
22
  };
23
23
  }
@@ -25,9 +25,12 @@ export const getMessageError = async (response: Response) => {
25
25
  return chatMessageError;
26
26
  };
27
27
 
28
+ type SSEFinishType = 'done' | 'error' | 'abort';
29
+
28
30
  export interface FetchSSEOptions {
31
+ onAbort?: (text: string) => Promise<void>;
29
32
  onErrorHandle?: (error: ChatMessageError) => void;
30
- onFinish?: (text: string) => Promise<void>;
33
+ onFinish?: (text: string, type: SSEFinishType) => Promise<void>;
31
34
  onMessageHandle?: (text: string) => void;
32
35
  }
33
36
 
@@ -57,17 +60,30 @@ export const fetchSSE = async (fetchFn: () => Promise<Response>, options: FetchS
57
60
  const decoder = new TextDecoder();
58
61
 
59
62
  let done = false;
63
+ let finishedType: SSEFinishType = 'done';
60
64
 
61
65
  while (!done) {
62
- const { value, done: doneReading } = await reader.read();
63
- done = doneReading;
64
- const chunkValue = decoder.decode(value, { stream: true });
65
-
66
- output += chunkValue;
67
- options.onMessageHandle?.(chunkValue);
66
+ try {
67
+ const { value, done: doneReading } = await reader.read();
68
+ done = doneReading;
69
+ const chunkValue = decoder.decode(value, { stream: true });
70
+
71
+ output += chunkValue;
72
+ options.onMessageHandle?.(chunkValue);
73
+ } catch (error) {
74
+ done = true;
75
+
76
+ if ((error as TypeError).name === 'AbortError') {
77
+ finishedType = 'abort';
78
+ options?.onAbort?.(output);
79
+ } else {
80
+ finishedType = 'error';
81
+ console.error(error);
82
+ }
83
+ }
68
84
  }
69
85
 
70
- await options?.onFinish?.(output);
86
+ await options?.onFinish?.(output, finishedType);
71
87
 
72
88
  return returnRes;
73
89
  };
@@ -1,27 +0,0 @@
1
- import { ActionIconGroup, type ActionIconGroupProps } from '@lobehub/ui';
2
- import { memo } from 'react';
3
-
4
- import { useChatListActionsBar } from '../../hooks/useChatListActionsBar';
5
-
6
- export interface ActionsBarProps extends ActionIconGroupProps {
7
- text?: {
8
- copy?: string;
9
- delete?: string;
10
- edit?: string;
11
- regenerate?: string;
12
- };
13
- }
14
-
15
- const ActionsBar = memo<ActionsBarProps>(({ text, ...rest }) => {
16
- const { regenerate, edit, copy, divider, del } = useChatListActionsBar(text);
17
- return (
18
- <ActionIconGroup
19
- dropdownMenu={[edit, copy, regenerate, divider, del]}
20
- items={[regenerate, edit]}
21
- type="ghost"
22
- {...rest}
23
- />
24
- );
25
- });
26
-
27
- export default ActionsBar;
@@ -1,236 +0,0 @@
1
- import { type AlertProps, ChatItem, type ChatItemProps } from '@lobehub/ui';
2
- import { ActionEvent, copyToClipboard } from '@lobehub/ui';
3
- import { App } from 'antd';
4
- import { FC, ReactNode, memo, useCallback, useMemo, useState } from 'react';
5
-
6
- import { LLMRoleType } from '@/types/llm';
7
- import { ChatMessage } from '@/types/message';
8
-
9
- import ActionsBar, { type ActionsBarProps } from './ActionsBar';
10
-
11
- export type OnMessageChange = (id: string, content: string) => void;
12
- export type OnActionsClick = (action: ActionEvent, message: ChatMessage) => void;
13
- export type OnAvatatsClick = (role: RenderRole) => ChatItemProps['onAvatarClick'];
14
- export type RenderRole = LLMRoleType | 'default' | string;
15
- export type RenderItem = FC<{ key: string } & ChatMessage & ListItemProps>;
16
- export type RenderMessage = FC<ChatMessage & { editableContent: ReactNode }>;
17
- export type RenderMessageExtra = FC<ChatMessage>;
18
- export interface RenderErrorMessage {
19
- Render?: FC<ChatMessage>;
20
- config?: AlertProps;
21
- }
22
- export type RenderAction = FC<ActionsBarProps & ChatMessage>;
23
-
24
- export interface ListItemProps {
25
- groupNav?: ChatItemProps['avatarAddon'];
26
- loading?: boolean;
27
- /**
28
- * @description 点击操作按钮的回调函数
29
- */
30
- onActionsClick?: OnActionsClick;
31
- onAvatarsClick?: OnAvatatsClick;
32
- /**
33
- * @description 消息变化的回调函数
34
- */
35
- onMessageChange?: OnMessageChange;
36
- renderActions?: {
37
- [actionKey: string]: RenderAction;
38
- };
39
- /**
40
- * @description 渲染错误消息的函数
41
- */
42
- renderErrorMessages?: {
43
- [errorType: 'default' | string]: RenderErrorMessage;
44
- };
45
- renderItems?: {
46
- [role: RenderRole]: RenderItem;
47
- };
48
- /**
49
- * @description 渲染消息的函数
50
- */
51
- renderMessages?: {
52
- [role: RenderRole]: RenderMessage;
53
- };
54
- /**
55
- * @description 渲染消息额外内容的函数
56
- */
57
- renderMessagesExtra?: {
58
- [role: RenderRole]: RenderMessageExtra;
59
- };
60
- /**
61
- * @description 是否显示聊天项的名称
62
- * @default false
63
- */
64
- showTitle?: boolean;
65
- /**
66
- * @description 文本内容
67
- */
68
- text?: ChatItemProps['text'] &
69
- ActionsBarProps['text'] & {
70
- copySuccess?: string;
71
- history?: string;
72
- } & {
73
- [key: string]: string;
74
- };
75
- /**
76
- * @description 聊天列表的类型
77
- * @default 'chat'
78
- */
79
- type?: 'docs' | 'chat';
80
- }
81
-
82
- export type ChatListItemProps = ChatMessage & ListItemProps;
83
-
84
- const Item = memo<ChatListItemProps>((props) => {
85
- const {
86
- renderMessagesExtra,
87
- showTitle,
88
- onActionsClick,
89
- onAvatarsClick,
90
- onMessageChange,
91
- type,
92
- text,
93
- renderMessages,
94
- renderErrorMessages,
95
- renderActions,
96
- loading,
97
- groupNav,
98
- renderItems,
99
- ...item
100
- } = props;
101
-
102
- const [editing, setEditing] = useState(false);
103
-
104
- const { message } = App.useApp();
105
-
106
- const RenderItem = useMemo(() => {
107
- if (!renderItems || !item?.role) return;
108
- let renderFunction;
109
- if (renderItems?.[item.role]) renderFunction = renderItems[item.role];
110
- if (!renderFunction && renderItems?.['default']) renderFunction = renderItems['default'];
111
- if (!renderFunction) return;
112
- return renderFunction;
113
- }, [renderItems?.[item.role]]);
114
-
115
- const RenderMessage = useCallback(
116
- ({ editableContent, data }: { data: ChatMessage; editableContent: ReactNode }) => {
117
- if (!renderMessages || !item?.role) return;
118
- let RenderFunction;
119
- if (renderMessages?.[item.role]) RenderFunction = renderMessages[item.role];
120
- if (!RenderFunction && renderMessages?.['default'])
121
- RenderFunction = renderMessages['default'];
122
- if (!RenderFunction) return;
123
- return <RenderFunction {...data} editableContent={editableContent} />;
124
- },
125
- [renderMessages?.[item.role]],
126
- );
127
-
128
- const MessageExtra = useCallback(
129
- ({ data }: { data: ChatMessage }) => {
130
- if (!renderMessagesExtra || !item?.role) return;
131
- let RenderFunction;
132
- if (renderMessagesExtra?.[item.role]) RenderFunction = renderMessagesExtra[item.role];
133
- if (renderMessagesExtra?.['default']) RenderFunction = renderMessagesExtra['default'];
134
- if (!RenderFunction) return;
135
- return <RenderFunction {...data} />;
136
- },
137
- [renderMessagesExtra?.[item.role]],
138
- );
139
-
140
- const ErrorMessage = useCallback(
141
- ({ data }: { data: ChatMessage }) => {
142
- if (!renderErrorMessages || !item?.error?.type) return;
143
- let RenderFunction;
144
- if (renderErrorMessages?.[item.error.type])
145
- RenderFunction = renderErrorMessages[item.error.type].Render;
146
- if (!RenderFunction && renderErrorMessages?.['default'])
147
- RenderFunction = renderErrorMessages['default'].Render;
148
- if (!RenderFunction) return;
149
- return <RenderFunction {...data} />;
150
- },
151
- [renderErrorMessages?.[item?.error?.type]],
152
- );
153
-
154
- const Actions = useCallback(
155
- ({ data }: { data: ChatMessage }) => {
156
- if (!renderActions || !item?.role) return;
157
- let RenderFunction;
158
- if (renderActions?.[item.role]) RenderFunction = renderActions[item.role];
159
- if (renderActions?.['default']) RenderFunction = renderActions['default'];
160
- if (!RenderFunction) RenderFunction = ActionsBar;
161
-
162
- const handleActionClick: ListItemProps['onActionsClick'] = async (action, data) => {
163
- switch (action.key) {
164
- case 'copy': {
165
- await copyToClipboard(data.content);
166
- message.success(text?.copySuccess || 'Copy Success');
167
- break;
168
- }
169
- case 'edit': {
170
- setEditing(true);
171
- }
172
- }
173
-
174
- onActionsClick?.(action, data);
175
- };
176
-
177
- return (
178
- <RenderFunction
179
- {...data}
180
- onActionClick={(actionKey) => handleActionClick?.(actionKey, data)}
181
- text={text}
182
- />
183
- );
184
- },
185
- [renderActions?.[item.role], text, onActionsClick],
186
- );
187
-
188
- const error = useMemo(() => {
189
- if (!item.error) return;
190
- const message = item.error?.message;
191
- let alertConfig = {};
192
- if (item.error.type && renderErrorMessages?.[item.error.type]) {
193
- alertConfig = renderErrorMessages[item.error.type]?.config as AlertProps;
194
- }
195
- return {
196
- message,
197
- ...alertConfig,
198
- };
199
- }, [renderErrorMessages, item.error]);
200
-
201
- if (RenderItem) return <RenderItem key={item.id} {...props} />;
202
-
203
- return (
204
- <ChatItem
205
- actions={<Actions data={item} />}
206
- avatar={item.meta}
207
- avatarAddon={groupNav}
208
- editing={editing}
209
- error={error}
210
- errorMessage={<ErrorMessage data={item} />}
211
- loading={loading}
212
- message={item.content}
213
- messageExtra={<MessageExtra data={item} />}
214
- onAvatarClick={onAvatarsClick?.(item.role)}
215
- onChange={(value) => onMessageChange?.(item.id, value)}
216
- onDoubleClick={(e) => {
217
- if (item.id === 'default' || item.error) return;
218
- if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
219
- setEditing(true);
220
- }
221
- }}
222
- onEditingChange={setEditing}
223
- placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
224
- primary={item.role === 'user'}
225
- renderMessage={(editableContent) => (
226
- <RenderMessage data={item} editableContent={editableContent} />
227
- )}
228
- showTitle={showTitle}
229
- text={text}
230
- time={item.updateAt || item.createAt}
231
- type={type === 'chat' ? 'block' : 'pure'}
232
- />
233
- );
234
- });
235
-
236
- export default Item;
@@ -1,83 +0,0 @@
1
- import isEqual from 'fast-deep-equal';
2
- import { memo } from 'react';
3
- import { useHotkeys } from 'react-hotkeys-hook';
4
- import { useTranslation } from 'react-i18next';
5
-
6
- import { PREFIX_KEY, REGENERATE_KEY } from '@/const/hotkeys';
7
- import { useChatStore } from '@/store/chat';
8
- import { chatSelectors } from '@/store/chat/selectors';
9
- import { useSessionStore } from '@/store/session';
10
- import { agentSelectors } from '@/store/session/selectors';
11
-
12
- import { renderActions, useActionsClick } from '../Actions';
13
- import { renderErrorMessages } from '../Error';
14
- import { renderMessagesExtra } from '../Extras';
15
- import { renderMessages, useAvatarsClick } from '../Messages';
16
- import ChatList from '../components/ChatList';
17
- import SkeletonList from '../components/SkeletonList';
18
-
19
- const List = memo(() => {
20
- const { t } = useTranslation('common');
21
- const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
22
- const data = useChatStore(chatSelectors.currentChatsWithGuideMessage(meta), isEqual);
23
-
24
- const [init, chatLoadingId, resendMessage, updateMessageContent] = useChatStore((s) => [
25
- s.messagesInit,
26
- s.chatLoadingId,
27
- s.resendMessage,
28
- s.updateMessageContent,
29
- ]);
30
-
31
- const [displayMode, enableHistoryCount, historyCount] = useSessionStore((s) => {
32
- const config = agentSelectors.currentAgentConfig(s);
33
- return [config.displayMode, config.enableHistoryCount, config.historyCount];
34
- });
35
- const onActionsClick = useActionsClick();
36
- const onAvatarsClick = useAvatarsClick();
37
-
38
- const hotkeys = [PREFIX_KEY, REGENERATE_KEY].join('+');
39
-
40
- useHotkeys(
41
- hotkeys,
42
- () => {
43
- const lastMessage = data.at(-1);
44
- if (!lastMessage || lastMessage.id === 'default' || lastMessage.role === 'system') return;
45
- resendMessage(lastMessage.id);
46
- },
47
- {
48
- enableOnFormTags: true,
49
- preventDefault: true,
50
- },
51
- );
52
-
53
- if (!init) return <SkeletonList />;
54
-
55
- return (
56
- <ChatList
57
- data={data as any}
58
- enableHistoryCount={enableHistoryCount}
59
- historyCount={historyCount}
60
- loadingId={chatLoadingId}
61
- onActionsClick={onActionsClick}
62
- onAvatarsClick={onAvatarsClick}
63
- onMessageChange={(id, content) => updateMessageContent(id, content)}
64
- renderActions={renderActions}
65
- renderErrorMessages={renderErrorMessages}
66
- renderMessages={renderMessages}
67
- renderMessagesExtra={renderMessagesExtra}
68
- text={{
69
- cancel: t('cancel'),
70
- confirm: t('ok'),
71
- copy: t('copy'),
72
- copySuccess: t('copySuccess'),
73
- delete: t('delete'),
74
- edit: t('edit'),
75
- history: t('historyRange'),
76
- regenerate: t('regenerate'),
77
- }}
78
- type={displayMode || 'chat'}
79
- />
80
- );
81
- });
82
-
83
- export default List;