@lobehub/chat 1.32.4 → 1.32.5

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 (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +2 -2
  3. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +39 -0
  4. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +20 -14
  5. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +44 -0
  6. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/index.tsx +17 -0
  7. package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +1 -13
  8. package/src/components/BrandWatermark/index.tsx +1 -0
  9. package/src/const/message.ts +1 -1
  10. package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +9 -13
  11. package/src/features/Conversation/components/ChatItem/index.tsx +183 -209
  12. package/src/features/Conversation/components/VirtualizedList/index.tsx +75 -84
  13. package/src/features/Conversation/index.ts +0 -1
  14. package/src/features/Portal/Artifacts/index.ts +1 -1
  15. package/src/features/Portal/FilePreview/index.ts +1 -1
  16. package/src/features/Portal/Home/{Header.tsx → Title.tsx} +2 -2
  17. package/src/features/Portal/Home/index.ts +1 -1
  18. package/src/features/Portal/MessageDetail/index.ts +1 -1
  19. package/src/features/Portal/Plugins/index.ts +1 -1
  20. package/src/features/Portal/components/Header.tsx +29 -0
  21. package/src/features/Portal/router.tsx +22 -3
  22. package/src/features/Portal/type.ts +3 -1
  23. package/src/features/{Conversation/components → ShareModal/ShareImage}/ChatList/index.tsx +3 -4
  24. package/src/features/ShareModal/ShareImage/Preview.tsx +1 -1
  25. package/src/features/ShareModal/ShareJSON/index.tsx +1 -1
  26. package/src/features/ShareModal/ShareText/index.tsx +1 -1
  27. package/src/layout/GlobalProvider/Debug.tsx +15 -0
  28. package/src/layout/GlobalProvider/index.tsx +2 -0
  29. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +5 -5
  30. package/src/store/chat/slices/message/action.ts +2 -2
  31. package/src/store/chat/slices/message/selectors.test.ts +0 -86
  32. package/src/store/chat/slices/message/selectors.ts +55 -73
  33. package/src/store/chat/slices/plugin/action.test.ts +2 -2
  34. package/src/store/chat/slices/plugin/action.ts +1 -1
  35. package/src/store/chat/slices/topic/action.ts +2 -2
  36. /package/src/{features/Conversation/components → app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem}/InboxWelcome/AgentsSuggest.tsx +0 -0
  37. /package/src/{features/Conversation/components → app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem}/InboxWelcome/QuestionSuggest.tsx +0 -0
  38. /package/src/{features/Conversation/components → app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem}/InboxWelcome/index.tsx +0 -0
@@ -5,13 +5,12 @@ import { createStyles } from 'antd-style';
5
5
  import isEqual from 'fast-deep-equal';
6
6
  import { MouseEventHandler, ReactNode, memo, useCallback, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
8
9
 
9
10
  import { useAgentStore } from '@/store/agent';
10
11
  import { agentSelectors } from '@/store/agent/selectors';
11
12
  import { useChatStore } from '@/store/chat';
12
13
  import { chatSelectors } from '@/store/chat/selectors';
13
- import { useSessionStore } from '@/store/session';
14
- import { sessionMetaSelectors } from '@/store/session/selectors';
15
14
  import { useUserStore } from '@/store/user';
16
15
  import { userGeneralSettingsSelectors } from '@/store/user/selectors';
17
16
  import { ChatMessage } from '@/types/message';
@@ -26,7 +25,6 @@ import {
26
25
  } from '../../Messages';
27
26
  import History from '../History';
28
27
  import { markdownElements } from '../MarkdownElements';
29
- import ActionsBar from './ActionsBar';
30
28
  import { processWithArtifact } from './utils';
31
29
 
32
30
  const rehypePlugins = markdownElements.map((element) => element.rehypePlugin);
@@ -36,6 +34,7 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
36
34
  opacity: 0.6;
37
35
  `,
38
36
  message: css`
37
+ position: relative;
39
38
  // prevent the textarea too long
40
39
  .${prefixCls}-input {
41
40
  max-height: 900px;
@@ -44,218 +43,193 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
44
43
  }));
