@lobehub/lobehub 2.0.0-next.277 → 2.0.0-next.278

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 (73) hide show
  1. package/.cursor/rules/db-migrations.mdc +1 -1
  2. package/.cursor/rules/debug-usage.mdc +7 -5
  3. package/.cursor/rules/desktop-controller-tests.mdc +2 -1
  4. package/.cursor/rules/desktop-feature-implementation.mdc +9 -5
  5. package/.cursor/rules/desktop-local-tools-implement.mdc +67 -66
  6. package/.cursor/rules/desktop-menu-configuration.mdc +21 -9
  7. package/.cursor/rules/desktop-window-management.mdc +17 -2
  8. package/.cursor/rules/drizzle-schema-style-guide.mdc +6 -6
  9. package/.cursor/rules/hotkey.mdc +1 -0
  10. package/.cursor/rules/i18n.mdc +1 -0
  11. package/.cursor/rules/project-structure.mdc +16 -3
  12. package/.cursor/rules/react.mdc +17 -5
  13. package/.cursor/rules/recent-data-usage.mdc +2 -1
  14. package/.cursor/rules/testing-guide/testing-guide.mdc +262 -238
  15. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +1 -1
  16. package/.cursor/rules/zustand-action-patterns.mdc +1 -1
  17. package/.cursor/rules/zustand-slice-organization.mdc +4 -4
  18. package/CHANGELOG.md +25 -0
  19. package/CLAUDE.md +1 -1
  20. package/GEMINI.md +1 -1
  21. package/changelog/v1.json +5 -0
  22. package/docs/development/database-schema.dbml +16 -0
  23. package/locales/en-US/chat.json +24 -0
  24. package/locales/zh-CN/chat.json +24 -0
  25. package/package.json +1 -1
  26. package/packages/business/const/src/index.ts +3 -0
  27. package/packages/database/migrations/0069_add_topic_shares_table.sql +22 -0
  28. package/packages/database/migrations/meta/0069_snapshot.json +9704 -0
  29. package/packages/database/migrations/meta/_journal.json +7 -0
  30. package/packages/database/src/models/__tests__/topicShare.test.ts +318 -0
  31. package/packages/database/src/models/topicShare.ts +177 -0
  32. package/packages/database/src/schemas/topic.ts +44 -2
  33. package/packages/types/src/conversation.ts +5 -0
  34. package/packages/types/src/topic/topic.ts +46 -0
  35. package/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +24 -9
  36. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +26 -9
  37. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +1 -2
  38. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageNum.tsx +54 -173
  39. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +22 -67
  40. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +18 -0
  41. package/src/app/[variants]/router/desktopRouter.config.tsx +18 -0
  42. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +54 -0
  43. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +170 -0
  44. package/src/app/[variants]/share/t/[id]/features/Portal/index.tsx +66 -0
  45. package/src/app/[variants]/share/t/[id]/index.tsx +112 -0
  46. package/src/app/robots.tsx +1 -1
  47. package/src/business/client/BusinessMobileRoutes.tsx +1 -1
  48. package/src/features/Conversation/ChatList/index.tsx +12 -5
  49. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/index.tsx +8 -4
  50. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +15 -10
  51. package/src/features/Conversation/Messages/AssistantGroup/Tools.tsx +3 -1
  52. package/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +3 -2
  53. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +2 -2
  54. package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +25 -26
  55. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +4 -2
  56. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +16 -12
  57. package/src/features/Conversation/Messages/Tool/index.tsx +20 -11
  58. package/src/features/Conversation/Messages/index.tsx +1 -1
  59. package/src/features/Conversation/store/slices/data/action.ts +2 -1
  60. package/src/features/SharePopover/index.tsx +215 -0
  61. package/src/features/SharePopover/style.ts +10 -0
  62. package/src/libs/next/proxy/define-config.ts +4 -1
  63. package/src/locales/default/chat.ts +26 -0
  64. package/src/proxy.ts +1 -0
  65. package/src/server/routers/lambda/__tests__/message.test.ts +152 -0
  66. package/src/server/routers/lambda/__tests__/share.test.ts +227 -0
  67. package/src/server/routers/lambda/__tests__/topic.test.ts +174 -0
  68. package/src/server/routers/lambda/index.ts +2 -0
  69. package/src/server/routers/lambda/message.ts +37 -4
  70. package/src/server/routers/lambda/share.ts +55 -0
  71. package/src/server/routers/lambda/topic.ts +45 -0
  72. package/src/services/message/index.ts +1 -0
  73. package/src/services/topic/index.ts +16 -0
