@lobehub/chat 1.32.3 → 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.
- package/CHANGELOG.md +50 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/locales/ar/models.json +12 -0
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/models.json +12 -0
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/models.json +12 -0
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/models.json +12 -0
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/models.json +12 -0
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/models.json +12 -0
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/models.json +12 -0
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/models.json +12 -0
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/models.json +12 -0
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/models.json +12 -0
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/models.json +12 -0
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/models.json +12 -0
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/models.json +12 -0
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/models.json +12 -0
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/models.json +12 -0
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/models.json +12 -0
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/models.json +12 -0
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/models.json +12 -0
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +2 -2
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +39 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +20 -14
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +44 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/index.tsx +17 -0
- package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +1 -13
- package/src/app/metadata.ts +2 -1
- package/src/components/BrandWatermark/index.tsx +1 -0
- package/src/const/message.ts +1 -1
- package/src/features/Conversation/components/ChatItem/ActionsBar.tsx +9 -13
- package/src/features/Conversation/components/ChatItem/index.tsx +183 -209
- package/src/features/Conversation/components/VirtualizedList/index.tsx +75 -84
- package/src/features/Conversation/index.ts +0 -1
- package/src/features/Portal/Artifacts/index.ts +1 -1
- package/src/features/Portal/FilePreview/index.ts +1 -1
- package/src/features/Portal/Home/{Header.tsx → Title.tsx} +2 -2
- package/src/features/Portal/Home/index.ts +1 -1
- package/src/features/Portal/MessageDetail/index.ts +1 -1
- package/src/features/Portal/Plugins/index.ts +1 -1
- package/src/features/Portal/components/Header.tsx +29 -0
- package/src/features/Portal/router.tsx +22 -3
- package/src/features/Portal/type.ts +3 -1
- package/src/features/{Conversation/components → ShareModal/ShareImage}/ChatList/index.tsx +3 -4
- package/src/features/ShareModal/ShareImage/Preview.tsx +1 -1
- package/src/features/ShareModal/ShareJSON/index.tsx +1 -1
- package/src/features/ShareModal/ShareText/index.tsx +1 -1
- package/src/layout/GlobalProvider/Debug.tsx +15 -0
- package/src/layout/GlobalProvider/index.tsx +4 -2
- package/src/locales/resources.ts +5 -2
- package/src/server/ld.ts +2 -2
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +5 -5
- package/src/store/chat/slices/message/action.ts +2 -2
- package/src/store/chat/slices/message/selectors.test.ts +0 -86
- package/src/store/chat/slices/message/selectors.ts +55 -73
- package/src/store/chat/slices/plugin/action.test.ts +2 -2
- package/src/store/chat/slices/plugin/action.ts +1 -1
- package/src/store/chat/slices/topic/action.ts +2 -2
- /package/src/{features/Conversation/components → app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem}/InboxWelcome/AgentsSuggest.tsx +0 -0
- /package/src/{features/Conversation/components → app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem}/InboxWelcome/QuestionSuggest.tsx +0 -0
- /package/src/{features/Conversation/components → app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem}/InboxWelcome/index.tsx +0 -0
@@ -4,8 +4,7 @@ import { memo, useCallback } from 'react';
|
|
4
4
|
|
5
5
|
import { useChatStore } from '@/store/chat';
|
6
6
|
import { chatSelectors } from '@/store/chat/selectors';
|
7
|
-
import {
|
8
|
-
import { sessionMetaSelectors } from '@/store/session/selectors';
|
7
|
+
import { MessageRoleType } from '@/types/message';
|
9
8
|
|
10
9
|
import { renderActions, useActionsClick } from '../../Actions';
|
11
10
|
import { useChatListActionsBar } from '../../hooks/useChatListActionsBar';
|
@@ -25,34 +24,31 @@ const ActionsBar = memo<ActionsBarProps>((props) => {
|
|
25
24
|
});
|
26
25
|
|
27
26
|
interface ActionsProps {
|
28
|
-
|
29
|
-
setEditing: (edit: boolean) => void;
|
27
|
+
id: string;
|
30
28
|
}
|
31
|
-
const Actions = memo<ActionsProps>(({ index, setEditing }) => {
|
32
|
-
const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
);
|
30
|
+
const Actions = memo<ActionsProps>(({ id }) => {
|
31
|
+
const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
|
32
|
+
const [toggleMessageEditing] = useChatStore((s) => [s.toggleMessageEditing]);
|
38
33
|
const onActionsClick = useActionsClick();
|
39
34
|
|
40
35
|
const handleActionClick = useCallback(
|
41
36
|
async (action: ActionEvent) => {
|
42
37
|
switch (action.key) {
|
43
38
|
case 'edit': {
|
44
|
-
|
39
|
+
toggleMessageEditing(id, true);
|
45
40
|
}
|
46
41
|
}
|
42
|
+
if (!item) return;
|
47
43
|
|
48
44
|
onActionsClick(action, item);
|
49
45
|
},
|
50
46
|
[item],
|
51
47
|
);
|
52
48
|
|
53
|
-
const RenderFunction = renderActions[item?.role] ?? ActionsBar;
|
49
|
+
const RenderFunction = renderActions[(item?.role || '') as MessageRoleType] ?? ActionsBar;
|
54
50
|
|
55
|
-
return <RenderFunction {...item} onActionClick={handleActionClick} />;
|
51
|
+
return <RenderFunction {...item!} onActionClick={handleActionClick} />;
|
56
52
|
});
|
57
53
|
|
58
54
|
export default Actions;
|
@@ -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
|
-
|
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>(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
const
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
const
|
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
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
const [id, isFirstLoading, isCurrentChatLoaded] = useChatStore((s) => [
|
29
|
+
chatSelectors.currentChatKey(s),
|
30
|
+
chatSelectors.currentChatLoadingState(s),
|
31
|
+
chatSelectors.isCurrentChatLoaded(s),
|
32
|
+
]);
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
useEffect(() => {
|
35
|
+
if (virtuosoRef.current) {
|
36
|
+
virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
|
37
|
+
}
|
38
|
+
}, [id]);
|
42
39
|
|
43
|
-
|
40
|
+
const prevDataLengthRef = useRef(dataSource.length);
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
</
|
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';
|