45
44
 
46
45
  export interface ChatListItemProps {
47
- hideActionBar?: boolean;
46
+ actionBar?: ReactNode;
47
+ className?: string;
48
+ enableHistoryDivider?: boolean;
49
+ endRender?: ReactNode;
48
50
  id: string;
49
51
  index: number;
50
- showThreadDivider?: boolean;
51
52
  }
52
53
 
53
- const Item = memo<ChatListItemProps>(({ index, id, hideActionBar }) => {
54
- const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
55
- const { t } = useTranslation('common');
56
- const { styles, cx } = useStyles();
57
- const [type = 'chat'] = useAgentStore((s) => {
58
- const config = agentSelectors.currentAgentChatConfig(s);
59
- return [config.displayMode];
60
- });
61
-
62
- const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
63
- const item = useChatStore((s) => {
64
- const chats = chatSelectors.currentChatsWithGuideMessage(meta)(s);
65
-
66
- if (index >= chats.length) return;
67
-
68
- return chats.find((s) => s.id === id);
69
- }, isEqual);
70
-
71
- const [
72
- isMessageLoading,
73
- generating,
74
- isInRAGFlow,
75
- editing,
76
- toggleMessageEditing,
77
- updateMessageContent,
78
- ] = useChatStore((s) => [
79
- chatSelectors.isMessageLoading(id)(s),
80
- chatSelectors.isMessageGenerating(id)(s),
81
- chatSelectors.isMessageInRAGFlow(id)(s),
82
- chatSelectors.isMessageEditing(id)(s),
83
- s.toggleMessageEditing,
84
- s.modifyMessageContent,
85
- ]);
86
-
87
- // when the message is in RAG flow or the AI generating, it should be in loading state
88
- const isProcessing = isInRAGFlow || generating;
89
-
90
- const onAvatarsClick = useAvatarsClick(item?.role);
91
-
92
- const renderMessage = useCallback(
93
- (editableContent: ReactNode) => {
94
- if (!item?.role) return;
95
- const RenderFunction = renderMessages[item.role] ?? renderMessages['default'];
96
-
97
- if (!RenderFunction) return;
98
-
99
- return <RenderFunction {...item} editableContent={editableContent} />;
100
- },
101
- [item],
102
- );
103
-
104
- const BelowMessage = useCallback(
105
- ({ data }: { data: ChatMessage }) => {
106
- if (!item?.role) return;
107
- const RenderFunction = renderBelowMessages[item.role] ?? renderBelowMessages['default'];
108
-
109
- if (!RenderFunction) return;
110
-
111
- return <RenderFunction {...data} />;
112
- },
113
- [item?.role],
114
- );
115
-
116
- const MessageExtra = useCallback(
117
- ({ data }: { data: ChatMessage }) => {
118
- if (!item?.role) return;
119
- let RenderFunction;
120
- if (renderMessagesExtra?.[item.role]) RenderFunction = renderMessagesExtra[item.role];
121
-
122
- if (!RenderFunction) return;
123
- return <RenderFunction {...data} />;
124
- },
125
- [item?.role],
126
- );
127
-
128
- const markdownCustomRender = useCallback(
129
- (dom: ReactNode, { text }: { text: string }) => {
130
- if (!item?.role) return dom;
131
- let RenderFunction;
132
-
133
- if (renderMessagesExtra?.[item.role]) RenderFunction = markdownCustomRenders[item.role];
134
- if (!RenderFunction) return dom;
135
-
136
- return <RenderFunction displayMode={type} dom={dom} id={id} text={text} />;
137
- },
138
- [item?.role, type],
139
- );
140
-
141
- const error = useErrorContent(item?.error);
142
-
143
- const [historyLength] = useChatStore((s) => [chatSelectors.currentChats(s).length]);
144
-
145
- const enableHistoryDivider = useAgentStore((s) => {
146
- const config = agentSelectors.currentAgentChatConfig(s);
54
+ const Item = memo<ChatListItemProps>(
55
+ ({ className, enableHistoryDivider, id, actionBar, endRender }) => {
56
+ const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
57
+ const { t } = useTranslation('common');
58
+ const { styles, cx } = useStyles();
59
+ const [type = 'chat'] = useAgentStore((s) => {
60
+ const config = agentSelectors.currentAgentChatConfig(s);
61
+ return [config.displayMode];
62
+ });
63
+
64
+ const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
65
+
66
+ const [
67
+ isMessageLoading,
68
+ generating,
69
+ isInRAGFlow,
70
+ editing,
71
+ toggleMessageEditing,
72
+ updateMessageContent,
73
+ ] = useChatStore((s) => [
74
+ chatSelectors.isMessageLoading(id)(s),
75
+ chatSelectors.isMessageGenerating(id)(s),
76
+ chatSelectors.isMessageInRAGFlow(id)(s),
77
+ chatSelectors.isMessageEditing(id)(s),
78
+ s.toggleMessageEditing,
79
+ s.modifyMessageContent,
80
+ ]);
81
+
82
+ // when the message is in RAG flow or the AI generating, it should be in loading state
83
+ const isProcessing = isInRAGFlow || generating;
84
+
85
+ const onAvatarsClick = useAvatarsClick(item?.role);
86
+
87
+ const renderMessage = useCallback(
88
+ (editableContent: ReactNode) => {
89
+ if (!item?.role) return;
90
+ const RenderFunction = renderMessages[item.role] ?? renderMessages['default'];
91
+
92
+ if (!RenderFunction) return;
93
+
94
+ return <RenderFunction {...item} editableContent={editableContent} />;
95
+ },
96
+ [item],
97
+ );
98
+
99
+ const BelowMessage = useCallback(
100
+ ({ data }: { data: ChatMessage }) => {
101
+ if (!item?.role) return;
102
+ const RenderFunction = renderBelowMessages[item.role] ?? renderBelowMessages['default'];
103
+
104
+ if (!RenderFunction) return;
105
+
106
+ return <RenderFunction {...data} />;
107
+ },
108
+ [item?.role],
109
+ );
110
+
111
+ const MessageExtra = useCallback(
112
+ ({ data }: { data: ChatMessage }) => {
113
+ if (!item?.role) return;
114
+ let RenderFunction;
115
+ if (renderMessagesExtra?.[item.role]) RenderFunction = renderMessagesExtra[item.role];
116
+
117
+ if (!RenderFunction) return;
118
+ return <RenderFunction {...data} />;
119
+ },
120
+ [item?.role],
121
+ );
122
+
123
+ const markdownCustomRender = useCallback(
124
+ (dom: ReactNode, { text }: { text: string }) => {
125
+ if (!item?.role) return dom;
126
+ let RenderFunction;
127
+
128
+ if (renderMessagesExtra?.[item.role]) RenderFunction = markdownCustomRenders[item.role];
129
+ if (!RenderFunction) return dom;
130
+
131
+ return <RenderFunction displayMode={type} dom={dom} id={id} text={text} />;
132
+ },
133
+ [item?.role, type],
134
+ );
135
+
136
+ const error = useErrorContent(item?.error);
137
+
138
+ // remove line breaks in artifact tag to make the ast transform easier
139
+ const message =
140
+ !editing && item?.role === 'assistant' ? processWithArtifact(item?.content) : item?.content;
141
+
142
+ // ======================= Performance Optimization ======================= //
143
+ // these useMemo/useCallback are all for the performance optimization
144
+ // maybe we can remove it in React 19
145
+ // ======================================================================== //
146
+
147
+ const components = useMemo(
148
+ () =>
149
+ Object.fromEntries(
150
+ markdownElements.map((element) => {
151
+ const Component = element.Component;
152
+
153
+ return [element.tag, (props: any) => <Component {...props} id={id} />];
154
+ }),
155
+ ),
156
+ [id],
157
+ );
158
+
159
+ const markdownProps = useMemo(
160
+ () => ({
161
+ components,
162
+ customRender: markdownCustomRender,
163
+ rehypePlugins,
164
+ }),
165
+ [components, markdownCustomRender],
166
+ );
167
+
168
+ const onChange = useCallback((value: string) => updateMessageContent(id, value), [id]);
169
+
170
+ const onDoubleClick = useCallback<MouseEventHandler<HTMLDivElement>>(
171
+ (e) => {
172
+ if (!item) return;
173
+ if (item.id === 'default' || item.error) return;
174
+ if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
175
+ toggleMessageEditing(id, true);
176
+ }
177
+ },
178
+ [item],
179
+ );
180
+
181
+ const text = useMemo(
182
+ () => ({
183
+ cancel: t('cancel'),
184
+ confirm: t('ok'),
185
+ edit: t('edit'),
186
+ }),
187
+ [t],
188
+ );
189
+
190
+ const onEditingChange = useCallback((edit: boolean) => {
191
+ toggleMessageEditing(id, edit);
192
+ }, []);
193
+
194
+ const belowMessage = useMemo(() => item && <BelowMessage data={item} />, [item]);
195
+ const errorMessage = useMemo(() => item && <ErrorMessageExtra data={item} />, [item]);
196
+ const messageExtra = useMemo(() => item && <MessageExtra data={item} />, [item]);
197
+
147
198
  return (
148
- config.enableHistoryCount &&
149
- historyLength > (config.historyCount ?? 0) &&
150
- config.historyCount === historyLength - index
199
+ item && (
200
+ <>
201
+ {enableHistoryDivider && <History />}
202
+ <Flexbox className={cx(styles.message, className, isMessageLoading && styles.loading)}>
203
+ <ChatItem
204
+ actions={actionBar}
205
+ avatar={item.meta}
206
+ belowMessage={belowMessage}
207
+ editing={editing}
208
+ error={error}
209
+ errorMessage={errorMessage}
210
+ fontSize={fontSize}
211
+ loading={isProcessing}
212
+ markdownProps={markdownProps}
213
+ message={message}
214
+ messageExtra={messageExtra}
215
+ onAvatarClick={onAvatarsClick}
216
+ onChange={onChange}
217
+ onDoubleClick={onDoubleClick}
218
+ onEditingChange={onEditingChange}
219
+ placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
220
+ primary={item.role === 'user'}
221
+ renderMessage={renderMessage}
222
+ text={text}
223
+ time={item.updatedAt || item.createdAt}
224
+ type={type === 'chat' ? 'block' : 'pure'}
225
+ />
226
+ {endRender}
227
+ </Flexbox>
228
+ </>
229
+ )
151
230
  );
152
- });
153
-
154
- // remove line breaks in artifact tag to make the ast transform easier
155
- const message =
156
- !editing && item?.role === 'assistant' ? processWithArtifact(item?.content) : item?.content;
157
-
158
- // ======================= Performance Optimization ======================= //
159
- // these useMemo/useCallback are all for the performance optimization
160
- // maybe we can remove it in React 19
161
- // ======================================================================== //
162
-
163
- const components = useMemo(
164
- () =>
165
- Object.fromEntries(
166
- markdownElements.map((element) => {
167
- const Component = element.Component;
168
-
169
- return [element.tag, (props: any) => <Component {...props} id={id} />];
170
- }),
171
- ),
172
- [id],
173
- );
174
-
175
- const markdownProps = useMemo(
176
- () => ({
177
- components,
178
- customRender: markdownCustomRender,
179
- rehypePlugins,
180
- }),
181
- [components, markdownCustomRender],
182
- );
183
-
184
- const onChange = useCallback((value: string) => updateMessageContent(id, value), [id]);
185
-
186
- const onDoubleClick = useCallback<MouseEventHandler<HTMLDivElement>>(
187
- (e) => {
188
- if (!item) return;
189
- if (item.id === 'default' || item.error) return;
190
- if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
191
- toggleMessageEditing(id, true);
192
- }
193
- },
194
- [item],
195
- );
196
-
197
- const text = useMemo(
198
- () => ({
199
- cancel: t('cancel'),
200
- confirm: t('ok'),
201
- edit: t('edit'),
202
- }),
203
- [t],
204
- );
205
-
206
- const onEditingChange = useCallback((edit: boolean) => {
207
- toggleMessageEditing(id, edit);
208
- }, []);
209
-
210
- const actions = useMemo(
211
- () =>
212
- !hideActionBar && (
213
- <ActionsBar
214
- index={index}
215
- setEditing={(edit) => {
216
- toggleMessageEditing(id, edit);
217
- }}
218
- />
219
- ),
220
- [hideActionBar, index, id],
221
- );
222
-
223
- const belowMessage = useMemo(() => item && <BelowMessage data={item} />, [item]);
224
- const errorMessage = useMemo(() => item && <ErrorMessageExtra data={item} />, [item]);
225
- const messageExtra = useMemo(() => item && <MessageExtra data={item} />, [item]);
226
-
227
- return (
228
- item && (
229
- <>
230
- {enableHistoryDivider && <History />}
231
- <ChatItem
232
- actions={actions}
233
- avatar={item.meta}
234
- belowMessage={belowMessage}
235
- className={cx(styles.message, isMessageLoading && styles.loading)}
236
- editing={editing}
237
- error={error}
238
- errorMessage={errorMessage}
239
- fontSize={fontSize}
240
- loading={isProcessing}
241
- markdownProps={markdownProps}
242
- message={message}
243
- messageExtra={messageExtra}
244
- onAvatarClick={onAvatarsClick}
245
- onChange={onChange}
246
- onDoubleClick={onDoubleClick}
247
- onEditingChange={onEditingChange}
248
- placement={type === 'chat' ? (item.role === 'user' ? 'right' : 'left') : 'left'}
249
- primary={item.role === 'user'}
250
- renderMessage={renderMessage}
251
- text={text}
252
- time={item.updatedAt || item.createdAt}
253
- type={type === 'chat' ? 'block' : 'pure'}
254
- />
255
- </>
256
- )
257
- );
258
- });
231
+ },
232
+ );
259
233
 