@@ -0,0 +1,170 @@
1
+ 'use client';
2
+
3
+ import { Avatar, Flexbox } from '@lobehub/ui';
4
+ import { Typography } from 'antd';
5
+ import { createStyles, cssVar } from 'antd-style';
6
+ import NextLink from 'next/link';
7
+ import { PropsWithChildren, memo, useEffect, useMemo } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Link, Outlet, useParams } from 'react-router-dom';
10
+ import useSWR from 'swr';
11
+
12
+ import { ProductLogo } from '@/components/Branding';
13
+ import { DEFAULT_AVATAR } from '@/const/meta';
14
+ import GroupAvatar from '@/features/GroupAvatar';
15
+ import UserAvatar from '@/features/User/UserAvatar';
16
+ import { lambdaClient } from '@/libs/trpc/client';
17
+ import { useAgentStore } from '@/store/agent';
18
+ import { useUserStore } from '@/store/user';
19
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
20
+
21
+ import SharePortal from '../features/Portal';
22
+
23
+ const useStyles = createStyles(({ css, token }) => ({
24
+ container: css`
25
+ width: 100vw;
26
+ min-height: 100vh;
27
+ background: ${token.colorBgLayout};
28
+ `,
29
+ content: css`
30
+ flex: 1;
31
+ width: 100%;
32
+ padding-block: 24px;
33
+ padding-inline: 24px;
34
+ `,
35
+ footer: css`
36
+ padding-block: 16px;
37
+ padding-inline: 24px;
38
+ color: ${token.colorTextTertiary};
39
+ text-align: center;
40
+ `,
41
+ header: css`
42
+ height: 52px;
43
+ padding: 8px;
44
+ `,
45
+ }));
46
+
47
+ const ShareTopicLayout = memo<PropsWithChildren>(({ children }) => {
48
+ const { styles } = useStyles();
49
+ const { t } = useTranslation('chat');
50
+ const { id } = useParams<{ id: string }>();
51
+ const dispatchAgentMap = useAgentStore((s) => s.internal_dispatchAgentMap);
52
+ const isLogin = useUserStore(authSelectors.isLogin);
53
+
54
+ const { data } = useSWR(
55
+ id ? ['shared-topic', id] : null,
56
+ () => lambdaClient.share.getSharedTopic.query({ shareId: id! }),
57
+ { revalidateOnFocus: false },
58
+ );
59
+
60
+ // Set agent meta to agentStore for avatar display
61
+ useEffect(() => {
62
+ if (data?.agentId && data.agentMeta) {
63
+ const meta = {
64
+ avatar: data.agentMeta.avatar ?? undefined,
65
+ backgroundColor: data.agentMeta.backgroundColor ?? undefined,
66
+ title: data.agentMeta.title ?? undefined,
67
+ };
68
+ dispatchAgentMap(data.agentId, meta);
69
+ }
70
+ }, [data?.agentId, data?.agentMeta, dispatchAgentMap]);
71
+
72
+ const isGroup = !!data?.groupId;
73
+ const isInboxAgent = !isGroup && data?.agentMeta?.slug === 'inbox';
74
+ const agentOrGroupTitle =
75
+ data?.groupMeta?.title || (isInboxAgent ? 'LobeAI' : data?.agentMeta?.title);
76
+ const agentMarketIdentifier = data?.agentMeta?.marketIdentifier;
77
+
78
+ // Build group avatars for GroupAvatar component
79
+ const groupAvatars = useMemo(() => {
80
+ if (!isGroup || !data?.groupMeta?.members) return [];
81
+ return data.groupMeta.members.map((member) => ({
82
+ avatar: member.avatar || DEFAULT_AVATAR,
83
+ backgroundColor: member.backgroundColor || undefined,
84
+ }));
85
+ }, [isGroup, data?.groupMeta?.members]);
86
+
87
+ const renderAgentOrGroupAvatar = () => {
88
+ // For group: use GroupAvatar with members
89
+ if (isGroup && groupAvatars.length > 0) {
90
+ return <GroupAvatar avatars={groupAvatars} size={24} />;
91
+ }
92
+
93
+ // For inbox agent: skip avatar as it's the same as product icon
94
+ if (isInboxAgent) {
95
+ return null;
96
+ }
97
+
98
+ // For agent: use single Avatar
99
+ if (data?.agentMeta?.avatar) {
100
+ return (
101
+ <Avatar
102
+ avatar={data.agentMeta.avatar}
103
+ background={data.agentMeta.backgroundColor || cssVar.colorFillTertiary}
104
+ shape="square"
105
+ size={24}
106
+ />
107
+ );
108
+ }
109
+
110
+ return null;
111
+ };
112
+
113
+ const renderAgentOrGroupTitle = () => {
114
+ if (!agentOrGroupTitle) return null;
115
+
116
+ // If agent has marketIdentifier, render as link to assistant page
117
+ if (agentMarketIdentifier && !data?.groupMeta?.title) {
118
+ return (
119
+ <a href={`/community/assistant/${agentMarketIdentifier}`} rel="noreferrer" target="_blank">
120
+ <Typography.Text ellipsis strong>
121
+ {agentOrGroupTitle}
122
+ </Typography.Text>
123
+ </a>
124
+ );
125
+ }
126
+
127
+ return (
128
+ <Typography.Text ellipsis strong>
129
+ {agentOrGroupTitle}
130
+ </Typography.Text>
131
+ );
132
+ };
133
+
134
+ return (
135
+ <Flexbox className={styles.container}>
136
+ <Flexbox align="center" className={styles.header} gap={12} horizontal justify="space-between">
137
+ <Flexbox align="center" flex={1} gap={12} horizontal>
138
+ {isLogin ? (
139
+ <Link to="/">
140
+ <ProductLogo size={24} />
141
+ </Link>
142
+ ) : (
143
+ <NextLink href="/login">
144
+ <ProductLogo size={24} />
145
+ </NextLink>
146
+ )}
147
+ {renderAgentOrGroupAvatar()}
148
+ {renderAgentOrGroupTitle()}
149
+ </Flexbox>
150
+ {data?.title && (
151
+ <Typography.Text ellipsis strong style={{ textAlign: 'center' }}>
152
+ {data.title}
153
+ </Typography.Text>
154
+ )}
155
+ <Flexbox align="center" flex={1} horizontal justify="flex-end">
156
+ {isLogin && <UserAvatar size={24} />}
157
+ </Flexbox>
158
+ </Flexbox>
159
+ <Flexbox className={styles.content} horizontal style={{ overflow: 'hidden' }}>
160
+ <Flexbox flex={1} style={{ overflow: 'hidden' }}>
161
+ {children ?? <Outlet />}
162
+ </Flexbox>
163
+ <SharePortal />
164
+ </Flexbox>
165
+ <Typography.Text className={styles.footer}>{t('sharePageDisclaimer')}</Typography.Text>
166
+ </Flexbox>
167
+ );
168
+ });
169
+
170
+ export default ShareTopicLayout;
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+ import { DraggablePanel } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import { memo } from 'react';
6
+
7
+ import { CHAT_PORTAL_TOOL_UI_WIDTH } from '@/const/layoutTokens';
8
+ import { PortalContent } from '@/features/Portal/router';
9
+ import { useChatStore } from '@/store/chat';
10
+ import { chatPortalSelectors } from '@/store/chat/selectors';
11
+
12
+ const useStyles = createStyles(({ css, token }) => ({
13
+ body: css`
14
+ overflow: hidden;
15
+ display: flex;
16
+ flex: 1;
17
+ flex-direction: column;
18
+
19
+ height: 0;
20
+ padding-block-end: 12px;
21
+ `,
22
+ content: css`
23
+ position: relative;
24
+
25
+ overflow: hidden;
26
+ display: flex;
27
+ flex-direction: column;
28
+
29
+ height: 100%;
30
+ min-height: 100%;
31
+ max-height: 100%;
32
+
33
+ background: ${token.colorBgContainer};
34
+ `,
35
+ drawer: css`
36
+ z-index: 10;
37
+ height: 100%;
38
+ background: ${token.colorBgContainer};
39
+ `,
40
+ }));
41
+
42
+ const SharePortal = memo(() => {
43
+ const { styles } = useStyles();
44
+ const showPortal = useChatStore(chatPortalSelectors.showPortal);
45
+
46
+ return (
47
+ <DraggablePanel
48
+ className={styles.drawer}
49
+ classNames={{ content: styles.content }}
50
+ defaultSize={{ width: CHAT_PORTAL_TOOL_UI_WIDTH }}
51
+ expand={showPortal}
52
+ expandable={false}
53
+ minWidth={CHAT_PORTAL_TOOL_UI_WIDTH}
54
+ placement="right"
55
+ showHandleWhenCollapsed={false}
56
+ showHandleWideArea={false}
57
+ size={{ height: '100%', width: CHAT_PORTAL_TOOL_UI_WIDTH }}
58
+ >
59
+ <PortalContent renderBody={(body) => <div className={styles.body}>{body}</div>} />
60
+ </DraggablePanel>
61
+ );
62
+ });
63
+
64
+ SharePortal.displayName = 'SharePortal';
65
+
66
+ export default SharePortal;
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+
3
+ import { Flexbox } from '@lobehub/ui';
4
+ import { TRPCClientError } from '@trpc/client';
5
+ import { Button, Result, Skeleton } from 'antd';
6
+ import { createStyles } from 'antd-style';
7
+ import { memo } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { useParams } from 'react-router-dom';
10
+ import useSWR from 'swr';
11
+
12
+ import { lambdaClient } from '@/libs/trpc/client';
13
+
14
+ import SharedMessageList from './SharedMessageList';
15
+
16
+ const useStyles = createStyles(({ css }) => ({
17
+ container: css`
18
+ flex: 1;
19
+ `,
20
+ errorContainer: css`
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ justify-content: center;
25
+
26
+ min-height: 400px;
27
+ padding: 48px;
28
+
29
+ text-align: center;
30
+ `,
31
+ }));
32
+
33
+ const ShareTopicPage = memo(() => {
34
+ const { styles } = useStyles();
35
+ const { t } = useTranslation('chat');
36
+ const { id } = useParams<{ id: string }>();
37
+
38
+ const { data, error, isLoading } = useSWR(
39
+ id ? ['shared-topic', id] : null,
40
+ () => lambdaClient.share.getSharedTopic.query({ shareId: id! }),
41
+ { revalidateOnFocus: false },
42
+ );
43
+
44
+ if (isLoading) {
45
+ return (
46
+ <Flexbox className={styles.container} gap={16}>
47
+ <Skeleton active paragraph={{ rows: 1 }} title={false} />
48
+ <Skeleton active paragraph={{ rows: 6 }} />
49
+ </Flexbox>
50
+ );
51
+ }
52
+
53
+ if (error) {
54
+ const trpcError = error instanceof TRPCClientError ? error : null;
55
+ const errorCode = trpcError?.data?.code;
56
+
57
+ if (errorCode === 'UNAUTHORIZED') {
58
+ return (
59
+ <Flexbox className={styles.errorContainer}>
60
+ <Result
61
+ extra={
62
+ <Button href="/login" type="primary">
63
+ {t('sharePage.error.unauthorized.action')}
64
+ </Button>
65
+ }
66
+ status="403"
67
+ subTitle={t('sharePage.error.unauthorized.subtitle')}
68
+ title={t('sharePage.error.unauthorized.title')}
69
+ />
70
+ </Flexbox>
71
+ );
72
+ }
73
+
74
+ if (errorCode === 'FORBIDDEN') {
75
+ return (
76
+ <Flexbox className={styles.errorContainer}>
77
+ <Result
78
+ status="403"
79
+ subTitle={t('sharePage.error.forbidden.subtitle')}
80
+ title={t('sharePage.error.forbidden.title')}
81
+ />
82
+ </Flexbox>
83
+ );
84
+ }
85
+
86
+ // NOT_FOUND or other errors
87
+ return (
88
+ <Flexbox className={styles.errorContainer}>
89
+ <Result
90
+ status="404"
91
+ subTitle={t('sharePage.error.notFound.subtitle')}
92
+ title={t('sharePage.error.notFound.title')}
93
+ />
94
+ </Flexbox>
95
+ );
96
+ }
97
+
98
+ if (!data) return null;
99
+
100
+ return (
101
+ <Flexbox className={styles.container}>
102
+ <SharedMessageList
103
+ agentId={data.agentId}
104
+ groupId={data.groupId}
105
+ shareId={data.shareId}
106
+ topicId={data.topicId}
107
+ />
108
+ </Flexbox>
109
+ );
110
+ });
111
+
112
+ export default ShareTopicPage;
@@ -26,7 +26,7 @@ const robots = (): MetadataRoute.Robots => {
26
26
  },
