@lobehub/chat 0.155.5 → 0.155.6
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 +25 -0
- package/package.json +1 -1
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +2 -1
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx +6 -6
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.tsx +2 -1
- package/src/app/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +2 -1
- package/src/components/ModelTag/ModelIcon.tsx +2 -2
- package/src/features/ChatInput/STT/browser.tsx +2 -1
- package/src/features/ChatInput/STT/openai.tsx +2 -1
- package/src/features/ChatInput/useChatInput.ts +2 -1
- package/src/features/ChatInput/useSend.ts +2 -1
- package/src/features/Conversation/Actions/customAction.ts +9 -0
- package/src/features/Conversation/Extras/Assistant.tsx +2 -1
- package/src/features/Conversation/Extras/User.tsx +2 -1
- package/src/features/Conversation/components/AutoScroll.tsx +1 -1
- package/src/features/Conversation/components/ChatItem/index.tsx +22 -12
- package/src/libs/agent-runtime/openrouter/index.ts +4 -1
- package/src/store/chat/slices/message/action.test.ts +52 -2
- package/src/store/chat/slices/message/action.ts +40 -13
- package/src/store/chat/slices/message/initialState.ts +13 -1
- package/src/store/chat/slices/message/selectors.test.ts +1 -1
- package/src/store/chat/slices/message/selectors.ts +10 -1
- package/src/store/chat/slices/plugin/action.test.ts +10 -2
- package/src/store/chat/slices/plugin/action.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.155.6](https://github.com/lobehub/lobe-chat/compare/v0.155.5...v0.155.6)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-05-08**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix editing long message issue.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix editing long message issue, closes [#2431](https://github.com/lobehub/lobe-chat/issues/2431) ([380d8da](https://github.com/lobehub/lobe-chat/commit/380d8da))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 0.155.5](https://github.com/lobehub/lobe-chat/compare/v0.155.4...v0.155.5)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2024-05-08**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.155.
|
|
3
|
+
"version": "0.155.6",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { useSendMessage } from '@/features/ChatInput/useSend';
|
|
|
13
13
|
import { useAgentStore } from '@/store/agent';
|
|
14
14
|
import { agentSelectors } from '@/store/agent/slices/chat';
|
|
15
15
|
import { useChatStore } from '@/store/chat';
|
|
16
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
16
17
|
import { useUserStore } from '@/store/user';
|
|
17
18
|
import { modelProviderSelectors, preferenceSelectors } from '@/store/user/selectors';
|
|
18
19
|
import { isMacOS } from '@/utils/platform';
|
|
@@ -60,7 +61,7 @@ const Footer = memo<FooterProps>(({ setExpand }) => {
|
|
|
60
61
|
const { theme, styles } = useStyles();
|
|
61
62
|
|
|
62
63
|
const [loading, stopGenerateMessage] = useChatStore((s) => [
|
|
63
|
-
|
|
64
|
+
chatSelectors.isAIGenerating(s),
|
|
64
65
|
s.stopGenerateMessage,
|
|
65
66
|
]);
|
|
66
67
|
|
package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.test.tsx
CHANGED
|
@@ -195,7 +195,7 @@ describe('<InputArea />', () => {
|
|
|
195
195
|
it('does not send message when loading or shift key is pressed', () => {
|
|
196
196
|
const sendMessageMock = vi.fn();
|
|
197
197
|
act(() => {
|
|
198
|
-
useChatStore.setState({
|
|
198
|
+
useChatStore.setState({ chatLoadingIds: ['123'], sendMessage: sendMessageMock });
|
|
199
199
|
});
|
|
200
200
|
|
|
201
201
|
render(<InputArea setExpand={setExpandMock} />);
|
|
@@ -209,7 +209,7 @@ describe('<InputArea />', () => {
|
|
|
209
209
|
const sendMessageMock = vi.fn();
|
|
210
210
|
act(() => {
|
|
211
211
|
useChatStore.setState({
|
|
212
|
-
|
|
212
|
+
chatLoadingIds: [],
|
|
213
213
|
inputMessage: 'abc',
|
|
214
214
|
sendMessage: sendMessageMock,
|
|
215
215
|
});
|
|
@@ -228,7 +228,7 @@ describe('<InputArea />', () => {
|
|
|
228
228
|
const sendMessageMock = vi.fn();
|
|
229
229
|
act(() => {
|
|
230
230
|
useChatStore.setState({
|
|
231
|
-
|
|
231
|
+
chatLoadingIds: [],
|
|
232
232
|
inputMessage: '123',
|
|
233
233
|
sendMessage: sendMessageMock,
|
|
234
234
|
});
|
|
@@ -247,7 +247,7 @@ describe('<InputArea />', () => {
|
|
|
247
247
|
const updateInputMessageMock = vi.fn();
|
|
248
248
|
act(() => {
|
|
249
249
|
useChatStore.setState({
|
|
250
|
-
|
|
250
|
+
chatLoadingIds: [],
|
|
251
251
|
inputMessage: 'Test',
|
|
252
252
|
sendMessage: sendMessageMock,
|
|
253
253
|
updateInputMessage: updateInputMessageMock,
|
|
@@ -271,7 +271,7 @@ describe('<InputArea />', () => {
|
|
|
271
271
|
const sendMessageMock = vi.fn();
|
|
272
272
|
act(() => {
|
|
273
273
|
useChatStore.setState({
|
|
274
|
-
|
|
274
|
+
chatLoadingIds: [],
|
|
275
275
|
inputMessage: '123',
|
|
276
276
|
sendMessage: sendMessageMock,
|
|
277
277
|
});
|
|
@@ -295,7 +295,7 @@ describe('<InputArea />', () => {
|
|
|
295
295
|
const updateInputMessageMock = vi.fn();
|
|
296
296
|
act(() => {
|
|
297
297
|
useChatStore.setState({
|
|
298
|
-
|
|
298
|
+
chatLoadingIds: [],
|
|
299
299
|
inputMessage: 'Test',
|
|
300
300
|
sendMessage: sendMessageMock,
|
|
301
301
|
updateInputMessage: updateInputMessageMock,
|
package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/TextArea.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import { useSendMessage } from '@/features/ChatInput/useSend';
|
|
8
8
|
import { useChatStore } from '@/store/chat';
|
|
9
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
9
10
|
import { useUserStore } from '@/store/user';
|
|
10
11
|
import { preferenceSelectors } from '@/store/user/selectors';
|
|
11
12
|
import { isCommandPressed } from '@/utils/keyboard';
|
|
@@ -42,7 +43,7 @@ const InputArea = memo<InputAreaProps>(({ setExpand }) => {
|
|
|
42
43
|
const isChineseInput = useRef(false);
|
|
43
44
|
|
|
44
45
|
const [loading, value, updateInputMessage] = useChatStore((s) => [
|
|
45
|
-
|
|
46
|
+
chatSelectors.isAIGenerating(s),
|
|
46
47
|
s.inputMessage,
|
|
47
48
|
s.updateInputMessage,
|
|
48
49
|
]);
|
|
@@ -5,6 +5,7 @@ import { shallow } from 'zustand/shallow';
|
|
|
5
5
|
import ModelTag from '@/components/ModelTag';
|
|
6
6
|
import { useAgentStore } from '@/store/agent';
|
|
7
7
|
import { useChatStore } from '@/store/chat';
|
|
8
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
8
9
|
import { useSessionStore } from '@/store/session';
|
|
9
10
|
import { sessionHelpers } from '@/store/session/helpers';
|
|
10
11
|
import { sessionMetaSelectors, sessionSelectors } from '@/store/session/selectors';
|
|
@@ -23,7 +24,7 @@ const SessionItem = memo<SessionItemProps>(({ id }) => {
|
|
|
23
24
|
const [defaultModel] = useAgentStore((s) => [s.defaultAgentConfig.model]);
|
|
24
25
|
|
|
25
26
|
const [active] = useSessionStore((s) => [s.activeId === id]);
|
|
26
|
-
const [loading] = useChatStore((s) => [
|
|
27
|
+
const [loading] = useChatStore((s) => [chatSelectors.isAIGenerating(s) && id === s.activeId]);
|
|
27
28
|
|
|
28
29
|
const [pin, title, description, avatar, avatarBackground, updateAt, model, group] =
|
|
29
30
|
useSessionStore((s) => {
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
Stability,
|
|
28
28
|
Tongyi,
|
|
29
29
|
Wenxin,
|
|
30
|
-
|
|
30
|
+
Yi,
|
|
31
31
|
} from '@lobehub/icons';
|
|
32
32
|
import { memo } from 'react';
|
|
33
33
|
|
|
@@ -54,7 +54,7 @@ const ModelIcon = memo<ModelIconProps>(({ model, size = 12 }) => {
|
|
|
54
54
|
if (model.includes('abab')) return <Minimax size={size} />;
|
|
55
55
|
if (model.includes('mistral') || model.includes('mixtral')) return <Mistral size={size} />;
|
|
56
56
|
if (model.includes('pplx') || model.includes('sonar')) return <Perplexity size={size} />;
|
|
57
|
-
if (model.includes('yi-')) return <
|
|
57
|
+
if (model.includes('yi-')) return <Yi size={size} />;
|
|
58
58
|
if (model.startsWith('openrouter')) return <OpenRouter size={size} />; // only for Cinematika and Auto
|
|
59
59
|
if (model.startsWith('openchat')) return <OpenChat size={size} />;
|
|
60
60
|
if (model.includes('command')) return <Cohere size={size} />;
|
|
@@ -7,6 +7,7 @@ import { SWRConfiguration } from 'swr';
|
|
|
7
7
|
import { useAgentStore } from '@/store/agent';
|
|
8
8
|
import { agentSelectors } from '@/store/agent/slices/chat';
|
|
9
9
|
import { useChatStore } from '@/store/chat';
|
|
10
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
10
11
|
import { useUserStore } from '@/store/user';
|
|
11
12
|
import { settingsSelectors } from '@/store/user/selectors';
|
|
12
13
|
import { ChatMessageError } from '@/types/message';
|
|
@@ -40,7 +41,7 @@ const BrowserSTT = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
40
41
|
const { t } = useTranslation('chat');
|
|
41
42
|
|
|
42
43
|
const [loading, updateInputMessage] = useChatStore((s) => [
|
|
43
|
-
|
|
44
|
+
chatSelectors.isAIGenerating(s),
|
|
44
45
|
s.updateInputMessage,
|
|
45
46
|
]);
|
|
46
47
|
|
|
@@ -10,6 +10,7 @@ import { API_ENDPOINTS } from '@/services/_url';
|
|
|
10
10
|
import { useAgentStore } from '@/store/agent';
|
|
11
11
|
import { agentSelectors } from '@/store/agent/selectors';
|
|
12
12
|
import { useChatStore } from '@/store/chat';
|
|
13
|
+
import { chatSelectors } from '@/store/chat/slices/message/selectors';
|
|
13
14
|
import { useUserStore } from '@/store/user';
|
|
14
15
|
import { settingsSelectors } from '@/store/user/selectors';
|
|
15
16
|
import { ChatMessageError } from '@/types/message';
|
|
@@ -51,7 +52,7 @@ const OpenaiSTT = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
51
52
|
const { t } = useTranslation('chat');
|
|
52
53
|
|
|
53
54
|
const [loading, updateInputMessage] = useChatStore((s) => [
|
|
54
|
-
|
|
55
|
+
chatSelectors.isAIGenerating(s),
|
|
55
56
|
s.updateInputMessage,
|
|
56
57
|
]);
|
|
57
58
|
|
|
@@ -4,6 +4,7 @@ import { useCallback, useRef, useState } from 'react';
|
|
|
4
4
|
import { useAgentStore } from '@/store/agent';
|
|
5
5
|
import { agentSelectors } from '@/store/agent/slices/chat';
|
|
6
6
|
import { useChatStore } from '@/store/chat';
|
|
7
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
7
8
|
import { useUserStore } from '@/store/user';
|
|
8
9
|
import { modelProviderSelectors } from '@/store/user/selectors';
|
|
9
10
|
|
|
@@ -18,7 +19,7 @@ export const useChatInput = () => {
|
|
|
18
19
|
const canUpload = useUserStore(modelProviderSelectors.isModelEnabledUpload(model));
|
|
19
20
|
|
|
20
21
|
const [loading, value, onInput, onStop] = useChatStore((s) => [
|
|
21
|
-
|
|
22
|
+
chatSelectors.isAIGenerating(s),
|
|
22
23
|
s.inputMessage,
|
|
23
24
|
s.updateInputMessage,
|
|
24
25
|
s.stopGenerateMessage,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useChatStore } from '@/store/chat';
|
|
4
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
4
5
|
import { SendMessageParams } from '@/store/chat/slices/message/action';
|
|
5
6
|
import { filesSelectors, useFileStore } from '@/store/file';
|
|
6
7
|
|
|
@@ -17,7 +18,7 @@ export const useSendMessage = () => {
|
|
|
17
18
|
|
|
18
19
|
return useCallback((params: UseSendMessageParams = {}) => {
|
|
19
20
|
const store = useChatStore.getState();
|
|
20
|
-
if (
|
|
21
|
+
if (chatSelectors.isAIGenerating(store)) return;
|
|
21
22
|
if (!store.inputMessage) return;
|
|
22
23
|
|
|
23
24
|
const imageList = filesSelectors.imageUrlOrBase64List(useFileStore.getState());
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { ActionIconGroupItems } from '@lobehub/ui/es/ActionIconGroup';
|
|
2
|
+
import { css, cx } from 'antd-style';
|
|
2
3
|
import { LanguagesIcon, Play } from 'lucide-react';
|
|
3
4
|
import { useMemo } from 'react';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
5
6
|
|
|
6
7
|
import { localeOptions } from '@/locales/resources';
|
|
7
8
|
|
|
9
|
+
const translateStyle = css`
|
|
10
|
+
.ant-dropdown-menu-sub {
|
|
11
|
+
overflow-y: scroll;
|
|
12
|
+
max-height: 400px;
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
8
16
|
export const useCustomActions = () => {
|
|
9
17
|
const { t } = useTranslation('chat');
|
|
10
18
|
|
|
@@ -16,6 +24,7 @@ export const useCustomActions = () => {
|
|
|
16
24
|
icon: LanguagesIcon,
|
|
17
25
|
key: 'translate',
|
|
18
26
|
label: t('translate.action'),
|
|
27
|
+
popupClassName: cx(translateStyle),
|
|
19
28
|
} as ActionIconGroupItems;
|
|
20
29
|
|
|
21
30
|
const tts = {
|
|
@@ -5,6 +5,7 @@ import ModelTag from '@/components/ModelTag';
|
|
|
5
5
|
import { useAgentStore } from '@/store/agent';
|
|
6
6
|
import { agentSelectors } from '@/store/agent/slices/chat';
|
|
7
7
|
import { useChatStore } from '@/store/chat';
|
|
8
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
8
9
|
import { ChatMessage } from '@/types/message';
|
|
9
10
|
|
|
10
11
|
import { RenderMessageExtra } from '../types';
|
|
@@ -15,7 +16,7 @@ import Translate from './Translate';
|
|
|
15
16
|
export const AssistantMessageExtra: RenderMessageExtra = memo<ChatMessage>(
|
|
16
17
|
({ extra, id, content }) => {
|
|
17
18
|
const model = useAgentStore(agentSelectors.currentAgentModel);
|
|
18
|
-
const loading = useChatStore((
|
|
19
|
+
const loading = useChatStore(chatSelectors.isMessageGenerating(id));
|
|
19
20
|
|
|
20
21
|
const showModelTag = extra?.fromModel && model !== extra?.fromModel;
|
|
21
22
|
const showTranslate = !!extra?.translate;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useChatStore } from '@/store/chat';
|
|
4
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
4
5
|
import { ChatMessage } from '@/types/message';
|
|
5
6
|
|
|
6
7
|
import { RenderMessageExtra } from '../types';
|
|
@@ -9,7 +10,7 @@ import TTS from './TTS';
|
|
|
9
10
|
import Translate from './Translate';
|
|
10
11
|
|
|
11
12
|
export const UserMessageExtra: RenderMessageExtra = memo<ChatMessage>(({ extra, id, content }) => {
|
|
12
|
-
const loading = useChatStore((
|
|
13
|
+
const loading = useChatStore(chatSelectors.isMessageGenerating(id));
|
|
13
14
|
|
|
14
15
|
const showTranslate = !!extra?.translate;
|
|
15
16
|
const showTTS = !!extra?.tts;
|
|
@@ -11,7 +11,7 @@ interface AutoScrollProps {
|
|
|
11
11
|
onScrollToBottom: (type: 'auto' | 'click') => void;
|
|
12
12
|
}
|
|
13
13
|
const AutoScroll = memo<AutoScrollProps>(({ atBottom, isScrolling, onScrollToBottom }) => {
|
|
14
|
-
const trackVisibility = useChatStore(
|
|
14
|
+
const trackVisibility = useChatStore(chatSelectors.isAIGenerating);
|
|
15
15
|
const str = useChatStore(chatSelectors.chatsMessageString);
|
|
16
16
|
|
|
17
17
|
useEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AlertProps, ChatItem } from '@lobehub/ui';
|
|
2
2
|
import { createStyles } from 'antd-style';
|
|
3
3
|
import isEqual from 'fast-deep-equal';
|
|
4
|
-
import { ReactNode, memo, useCallback, useMemo
|
|
4
|
+
import { ReactNode, memo, useCallback, useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { useAgentStore } from '@/store/agent';
|
|
@@ -41,7 +41,6 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
|
41
41
|
const fontSize = useUserStore((s) => settingsSelectors.currentSettings(s).fontSize);
|
|
42
42
|
const { t } = useTranslation('common');
|
|
43
43
|
const { styles, cx } = useStyles();
|
|
44
|
-
const [editing, setEditing] = useState(false);
|
|
45
44
|
const [type = 'chat'] = useAgentStore((s) => {
|
|
46
45
|
const config = agentSelectors.currentAgentConfig(s);
|
|
47
46
|
return [config.displayMode];
|
|
@@ -58,12 +57,14 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
|
58
57
|
|
|
59
58
|
const historyLength = useChatStore((s) => chatSelectors.currentChats(s).length);
|
|
60
59
|
|
|
61
|
-
const [
|
|
62
|
-
s
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
const [isMessageLoading, generating, editing, toggleMessageEditing, updateMessageContent] =
|
|
61
|
+
useChatStore((s) => [
|
|
62
|
+
chatSelectors.isMessageLoading(id)(s),
|
|
63
|
+
chatSelectors.isMessageGenerating(id)(s),
|
|
64
|
+
chatSelectors.isMessageEditing(id)(s),
|
|
65
|
+
s.toggleMessageEditing,
|
|
66
|
+
s.modifyMessageContent,
|
|
67
|
+
]);
|
|
67
68
|
|
|
68
69
|
const onAvatarsClick = useAvatarsClick();
|
|
69
70
|
|
|
@@ -115,14 +116,21 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
|
115
116
|
<>
|
|
116
117
|
<HistoryDivider enable={enableHistoryDivider} />
|
|
117
118
|
<ChatItem
|
|
118
|
-
actions={
|
|
119
|
+
actions={
|
|
120
|
+
<ActionsBar
|
|
121
|
+
index={index}
|
|
122
|
+
setEditing={(edit) => {
|
|
123
|
+
toggleMessageEditing(id, edit);
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
}
|
|
119
127
|
avatar={item.meta}
|
|
120
128
|
className={cx(styles.message, isMessageLoading && styles.loading)}
|
|
121
129
|
editing={editing}
|
|
122
130
|
error={error}
|
|
123
131
|
errorMessage={<ErrorMessageExtra data={item} />}
|
|
124
132
|
fontSize={fontSize}
|
|
125
|
-
loading={
|
|
133
|
+
loading={generating}
|
|
126
134
|
message={item.content}
|
|
127
135
|
messageExtra={<MessageExtra data={item} />}
|
|
128
136
|
onAvatarClick={onAvatarsClick?.(item.role)}
|
|
@@ -130,10 +138,12 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
|
130
138
|
onDoubleClick={(e) => {
|
|
131
139
|
if (item.id === 'default' || item.error) return;
|
|
132
140
|
if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
|
|
133
|
-
|
|
141
|
+
toggleMessageEditing(id, true);
|
|
134
142
|
}
|
|
135
143
|
}}
|
|
136
|
-
onEditingChange={
|
|
144
|
+
onEditingChange={(edit) => {
|
|
145
|
+
toggleMessageEditing(id, edit);
|
|
146
|
+
}}
|
|
137
147
|
placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
|
|
138
148
|
primary={item.role === 'user'}
|
|
139
149
|
renderMessage={(editableContent) => (
|
|
@@ -36,7 +36,10 @@ export const LobeOpenRouterAI = LobeOpenAICompatibleFactory({
|
|
|
36
36
|
? model.top_provider.max_completion_tokens
|
|
37
37
|
: undefined,
|
|
38
38
|
tokens: model.context_length,
|
|
39
|
-
vision:
|
|
39
|
+
vision:
|
|
40
|
+
model.description.includes('vision') ||
|
|
41
|
+
model.description.includes('multimodal') ||
|
|
42
|
+
model.id.includes('vision'),
|
|
40
43
|
};
|
|
41
44
|
},
|
|
42
45
|
},
|
|
@@ -365,6 +365,31 @@ describe('chatMessage actions', () => {
|
|
|
365
365
|
});
|
|
366
366
|
});
|
|
367
367
|
|
|
368
|
+
describe('toggleMessageEditing action', () => {
|
|
369
|
+
it('should add message id to messageEditingIds when editing is true', () => {
|
|
370
|
+
const { result } = renderHook(() => useChatStore());
|
|
371
|
+
const messageId = 'message-id';
|
|
372
|
+
|
|
373
|
+
act(() => {
|
|
374
|
+
result.current.toggleMessageEditing(messageId, true);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
expect(result.current.messageEditingIds).toContain(messageId);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should remove message id from messageEditingIds when editing is false', () => {
|
|
381
|
+
const { result } = renderHook(() => useChatStore());
|
|
382
|
+
const messageId = 'abc';
|
|
383
|
+
|
|
384
|
+
act(() => {
|
|
385
|
+
result.current.toggleMessageEditing(messageId, true);
|
|
386
|
+
result.current.toggleMessageEditing(messageId, false);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(result.current.messageEditingIds).not.toContain(messageId);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
368
393
|
describe('internal_resendMessage action', () => {
|
|
369
394
|
it('should resend a message by id and refresh messages', async () => {
|
|
370
395
|
const { result } = renderHook(() => useChatStore());
|
|
@@ -701,7 +726,7 @@ describe('chatMessage actions', () => {
|
|
|
701
726
|
|
|
702
727
|
const state = useChatStore.getState();
|
|
703
728
|
expect(state.abortController).toBeInstanceOf(AbortController);
|
|
704
|
-
expect(state.
|
|
729
|
+
expect(state.chatLoadingIds).toEqual(['message-id']);
|
|
705
730
|
});
|
|
706
731
|
|
|
707
732
|
it('should clear loading state and abort controller when loading is false', () => {
|
|
@@ -720,7 +745,7 @@ describe('chatMessage actions', () => {
|
|
|
720
745
|
|
|
721
746
|
const state = useChatStore.getState();
|
|
722
747
|
expect(state.abortController).toBeUndefined();
|
|
723
|
-
expect(state.
|
|
748
|
+
expect(state.chatLoadingIds).toEqual([]);
|
|
724
749
|
});
|
|
725
750
|
|
|
726
751
|
it('should attach beforeunload event listener when loading starts', () => {
|
|
@@ -760,4 +785,29 @@ describe('chatMessage actions', () => {
|
|
|
760
785
|
expect(state.abortController).toEqual(abortController);
|
|
761
786
|
});
|
|
762
787
|
});
|
|
788
|
+
|
|
789
|
+
describe('internal_toggleMessageLoading action', () => {
|
|
790
|
+
it('should add message id to messageLoadingIds when loading is true', () => {
|
|
791
|
+
const { result } = renderHook(() => useChatStore());
|
|
792
|
+
const messageId = 'message-id';
|
|
793
|
+
|
|
794
|
+
act(() => {
|
|
795
|
+
result.current.internal_toggleMessageLoading(true, messageId);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
expect(result.current.messageLoadingIds).toContain(messageId);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('should remove message id from messageLoadingIds when loading is false', () => {
|
|
802
|
+
const { result } = renderHook(() => useChatStore());
|
|
803
|
+
const messageId = 'ddd-id';
|
|
804
|
+
|
|
805
|
+
act(() => {
|
|
806
|
+
result.current.internal_toggleMessageLoading(true, messageId);
|
|
807
|
+
result.current.internal_toggleMessageLoading(false, messageId);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
expect(result.current.messageLoadingIds).not.toContain(messageId);
|
|
811
|
+
});
|
|
812
|
+
});
|
|
763
813
|
});
|
|
@@ -72,7 +72,7 @@ export interface ChatMessageAction {
|
|
|
72
72
|
stopGenerateMessage: () => void;
|
|
73
73
|
copyMessage: (id: string, content: string) => Promise<void>;
|
|
74
74
|
refreshMessages: () => Promise<void>;
|
|
75
|
-
|
|
75
|
+
toggleMessageEditing: (id: string, editing: boolean) => void;
|
|
76
76
|
// ========= ↓ Internal Method ↓ ========== //
|
|
77
77
|
// ========================================== //
|
|
78
78
|
// ========================================== //
|
|
@@ -137,6 +137,18 @@ const preventLeavingFn = (e: BeforeUnloadEvent) => {
|
|
|
137
137
|
e.returnValue = '你有正在生成中的请求,确定要离开吗?';
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
+
const toggleBooleanList = (ids: string[], id: string, loading: boolean) => {
|
|
141
|
+
return produce(ids, (draft) => {
|
|
142
|
+
if (loading) {
|
|
143
|
+
draft.push(id);
|
|
144
|
+
} else {
|
|
145
|
+
const index = draft.indexOf(id);
|
|
146
|
+
|
|
147
|
+
if (index >= 0) draft.splice(index, 1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
140
152
|
export const chatMessage: StateCreator<
|
|
141
153
|
ChatStore,
|
|
142
154
|
[['zustand/devtools', never]],
|
|
@@ -244,7 +256,13 @@ export const chatMessage: StateCreator<
|
|
|
244
256
|
|
|
245
257
|
get().internal_traceMessage(id, { eventType: TraceEventType.CopyMessage });
|
|
246
258
|
},
|
|
247
|
-
|
|
259
|
+
toggleMessageEditing: (id, editing) => {
|
|
260
|
+
set(
|
|
261
|
+
{ messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
|
|
262
|
+
false,
|
|
263
|
+
'toggleMessageEditing',
|
|
264
|
+
);
|
|
265
|
+
},
|
|
248
266
|
stopGenerateMessage: () => {
|
|
249
267
|
const { abortController, internal_toggleChatLoading } = get();
|
|
250
268
|
if (!abortController) return;
|
|
@@ -518,11 +536,28 @@ export const chatMessage: StateCreator<
|
|
|
518
536
|
window.addEventListener('beforeunload', preventLeavingFn);
|
|
519
537
|
|
|
520
538
|
const abortController = new AbortController();
|
|
521
|
-
set(
|
|
539
|
+
set(
|
|
540
|
+
{
|
|
541
|
+
abortController,
|
|
542
|
+
chatLoadingIds: toggleBooleanList(get().messageLoadingIds, id!, loading),
|
|
543
|
+
},
|
|
544
|
+
false,
|
|
545
|
+
action,
|
|
546
|
+
);
|
|
522
547
|
|
|
523
548
|
return abortController;
|
|
524
549
|
} else {
|
|
525
|
-
|
|
550
|
+
if (!id) {
|
|
551
|
+
set({ abortController: undefined, chatLoadingIds: [] }, false, action);
|
|
552
|
+
} else
|
|
553
|
+
set(
|
|
554
|
+
{
|
|
555
|
+
abortController: undefined,
|
|
556
|
+
chatLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
|
|
557
|
+
},
|
|
558
|
+
false,
|
|
559
|
+
action,
|
|
560
|
+
);
|
|
526
561
|
|
|
527
562
|
window.removeEventListener('beforeunload', preventLeavingFn);
|
|
528
563
|
}
|
|
@@ -530,15 +565,7 @@ export const chatMessage: StateCreator<
|
|
|
530
565
|
internal_toggleMessageLoading: (loading, id) => {
|
|
531
566
|
set(
|
|
532
567
|
{
|
|
533
|
-
messageLoadingIds:
|
|
534
|
-
if (loading) {
|
|
535
|
-
draft.push(id);
|
|
536
|
-
} else {
|
|
537
|
-
const index = draft.indexOf(id);
|
|
538
|
-
|
|
539
|
-
if (index >= 0) draft.splice(index, 1);
|
|
540
|
-
}
|
|
541
|
-
}),
|
|
568
|
+
messageLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
|
|
542
569
|
},
|
|
543
570
|
false,
|
|
544
571
|
'internal_toggleMessageLoading',
|
|
@@ -7,8 +7,18 @@ export interface ChatMessageState {
|
|
|
7
7
|
* @description 当前正在编辑或查看的会话
|
|
8
8
|
*/
|
|
9
9
|
activeId: string;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* is the AI message is generating
|
|
12
|
+
*/
|
|
13
|
+
chatLoadingIds: string[];
|
|
11
14
|
inputMessage: string;
|
|
15
|
+
/**
|
|
16
|
+
* is the message is editing
|
|
17
|
+
*/
|
|
18
|
+
messageEditingIds: string[];
|
|
19
|
+
/**
|
|
20
|
+
* is the message is creating or updating in the service
|
|
21
|
+
*/
|
|
12
22
|
messageLoadingIds: string[];
|
|
13
23
|
messages: ChatMessage[];
|
|
14
24
|
/**
|
|
@@ -19,7 +29,9 @@ export interface ChatMessageState {
|
|
|
19
29
|
|
|
20
30
|
export const initialMessageState: ChatMessageState = {
|
|
21
31
|
activeId: 'inbox',
|
|
32
|
+
chatLoadingIds: [],
|
|
22
33
|
inputMessage: '',
|
|
34
|
+
messageEditingIds: [],
|
|
23
35
|
messageLoadingIds: [],
|
|
24
36
|
messages: [],
|
|
25
37
|
messagesInit: false,
|
|
@@ -107,7 +107,7 @@ describe('chatSelectors', () => {
|
|
|
107
107
|
it('should return the properties of a function message', () => {
|
|
108
108
|
const state = merge(initialStore, {
|
|
109
109
|
messages: mockMessages,
|
|
110
|
-
|
|
110
|
+
chatLoadingIds: ['msg3'], // Assuming this id represents a loading state
|
|
111
111
|
});
|
|
112
112
|
const props = chatSelectors.getFunctionMessageProps(mockMessages[2])(state);
|
|
113
113
|
expect(props).toEqual({
|
|
@@ -123,7 +123,7 @@ const getFunctionMessageProps =
|
|
|
123
123
|
command: plugin,
|
|
124
124
|
content,
|
|
125
125
|
id: plugin?.identifier,
|
|
126
|
-
loading:
|
|
126
|
+
loading: s.chatLoadingIds.includes(id),
|
|
127
127
|
type: plugin?.type as LobePluginType,
|
|
128
128
|
});
|
|
129
129
|
|
|
@@ -134,6 +134,11 @@ const latestMessage = (s: ChatStore) => currentChats(s).at(-1);
|
|
|
134
134
|
|
|
135
135
|
const currentChatLoadingState = (s: ChatStore) => !s.messagesInit;
|
|
136
136
|
|
|
137
|
+
const isMessageEditing = (id: string) => (s: ChatStore) => s.messageEditingIds.includes(id);
|
|
138
|
+
const isMessageLoading = (id: string) => (s: ChatStore) => s.messageLoadingIds.includes(id);
|
|
139
|
+
const isMessageGenerating = (id: string) => (s: ChatStore) => s.chatLoadingIds.includes(id);
|
|
140
|
+
const isAIGenerating = (s: ChatStore) => s.chatLoadingIds.length > 0;
|
|
141
|
+
|
|
137
142
|
export const chatSelectors = {
|
|
138
143
|
chatsMessageString,
|
|
139
144
|
currentChatIDsWithGuideMessage,
|
|
@@ -145,6 +150,10 @@ export const chatSelectors = {
|
|
|
145
150
|
getFunctionMessageProps,
|
|
146
151
|
getMessageById,
|
|
147
152
|
getTraceIdByMessageId,
|
|
153
|
+
isAIGenerating,
|
|
154
|
+
isMessageEditing,
|
|
155
|
+
isMessageGenerating,
|
|
156
|
+
isMessageLoading,
|
|
148
157
|
latestMessage,
|
|
149
158
|
showInboxWelcome,
|
|
150
159
|
};
|
|
@@ -131,7 +131,11 @@ describe('ChatPluginAction', () => {
|
|
|
131
131
|
});
|
|
132
132
|
expect(storeState.refreshMessages).toHaveBeenCalled();
|
|
133
133
|
expect(storeState.triggerAIMessage).toHaveBeenCalled();
|
|
134
|
-
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
134
|
+
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
135
|
+
false,
|
|
136
|
+
'message-id',
|
|
137
|
+
'plugin/fetchPlugin/end',
|
|
138
|
+
);
|
|
135
139
|
});
|
|
136
140
|
|
|
137
141
|
it('should handle errors when the plugin API call fails', async () => {
|
|
@@ -159,7 +163,11 @@ describe('ChatPluginAction', () => {
|
|
|
159
163
|
expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { trace: {} });
|
|
160
164
|
expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error);
|
|
161
165
|
expect(storeState.refreshMessages).toHaveBeenCalled();
|
|
162
|
-
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
166
|
+
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
167
|
+
false,
|
|
168
|
+
'message-id',
|
|
169
|
+
'plugin/fetchPlugin/end',
|
|
170
|
+
);
|
|
163
171
|
expect(storeState.triggerAIMessage).not.toHaveBeenCalled(); // 确保在错误情况下不调用此方法
|
|
164
172
|
});
|
|
165
173
|
});
|
|
@@ -135,7 +135,11 @@ export const chatPlugin: StateCreator<
|
|
|
135
135
|
let data: string;
|
|
136
136
|
|
|
137
137
|
try {
|
|
138
|
-
const abortController = internal_toggleChatLoading(
|
|
138
|
+
const abortController = internal_toggleChatLoading(
|
|
139
|
+
true,
|
|
140
|
+
id,
|
|
141
|
+
n('fetchPlugin/start') as string,
|
|
142
|
+
);
|
|
139
143
|
|
|
140
144
|
const message = chatSelectors.getMessageById(id)(get());
|
|
141
145
|
|
|
@@ -162,7 +166,7 @@ export const chatPlugin: StateCreator<
|
|
|
162
166
|
data = '';
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
internal_toggleChatLoading(false);
|
|
169
|
+
internal_toggleChatLoading(false, id, n('fetchPlugin/end') as string);
|
|
166
170
|
// 如果报错则结束了
|
|
167
171
|
if (!data) return;
|
|
168
172
|
|