@lobehub/lobehub 2.0.0-next.276 → 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 (98) 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 +51 -0
  19. package/CLAUDE.md +1 -1
  20. package/GEMINI.md +1 -1
  21. package/changelog/v1.json +14 -0
  22. package/docs/development/database-schema.dbml +16 -0
  23. package/locales/en-US/chat.json +24 -0
  24. package/locales/en-US/setting.json +11 -0
  25. package/locales/zh-CN/chat.json +24 -0
  26. package/locales/zh-CN/setting.json +11 -0
  27. package/package.json +1 -1
  28. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/BatchCreateAgents/index.tsx +2 -2
  29. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/UpdateGroup/index.tsx +56 -56
  30. package/packages/builtin-tool-group-agent-builder/src/client/Render/BatchCreateAgents.tsx +3 -2
  31. package/packages/builtin-tool-group-agent-builder/src/executor.ts +2 -1
  32. package/packages/business/const/src/index.ts +3 -0
  33. package/packages/database/migrations/0069_add_topic_shares_table.sql +22 -0
  34. package/packages/database/migrations/meta/0069_snapshot.json +9704 -0
  35. package/packages/database/migrations/meta/_journal.json +7 -0
  36. package/packages/database/src/models/__tests__/topicShare.test.ts +318 -0
  37. package/packages/database/src/models/topicShare.ts +177 -0
  38. package/packages/database/src/schemas/topic.ts +44 -2
  39. package/packages/types/src/agentCronJob/index.ts +19 -23
  40. package/packages/types/src/conversation.ts +5 -0
  41. package/packages/types/src/serverConfig.ts +1 -0
  42. package/packages/types/src/topic/topic.ts +46 -0
  43. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/Actions.tsx +31 -0
  44. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/CronTopicGroup.tsx +10 -6
  45. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/index.tsx +7 -11
  46. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Cron/useDropdownMenu.tsx +102 -0
  47. package/src/app/[variants]/(main)/agent/cron/[cronId]/CronConfig.ts +179 -0
  48. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +111 -0
  49. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobHeader.tsx +45 -0
  50. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobSaveButton.tsx +31 -0
  51. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +213 -0
  52. package/src/app/[variants]/(main)/agent/cron/[cronId]/index.tsx +186 -344
  53. package/src/app/[variants]/(main)/agent/features/Conversation/Header/ShareButton/index.tsx +24 -9
  54. package/src/app/[variants]/(main)/agent/profile/features/AgentCronJobs/index.tsx +42 -97
  55. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +4 -20
  56. package/src/app/[variants]/(main)/community/features/UserAvatar/index.tsx +15 -5
  57. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/AgentProfilePopup.tsx +1 -6
  58. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +26 -9
  59. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +1 -2
  60. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageNum.tsx +54 -173
  61. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +22 -67
  62. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +18 -0
  63. package/src/app/[variants]/router/desktopRouter.config.tsx +18 -0
  64. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +54 -0
  65. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +170 -0
  66. package/src/app/[variants]/share/t/[id]/features/Portal/index.tsx +66 -0
  67. package/src/app/[variants]/share/t/[id]/index.tsx +112 -0
  68. package/src/app/robots.tsx +1 -1
  69. package/src/business/client/BusinessMobileRoutes.tsx +1 -1
  70. package/src/features/Conversation/ChatList/index.tsx +12 -5
  71. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/index.tsx +8 -4
  72. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +15 -10
  73. package/src/features/Conversation/Messages/AssistantGroup/Tools.tsx +3 -1
  74. package/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +3 -2
  75. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +2 -2
  76. package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +25 -26
  77. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +4 -2
  78. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +16 -12
  79. package/src/features/Conversation/Messages/Tool/index.tsx +20 -11
  80. package/src/features/Conversation/Messages/index.tsx +1 -1
  81. package/src/features/Conversation/store/slices/data/action.ts +2 -1
  82. package/src/features/SharePopover/index.tsx +215 -0
  83. package/src/features/SharePopover/style.ts +10 -0
  84. package/src/libs/next/proxy/define-config.ts +4 -1
  85. package/src/locales/default/chat.ts +26 -0
  86. package/src/proxy.ts +1 -0
  87. package/src/server/globalConfig/index.ts +1 -0
  88. package/src/server/routers/lambda/__tests__/message.test.ts +152 -0
  89. package/src/server/routers/lambda/__tests__/share.test.ts +227 -0
  90. package/src/server/routers/lambda/__tests__/topic.test.ts +174 -0
  91. package/src/server/routers/lambda/index.ts +2 -0
  92. package/src/server/routers/lambda/message.ts +37 -4
  93. package/src/server/routers/lambda/share.ts +55 -0
  94. package/src/server/routers/lambda/topic.ts +45 -0
  95. package/src/services/chatGroup/index.ts +1 -4
  96. package/src/services/message/index.ts +1 -0
  97. package/src/services/topic/index.ts +16 -0
  98. package/src/store/serverConfig/selectors.ts +1 -0
