@lobehub/chat 1.135.3 → 1.135.4
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 +33 -0
- package/changelog/v1.json +9 -0
- package/package.json +2 -2
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/settings/index.ts +1 -0
- package/packages/model-bank/src/aiModels/aihubmix.ts +25 -0
- package/packages/model-bank/src/aiModels/nvidia.ts +17 -1
- package/packages/model-bank/src/aiModels/openai.ts +80 -1
- package/packages/model-runtime/src/const/models.ts +2 -0
- package/packages/model-runtime/src/providers/openai/index.ts +15 -11
- package/packages/model-runtime/src/providers/openrouter/index.ts +7 -2
- package/packages/model-runtime/src/providers/openrouter/type.ts +4 -2
- package/packages/model-runtime/src/providers/vercelaigateway/index.ts +7 -0
- package/packages/model-runtime/src/utils/modelParse.ts +31 -3
- package/packages/types/src/aiProvider.ts +1 -2
- package/packages/types/src/discover/models.ts +3 -2
- package/packages/types/src/discover/providers.ts +3 -2
- package/packages/types/src/message/chat.ts +13 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +1 -5
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +1 -1
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/Form.tsx +1 -2
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +1 -4
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/SortModelModal/ListItem.tsx +1 -2
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/SortModelModal/index.tsx +1 -1
- package/src/{components → features}/ChatItem/ChatItem.tsx +5 -35
- package/src/{components → features}/ChatItem/components/MessageContent.tsx +27 -7
- package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeArtifact/Render/index.tsx +1 -1
- package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/remarkPlugins/getNodeContent.test.ts +1 -1
- package/src/features/Conversation/{Actions → Messages/Assistant/Actions}/Error.tsx +1 -1
- package/src/features/Conversation/{components/ChatItem/ActionsBar.tsx → Messages/Assistant/Actions/index.tsx} +71 -44
- package/src/features/Conversation/Messages/Assistant/Block.tsx +63 -0
- package/src/features/Conversation/{Extras/Assistant.test.tsx → Messages/Assistant/Extra/index.test.tsx} +36 -31
- package/src/features/Conversation/{Extras/Assistant.tsx → Messages/Assistant/Extra/index.tsx} +13 -7
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +102 -0
- package/src/features/Conversation/Messages/Assistant/index.tsx +235 -84
- package/src/features/Conversation/Messages/User/Actions.tsx +153 -0
- package/src/features/Conversation/Messages/User/BelowMessage.tsx +7 -2
- package/src/features/Conversation/{Extras/User.tsx → Messages/User/Extra.tsx} +9 -7
- package/src/features/Conversation/Messages/User/MessageContent.tsx +31 -0
- package/src/features/Conversation/Messages/User/index.tsx +127 -24
- package/src/features/Conversation/Messages/index.tsx +152 -0
- package/src/features/Conversation/{Extras → components/Extras}/Usage/UsageDetail/ModelCard.tsx +4 -3
- package/src/features/Conversation/{Extras → components/Extras}/Usage/UsageDetail/pricing.ts +3 -2
- package/src/features/Conversation/{Extras → components/Extras}/Usage/UsageDetail/tokens.test.ts +2 -3
- package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareImage/Preview.tsx +1 -1
- package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareText/index.tsx +1 -1
- package/src/features/Conversation/components/VirtualizedList/index.tsx +1 -0
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +29 -1
- package/src/features/Conversation/hooks/useDoubleClickEdit.ts +42 -0
- package/src/features/Conversation/index.ts +1 -1
- package/src/features/Conversation/types/{index.tsx → index.ts} +0 -7
- package/src/features/Portal/Thread/Chat/ChatItem.tsx +0 -7
- package/src/hooks/useUserAvatar.test.ts +129 -0
- package/src/hooks/useUserAvatar.ts +19 -0
- package/src/server/routers/lambda/aiModel.ts +7 -8
- package/src/store/user/slices/settings/selectors/settings.ts +6 -5
- package/src/features/ChatItem/index.tsx +0 -58
- package/src/features/Conversation/Actions/Assistant.tsx +0 -68
- package/src/features/Conversation/Actions/Fallback.tsx +0 -19
- package/src/features/Conversation/Actions/Tool.tsx +0 -33
- package/src/features/Conversation/Actions/User.tsx +0 -39
- package/src/features/Conversation/Actions/customAction.ts +0 -37
- package/src/features/Conversation/Actions/index.ts +0 -14
- package/src/features/Conversation/Extras/index.ts +0 -8
- package/src/features/Conversation/Extras/type.ts +0 -5
- package/src/features/Conversation/Messages/index.ts +0 -45
- package/src/features/Conversation/components/ChatItem/index.tsx +0 -358
- /package/src/{components → features}/ChatItem/components/Actions.tsx +0 -0
- /package/src/{components → features}/ChatItem/components/Avatar.tsx +0 -0
- /package/src/{components → features}/ChatItem/components/BorderSpacing.tsx +0 -0
- /package/src/{components → features}/ChatItem/components/ErrorContent.tsx +0 -0
- /package/src/{components → features}/ChatItem/components/Loading.tsx +0 -0
- /package/src/{components → features}/ChatItem/components/Title.tsx +0 -0
- /package/src/{components → features}/ChatItem/index.ts +0 -0
- /package/src/{components → features}/ChatItem/style.ts +0 -0
- /package/src/{components → features}/ChatItem/type.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeArtifact/Render/Icon.tsx +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeArtifact/index.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeArtifact/rehypePlugin.test.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeArtifact/rehypePlugin.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeThinking/Render.tsx +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LobeThinking/index.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LocalFile/Render/index.tsx +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/LocalFile/index.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/Thinking/Render.tsx +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/Thinking/index.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/index.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/remarkPlugins/createRemarkCustomTagPlugin.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/remarkPlugins/getNodeContent.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/type.ts +0 -0
- /package/src/features/Conversation/{components/MarkdownElements → MarkdownElements}/utils.ts +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/ExtraContainer.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/TTS/FilePlayer.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/TTS/InitPlayer.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/TTS/Player.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/TTS/index.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/Translate.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/Usage/UsageDetail/TokenProgress.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/Usage/UsageDetail/index.tsx +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/Usage/UsageDetail/tokens.ts +0 -0
- /package/src/features/Conversation/{Extras → components/Extras}/Usage/index.tsx +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareImage/index.tsx +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareImage/style.ts +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareImage/type.ts +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareText/Preview.tsx +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareText/template.test.ts +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareText/template.ts +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/ShareText/type.ts +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/index.tsx +0 -0
- /package/src/features/Conversation/components/{ChatItem/ShareMessageModal → ShareMessageModal}/style.ts +0 -0
- /package/src/features/Conversation/{components/ChatItem → context}/InPortalThreadContext.ts +0 -0
- /package/src/features/Conversation/{components/ChatItem/utils.test.ts → utils.test.ts} +0 -0
- /package/src/features/Conversation/{components/ChatItem/utils.ts → utils.ts} +0 -0
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { MarkdownProps } from '@lobehub/ui';
|
|
2
2
|
import { EditableMessage } from '@lobehub/ui/chat';
|
|
3
3
|
import { useResponsive } from 'antd-style';
|
|
4
|
-
import { type ReactNode, memo } from 'react';
|
|
4
|
+
import { type ReactNode, memo, useMemo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
5
6
|
import { Flexbox } from 'react-layout-kit';
|
|
6
7
|
|
|
8
|
+
import { useChatStore } from '@/store/chat';
|
|
9
|
+
import { useUserStore } from '@/store/user';
|
|
10
|
+
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
|
11
|
+
|
|
7
12
|
import { useStyles } from '../style';
|
|
8
13
|
import { ChatItemProps } from '../type';
|
|
9
14
|
|
|
10
15
|
export interface MessageContentProps {
|
|
11
16
|
editing?: ChatItemProps['editing'];
|
|
12
|
-
|
|
17
|
+
id: string;
|
|
13
18
|
markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
|
|
14
19
|
message?: ReactNode;
|
|
15
20
|
messageExtra?: ChatItemProps['messageExtra'];
|
|
@@ -19,16 +24,13 @@ export interface MessageContentProps {
|
|
|
19
24
|
placement?: ChatItemProps['placement'];
|
|
20
25
|
primary?: ChatItemProps['primary'];
|
|
21
26
|
renderMessage?: ChatItemProps['renderMessage'];
|
|
22
|
-
text?: ChatItemProps['text'];
|
|
23
27
|
variant?: ChatItemProps['variant'];
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
const MessageContent = memo<MessageContentProps>(
|
|
27
31
|
({
|
|
28
32
|
editing,
|
|
29
|
-
|
|
30
|
-
onEditingChange,
|
|
31
|
-
text,
|
|
33
|
+
id,
|
|
32
34
|
message,
|
|
33
35
|
placement,
|
|
34
36
|
messageExtra,
|
|
@@ -36,11 +38,29 @@ const MessageContent = memo<MessageContentProps>(
|
|
|
36
38
|
variant,
|
|
37
39
|
primary,
|
|
38
40
|
onDoubleClick,
|
|
39
|
-
fontSize,
|
|
40
41
|
markdownProps,
|
|
41
42
|
}) => {
|
|
43
|
+
const { t } = useTranslation('common');
|
|
42
44
|
const { cx, styles } = useStyles({ editing, placement, primary, variant });
|
|
45
|
+
const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
|
|
43
46
|
const { mobile } = useResponsive();
|
|
47
|
+
const text = useMemo(
|
|
48
|
+
() => ({
|
|
49
|
+
cancel: t('cancel'),
|
|
50
|
+
confirm: t('ok'),
|
|
51
|
+
edit: t('edit'),
|
|
52
|
+
}),
|
|
53
|
+
[],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const [toggleMessageEditing, updateMessageContent] = useChatStore((s) => [
|
|
57
|
+
s.toggleMessageEditing,
|
|
58
|
+
s.modifyMessageContent,
|
|
59
|
+
]);
|
|
60
|
+
const onChange = (value: string) => {
|
|
61
|
+
updateMessageContent(id, value);
|
|
62
|
+
};
|
|
63
|
+
const onEditingChange = (edit: boolean) => toggleMessageEditing(id, edit);
|
|
44
64
|
|
|
45
65
|
const content = (
|
|
46
66
|
<EditableMessage
|
|
@@ -10,7 +10,7 @@ import { useChatStore } from '@/store/chat';
|
|
|
10
10
|
import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
|
|
11
11
|
import { dotLoading } from '@/styles/loading';
|
|
12
12
|
|
|
13
|
-
import { InPortalThreadContext } from '../../../
|
|
13
|
+
import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
|
|
14
14
|
import { MarkdownElementProps } from '../../type';
|
|
15
15
|
import ArtifactIcon from './Icon';
|
|
16
16
|
|
|
@@ -2,7 +2,7 @@ import { toMarkdown } from 'mdast-util-to-markdown';
|
|
|
2
2
|
import { Parent } from 'unist';
|
|
3
3
|
import { expect } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import { treeNodeToString } from '@/features/Conversation/
|
|
5
|
+
import { treeNodeToString } from '@/features/Conversation/MarkdownElements/remarkPlugins/getNodeContent';
|
|
6
6
|
|
|
7
7
|
describe('treeNodeToString', () => {
|
|
8
8
|
it('with latex', () => {
|
|
@@ -2,7 +2,7 @@ import { ActionIconGroup } from '@lobehub/ui';
|
|
|
2
2
|
import type { ChatActionsBarProps } from '@lobehub/ui/chat';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
|
|
5
|
-
import { useChatListActionsBar } from '
|
|
5
|
+
import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
|
|
6
6
|
|
|
7
7
|
export const ErrorActionsBar = memo<ChatActionsBarProps>(({ onActionClick }) => {
|
|
8
8
|
const { regenerate, copy, edit, del, divider } = useChatListActionsBar();
|
|
@@ -1,43 +1,57 @@
|
|
|
1
|
-
import { ActionIconGroup, type ActionIconGroupEvent,
|
|
1
|
+
import { ActionIconGroup, type ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
|
|
2
2
|
import { App } from 'antd';
|
|
3
|
-
import isEqual from 'fast-deep-equal';
|
|
4
3
|
import { useSearchParams } from 'next/navigation';
|
|
5
|
-
import { memo, use, useCallback, useState } from 'react';
|
|
4
|
+
import { memo, use, useCallback, useContext, useMemo, useState } from 'react';
|
|
6
5
|
import { useTranslation } from 'react-i18next';
|
|
7
6
|
|
|
7
|
+
import ShareMessageModal from '@/features/Conversation/components/ShareMessageModal';
|
|
8
8
|
import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
|
|
9
9
|
import { useChatStore } from '@/store/chat';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { threadSelectors } from '@/store/chat/selectors';
|
|
11
|
+
import { ChatMessage } from '@/types/message';
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { useChatListActionsBar } from '
|
|
15
|
-
import
|
|
13
|
+
import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
|
|
14
|
+
import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
|
|
15
|
+
import { ErrorActionsBar } from './Error';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const ActionsBar = memo<ActionsBarProps>((props) => {
|
|
20
|
-
const { regenerate, edit, copy, divider, del } = useChatListActionsBar();
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<ActionIconGroup
|
|
24
|
-
items={[regenerate, edit]}
|
|
25
|
-
menu={{
|
|
26
|
-
items: [edit, copy, regenerate, divider, del],
|
|
27
|
-
}}
|
|
28
|
-
{...props}
|
|
29
|
-
/>
|
|
30
|
-
);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
interface ActionsProps {
|
|
17
|
+
interface AssistantActionsProps {
|
|
18
|
+
data: ChatMessage;
|
|
34
19
|
id: string;
|
|
35
|
-
inPortalThread?: boolean;
|
|
36
20
|
index: number;
|
|
37
21
|
}
|
|
22
|
+
export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, index }) => {
|
|
23
|
+
const { error, tools } = data;
|
|
24
|
+
const [isThreadMode, hasThread] = useChatStore((s) => [
|
|
25
|
+
!!s.activeThreadId,
|
|
26
|
+
threadSelectors.hasThreadBySourceMsgId(id)(s),
|
|
27
|
+
]);
|
|
28
|
+
const [showShareModal, setShareModal] = useState(false);
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
regenerate,
|
|
32
|
+
edit,
|
|
33
|
+
delAndRegenerate,
|
|
34
|
+
copy,
|
|
35
|
+
divider,
|
|
36
|
+
del,
|
|
37
|
+
branching,
|
|
38
|
+
// export: exportPDF,
|
|
39
|
+
share,
|
|
40
|
+
tts,
|
|
41
|
+
translate,
|
|
42
|
+
} = useChatListActionsBar({ hasThread });
|
|
43
|
+
|
|
44
|
+
const hasTools = !!tools;
|
|
45
|
+
|
|
46
|
+
const inPortalThread = useContext(InPortalThreadContext);
|
|
47
|
+
const inThread = isThreadMode || inPortalThread;
|
|
48
|
+
|
|
49
|
+
const items = useMemo(() => {
|
|
50
|
+
if (hasTools) return [delAndRegenerate, copy];
|
|
51
|
+
|
|
52
|
+
return [edit, copy, inThread ? null : branching].filter(Boolean) as ActionIconGroupItemType[];
|
|
53
|
+
}, [inThread, hasTools]);
|
|
38
54
|
|
|
39
|
-
const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
|
40
|
-
const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
|
|
41
55
|
const { t } = useTranslation('common');
|
|
42
56
|
const searchParams = useSearchParams();
|
|
43
57
|
const topic = searchParams.get('topic');
|
|
@@ -67,9 +81,7 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
|
|
67
81
|
const { message } = App.useApp();
|
|
68
82
|
const virtuosoRef = use(VirtuosoContext);
|
|
69
83
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const handleActionClick = useCallback(
|
|
84
|
+
const onActionClick = useCallback(
|
|
73
85
|
async (action: ActionIconGroupEvent) => {
|
|
74
86
|
switch (action.key) {
|
|
75
87
|
case 'edit': {
|
|
@@ -78,11 +90,11 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
|
|
78
90
|
virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
|
-
if (!
|
|
93
|
+
if (!data) return;
|
|
82
94
|
|
|
83
95
|
switch (action.key) {
|
|
84
96
|
case 'copy': {
|
|
85
|
-
await copyMessage(id,
|
|
97
|
+
await copyMessage(id, data.content);
|
|
86
98
|
message.success(t('copySuccess', { defaultValue: 'Copy Success' }));
|
|
87
99
|
break;
|
|
88
100
|
}
|
|
@@ -106,7 +118,7 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
|
|
106
118
|
} else regenerateMessage(id);
|
|
107
119
|
|
|
108
120
|
// if this message is an error message, we need to delete it
|
|
109
|
-
if (
|
|
121
|
+
if (data.error) deleteMessage(id);
|
|
110
122
|
break;
|
|
111
123
|
}
|
|
112
124
|
|
|
@@ -136,28 +148,45 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
if (action.keyPath.at(-1) === 'translate') {
|
|
139
|
-
// click the menu
|
|
151
|
+
// click the menu data with translate data, the result is:
|
|
140
152
|
// key: 'en-US'
|
|
141
153
|
// keyPath: ['en-US','translate']
|
|
142
154
|
const lang = action.keyPath[0];
|
|
143
155
|
translateMessage(id, lang);
|
|
144
156
|
}
|
|
145
157
|
},
|
|
146
|
-
[
|
|
158
|
+
[data],
|
|
147
159
|
);
|
|
148
160
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (!item) return null;
|
|
161
|
+
if (error) return <ErrorActionsBar onActionClick={onActionClick} />;
|
|
152
162
|
|
|
153
163
|
return (
|
|
154
164
|
<>
|
|
155
|
-
<
|
|
165
|
+
<ActionIconGroup
|
|
166
|
+
items={items}
|
|
167
|
+
menu={{
|
|
168
|
+
items: [
|
|
169
|
+
edit,
|
|
170
|
+
copy,
|
|
171
|
+
divider,
|
|
172
|
+
tts,
|
|
173
|
+
translate,
|
|
174
|
+
divider,
|
|
175
|
+
share,
|
|
176
|
+
// exportPDF,
|
|
177
|
+
divider,
|
|
178
|
+
regenerate,
|
|
179
|
+
delAndRegenerate,
|
|
180
|
+
del,
|
|
181
|
+
],
|
|
182
|
+
}}
|
|
183
|
+
onActionClick={onActionClick}
|
|
184
|
+
/>
|
|
156
185
|
{/*{showModal && (*/}
|
|
157
|
-
{/* <ExportPreview content={
|
|
186
|
+
{/* <ExportPreview content={data.content} onClose={() => setModal(false)} open={showModal} />*/}
|
|
158
187
|
{/*)}*/}
|
|
159
188
|
<ShareMessageModal
|
|
160
|
-
message={
|
|
189
|
+
message={data!}
|
|
161
190
|
onCancel={() => {
|
|
162
191
|
setShareModal(false);
|
|
163
192
|
}}
|
|
@@ -166,5 +195,3 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
|
|
|
166
195
|
</>
|
|
167
196
|
);
|
|
168
197
|
});
|
|
169
|
-
|
|
170
|
-
export default Actions;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Markdown } from '@lobehub/ui';
|
|
2
|
+
import { ReactNode, memo } from 'react';
|
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
|
4
|
+
|
|
5
|
+
import { LOADING_FLAT } from '@/const/message';
|
|
6
|
+
import ImageFileListViewer from '@/features/Conversation/Messages/User/ImageFileListViewer';
|
|
7
|
+
import { useChatStore } from '@/store/chat';
|
|
8
|
+
import { chatSelectors } from '@/store/chat/selectors';
|
|
9
|
+
import { AssistantContentBlock } from '@/types/message';
|
|
10
|
+
|
|
11
|
+
import { DefaultMessage } from '../Default';
|
|
12
|
+
import Tool from './Tool';
|
|
13
|
+
|
|
14
|
+
interface AssistantBlockProps extends AssistantContentBlock {
|
|
15
|
+
editableContent: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export const AssistantBlock = memo<AssistantBlockProps>(
|
|
18
|
+
({ id, tools, content, imageList, ...props }) => {
|
|
19
|
+
const editing = useChatStore(chatSelectors.isMessageEditing(id));
|
|
20
|
+
const generating = useChatStore(chatSelectors.isMessageGenerating(id));
|
|
21
|
+
|
|
22
|
+
console.log(id, content, props);
|
|
23
|
+
const isToolCallGenerating = generating && (content === LOADING_FLAT || !content) && !!tools;
|
|
24
|
+
|
|
25
|
+
const showImageItems = !!imageList && imageList.length > 0;
|
|
26
|
+
|
|
27
|
+
if (tools && tools.length > 0)
|
|
28
|
+
return (
|
|
29
|
+
<Flexbox gap={8}>
|
|
30
|
+
{tools.map((toolCall, index) => (
|
|
31
|
+
<Tool
|
|
32
|
+
apiName={toolCall.apiName}
|
|
33
|
+
arguments={toolCall.arguments}
|
|
34
|
+
id={toolCall.id}
|
|
35
|
+
identifier={toolCall.identifier}
|
|
36
|
+
index={index}
|
|
37
|
+
key={toolCall.id}
|
|
38
|
+
messageId={id}
|
|
39
|
+
payload={toolCall}
|
|
40
|
+
type={toolCall.type}
|
|
41
|
+
/>
|
|
42
|
+
))}
|
|
43
|
+
</Flexbox>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (editing)
|
|
47
|
+
return (
|
|
48
|
+
<DefaultMessage
|
|
49
|
+
content={content}
|
|
50
|
+
id={id}
|
|
51
|
+
isToolCallGenerating={isToolCallGenerating}
|
|
52
|
+
{...(props as any)}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Flexbox gap={8} id={id}>
|
|
58
|
+
<Markdown variant={'chat'}>{content}</Markdown>
|
|
59
|
+
{showImageItems && <ImageFileListViewer items={imageList} />}
|
|
60
|
+
</Flexbox>
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { agentSelectors } from '@/store/agent/selectors';
|
|
4
|
+
import { useChatStore } from '@/store/chat';
|
|
6
5
|
import { ChatMessage } from '@/types/message';
|
|
7
6
|
|
|
8
|
-
import { AssistantMessageExtra } from './
|
|
7
|
+
import { AssistantMessageExtra } from './index';
|
|
8
|
+
|
|
9
|
+
vi.mock('zustand/traditional');
|
|
9
10
|
|
|
10
11
|
// Mock TTS and Translate components
|
|
11
|
-
vi.mock('
|
|
12
|
+
vi.mock('@/features/Conversation/components/Extras/TTS', () => ({
|
|
12
13
|
default: vi.fn(() => <div>TTS Component</div>),
|
|
13
14
|
}));
|
|
14
|
-
vi.mock('
|
|
15
|
+
vi.mock('@/features/Conversation/components/Extras/Translate', () => ({
|
|
15
16
|
default: vi.fn(() => <div>Translate Component</div>),
|
|
16
17
|
}));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
vi.mock('@/store/agent', () => ({
|
|
20
|
-
useAgentStore: vi.fn(),
|
|
18
|
+
vi.mock('@/features/Conversation/components/Extras/Usage', () => ({
|
|
19
|
+
default: vi.fn(() => <div>Usage Component</div>),
|
|
21
20
|
}));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
|
|
22
|
+
// Mock store
|
|
23
|
+
vi.mock('@/store/chat', () => ({
|
|
24
|
+
useChatStore: vi.fn(),
|
|
26
25
|
}));
|
|
27
26
|
|
|
28
27
|
const mockData: ChatMessage = {
|
|
@@ -36,37 +35,38 @@ const mockData: ChatMessage = {
|
|
|
36
35
|
|
|
37
36
|
describe('AssistantMessageExtra', () => {
|
|
38
37
|
beforeEach(() => {
|
|
39
|
-
//
|
|
40
|
-
(
|
|
41
|
-
chatLoadingId: null,
|
|
42
|
-
}));
|
|
43
|
-
(agentSelectors.currentAgentModel as Mock).mockReturnValue('defaultModel');
|
|
38
|
+
// Mock useChatStore to return false for loading state
|
|
39
|
+
(useChatStore as unknown as Mock).mockReturnValue(false);
|
|
44
40
|
});
|
|
45
41
|
|
|
46
42
|
it('should not render content if extra is undefined', async () => {
|
|
47
43
|
render(<AssistantMessageExtra {...mockData} />);
|
|
48
|
-
expect(screen.queryByText('
|
|
44
|
+
expect(screen.queryByText('Usage Component')).toBeNull();
|
|
49
45
|
expect(screen.queryByText('TTS Component')).toBeNull();
|
|
50
46
|
expect(screen.queryByText('Translate Component')).toBeNull();
|
|
51
47
|
});
|
|
52
48
|
|
|
53
49
|
it('should not render content if extra is defined but does not contain fromModel, tts, or translate', async () => {
|
|
54
|
-
render(<AssistantMessageExtra {...mockData} />);
|
|
55
|
-
expect(screen.queryByText('
|
|
50
|
+
render(<AssistantMessageExtra {...mockData} extra={{}} />);
|
|
51
|
+
expect(screen.queryByText('Usage Component')).toBeNull();
|
|
56
52
|
expect(screen.queryByText('TTS Component')).toBeNull();
|
|
57
53
|
expect(screen.queryByText('Translate Component')).toBeNull();
|
|
58
54
|
});
|
|
59
55
|
|
|
60
|
-
it('should render
|
|
61
|
-
render(
|
|
56
|
+
it('should render Usage component if extra.fromModel exists', async () => {
|
|
57
|
+
render(
|
|
58
|
+
<AssistantMessageExtra
|
|
59
|
+
{...mockData}
|
|
60
|
+
extra={{ fromModel: 'gpt-4', fromProvider: 'openai' }}
|
|
61
|
+
/>,
|
|
62
|
+
);
|
|
62
63
|
|
|
63
|
-
expect(screen.getByText('
|
|
64
|
+
expect(screen.getByText('Usage Component')).toBeInTheDocument();
|
|
64
65
|
});
|
|
65
66
|
|
|
66
|
-
it('should render TTS component if extra.
|
|
67
|
-
render(<AssistantMessageExtra {...mockData} extra={{
|
|
67
|
+
it('should render TTS component if extra.tts exists', async () => {
|
|
68
|
+
render(<AssistantMessageExtra {...mockData} extra={{ tts: {} }} />);
|
|
68
69
|
|
|
69
|
-
expect(screen.getByText('otherModel')).toBeInTheDocument();
|
|
70
70
|
expect(screen.getByText('TTS Component')).toBeInTheDocument();
|
|
71
71
|
});
|
|
72
72
|
|
|
@@ -75,10 +75,15 @@ describe('AssistantMessageExtra', () => {
|
|
|
75
75
|
expect(screen.getByText('Translate Component')).toBeInTheDocument();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it('should
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
it('should render both TTS and Translate components when both exist in extra', async () => {
|
|
79
|
+
render(<AssistantMessageExtra {...mockData} extra={{ translate: { to: 'abc' }, tts: {} }} />);
|
|
80
|
+
expect(screen.getByText('TTS Component')).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByText('Translate Component')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should pass loading state to TTS and Translate components', async () => {
|
|
85
|
+
(useChatStore as unknown as Mock).mockReturnValue(true);
|
|
86
|
+
|
|
82
87
|
render(<AssistantMessageExtra {...mockData} extra={{ translate: { to: 'abc' }, tts: {} }} />);
|
|
83
88
|
expect(screen.getByText('TTS Component')).toBeInTheDocument();
|
|
84
89
|
expect(screen.getByText('Translate Component')).toBeInTheDocument();
|
package/src/features/Conversation/{Extras/Assistant.tsx → Messages/Assistant/Extra/index.tsx}
RENAMED
|
@@ -2,17 +2,23 @@ import { memo } from 'react';
|
|
|
2
2
|
import { Flexbox } from 'react-layout-kit';
|
|
3
3
|
|
|
4
4
|
import { LOADING_FLAT } from '@/const/message';
|
|
5
|
+
import ExtraContainer from '@/features/Conversation/components/Extras/ExtraContainer';
|
|
6
|
+
import TTS from '@/features/Conversation/components/Extras/TTS';
|
|
7
|
+
import Translate from '@/features/Conversation/components/Extras/Translate';
|
|
8
|
+
import Usage from '@/features/Conversation/components/Extras/Usage';
|
|
5
9
|
import { useChatStore } from '@/store/chat';
|
|
6
10
|
import { chatSelectors } from '@/store/chat/selectors';
|
|
7
|
-
import {
|
|
11
|
+
import { type MessageMetadata } from '@/types/message';
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
interface AssistantMessageExtraProps {
|
|
14
|
+
content: string;
|
|
15
|
+
extra?: any;
|
|
16
|
+
id: string;
|
|
17
|
+
metadata?: MessageMetadata | null;
|
|
18
|
+
tools?: any[];
|
|
19
|
+
}
|
|
14
20
|
|
|
15
|
-
export const AssistantMessageExtra
|
|
21
|
+
export const AssistantMessageExtra = memo<AssistantMessageExtraProps>(
|
|
16
22
|
({ extra, id, content, metadata, tools }) => {
|
|
17
23
|
const loading = useChatStore(chatSelectors.isMessageGenerating(id));
|
|
18
24
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ReactNode, memo } from 'react';
|
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
|
3
|
+
|
|
4
|
+
import { LOADING_FLAT } from '@/const/message';
|
|
5
|
+
import { AssistantBlock } from '@/features/Conversation/Messages/Assistant/Block';
|
|
6
|
+
import ImageFileListViewer from '@/features/Conversation/Messages/User/ImageFileListViewer';
|
|
7
|
+
import VideoFileListViewer from '@/features/Conversation/Messages/User/VideoFileListViewer';
|
|
8
|
+
import { useChatStore } from '@/store/chat';
|
|
9
|
+
import { aiChatSelectors, chatSelectors } from '@/store/chat/selectors';
|
|
10
|
+
import { ChatMessage } from '@/types/message';
|
|
11
|
+
|
|
12
|
+
import { DefaultMessage } from '../Default';
|
|
13
|
+
import FileChunks from './FileChunks';
|
|
14
|
+
import IntentUnderstanding from './IntentUnderstanding';
|
|
15
|
+
import Reasoning from './Reasoning';
|
|
16
|
+
import SearchGrounding from './SearchGrounding';
|
|
17
|
+
import Tool from './Tool';
|
|
18
|
+
|
|
19
|
+
export const AssistantMessageContent = memo<
|
|
20
|
+
ChatMessage & {
|
|
21
|
+
editableContent: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
>(({ id, tools, content, chunksList, search, imageList, videoList, children, ...props }) => {
|
|
24
|
+
const editing = useChatStore(chatSelectors.isMessageEditing(id));
|
|
25
|
+
const generating = useChatStore(chatSelectors.isMessageGenerating(id));
|
|
26
|
+
|
|
27
|
+
const isToolCallGenerating = generating && (content === LOADING_FLAT || !content) && !!tools;
|
|
28
|
+
|
|
29
|
+
const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
|
|
30
|
+
|
|
31
|
+
const isIntentUnderstanding = useChatStore(aiChatSelectors.isIntentUnderstanding(id));
|
|
32
|
+
|
|
33
|
+
const showSearch = !!search && !!search.citations?.length;
|
|
34
|
+
const showImageItems = !!imageList && imageList.length > 0;
|
|
35
|
+
const showVideoItems = !!videoList && videoList.length > 0;
|
|
36
|
+
|
|
37
|
+
// remove \n to avoid empty content
|
|
38
|
+
// refs: https://github.com/lobehub/lobe-chat/pull/6153
|
|
39
|
+
const showReasoning =
|
|
40
|
+
(!!props.reasoning && props.reasoning.content?.trim() !== '') ||
|
|
41
|
+
(!props.reasoning && isReasoning);
|
|
42
|
+
|
|
43
|
+
const showFileChunks = !!chunksList && chunksList.length > 0;
|
|
44
|
+
|
|
45
|
+
if (children && children?.length > 0)
|
|
46
|
+
return (
|
|
47
|
+
<Flexbox gap={8}>
|
|
48
|
+
{children.map((item) => (
|
|
49
|
+
<AssistantBlock key={item.id} {...item} editableContent={props.editableContent} />
|
|
50
|
+
))}
|
|
51
|
+
</Flexbox>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return editing ? (
|
|
55
|
+
<DefaultMessage
|
|
56
|
+
content={content}
|
|
57
|
+
id={id}
|
|
58
|
+
isToolCallGenerating={isToolCallGenerating}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
) : (
|
|
62
|
+
<Flexbox gap={8} id={id}>
|
|
63
|
+
{showSearch && (
|
|
64
|
+
<SearchGrounding citations={search?.citations} searchQueries={search?.searchQueries} />
|
|
65
|
+
)}
|
|
66
|
+
{showFileChunks && <FileChunks data={chunksList} />}
|
|
67
|
+
{showReasoning && <Reasoning {...props.reasoning} id={id} />}
|
|
68
|
+
{isIntentUnderstanding ? (
|
|
69
|
+
<IntentUnderstanding />
|
|
70
|
+
) : (
|
|
71
|
+
content && (
|
|
72
|
+
<DefaultMessage
|
|
73
|
+
addIdOnDOM={false}
|
|
74
|
+
content={content}
|
|
75
|
+
id={id}
|
|
76
|
+
isToolCallGenerating={isToolCallGenerating}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
)}
|
|
81
|
+
{showImageItems && <ImageFileListViewer items={imageList} />}
|
|
82
|
+
{showVideoItems && <VideoFileListViewer items={videoList} />}
|
|
83
|
+
{tools && (
|
|
84
|
+
<Flexbox gap={8}>
|
|
85
|
+
{tools.map((toolCall, index) => (
|
|
86
|
+
<Tool
|
|
87
|
+
apiName={toolCall.apiName}
|
|
88
|
+
arguments={toolCall.arguments}
|
|
89
|
+
id={toolCall.id}
|
|
90
|
+
identifier={toolCall.identifier}
|
|
91
|
+
index={index}
|
|
92
|
+
key={toolCall.id}
|
|
93
|
+
messageId={id}
|
|
94
|
+
payload={toolCall}
|
|
95
|
+
type={toolCall.type}
|
|
96
|
+
/>
|
|
97
|
+
))}
|
|
98
|
+
</Flexbox>
|
|
99
|
+
)}
|
|
100
|
+
</Flexbox>
|
|
101
|
+
);
|
|
102
|
+
});
|