@lobehub/lobehub 2.0.0-next.24 → 2.0.0-next.25

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 (37) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/package.json +1 -1
  4. package/src/app/[variants]/(main)/labs/page.tsx +9 -8
  5. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +152 -0
  6. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +70 -0
  7. package/src/features/Conversation/Messages/Group/Actions/index.tsx +21 -0
  8. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +91 -0
  9. package/src/features/Conversation/Messages/Group/EditState.tsx +51 -0
  10. package/src/features/Conversation/Messages/Group/Error/index.tsx +53 -0
  11. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +73 -0
  12. package/src/features/Conversation/Messages/Group/MessageContent.tsx +39 -0
  13. package/src/features/Conversation/Messages/Group/Tool/Inspector/BuiltinPluginTitle.tsx +49 -0
  14. package/src/features/Conversation/Messages/Group/Tool/Inspector/Debug.tsx +70 -0
  15. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginResult.tsx +34 -0
  16. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginState.tsx +18 -0
  17. package/src/features/Conversation/Messages/Group/Tool/Inspector/Settings.tsx +40 -0
  18. package/src/features/Conversation/Messages/Group/Tool/Inspector/ToolTitle.tsx +92 -0
  19. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +176 -0
  20. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
  21. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ValueCell.tsx +43 -0
  22. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/index.tsx +134 -0
  23. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +88 -0
  24. package/src/features/Conversation/Messages/Group/Tool/Render/ErrorResponse.tsx +35 -0
  25. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +29 -0
  26. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +66 -0
  27. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +105 -0
  28. package/src/features/Conversation/Messages/Group/Tool/index.tsx +75 -0
  29. package/src/features/Conversation/Messages/Group/Tools.tsx +46 -0
  30. package/src/features/Conversation/Messages/Group/index.tsx +140 -0
  31. package/src/features/Conversation/Messages/index.tsx +12 -0
  32. package/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx +2 -2
  33. package/src/services/chat/contextEngineering.ts +6 -5
  34. package/src/services/message/server.ts +10 -0
  35. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +309 -2
  36. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +2 -22
  37. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +272 -14
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.25](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.24...v2.0.0-next.25)
6
+
7
+ <sup>Released on **2025-11-04**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Display assistant message in group.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Display assistant message in group, closes [#9941](https://github.com/lobehub/lobe-chat/issues/9941) ([59b6ac3](https://github.com/lobehub/lobe-chat/commit/59b6ac3))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.24](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.23...v2.0.0-next.24)
6
31
 
7
32
  <sup>Released on **2025-11-04**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Display assistant message in group."
6
+ ]
7
+ },
8
+ "date": "2025-11-04",
9
+ "version": "2.0.0-next.25"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.24",
3
+ "version": "2.0.0-next.25",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent 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",
@@ -24,13 +24,13 @@ const LabsPage = memo(() => {
24
24
  const [
25
25
  isPreferenceInit,
26
26
  enableInputMarkdown,
27
- // enableAssistantMessageGroup,
27
+ enableAssistantMessageGroup,
28
28
  // enableGroupChat,
29
29
  updateLab,
30
30
  ] = useUserStore((s) => [
31
31
  preferenceSelectors.isPreferenceInit(s),
32
32
  labPreferSelectors.enableInputMarkdown(s),
33
- // labPreferSelectors.enableAssistantMessageGroup(s),
33
+ labPreferSelectors.enableAssistantMessageGroup(s),
34
34
  // labPreferSelectors.enableGroupChat(s),
35
35
  s.updateLab,
36
36
  ]);
@@ -43,12 +43,13 @@ const LabsPage = memo(() => {
43
43
  key: 'enableInputMarkdown',
44
44
  title: t('features.inputMarkdown.title'),
45
45
  },
46
- // {
47
- // checked: enableAssistantMessageGroup,
48
- // desc: t('features.assistantMessageGroup.desc'),
49
- // key: 'enableAssistantMessageGroup',
50
- // title: t('features.assistantMessageGroup.title'),
51
- // },
46
+ {
47
+ checked: enableAssistantMessageGroup,
48
+ cover: 'https://github.com/user-attachments/assets/ba517751-1f3b-4269-979e-f8471e3ebb89',
49
+ desc: t('features.assistantMessageGroup.desc'),
50
+ key: 'enableAssistantMessageGroup',
51
+ title: t('features.assistantMessageGroup.title'),
52
+ },
52
53
  // {
53
54
  // checked: enableGroupChat,
54
55
  // cover: 'https://github.com/user-attachments/assets/72894d24-a96a-4d7c-a823-ff9e6a1a8b6d',
@@ -0,0 +1,152 @@
1
+ import { AssistantContentBlock, UIChatMessage } from '@lobechat/types';
2
+ import { ActionIconGroup, type ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
3
+ import { App } from 'antd';
4
+ import { useSearchParams } from 'next/navigation';
5
+ import { memo, use, useCallback, useContext, useMemo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import ShareMessageModal from '@/features/Conversation/components/ShareMessageModal';
9
+ import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
10
+ import { useChatStore } from '@/store/chat';
11
+ import { threadSelectors } from '@/store/chat/selectors';
12
+ import { useSessionStore } from '@/store/session';
13
+ import { sessionSelectors } from '@/store/session/selectors';
14
+
15
+ import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
16
+ import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
17
+
18
+ interface GroupActionsProps {
19
+ contentBlock?: AssistantContentBlock;
20
+ data: UIChatMessage;
21
+ id: string;
22
+ index: number;
23
+ }
24
+
25
+ const WithContentId = memo<GroupActionsProps>(({ id, data, index, contentBlock }) => {
26
+ const { tools } = data;
27
+ const [isThreadMode, hasThread] = useChatStore((s) => [
28
+ !!s.activeThreadId,
29
+ threadSelectors.hasThreadBySourceMsgId(id)(s),
30
+ ]);
31
+ const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
32
+ const [showShareModal, setShareModal] = useState(false);
33
+
34
+ const { edit, delAndRegenerate, copy, divider, del, branching, share } = useChatListActionsBar({
35
+ hasThread,
36
+ });
37
+
38
+ const hasTools = !!tools;
39
+
40
+ const inPortalThread = useContext(InPortalThreadContext);
41
+ const inThread = isThreadMode || inPortalThread;
42
+
43
+ const items = useMemo(() => {
44
+ if (hasTools) return [delAndRegenerate, copy];
45
+
46
+ return [edit, copy, inThread || isGroupSession ? null : branching].filter(
47
+ Boolean,
48
+ ) as ActionIconGroupItemType[];
49
+ }, [inThread, hasTools, isGroupSession]);
50
+
51
+ const { t } = useTranslation('common');
52
+ const searchParams = useSearchParams();
53
+ const topic = searchParams.get('topic');
54
+ const [
55
+ deleteMessage,
56
+ translateMessage,
57
+ delAndRegenerateMessage,
58
+ copyMessage,
59
+ openThreadCreator,
60
+ delAndResendThreadMessage,
61
+ toggleMessageEditing,
62
+ ] = useChatStore((s) => [
63
+ s.deleteMessage,
64
+ s.translateMessage,
65
+ s.delAndRegenerateMessage,
66
+ s.copyMessage,
67
+ s.openThreadCreator,
68
+ s.delAndResendThreadMessage,
69
+ s.toggleMessageEditing,
70
+ ]);
71
+ const { message } = App.useApp();
72
+ const virtuosoRef = use(VirtuosoContext);
73
+
74
+ const onActionClick = useCallback(
75
+ async (action: ActionIconGroupEvent) => {
76
+ switch (action.key) {
77
+ case 'edit': {
78
+ toggleMessageEditing(id, true);
79
+
80
+ virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
81
+ }
82
+ }
83
+ if (!data) return;
84
+
85
+ switch (action.key) {
86
+ case 'copy': {
87
+ if (!contentBlock) return;
88
+ await copyMessage(id, contentBlock.content);
89
+ message.success(t('copySuccess', { defaultValue: 'Copy Success' }));
90
+ break;
91
+ }
92
+ case 'branching': {
93
+ if (!topic) {
94
+ message.warning(t('branchingRequiresSavedTopic'));
95
+ break;
96
+ }
97
+ openThreadCreator(id);
98
+ break;
99
+ }
100
+
101
+ case 'del': {
102
+ deleteMessage(id);
103
+ break;
104
+ }
105
+
106
+ case 'delAndRegenerate': {
107
+ if (inPortalThread) {
108
+ delAndResendThreadMessage(id);
109
+ } else {
110
+ delAndRegenerateMessage(id);
111
+ }
112
+ break;
113
+ }
114
+
115
+ case 'share': {
116
+ setShareModal(true);
117
+ break;
118
+ }
119
+ }
120
+
121
+ if (action.keyPath.at(-1) === 'translate') {
122
+ // click the menu data with translate data, the result is:
123
+ // key: 'en-US'
124
+ // keyPath: ['en-US','translate']
125
+ const lang = action.keyPath[0];
126
+ translateMessage(id, lang);
127
+ }
128
+ },
129
+ [data, topic],
130
+ );
131
+
132
+ return (
133
+ <>
134
+ <ActionIconGroup
135
+ items={items}
136
+ menu={{
137
+ items: [edit, copy, divider, share, divider, delAndRegenerate, del],
138
+ }}
139
+ onActionClick={onActionClick}
140
+ />
141
+ <ShareMessageModal
142
+ message={data!}
143
+ onCancel={() => {
144
+ setShareModal(false);
145
+ }}
146
+ open={showShareModal}
147
+ />
148
+ </>
149
+ );
150
+ });
151
+
152
+ export default WithContentId;
@@ -0,0 +1,70 @@
1
+ import { UIChatMessage } from '@lobechat/types';
2
+ import { ActionIconGroup, type ActionIconGroupEvent, ActionIconGroupItemType } from '@lobehub/ui';
3
+ import { useSearchParams } from 'next/navigation';
4
+ import { memo, useCallback, useContext, useMemo } from 'react';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+ import { threadSelectors } from '@/store/chat/selectors';
8
+ import { useSessionStore } from '@/store/session';
9
+ import { sessionSelectors } from '@/store/session/selectors';
10
+
11
+ import { InPortalThreadContext } from '../../../context/InPortalThreadContext';
12
+ import { useChatListActionsBar } from '../../../hooks/useChatListActionsBar';
13
+
14
+ interface GroupActionsProps {
15
+ data: UIChatMessage;
16
+ id: string;
17
+ }
18
+
19
+ const WithoutContentId = memo<GroupActionsProps>(({ id, data }) => {
20
+ const [isThreadMode, hasThread] = useChatStore((s) => [
21
+ !!s.activeThreadId,
22
+ threadSelectors.hasThreadBySourceMsgId(id)(s),
23
+ ]);
24
+ const isGroupSession = useSessionStore(sessionSelectors.isCurrentSessionGroupSession);
25
+
26
+ const { delAndRegenerate, del } = useChatListActionsBar({ hasThread });
27
+
28
+ const inPortalThread = useContext(InPortalThreadContext);
29
+ const inThread = isThreadMode || inPortalThread;
30
+
31
+ const items = useMemo(() => {
32
+ return [delAndRegenerate, del].filter(Boolean) as ActionIconGroupItemType[];
33
+ }, [inThread, isGroupSession]);
34
+
35
+ const searchParams = useSearchParams();
36
+ const topic = searchParams.get('topic');
37
+
38
+ const [deleteMessage, delAndRegenerateMessage, delAndResendThreadMessage] = useChatStore((s) => [
39
+ s.deleteMessage,
40
+ s.delAndRegenerateMessage,
41
+ s.delAndResendThreadMessage,
42
+ ]);
43
+
44
+ const onActionClick = useCallback(
45
+ async (action: ActionIconGroupEvent) => {
46
+ if (!data) return;
47
+
48
+ switch (action.key) {
49
+ case 'del': {
50
+ deleteMessage(id);
51
+ break;
52
+ }
53
+
54
+ case 'delAndRegenerate': {
55
+ if (inPortalThread) {
56
+ delAndResendThreadMessage(id);
57
+ } else {
58
+ delAndRegenerateMessage(id);
59
+ }
60
+ break;
61
+ }
62
+ }
63
+ },
64
+ [data, topic],
65
+ );
66
+
67
+ return <ActionIconGroup items={items} onActionClick={onActionClick} />;
68
+ });
69
+
70
+ export default WithoutContentId;
@@ -0,0 +1,21 @@
1
+ import { AssistantContentBlock, UIChatMessage } from '@lobechat/types';
2
+ import { memo } from 'react';
3
+
4
+ import WithContentId from './WithContentId';
5
+ import WithoutContentId from './WithoutContentId';
6
+
7
+ interface GroupActionsProps {
8
+ contentBlock?: AssistantContentBlock;
9
+ contentId?: string;
10
+ data: UIChatMessage;
11
+ id: string;
12
+ index: number;
13
+ }
14
+
15
+ export const GroupActionsBar = memo<GroupActionsProps>(
16
+ ({ id, data, contentBlock, index, contentId }) => {
17
+ if (!contentId) return <WithoutContentId data={data} id={id} />;
18
+
19
+ return <WithContentId contentBlock={contentBlock} data={data} id={contentId} index={index} />;
20
+ },
21
+ );
@@ -0,0 +1,91 @@
1
+ import { AssistantContentBlock } from '@lobechat/types';
2
+ import { MarkdownProps } from '@lobehub/ui';
3
+ import { memo, useMemo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { LOADING_FLAT } from '@/const/message';
7
+ import { markdownElements } from '@/features/Conversation/MarkdownElements';
8
+ import Reasoning from '@/features/Conversation/Messages/Assistant/Reasoning';
9
+ import { useChatStore } from '@/store/chat';
10
+ import { aiChatSelectors, messageStateSelectors } from '@/store/chat/selectors';
11
+ import { useUserStore } from '@/store/user';
12
+ import { userGeneralSettingsSelectors } from '@/store/user/selectors';
13
+
14
+ import ImageFileListViewer from '../User/ImageFileListViewer';
15
+ import ErrorContent from './Error';
16
+ import MessageContent from './MessageContent';
17
+ import { Tools } from './Tools';
18
+
19
+ const rehypePlugins = markdownElements.map((element) => element.rehypePlugin).filter(Boolean);
20
+ const remarkPlugins = markdownElements.map((element) => element.remarkPlugin).filter(Boolean);
21
+
22
+ interface ContentBlockProps extends AssistantContentBlock {
23
+ index: number;
24
+ }
25
+
26
+ export const ContentBlock = memo<ContentBlockProps>((props) => {
27
+ const { id, tools, content, imageList, reasoning, error } = props;
28
+ const showImageItems = !!imageList && imageList.length > 0;
29
+ const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
30
+
31
+ const hasTools = tools && tools.length > 0;
32
+ const showReasoning =
33
+ (!!reasoning && reasoning.content?.trim() !== '') || (!reasoning && isReasoning);
34
+
35
+ const { transitionMode, highlighterTheme, mermaidTheme } = useUserStore(
36
+ userGeneralSettingsSelectors.config,
37
+ );
38
+
39
+ const generating = useChatStore(messageStateSelectors.isMessageGenerating(id));
40
+
41
+ const animated = transitionMode === 'fadeIn' && generating;
42
+
43
+ const components = useMemo(
44
+ () =>
45
+ Object.fromEntries(
46
+ markdownElements.map((element) => {
47
+ const Component = element.Component;
48
+
49
+ return [element.tag, (props: any) => <Component {...props} id={id} />];
50
+ }),
51
+ ),
52
+ [id],
53
+ );
54
+
55
+ const markdownProps: Omit<MarkdownProps, 'className' | 'style' | 'children'> = useMemo(
56
+ () => ({
57
+ animated,
58
+ componentProps: {
59
+ highlight: {
60
+ theme: highlighterTheme,
61
+ },
62
+ mermaid: { theme: mermaidTheme },
63
+ },
64
+ components,
65
+ enableCustomFootnotes: true,
66
+ rehypePlugins,
67
+ remarkPlugins,
68
+ }),
69
+ [animated, components, highlighterTheme, mermaidTheme],
70
+ );
71
+
72
+ if (error && (content === LOADING_FLAT || !content))
73
+ return <ErrorContent error={error} id={id} />;
74
+
75
+ return (
76
+ <Flexbox gap={8} id={id}>
77
+ {showReasoning && <Reasoning {...reasoning} id={id} />}
78
+
79
+ {/* Content - markdown text */}
80
+ {content && (
81
+ <MessageContent content={content} hasTools={hasTools} markdownProps={markdownProps} />
82
+ )}
83
+
84
+ {/* Image files */}
85
+ {showImageItems && <ImageFileListViewer items={imageList} />}
86
+
87
+ {/* Tools */}
88
+ {hasTools && <Tools messageId={id} tools={tools} />}
89
+ </Flexbox>
90
+ );
91
+ });
@@ -0,0 +1,51 @@
1
+ import { MessageInput } from '@lobehub/ui/chat';
2
+ import { memo, useMemo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useChatStore } from '@/store/chat';
7
+
8
+ export interface EditStateProps {
9
+ content: string;
10
+ id: string;
11
+ }
12
+
13
+ const EditState = memo<EditStateProps>(({ id, content }) => {
14
+ const { t } = useTranslation('common');
15
+
16
+ const text = useMemo(
17
+ () => ({
18
+ cancel: t('cancel'),
19
+ confirm: t('ok'),
20
+ edit: t('edit'),
21
+ }),
22
+ [],
23
+ );
24
+
25
+ const [toggleMessageEditing, updateMessageContent] = useChatStore((s) => [
26
+ s.toggleMessageEditing,
27
+ s.modifyMessageContent,
28
+ ]);
29
+
30
+ const onEditingChange = (value: string) => {
31
+ updateMessageContent(id, value);
32
+ toggleMessageEditing(id, false);
33
+ };
34
+
35
+ return (
36
+ <Flexbox paddingBlock={'0 8px'}>
37
+ <MessageInput
38
+ defaultValue={content ? String(content) : ''}
39
+ editButtonSize={'small'}
40
+ onCancel={() => {
41
+ toggleMessageEditing(id, false);
42
+ }}
43
+ onConfirm={onEditingChange}
44
+ text={text}
45
+ variant={'outlined'}
46
+ />
47
+ </Flexbox>
48
+ );
49
+ });
50
+
51
+ export default EditState;
@@ -0,0 +1,53 @@
1
+ import { ChatMessageError } from '@lobechat/types';
2
+ import { Alert } from '@lobehub/ui';
3
+ import { Button } from 'antd';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import ErrorMessageExtra, { useErrorContent } from '@/features/Conversation/Error';
9
+ import { useChatStore } from '@/store/chat';
10
+
11
+ export interface ErrorContentProps {
12
+ error?: ChatMessageError;
13
+ id: string;
14
+ }
15
+
16
+ const ErrorContent = memo<ErrorContentProps>(({ error, id }) => {
17
+ const { t } = useTranslation('common');
18
+ const errorProps = useErrorContent(error);
19
+
20
+ const [deleteMessage] = useChatStore((s) => [s.deleteMessage]);
21
+ const message = <ErrorMessageExtra block data={{ error, id }} />;
22
+
23
+ if (!error?.message) {
24
+ if (!message) return null;
25
+ return <Flexbox>{message}</Flexbox>;
26
+ }
27
+
28
+ return (
29
+ <Flexbox>
30
+ <Alert
31
+ action={
32
+ <Button
33
+ color={'default'}
34
+ onClick={() => {
35
+ deleteMessage(id);
36
+ }}
37
+ size={'small'}
38
+ variant={'filled'}
39
+ >
40
+ {t('delete')}
41
+ </Button>
42
+ }
43
+ closable={false}
44
+ extra={message}
45
+ showIcon
46
+ type={'error'}
47
+ {...errorProps}
48
+ />
49
+ </Flexbox>
50
+ );
51
+ });
52
+
53
+ export default ErrorContent;
@@ -0,0 +1,73 @@
1
+ import { AssistantContentBlock } from '@lobechat/types';
2
+ import { createStyles } from 'antd-style';
3
+ import { motion } from 'framer-motion';
4
+ import { memo, use } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
8
+ import { useChatStore } from '@/store/chat';
9
+
10
+ import { ContentBlock } from './ContentBlock';
11
+
12
+ const useStyles = createStyles(({ css }) => {
13
+ return {
14
+ container: css`
15
+ &:has(.tool-blocks) {
16
+ width: 100%;
17
+ }
18
+ `,
19
+ };
20
+ });
21
+
22
+ interface GroupChildrenProps {
23
+ blocks: AssistantContentBlock[];
24
+ contentId?: string;
25
+ disableEditing?: boolean;
26
+ messageIndex: number;
27
+ }
28
+
29
+ const GroupChildren = memo<GroupChildrenProps>(
30
+ ({ blocks, contentId, disableEditing, messageIndex }) => {
31
+ const { styles } = useStyles();
32
+
33
+ const [toggleMessageEditing] = useChatStore((s) => [s.toggleMessageEditing]);
34
+ const virtuosoRef = use(VirtuosoContext);
35
+
36
+ return (
37
+ <Flexbox className={styles.container} gap={8}>
38
+ {blocks.map((item, index) => {
39
+ return item.id === contentId ? (
40
+ <Flexbox
41
+ key={index}
42
+ onDoubleClick={(e) => {
43
+ if (disableEditing || item.error || !e.altKey) return;
44
+
45
+ toggleMessageEditing(item.id, true);
46
+ virtuosoRef?.current?.scrollIntoView({
47
+ align: 'start',
48
+ behavior: 'auto',
49
+ index: messageIndex,
50
+ });
51
+ }}
52
+ >
53
+ <ContentBlock index={index} {...item} />
54
+ </Flexbox>
55
+ ) : (
56
+ <motion.div
57
+ animate={{ height: 'auto', opacity: 1 }}
58
+ exit={{ height: 0, opacity: 0 }}
59
+ initial={{ height: 0, opacity: 0 }}
60
+ key={index}
61
+ style={{ overflow: 'hidden' }}
62
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
63
+ >
64
+ <ContentBlock index={index} {...item} />
65
+ </motion.div>
66
+ );
67
+ })}
68
+ </Flexbox>
69
+ );
70
+ },
71
+ );
72
+
73
+ export default GroupChildren;
@@ -0,0 +1,39 @@
1
+ import { Markdown, MarkdownProps } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { memo } from 'react';
4
+
5
+ import BubblesLoading from '@/components/BubblesLoading';
6
+ import { LOADING_FLAT } from '@/const/message';
7
+
8
+ import { normalizeThinkTags, processWithArtifact } from '../../utils/markdown';
9
+
10
+ const useStyles = createStyles(({ css, token }) => {
11
+ return {
12
+ pWithTool: css`
13
+ color: ${token.colorTextTertiary};
14
+ `,
15
+ };
16
+ });
17
+ interface ContentBlockProps {
18
+ content: string;
19
+ hasTools?: boolean;
20
+ markdownProps?: Omit<MarkdownProps, 'className' | 'style' | 'children'>;
21
+ }
22
+
23
+ const MessageContent = memo<ContentBlockProps>(({ content, hasTools, markdownProps }) => {
24
+ const message = normalizeThinkTags(processWithArtifact(content));
25
+
26
+ const { styles, cx } = useStyles();
27
+
28
+ if (content === LOADING_FLAT) return <BubblesLoading />;
29
+
30
+ return (
31
+ content && (
32
+ <Markdown {...markdownProps} className={cx(hasTools && styles.pWithTool)} variant={'chat'}>
33
+ {message}
34
+ </Markdown>
35
+ )
36
+ );
37
+ });
38
+
39
+ export default MessageContent;