260
234
  Item.displayName = 'ChatItem';
261
235
 
@@ -12,105 +12,96 @@ import { useChatStore } from '@/store/chat';
12
12
  import { chatSelectors } from '@/store/chat/selectors';
13
13
 
14
14
  import AutoScroll from '../AutoScroll';
15
- import Item from '../ChatItem';
16
15
  import SkeletonList from '../SkeletonList';
17
16
 
18
17
  interface VirtualizedListProps {
19
18
  dataSource: string[];
20
- hideActionBar?: boolean;
21
- itemContent?: (index: number, data: any, context: any) => ReactNode;
19
+ itemContent: (index: number, data: any, context: any) => ReactNode;
22
20
  mobile?: boolean;
23
21
  }
24
22
 
25
- const VirtualizedList = memo<VirtualizedListProps>(
26
- ({ mobile, dataSource, hideActionBar, itemContent }) => {
27
- const virtuosoRef = useRef<VirtuosoHandle>(null);
28
- const [atBottom, setAtBottom] = useState(true);
29
- const [isScrolling, setIsScrolling] = useState(false);
23
+ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemContent }) => {
24
+ const virtuosoRef = useRef<VirtuosoHandle>(null);
25
+ const [atBottom, setAtBottom] = useState(true);
26
+ const [isScrolling, setIsScrolling] = useState(false);
30
27
 
31
- const [id, isFirstLoading, isCurrentChatLoaded] = useChatStore((s) => [
32
- chatSelectors.currentChatKey(s),
33
- chatSelectors.currentChatLoadingState(s),
34
- chatSelectors.isCurrentChatLoaded(s),
35
- ]);
28
+ const [id, isFirstLoading, isCurrentChatLoaded] = useChatStore((s) => [
29
+ chatSelectors.currentChatKey(s),
30
+ chatSelectors.currentChatLoadingState(s),
31
+ chatSelectors.isCurrentChatLoaded(s),
32
+ ]);
36
33
 
37
- useEffect(() => {
38
- if (virtuosoRef.current) {
39
- virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
40
- }
41
- }, [id]);
34
+ useEffect(() => {
35
+ if (virtuosoRef.current) {
36
+ virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
37
+ }
38
+ }, [id]);
42
39
 
43
- const prevDataLengthRef = useRef(dataSource.length);
40
+ const prevDataLengthRef = useRef(dataSource.length);
44
41
 
45
- const getFollowOutput = useCallback(() => {
46
- const newFollowOutput = dataSource.length > prevDataLengthRef.current ? 'auto' : false;
47
- prevDataLengthRef.current = dataSource.length;
48
- return newFollowOutput;
49
- }, [dataSource.length]);
42
+ const getFollowOutput = useCallback(() => {
43
+ const newFollowOutput = dataSource.length > prevDataLengthRef.current ? 'auto' : false;
44
+ prevDataLengthRef.current = dataSource.length;
45
+ return newFollowOutput;
46
+ }, [dataSource.length]);
50
47
 
51
- const theme = useTheme();
52
- // overscan should be 3 times the height of the window
53
- const overscan = typeof window !== 'undefined' ? window.innerHeight * 3 : 0;
48
+ const theme = useTheme();
49
+ // overscan should be 3 times the height of the window
50
+ const overscan = typeof window !== 'undefined' ? window.innerHeight * 3 : 0;
54
51
 
55
- const defaultItemContent = useCallback(
56
- (index: number, id: string) => <Item hideActionBar={hideActionBar} id={id} index={index} />,
57
- [mobile, hideActionBar],
58
- );
59
-
60
- // first time loading or not loaded
61
- if (isFirstLoading) return <SkeletonList mobile={mobile} />;
52
+ // first time loading or not loaded
53
+ if (isFirstLoading) return <SkeletonList mobile={mobile} />;
62
54
 
63
- if (!isCurrentChatLoaded)
64
- // use skeleton list when not loaded in server mode due to the loading duration is much longer than client mode
65
- return isServerMode ? (
66
- <SkeletonList mobile={mobile} />
67
- ) : (
68
- // in client mode and switch page, using the center loading for smooth transition
69
- <Center height={'100%'} width={'100%'}>
70
- <Icon
71
- icon={Loader2Icon}
72
- size={{ fontSize: 32 }}
73
- spin
74
- style={{ color: theme.colorTextTertiary }}
75
- />
76
- </Center>
77
- );
78
-
79
- return (
80
- <Flexbox height={'100%'}>
81
- <Virtuoso
82
- atBottomStateChange={setAtBottom}
83
- atBottomThreshold={50 * (mobile ? 2 : 1)}
84
- computeItemKey={(_, item) => item}
85
- data={dataSource}
86
- followOutput={getFollowOutput}
87
- increaseViewportBy={overscan}
88
- initialTopMostItemIndex={dataSource?.length - 1}
89
- isScrolling={setIsScrolling}
90
- itemContent={itemContent ?? defaultItemContent}
91
- overscan={overscan}
92
- ref={virtuosoRef}
93
- />
94
- <AutoScroll
95
- atBottom={atBottom}
96
- isScrolling={isScrolling}
97
- onScrollToBottom={(type) => {
98
- const virtuoso = virtuosoRef.current;
99
- switch (type) {
100
- case 'auto': {
101
- virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
102
- break;
103
- }
104
- case 'click': {
105
- virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
106
- break;
107
- }
108
- }
109
- }}
55
+ if (!isCurrentChatLoaded)
56
+ // use skeleton list when not loaded in server mode due to the loading duration is much longer than client mode
57
+ return isServerMode ? (
58
+ <SkeletonList mobile={mobile} />
59
+ ) : (
60
+ // in client mode and switch page, using the center loading for smooth transition
61
+ <Center height={'100%'} width={'100%'}>
62
+ <Icon
63
+ icon={Loader2Icon}
64
+ size={{ fontSize: 32 }}
65
+ spin
66
+ style={{ color: theme.colorTextTertiary }}
110
67
  />
111
- </Flexbox>
68
+ </Center>
112
69
  );
113
- },
114
- );
70
+
71
+ return (
72
+ <Flexbox height={'100%'}>
73
+ <Virtuoso
74
+ atBottomStateChange={setAtBottom}
75
+ atBottomThreshold={50 * (mobile ? 2 : 1)}
76
+ computeItemKey={(_, item) => item}
77
+ data={dataSource}
78
+ followOutput={getFollowOutput}
79
+ increaseViewportBy={overscan}
80
+ initialTopMostItemIndex={dataSource?.length - 1}
81
+ isScrolling={setIsScrolling}
82
+ itemContent={itemContent}
83
+ overscan={overscan}
84
+ ref={virtuosoRef}
85
+ />
86
+ <AutoScroll
87
+ atBottom={atBottom}
88
+ isScrolling={isScrolling}
89
+ onScrollToBottom={(type) => {
90
+ const virtuoso = virtuosoRef.current;
91
+ switch (type) {
92
+ case 'auto': {
93
+ virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
94
+ break;
95
+ }
96
+ case 'click': {
97
+ virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
98
+ break;
99
+ }
100
+ }
101
+ }}
102
+ />
103
+ </Flexbox>
104
+ );
105
+ });
115
106
 