@@ -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>
@@ -20,6 +20,7 @@ const Render = dynamic(() => import('../../AssistantGroup/Tool/Render'), {
20
20
  export interface InspectorProps {
21
21
  apiName: string;
22
22
  arguments?: string;
23
+ disableEditing?: boolean;
23
24
  identifier: string;
24
25
  index: number;
25
26
  messageId: string;
@@ -32,7 +33,7 @@ export interface InspectorProps {
32
33
  * Tool message component - adapts Tool message data to use AssistantGroup/Tool components
33
34
  */
34
35
  const Tool = memo<InspectorProps>(
35
- ({ arguments: requestArgs, apiName, messageId, toolCallId, index, identifier, type }) => {
36
+ ({ arguments: requestArgs, apiName, disableEditing, messageId, toolCallId, index, identifier, type }) => {
36
37
  const [showDebug, setShowDebug] = useState(false);
37
38
  const [showPluginRender, setShowPluginRender] = useState(false);
38
39
  const [expand, setExpand] = useState(true);
@@ -66,16 +67,18 @@ const Tool = memo<InspectorProps>(
66
67
  >
67
68
  <AccordionItem
68
69
  action={
69
- <Actions
70
- assistantMessageId={messageId}
71
- handleExpand={(expand) => setExpand(!!expand)}
72
- identifier={identifier}
73
- setShowDebug={setShowDebug}
74
- setShowPluginRender={setShowPluginRender}
75
- showCustomPluginRender={false}
76
- showDebug={showDebug}
77
- showPluginRender={showPluginRender}
78
- />
70
+ !disableEditing && (
71
+ <Actions
72
+ assistantMessageId={messageId}
73
+ handleExpand={(expand) => setExpand(!!expand)}
74
+ identifier={identifier}
75
+ setShowDebug={setShowDebug}
76
+ setShowPluginRender={setShowPluginRender}
77
+ showCustomPluginRender={false}
78
+ showDebug={showDebug}
79
+ showPluginRender={showPluginRender}
80
+ />
81
+ )
79
82
  }
80
83
  itemKey={'tool'}
81
84
  paddingBlock={4}
@@ -83,7 +86,7 @@ const Tool = memo<InspectorProps>(
83
86
  title={<Inspectors apiName={apiName} identifier={identifier} result={result} />}
84
87
  >
85
88
  <Flexbox gap={8} paddingBlock={8}>
86
- {showDebug && (
89
+ {showDebug && !disableEditing && (
87
90
  <Debug
88
91
  apiName={apiName}
89
92
  identifier={identifier}
@@ -96,6 +99,7 @@ const Tool = memo<InspectorProps>(
96
99
  <Render
97
100
  apiName={apiName}
98
101
  arguments={requestArgs}
102
+ disableEditing={disableEditing}
99
103
  identifier={identifier}
100
104
  messageId={messageId}
101
105
  result={result}
@@ -8,11 +8,12 @@ import { dataSelectors, useConversationStore } from '../../store';
8
8
  import Tool from './Tool';
9
9
 
10
10
  interface ToolMessageProps {
11
+ disableEditing?: boolean;
11
12
  id: string;
12
13
  index: number;
13
14
  }
14
15
 
15
- const ToolMessage = memo<ToolMessageProps>(({ id, index }) => {
16
+ const ToolMessage = memo<ToolMessageProps>(({ disableEditing, id, index }) => {
16
17
  const { t } = useTranslation('plugin');
17
18
  const item = useConversationStore(dataSelectors.getDbMessageById(id), isEqual) as UIChatMessage;
18
19
  const deleteToolMessage = useConversationStore((s) => s.deleteToolMessage);
@@ -29,17 +30,25 @@ const ToolMessage = memo<ToolMessageProps>(({ id, index }) => {
29
30
 
30
31
  return (
31
32
  <Flexbox gap={4} paddingBlock={12}>
32
- <Alert
33
- action={
34
- <Button loading={loading} onClick={handleDelete} size={'small'} type={'primary'}>
35
- {t('inspector.delete')}
36
- </Button>
37
- }
38
- title={t('inspector.orphanedToolCall')}
39
- type={'secondary'}
40
- />
33
+ {!disableEditing && (
34
+ <Alert
35
+ action={
36
+ <Button loading={loading} onClick={handleDelete} size={'small'} type={'primary'}>
37
+ {t('inspector.delete')}
38
+ </Button>
39
+ }
40
+ title={t('inspector.orphanedToolCall')}
41
+ type={'secondary'}
42
+ />
43
+ )}
41
44
  {item.plugin && (
42
- <Tool {...item.plugin} index={index} messageId={id} toolCallId={item.tool_call_id!} />
45
+ <Tool
46
+ {...item.plugin}
47
+ disableEditing={disableEditing}
48
+ index={index}
49
+ messageId={id}
50
+ toolCallId={item.tool_call_id!}
51
+ />
43
52
  )}
44
53
  </Flexbox>
45
54
  );
@@ -158,7 +158,7 @@ const MessageItem = memo<MessageItemProps>(
158
158
  }
159
159
 
160
160
  case 'tool': {
161
- return <ToolMessage id={id} index={index} />;
161
+ return <ToolMessage disableEditing={disableEditing} id={id} index={index} />;
162
162
  }
163
163
  }
164
164
 
@@ -103,13 +103,14 @@ export const dataSlice: StateCreator<
103
103
  useFetchMessages: (context, skipFetch) => {
104
104
  // When skipFetch is true, SWR key is null - no fetch occurs
105
105
  // This is used when external messages are provided (e.g., creating new thread)
106
+ // Allow fetch if: has agentId (both agent topics and group topics have agentId)
106
107
  const shouldFetch = !skipFetch && !!context.agentId;
107
108
 
108
109
  return useClientDataSWRWithSync<UIChatMessage[]>(
109
110
  shouldFetch ? ['CONVERSATION_FETCH_MESSAGES', context] : null,
110
111
 
111
112
  async () => {
112
- return messageService.getMessages(context as any);
113
+ return messageService.getMessages(context);
113
114
  },
114
115
  {
115
116
  onData: (data) => {