27
27
  {
28
28
  allow: ['/'],
29
- disallow: ['/api/*', '/login', '/signup', '/knowledge/*'],
29
+ disallow: ['/api/*', '/login', '/signup', '/knowledge/*', '/share/*'],
30
30
  userAgent: '*',
31
31
  },
32
32
  ],
@@ -1,4 +1,4 @@
1
- import { RouteConfig } from '@/utils/router';
1
+ import { type RouteConfig } from '@/utils/router';
2
2
 
3
3
  export const BusinessMobileRoutesWithMainLayout: RouteConfig[] = [];
4
4
  export const BusinessMobileRoutesWithSettingsLayout: RouteConfig[] = [];
@@ -15,6 +15,10 @@ import { dataSelectors, useConversationStore } from '../store';
15
15
  import VirtualizedList from './components/VirtualizedList';
16
16
 
17
17
  export interface ChatListProps {
18
+ /**
19
+ * Disable the actions bar for all messages (e.g., in share page)
20
+ */
21
+ disableActionsBar?: boolean;
18
22
  /**
19
23
  * Custom item renderer. If not provided, uses default ChatItem.
20
24
  */
@@ -29,7 +33,7 @@ export interface ChatListProps {
29
33
  *
30
34
  * Uses ConversationStore for message data and fetching.
31
35
  */
32
- const ChatList = memo<ChatListProps>(({ welcome, itemContent }) => {
36
+ const ChatList = memo<ChatListProps>(({ disableActionsBar, welcome, itemContent }) => {
33
37
  // Fetch messages (SWR key is null when skipFetch is true)
34
38
  const context = useConversationStore((s) => s.context);
35
39
  const enableUserMemories = useUserStore(settingsSelectors.memoryEnabled);
@@ -39,9 +43,12 @@ const ChatList = memo<ChatListProps>(({ welcome, itemContent }) => {
39
43
  ]);
40
44
  useFetchMessages(context, skipFetch);
41
45
 
42
- // Fetch notebook documents when topic is selected
43
- useFetchNotebookDocuments(context.topicId!);
44
- useFetchTopicMemories(enableUserMemories ? context.topicId : undefined);
46
+ // Skip fetching notebook and memories for share pages (they require authentication)
47
+ const isSharePage = !!context.topicShareId;
48
+
49
+ // Fetch notebook documents when topic is selected (skip for share pages)
50
+ useFetchNotebookDocuments(isSharePage ? undefined : context.topicId!);
51
+ useFetchTopicMemories(enableUserMemories && !isSharePage ? context.topicId : undefined);
45
52
 
46
53
  // Use selectors for data
47
54
 
@@ -77,7 +84,7 @@ const ChatList = memo<ChatListProps>(({ welcome, itemContent }) => {
77
84
  }
78
85
 
79
86
  return (
80
- <MessageActionProvider withSingletonActionsBar>
87
+ <MessageActionProvider withSingletonActionsBar={!disableActionsBar}>
81
88
  <VirtualizedList
82
89
  dataSource={displayMessageIds}
83
90
  // isGenerating={isGenerating}
@@ -16,6 +16,7 @@ import RejectedResponse from './RejectedResponse';
16
16
  interface RenderProps {
17
17
  apiName: string;
18
18
  arguments?: string;
19
+ disableEditing?: boolean;
19
20
  identifier: string;
20
21
  intervention?: ToolIntervention;
21
22
  isArgumentsStreaming?: boolean;
@@ -43,6 +44,7 @@ const Render = memo<RenderProps>(
43
44
  toolCallId,
44
45
  messageId,
45
46
  arguments: requestArgs,
47
+ disableEditing,
46
48
  showPluginRender,
47
49
  setShowPluginRender,
48
50
  identifier,
@@ -54,7 +56,7 @@ const Render = memo<RenderProps>(
54
56
  isArgumentsStreaming,
55
57
  isToolCalling,
56
58
  }) => {
57
- if (toolMessageId && intervention?.status === 'pending') {
59
+ if (toolMessageId && intervention?.status === 'pending' && !disableEditing) {
58
60
  return (
59
61
  <Intervention
60
62
  apiName={apiName}
@@ -150,9 +152,11 @@ const Render = memo<RenderProps>(
150
152
  showPluginRender={showPluginRender}
151
153
  toolCallId={toolCallId}
152
154
  />
153
- <div>
154
- <ModeSelector />
155
- </div>
155
+ {!disableEditing && (
156
+ <div>
157
+ <ModeSelector />
158
+ </div>
159
+ )}
156
160
  </Flexbox>
157
161
  </Suspense>
158
162
  );
@@ -30,6 +30,7 @@ export interface GroupToolProps {
30
30
  apiName: string;
31
31
  arguments?: string;
32
32
  assistantMessageId: string;
33
+ disableEditing?: boolean;
33
34
  id: string;
34
35
  identifier: string;
35
36
  intervention?: ToolIntervention;
@@ -43,6 +44,7 @@ const Tool = memo<GroupToolProps>(
43
44
  arguments: requestArgs,
44
45
  apiName,
45
46
  assistantMessageId,
47
+ disableEditing,
46
48
  id,
47
49
  intervention,
48
50
  identifier,
@@ -106,16 +108,18 @@ const Tool = memo<GroupToolProps>(
106
108
  return (
107
109
  <AccordionItem
108
110
  action={
109
- <Actions
110
- assistantMessageId={assistantMessageId}
111
- handleExpand={handleExpand}
112
- identifier={identifier}
113
- setShowDebug={setShowDebug}
114
- setShowPluginRender={setShowPluginRender}
115
- showCustomPluginRender={showCustomPluginRender}
116
- showDebug={showDebug}
117
- showPluginRender={showPluginRender}
118
- />
111
+ !disableEditing && (
112
+ <Actions
113
+ assistantMessageId={assistantMessageId}
114
+ handleExpand={handleExpand}
115
+ identifier={identifier}
116
+ setShowDebug={setShowDebug}
117
+ setShowPluginRender={setShowPluginRender}
118
+ showCustomPluginRender={showCustomPluginRender}
119
+ showDebug={showDebug}
120
+ showPluginRender={showPluginRender}
121
+ />
122
+ )
119
123
  }
120
124
  allowExpand={hasCustomRender}
121
125
  expand={isToolRenderExpand}
@@ -150,6 +154,7 @@ const Tool = memo<GroupToolProps>(
150
154
  <Render
151
155
  apiName={apiName}
152
156
  arguments={requestArgs}
157
+ disableEditing={disableEditing}
153
158
  identifier={identifier}
154
159
  intervention={intervention}
155
160
  isArgumentsStreaming={isArgumentsStreaming}
@@ -5,11 +5,12 @@ import { memo } from 'react';
5
5
  import Tool from './Tool';
6
6
 
7
7
  interface ToolsRendererProps {
8
+ disableEditing?: boolean;
8
9
  messageId: string;
9
10
  tools: ChatToolPayloadWithResult[];
10
11
  }
11
12
 
12
- export const Tools = memo<ToolsRendererProps>(({ messageId, tools }) => {
13
+ export const Tools = memo<ToolsRendererProps>(({ disableEditing, messageId, tools }) => {
13
14
  if (!tools || tools.length === 0) return null;
14
15
 
15
16
  return (
@@ -19,6 +20,7 @@ export const Tools = memo<ToolsRendererProps>(({ messageId, tools }) => {
19
20
  apiName={tool.apiName}
20
21
  arguments={tool.arguments}
21
22
  assistantMessageId={messageId}
23
+ disableEditing={disableEditing}
22
24
  id={tool.id}
23
25
  identifier={tool.identifier}
24
26
  intervention={tool.intervention}
@@ -14,9 +14,10 @@ import MessageContent from './MessageContent';
14
14
 
15
15
  interface ContentBlockProps extends AssistantContentBlock {
16
16
  assistantId: string;
17
+ disableEditing?: boolean;
17
18
  }
18
19
  const ContentBlock = memo<ContentBlockProps>(
19
- ({ id, tools, content, imageList, reasoning, error, assistantId }) => {
20
+ ({ id, tools, content, imageList, reasoning, error, assistantId, disableEditing }) => {
20
21
  const errorContent = useErrorContent(error);
21
22
  const showImageItems = !!imageList && imageList.length > 0;
22
23
  const [isReasoning, deleteMessage, continueGeneration] = useConversationStore((s) => [
@@ -70,7 +71,7 @@ const ContentBlock = memo<ContentBlockProps>(
70
71
  {showImageItems && <ImageFileListViewer items={imageList} />}
71
72
 
72
73
  {/* Tools */}
73
- {hasTools && <Tools messageId={id} tools={tools} />}
74
+ {hasTools && <Tools disableEditing={disableEditing} messageId={id} tools={tools} />}
74
75
  </Flexbox>
75
76
  );
76
77
  },
@@ -25,10 +25,10 @@ const GroupItem = memo<GroupItemProps>(
25
25
  toggleMessageEditing(item.id, true);
26
26
  }}
27
27
  >
28
- <ContentBlock {...item} assistantId={assistantId} error={error} />
28
+ <ContentBlock {...item} assistantId={assistantId} disableEditing={disableEditing} error={error} />
29
29
  </Flexbox>
30
30
  ) : (
31
- <ContentBlock {...item} assistantId={assistantId} error={error} />
31
+ <ContentBlock {...item} assistantId={assistantId} disableEditing={disableEditing} error={error} />
32
32
  );
33
33
  },
34
34
  isEqual,
@@ -11,34 +11,33 @@ import { Tools } from '../../AssistantGroup/Tools';
11
11
  import Reasoning from '../../components/Reasoning';
12
12
  import MessageContent from './MessageContent';
13
13
 
14
- const ContentBlock = memo<AssistantContentBlock>(({ id, tools, content, reasoning, error }) => {
15
- const errorContent = useErrorContent(error);
16
- const isReasoning = useConversationStore(messageStateSelectors.isMessageInReasoning(id));
17
- const hasTools = tools && tools.length > 0;
18
- const showReasoning =
19
- (!!reasoning && reasoning.content?.trim() !== '') || (!reasoning && isReasoning);
20
-
21
- if (error && (content === LOADING_FLAT || !content))
22
- return (
23
- <ErrorContent
24
- error={
25
- errorContent && error && (content === LOADING_FLAT || !content) ? errorContent : undefined
26
- }
27
- id={id}
28
- />
29
- );
14
+ interface ContentBlockProps extends AssistantContentBlock {
15
+ disableEditing?: boolean;
16
+ }
17
+
18
+ const ContentBlock = memo<ContentBlockProps>(
19
+ ({ id, tools, content, reasoning, error, disableEditing }) => {
20
+ const errorContent = useErrorContent(error);
21
+ const isReasoning = useConversationStore(messageStateSelectors.isMessageInReasoning(id));
22
+ const hasTools = tools && tools.length > 0;
23
+ const showReasoning =
24
+ (!!reasoning && reasoning.content?.trim() !== '') || (!reasoning && isReasoning);
30
25
 
31
- return (
32
- <Flexbox gap={8} id={id}>
33
- {showReasoning && <Reasoning {...reasoning} id={id} />}
26
+ if (error && (content === LOADING_FLAT || !content))
27
+ return <ErrorContent error={errorContent} id={id} />;
34
28
 
35
- {/* Content - markdown text */}
36
- <MessageContent content={content} hasTools={hasTools} id={id} />
29
+ return (
30
+ <Flexbox gap={8} id={id}>
31
+ {showReasoning && <Reasoning {...reasoning} id={id} />}
32
+
33
+ {/* Content - markdown text */}
34
+ <MessageContent content={content} hasTools={hasTools} id={id} />
37
35
 
38
- {/* Tools */}
39
- {hasTools && <Tools messageId={id} tools={tools} />}
40
- </Flexbox>
41
- );
42
- });
36
+ {/* Tools */}
37
+ {hasTools && <Tools disableEditing={disableEditing} messageId={id} tools={tools} />}
38
+ </Flexbox>
39
+ );
40
+ },
41
+ );
43
42
 
44
43
  export default ContentBlock;
@@ -29,7 +29,7 @@ interface GroupChildrenProps {
29
29
  messageIndex: number;
30
30
  }
31
31
 
32
- const Group = memo<GroupChildrenProps>(({ blocks, id, content }) => {
32
+ const Group = memo<GroupChildrenProps>(({ blocks, id, content, disableEditing }) => {
33
33
  const isCollapsed = useConversationStore(messageStateSelectors.isMessageCollapsed(id));
34
34
  const contextValue = useMemo(() => ({ assistantGroupId: id }), [id]);
35
35
 
@@ -46,7 +46,9 @@ const Group = memo<GroupChildrenProps>(({ blocks, id, content }) => {
46
46
  <MessageAggregationContext value={contextValue}>
47
47
  <Flexbox className={styles.container} gap={8}>
48
48
  {blocks.map((item) => {
49
- return <ContentBlock {...item} key={id + '.' + item.id} />;
49
+ return (
50
+ <ContentBlock {...item} disableEditing={disableEditing} key={id + '.' + item.id} />
51
+ );
50
52
  })}
51
53
  </Flexbox>
52
54
  </MessageAggregationContext>