116
107
  export default VirtualizedList;
@@ -1,4 +1,3 @@
1
1
  export { default as ChatItem } from './components/ChatItem';
2
- export { default as InboxWelcome } from './components/InboxWelcome';
3
2
  export { default as SkeletonList } from './components/SkeletonList';
4
3
  export { default as VirtualizedList } from './components/VirtualizedList';
@@ -5,6 +5,6 @@ import { useEnable } from './useEnable';
5
5
 
6
6
  export const Artifacts: PortalImpl = {
7
7
  Body,
8
- Header,
8
+ Title: Header,
9
9
  useEnable,
10
10
  };
@@ -5,6 +5,6 @@ import { useEnable } from './useEnable';
5
5
 
6
6
  export const FilePreview: PortalImpl = {
7
7
  Body,
8
- Header,
8
+ Title: Header,
9
9
  useEnable,
10
10
  };
@@ -4,7 +4,7 @@ import { Typography } from 'antd';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- const Header = memo(() => {
7
+ const Title = memo(() => {
8
8
  const { t } = useTranslation('portal');
9
9
 
10
10
  return (
@@ -14,4 +14,4 @@ const Header = memo(() => {
14
14
  );
15
15
  });
16
16
 
17
- export default Header;
17
+ export default Title;
@@ -1,2 +1,2 @@
1
1
  export { default as HomeBody } from './Body';
2
- export { default as HomeHeader } from './Header';
2
+ export { default as HomeTitle } from './Title';
@@ -5,6 +5,6 @@ import { useEnable } from './useEnable';
5
5
 
6
6
  export const MessageDetail: PortalImpl = {
7
7
  Body,
8
- Header,
8
+ Title: Header,
9
9
  useEnable,
10
10
  };
@@ -5,6 +5,6 @@ import { useEnable } from './useEnable';
5
5
 
6
6
  export const Plugins: PortalImpl = {
7
7
  Body,
8
- Header,
8
+ Title: Header,
9
9
  useEnable,
10
10
  };