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

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 (91) 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/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)/agent/features/Conversation/ThreadHydration.tsx +2 -1
  37. package/src/app/[variants]/(main)/agent/features/Portal/_layout/Mobile.tsx +3 -3
  38. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/GroupMember.tsx +3 -2
  39. package/src/app/[variants]/(main)/group/features/Conversation/Header/ShareButton/index.tsx +26 -9
  40. package/src/app/[variants]/(main)/group/features/Conversation/ThreadHydration.tsx +2 -1
  41. package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +3 -3
  42. package/src/app/[variants]/(main)/group/profile/features/MemberProfile/AgentTool.tsx +4 -1
  43. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +1 -2
  44. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ImageNum.tsx +54 -173
  45. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ResolutionSelect.tsx +22 -67
  46. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +18 -0
  47. package/src/app/[variants]/router/desktopRouter.config.tsx +18 -0
  48. package/src/app/[variants]/share/t/[id]/SharedMessageList.tsx +54 -0
  49. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +170 -0
  50. package/src/app/[variants]/share/t/[id]/features/Portal/index.tsx +66 -0
  51. package/src/app/[variants]/share/t/[id]/index.tsx +112 -0
  52. package/src/app/robots.tsx +1 -1
  53. package/src/business/client/BusinessMobileRoutes.tsx +1 -1
  54. package/src/features/Conversation/ChatList/index.tsx +17 -6
  55. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/index.tsx +8 -4
  56. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +15 -10
  57. package/src/features/Conversation/Messages/AssistantGroup/Tools.tsx +3 -1
  58. package/src/features/Conversation/Messages/AssistantGroup/components/ContentBlock.tsx +3 -2
  59. package/src/features/Conversation/Messages/AssistantGroup/components/GroupItem.tsx +2 -2
  60. package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +25 -26
  61. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +4 -2
  62. package/src/features/Conversation/Messages/Tool/Tool/index.tsx +16 -12
  63. package/src/features/Conversation/Messages/Tool/index.tsx +20 -11
  64. package/src/features/Conversation/Messages/index.tsx +1 -1
  65. package/src/features/Conversation/store/slices/data/action.test.ts +42 -0
  66. package/src/features/Conversation/store/slices/data/action.ts +4 -2
  67. package/src/features/Portal/GroupThread/Header/index.tsx +2 -2
  68. package/src/features/Portal/MessageDetail/Body/index.tsx +3 -3
  69. package/src/features/Portal/components/Header.tsx +3 -3
  70. package/src/features/ProfileEditor/AgentTool.tsx +50 -19
  71. package/src/features/SharePopover/index.tsx +215 -0
  72. package/src/features/SharePopover/style.ts +10 -0
  73. package/src/hooks/useNavigateToAgent.ts +3 -3
  74. package/src/libs/next/proxy/define-config.ts +4 -1
  75. package/src/locales/default/chat.ts +26 -0
  76. package/src/proxy.ts +1 -0
  77. package/src/server/routers/lambda/__tests__/message.test.ts +152 -0
  78. package/src/server/routers/lambda/__tests__/share.test.ts +227 -0
  79. package/src/server/routers/lambda/__tests__/topic.test.ts +174 -0
  80. package/src/server/routers/lambda/index.ts +2 -0
  81. package/src/server/routers/lambda/message.ts +37 -4
  82. package/src/server/routers/lambda/share.ts +55 -0
  83. package/src/server/routers/lambda/topic.ts +45 -0
  84. package/src/services/message/index.ts +1 -0
  85. package/src/services/topic/index.ts +16 -0
  86. package/src/store/chat/slices/portal/action.test.ts +0 -41
  87. package/src/store/chat/slices/portal/action.ts +0 -25
  88. package/src/store/chat/slices/thread/action.test.ts +10 -6
  89. package/src/store/chat/slices/thread/action.ts +10 -3
  90. package/src/app/[variants]/(main)/group/features/Portal/features/Portal.tsx +0 -105
  91. package/src/app/[variants]/(main)/group/features/Portal/features/PortalPanel.tsx +0 -23
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { Flexbox } from '@lobehub/ui';
4
+ import { memo, useCallback, useMemo } from 'react';
5
+
6
+ import { ChatList, ConversationProvider, MessageItem } from '@/features/Conversation';
7
+ import { useChatStore } from '@/store/chat';
8
+ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
9
+
10
+ interface SharedMessageListProps {
11
+ agentId: string | null;
12
+ groupId: string | null;
13
+ shareId: string;
14
+ topicId: string;
15
+ }
16
+
17
+ const SharedMessageList = memo<SharedMessageListProps>(({ agentId, groupId, shareId, topicId }) => {
18
+ const context = useMemo(
19
+ () => ({
20
+ agentId: agentId ?? '',
21
+ groupId: groupId ?? undefined,
22
+ topicId,
23
+ topicShareId: shareId,
24
+ }),
25
+ [agentId, groupId, shareId, topicId],
26
+ );
27
+
28
+ // Sync messages to chatStore for artifact selectors to work
29
+ const chatKey = useMemo(() => messageMapKey(context), [context]);
30
+ const replaceMessages = useChatStore((s) => s.replaceMessages);
31
+ const messages = useChatStore((s) => s.dbMessagesMap[chatKey]);
32
+
33
+ const itemContent = useCallback(
34
+ (index: number, id: string) => <MessageItem disableEditing id={id} index={index} key={id} />,
35
+ [],
36
+ );
37
+
38
+ return (
39
+ <ConversationProvider
40
+ context={context}
41
+ hasInitMessages={!!messages}
42
+ messages={messages}
43
+ onMessagesChange={(messages) => {
44
+ replaceMessages(messages, { context });
45
+ }}
46
+ >
47
+ <Flexbox flex={1}>
48
+ <ChatList disableActionsBar itemContent={itemContent} />
49
+ </Flexbox>
50
+ </ConversationProvider>
51
+ );
52
+ });
53
+
54
+ export default SharedMessageList;
@@ -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
 
@@ -56,7 +63,11 @@ const ChatList = memo<ChatListProps>(({ welcome, itemContent }) => {
56
63
  );
57
64
  const messagesInit = useConversationStore(dataSelectors.messagesInit);
58
65
 
59
- if (!messagesInit) {
66
+ // When topicId is null (new conversation), show welcome directly without waiting for fetch
67
+ // because there's no server data to fetch - only local optimistic updates exist
68
+ const isNewConversation = !context.topicId;
69
+
70
+ if (!messagesInit && !isNewConversation) {
60
71
  return <SkeletonList />;
61
72
  }
62
73
 
@@ -77,7 +88,7 @@ const ChatList = memo<ChatListProps>(({ welcome, itemContent }) => {
77
88
  }
78
89
 
79
90
  return (
80
- <MessageActionProvider withSingletonActionsBar>
91
+ <MessageActionProvider withSingletonActionsBar={!disableActionsBar}>
81
92
  <VirtualizedList
82
93
  dataSource={displayMessageIds}
83
94
  // 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,