@agentscope-ai/chat 1.1.72-beta.1783304718621 → 1.1.72-beta.1783337191111
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/components/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Actions.tsx +2 -5
- package/components/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Card.tsx +1 -4
- package/components/AgentScopeRuntimeWebUI/core/Chat/Input/index.tsx +47 -148
- package/components/AgentScopeRuntimeWebUI/core/Chat/MessageList/UserMessageAnchors/index.tsx +12 -2
- package/components/AgentScopeRuntimeWebUI/core/Chat/MessageList/index.tsx +57 -32
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.tsx +122 -265
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.tsx +56 -49
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.tsx +9 -17
- package/components/AgentScopeRuntimeWebUI/core/Chat/index.tsx +3 -42
- package/components/AgentScopeRuntimeWebUI/core/Chat/styles.tsx +3 -324
- package/components/AgentScopeRuntimeWebUI/core/ChatAnywhere/index.tsx +1 -1
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.tsx +8 -34
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereInputContext.tsx +11 -4
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereOptionsContext.tsx +2 -13
- package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.tsx +15 -82
- package/components/AgentScopeRuntimeWebUI/core/types/IChatAnywhere.ts +8 -153
- package/components/AgentScopeRuntimeWebUI/core/types/ISessions.tsx +1 -5
- package/components/AgentScopeRuntimeWebUI/starter/index.tsx +13 -99
- package/components/Bubble/style/list.ts +5 -0
- package/components/Sender/index.tsx +2 -11
- package/lib/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Actions.js +4 -6
- package/lib/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Card.js +2 -3
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/index.d.ts +1 -9
- package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/index.js +19 -116
- package/lib/AgentScopeRuntimeWebUI/core/Chat/MessageList/UserMessageAnchors/index.js +13 -2
- package/lib/AgentScopeRuntimeWebUI/core/Chat/MessageList/index.js +67 -49
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.d.ts +2 -16
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.js +148 -260
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.d.ts +1 -1
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.js +89 -91
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.d.ts +3 -3
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.js +12 -33
- package/lib/AgentScopeRuntimeWebUI/core/Chat/index.js +2 -33
- package/lib/AgentScopeRuntimeWebUI/core/Chat/styles.js +1 -161
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.d.ts +1 -23
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.js +8 -32
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereInputContext.js +8 -4
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereOptionsContext.js +2 -13
- package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.js +21 -110
- package/lib/AgentScopeRuntimeWebUI/core/types/IChatAnywhere.d.ts +2 -138
- package/lib/AgentScopeRuntimeWebUI/core/types/ISessions.d.ts +0 -4
- package/lib/AgentScopeRuntimeWebUI/starter/index.js +20 -144
- package/lib/Bubble/style/list.js +3 -1
- package/lib/Sender/index.d.ts +0 -5
- package/lib/Sender/index.js +5 -8
- package/package.json +1 -2
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.tsx +0 -321
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/__tests__/inputQueue.scenarios.zh-CN.md +0 -59
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/__tests__/inputQueue.test.ts +0 -801
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.ts +0 -514
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.ts +0 -415
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.ts +0 -102
- package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.ts +0 -87
- package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.tsx +0 -1477
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.d.ts +0 -15
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.js +0 -304
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.d.ts +0 -17
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.js +0 -653
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.d.ts +0 -102
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.js +0 -254
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.d.ts +0 -17
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.js +0 -39
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.d.ts +0 -5
- package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.js +0 -108
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.d.ts +0 -59
- package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.js +0 -1114
|
@@ -26,7 +26,7 @@ export default function Tools(props: {
|
|
|
26
26
|
isLast?: boolean;
|
|
27
27
|
}) {
|
|
28
28
|
const { t } = useTranslation();
|
|
29
|
-
const
|
|
29
|
+
const actionsOptionsList = useChatAnywhereOptions(v => v.actions?.list) || [
|
|
30
30
|
{
|
|
31
31
|
icon: <SparkCopyLine />,
|
|
32
32
|
onClick: () => {
|
|
@@ -34,10 +34,6 @@ export default function Tools(props: {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
];
|
|
37
|
-
const configuredActions = useChatAnywhereOptions(v => v.actions?.list);
|
|
38
|
-
const actionsOptionsList = Array.isArray(configuredActions)
|
|
39
|
-
? configuredActions
|
|
40
|
-
: defaultActions;
|
|
41
37
|
|
|
42
38
|
const replace = useChatAnywhereOptions(v => v.actions?.replace) ?? true;
|
|
43
39
|
const rightOption = useChatAnywhereOptions(v => v.actions?.right);
|
|
@@ -88,3 +84,4 @@ export default function Tools(props: {
|
|
|
88
84
|
right={rightNode}
|
|
89
85
|
/>
|
|
90
86
|
}
|
|
87
|
+
|
|
@@ -33,9 +33,6 @@ function DefaultResponseRender(props: {
|
|
|
33
33
|
}) {
|
|
34
34
|
const avatar = useChatAnywhereOptions(v => v.welcome.avatar);
|
|
35
35
|
const nick = useChatAnywhereOptions(v => v.welcome.nick);
|
|
36
|
-
const nickNode = typeof nick === 'string' || React.isValidElement(nick)
|
|
37
|
-
? nick
|
|
38
|
-
: null;
|
|
39
36
|
const messages = useMemo(() => {
|
|
40
37
|
return AgentScopeRuntimeResponseBuilder.mergeToolMessages(props.data.output);
|
|
41
38
|
}, [props.data.output])
|
|
@@ -46,7 +43,7 @@ function DefaultResponseRender(props: {
|
|
|
46
43
|
return <>
|
|
47
44
|
{avatar && <Flex align="center" gap={8} style={{ marginBottom: 8 }}>
|
|
48
45
|
<Avatar src={avatar} />
|
|
49
|
-
{
|
|
46
|
+
{nick && <span>{nick as string}</span>}
|
|
50
47
|
</Flex>}
|
|
51
48
|
{props.contentPrepend ?? null}
|
|
52
49
|
{
|
|
@@ -1,36 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
IAgentScopeRuntimeWebUIInputData,
|
|
5
|
-
useProviderContext,
|
|
6
|
-
} from '@agentscope-ai/chat';
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useProviderContext, ChatInput, Disclaimer } from '@agentscope-ai/chat';
|
|
3
|
+
import { useChatAnywhereOptions } from "../../Context/ChatAnywhereOptionsContext";
|
|
7
4
|
import { useGetState } from 'ahooks';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import type { QueueEnqueueResult, QueuedInputItem } from '../InputQueue';
|
|
12
|
-
import useAttachments from './useAttachments';
|
|
5
|
+
import { useChatAnywhereInput } from "../../Context/ChatAnywhereInputContext";
|
|
6
|
+
import useAttachments from "./useAttachments";
|
|
7
|
+
import { IAgentScopeRuntimeWebUIInputData } from "@agentscope-ai/chat";
|
|
13
8
|
|
|
14
9
|
export interface InputProps {
|
|
15
10
|
onCancel: () => void;
|
|
16
|
-
onSubmit: (
|
|
17
|
-
data: IAgentScopeRuntimeWebUIInputData,
|
|
18
|
-
) => void | QueueEnqueueResult | Promise<void | QueueEnqueueResult>;
|
|
19
|
-
queue?: {
|
|
20
|
-
items: QueuedInputItem[];
|
|
21
|
-
isOwner: boolean;
|
|
22
|
-
panel?: ReactNode;
|
|
23
|
-
onEnqueue: (
|
|
24
|
-
data: IAgentScopeRuntimeWebUIInputData,
|
|
25
|
-
) => QueueEnqueueResult | Promise<QueueEnqueueResult>;
|
|
26
|
-
};
|
|
11
|
+
onSubmit: (data: IAgentScopeRuntimeWebUIInputData) => void;
|
|
27
12
|
}
|
|
28
13
|
|
|
29
14
|
export default function Input(props: InputProps) {
|
|
30
15
|
const [content, setContent, getContent] = useGetState('');
|
|
31
16
|
const prefixCls = useProviderContext().getPrefixCls('chat-anywhere-input');
|
|
32
|
-
const senderOptions = useChatAnywhereOptions(
|
|
33
|
-
const inputContext = useChatAnywhereInput(
|
|
17
|
+
const senderOptions = useChatAnywhereOptions(v => v.sender);
|
|
18
|
+
const inputContext = useChatAnywhereInput(v => v);
|
|
34
19
|
|
|
35
20
|
const {
|
|
36
21
|
placeholder = '',
|
|
@@ -54,139 +39,53 @@ export default function Input(props: InputProps) {
|
|
|
54
39
|
handlePasteFile,
|
|
55
40
|
handleDropFile,
|
|
56
41
|
uploadIconButton,
|
|
57
|
-
uploadFileListHeader
|
|
42
|
+
uploadFileListHeader
|
|
58
43
|
} = useAttachments(attachments, { disabled: !!inputContext.disabled });
|
|
59
44
|
|
|
60
|
-
const getSubmittableData = useCallback(() => {
|
|
61
|
-
const fileList = (getFileList?.() || []).filter((i) => i.response?.url);
|
|
62
|
-
const query = getContent();
|
|
63
|
-
if (!query.trim() && fileList.length === 0) return;
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
query,
|
|
67
|
-
text: query,
|
|
68
|
-
fileList,
|
|
69
|
-
attachments: fileList,
|
|
70
|
-
};
|
|
71
|
-
}, [getContent, getFileList]);
|
|
72
|
-
|
|
73
|
-
const clearInput = useCallback(() => {
|
|
74
|
-
setContent('');
|
|
75
|
-
setFileList?.([]);
|
|
76
|
-
}, [setContent, setFileList]);
|
|
77
|
-
|
|
78
|
-
const handleEnqueue = useCallback(async () => {
|
|
79
|
-
const next = await beforeSubmit();
|
|
80
|
-
if (!next) return;
|
|
81
|
-
|
|
82
|
-
const data = getSubmittableData();
|
|
83
|
-
if (!data) return;
|
|
84
|
-
if (!props.queue) return;
|
|
85
|
-
|
|
86
|
-
const result = await props.queue.onEnqueue(data);
|
|
87
|
-
if (result.ok) {
|
|
88
|
-
clearInput();
|
|
89
|
-
}
|
|
90
|
-
}, [beforeSubmit, clearInput, getSubmittableData, props.queue]);
|
|
91
45
|
|
|
92
46
|
const handleSubmit = useCallback(async () => {
|
|
93
47
|
const next = await beforeSubmit();
|
|
94
48
|
if (!next) return;
|
|
95
49
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
(inputContext.loading ||
|
|
102
|
-
props.queue.items.length ||
|
|
103
|
-
props.queue.isOwner === false)
|
|
104
|
-
) {
|
|
105
|
-
const result = await props.queue.onEnqueue(data);
|
|
106
|
-
if (result.ok) {
|
|
107
|
-
clearInput();
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
const result = await props.onSubmit(data);
|
|
111
|
-
if (result && !result.ok) return;
|
|
112
|
-
clearInput();
|
|
113
|
-
}
|
|
114
|
-
}, [
|
|
115
|
-
beforeSubmit,
|
|
116
|
-
clearInput,
|
|
117
|
-
getSubmittableData,
|
|
118
|
-
inputContext.loading,
|
|
119
|
-
props.onSubmit,
|
|
120
|
-
props.queue,
|
|
121
|
-
]);
|
|
122
|
-
|
|
123
|
-
const handleKeyDownCapture = useCallback(
|
|
124
|
-
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
125
|
-
if (event.key !== 'Enter' || event.shiftKey) return;
|
|
126
|
-
if (event.nativeEvent?.isComposing) return;
|
|
127
|
-
if (!props.queue) return;
|
|
128
|
-
const forceEnqueue = event.ctrlKey || event.metaKey;
|
|
129
|
-
if (!forceEnqueue && !inputContext.loading && !props.queue?.items.length)
|
|
130
|
-
return;
|
|
131
|
-
|
|
132
|
-
const data = getSubmittableData();
|
|
133
|
-
if (!data) return;
|
|
134
|
-
|
|
135
|
-
event.preventDefault();
|
|
136
|
-
event.stopPropagation();
|
|
137
|
-
void (forceEnqueue ? handleEnqueue() : handleSubmit());
|
|
138
|
-
},
|
|
139
|
-
[
|
|
140
|
-
getSubmittableData,
|
|
141
|
-
handleEnqueue,
|
|
142
|
-
handleSubmit,
|
|
143
|
-
inputContext.loading,
|
|
144
|
-
props.queue,
|
|
145
|
-
props.queue?.items.length,
|
|
146
|
-
],
|
|
147
|
-
);
|
|
50
|
+
const fileList = (getFileList?.() || []).filter(i => i.response?.url);
|
|
51
|
+
props.onSubmit({ query: getContent(), fileList });
|
|
52
|
+
setContent('');
|
|
53
|
+
setFileList?.([]);
|
|
54
|
+
}, []);
|
|
148
55
|
|
|
149
56
|
const handleCancel = useCallback(() => {
|
|
150
57
|
props.onCancel();
|
|
151
|
-
}, [
|
|
152
|
-
|
|
153
|
-
return
|
|
154
|
-
<div className={prefixCls}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
prefix
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
onPasteFile={handlePasteFile}
|
|
180
|
-
onDropFile={handleDropFile}
|
|
181
|
-
suggestions={suggestions}
|
|
182
|
-
/>
|
|
183
|
-
{afterUI}
|
|
184
|
-
</div>
|
|
185
|
-
{disclaimer ? (
|
|
186
|
-
<Disclaimer desc={disclaimer} />
|
|
187
|
-
) : (
|
|
188
|
-
<div className={`${prefixCls}-blank`}></div>
|
|
189
|
-
)}
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
return <div className={prefixCls}>
|
|
61
|
+
<div className={`${prefixCls}-wrapper`}>
|
|
62
|
+
{beforeUI}
|
|
63
|
+
<ChatInput
|
|
64
|
+
loading={inputContext.loading}
|
|
65
|
+
disabled={inputContext.disabled}
|
|
66
|
+
placeholder={placeholder}
|
|
67
|
+
value={content}
|
|
68
|
+
prefix={<>
|
|
69
|
+
{uploadIconButton}
|
|
70
|
+
{prefix}
|
|
71
|
+
</>}
|
|
72
|
+
header={uploadFileListHeader}
|
|
73
|
+
onChange={setContent}
|
|
74
|
+
maxLength={maxLength}
|
|
75
|
+
showCharacterCount={showCharacterCount}
|
|
76
|
+
characterCountRender={characterCountRender}
|
|
77
|
+
actionAffix={actionAffix}
|
|
78
|
+
onSubmit={handleSubmit}
|
|
79
|
+
onCancel={handleCancel}
|
|
80
|
+
allowSpeech={allowSpeech}
|
|
81
|
+
onPasteFile={handlePasteFile}
|
|
82
|
+
onDropFile={handleDropFile}
|
|
83
|
+
suggestions={suggestions}
|
|
84
|
+
/>
|
|
85
|
+
{afterUI}
|
|
190
86
|
</div>
|
|
191
|
-
|
|
87
|
+
{
|
|
88
|
+
disclaimer ? <Disclaimer desc={disclaimer} /> : <div className={`${prefixCls}-blank`}></div>
|
|
89
|
+
}
|
|
90
|
+
</div>;
|
|
192
91
|
}
|
package/components/AgentScopeRuntimeWebUI/core/Chat/MessageList/UserMessageAnchors/index.tsx
CHANGED
|
@@ -348,11 +348,21 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
|
|
|
348
348
|
const [trackHeight, setTrackHeight] = useState(0);
|
|
349
349
|
const normalizedMinGap = useMemo(() => getUserMessageAnchorMinGap(minGap), [minGap]);
|
|
350
350
|
const normalizedMinCount = useMemo(() => getUserMessageAnchorMinCount(minCount), [minCount]);
|
|
351
|
+
const itemsRef = React.useRef(items);
|
|
352
|
+
itemsRef.current = items;
|
|
353
|
+
|
|
354
|
+
const userMessagesKey = useMemo(() => {
|
|
355
|
+
if (!enabled) return '';
|
|
356
|
+
return items
|
|
357
|
+
.filter((message) => message.role === 'user' && message.id)
|
|
358
|
+
.map((message) => `${message.id}:${message.cards?.length ?? 0}`)
|
|
359
|
+
.join('|');
|
|
360
|
+
}, [enabled, items]);
|
|
351
361
|
|
|
352
362
|
const anchors = useMemo<UserMessageAnchor[]>(() => {
|
|
353
363
|
if (!enabled) return [];
|
|
354
364
|
|
|
355
|
-
const visualItems =
|
|
365
|
+
const visualItems = itemsRef.current.slice().reverse();
|
|
356
366
|
const totalCount = Math.max(visualItems.length, 1);
|
|
357
367
|
|
|
358
368
|
return visualItems.reduce<UserMessageAnchor[]>((result, message, index) => {
|
|
@@ -365,7 +375,7 @@ export default function UserMessageAnchors(props: UserMessageAnchorsProps) {
|
|
|
365
375
|
});
|
|
366
376
|
return result;
|
|
367
377
|
}, []);
|
|
368
|
-
}, [enabled,
|
|
378
|
+
}, [enabled, userMessagesKey]);
|
|
369
379
|
const anchorIdsKey = useMemo(() => anchors.map((anchor) => anchor.id).join('|'), [anchors]);
|
|
370
380
|
const visible = enabled && anchors.length > 0 && anchors.length >= normalizedMinCount;
|
|
371
381
|
|
|
@@ -10,13 +10,13 @@ import React, { useCallback, useMemo, useState } from "react";
|
|
|
10
10
|
import { flushSync } from "react-dom";
|
|
11
11
|
import UserMessageAnchors from "./UserMessageAnchors";
|
|
12
12
|
|
|
13
|
-
const PAGE_SIZE =
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const PAGE_SIZE = 20;
|
|
14
|
+
const INITIAL_VISIBLE_MESSAGE_COUNT = 60;
|
|
15
|
+
const ANCHOR_JUMP_RENDER_BUFFER = 24;
|
|
16
16
|
|
|
17
17
|
type MessageWithHistory = IAgentScopeRuntimeWebUIMessage & { history?: boolean };
|
|
18
18
|
type HistoryRange = { start: number; end: number };
|
|
19
|
-
const INITIAL_HISTORY_RANGE: HistoryRange = { start: 0, end:
|
|
19
|
+
const INITIAL_HISTORY_RANGE: HistoryRange = { start: 0, end: INITIAL_VISIBLE_MESSAGE_COUNT };
|
|
20
20
|
|
|
21
21
|
function waitForNextFrame() {
|
|
22
22
|
return new Promise<void>((resolve) => {
|
|
@@ -26,8 +26,11 @@ function waitForNextFrame() {
|
|
|
26
26
|
|
|
27
27
|
function getAnchorJumpHistoryRange(historyIndex: number, historyLength: number): HistoryRange {
|
|
28
28
|
return {
|
|
29
|
-
start:
|
|
30
|
-
end: Math.min(
|
|
29
|
+
start: 0,
|
|
30
|
+
end: Math.min(
|
|
31
|
+
historyLength,
|
|
32
|
+
Math.max(INITIAL_VISIBLE_MESSAGE_COUNT, historyIndex + ANCHOR_JUMP_RENDER_BUFFER),
|
|
33
|
+
),
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -36,13 +39,15 @@ function areHistoryRangesEqual(prev: HistoryRange, next: HistoryRange) {
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
+
* Render only the newest window of the conversation, then reveal older
|
|
43
|
+
* messages on demand. Sessions loaded from storage and messages produced in
|
|
44
|
+
* the current run use the same window so long-running chats do not accumulate
|
|
45
|
+
* an unbounded number of mounted bubbles.
|
|
42
46
|
*/
|
|
43
|
-
function
|
|
47
|
+
function useMessageWindowPagination(
|
|
44
48
|
allMessages: MessageWithHistory[],
|
|
45
49
|
sessionId: string | undefined,
|
|
50
|
+
enabled: boolean,
|
|
46
51
|
) {
|
|
47
52
|
const [historyRange, setHistoryRangeState] = useState<HistoryRange>(INITIAL_HISTORY_RANGE);
|
|
48
53
|
const historyRangeRef = React.useRef<HistoryRange>(INITIAL_HISTORY_RANGE);
|
|
@@ -61,25 +66,34 @@ function useSimulatedMessagePagination(
|
|
|
61
66
|
|
|
62
67
|
React.useLayoutEffect(() => {
|
|
63
68
|
ensureMessageSequenceRef.current += 1;
|
|
64
|
-
setHistoryRange(
|
|
65
|
-
|
|
69
|
+
setHistoryRange(enabled
|
|
70
|
+
? INITIAL_HISTORY_RANGE
|
|
71
|
+
: { start: 0, end: allMessages.length });
|
|
72
|
+
}, [allMessages.length, enabled, sessionId, setHistoryRange]);
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
() => allMessages.filter((m) => !m.history),
|
|
73
|
-
[allMessages],
|
|
74
|
-
);
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
if (!enabled) {
|
|
76
|
+
setHistoryRange({ start: 0, end: allMessages.length });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
updateHistoryRange((prev) => {
|
|
81
|
+
if (allMessages.length <= prev.end) return prev;
|
|
82
|
+
return {
|
|
83
|
+
start: 0,
|
|
84
|
+
end: Math.min(
|
|
85
|
+
allMessages.length,
|
|
86
|
+
Math.max(prev.end, INITIAL_VISIBLE_MESSAGE_COUNT),
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}, [allMessages.length, enabled, setHistoryRange, updateHistoryRange]);
|
|
91
|
+
|
|
92
|
+
const noMore = !enabled || historyRange.end >= allMessages.length;
|
|
78
93
|
|
|
79
|
-
// Show new messages first, then paginated history messages.
|
|
80
94
|
const visibleMessages = useMemo(
|
|
81
|
-
() =>
|
|
82
|
-
[
|
|
95
|
+
() => enabled ? allMessages.slice(historyRange.start, historyRange.end) : allMessages,
|
|
96
|
+
[allMessages, enabled, historyRange.end, historyRange.start],
|
|
83
97
|
);
|
|
84
98
|
|
|
85
99
|
const loadMore = useCallback(() => {
|
|
@@ -88,19 +102,24 @@ function useSimulatedMessagePagination(
|
|
|
88
102
|
flushSync(() => {
|
|
89
103
|
updateHistoryRange((prev) => ({
|
|
90
104
|
start: prev.start,
|
|
91
|
-
end: Math.min(
|
|
105
|
+
end: Math.min(allMessages.length, prev.end + PAGE_SIZE),
|
|
92
106
|
}));
|
|
93
107
|
});
|
|
94
108
|
resolve();
|
|
95
109
|
}, 300);
|
|
96
110
|
});
|
|
97
|
-
}, [
|
|
111
|
+
}, [allMessages.length, updateHistoryRange]);
|
|
98
112
|
|
|
99
113
|
const ensureMessageVisible = useCallback(async (messageId: string) => {
|
|
114
|
+
if (!enabled) {
|
|
115
|
+
await waitForNextFrame();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
const sequence = ensureMessageSequenceRef.current + 1;
|
|
101
120
|
ensureMessageSequenceRef.current = sequence;
|
|
102
121
|
|
|
103
|
-
const historyIndex =
|
|
122
|
+
const historyIndex = allMessages.findIndex((message) => message.id === messageId);
|
|
104
123
|
await waitForNextFrame();
|
|
105
124
|
|
|
106
125
|
if (ensureMessageSequenceRef.current !== sequence) {
|
|
@@ -111,7 +130,7 @@ function useSimulatedMessagePagination(
|
|
|
111
130
|
const currentRange = historyRangeRef.current;
|
|
112
131
|
const targetVisible = historyIndex >= currentRange.start && historyIndex < currentRange.end;
|
|
113
132
|
if (!targetVisible) {
|
|
114
|
-
const nextRange = getAnchorJumpHistoryRange(historyIndex,
|
|
133
|
+
const nextRange = getAnchorJumpHistoryRange(historyIndex, allMessages.length);
|
|
115
134
|
if (!areHistoryRangesEqual(currentRange, nextRange)) {
|
|
116
135
|
flushSync(() => {
|
|
117
136
|
setHistoryRange(nextRange);
|
|
@@ -124,7 +143,7 @@ function useSimulatedMessagePagination(
|
|
|
124
143
|
throw new Error('Message visibility request was superseded');
|
|
125
144
|
}
|
|
126
145
|
await waitForNextFrame();
|
|
127
|
-
}, [
|
|
146
|
+
}, [allMessages, enabled, setHistoryRange]);
|
|
128
147
|
|
|
129
148
|
return { visibleMessages, noMore, loadMore, ensureMessageVisible };
|
|
130
149
|
}
|
|
@@ -135,11 +154,17 @@ export default function MessageList(props: { onSubmit: (data: { query: string; f
|
|
|
135
154
|
const prefixCls = useProviderContext().getPrefixCls('chat-anywhere-message-list');
|
|
136
155
|
const scrollContainerClassName = `${prefixCls}-bubble-scroll`;
|
|
137
156
|
const currentSessionId = useContextSelector(ChatAnywhereSessionsContext, v => v.currentSessionId);
|
|
138
|
-
const
|
|
157
|
+
const bubbleListOptions = useChatAnywhereOptions(v => v.theme?.bubbleList);
|
|
158
|
+
const userMessageAnchorsOptions = bubbleListOptions?.userMessageAnchors;
|
|
159
|
+
const paginationEnabled = bubbleListOptions?.pagination !== false;
|
|
139
160
|
const listRef = React.useRef<{ scrollToBottom: () => void } | null>(null);
|
|
140
161
|
const prevMessagesLengthRef = React.useRef(safeMessages.length);
|
|
141
162
|
|
|
142
|
-
const { visibleMessages, noMore, loadMore, ensureMessageVisible } =
|
|
163
|
+
const { visibleMessages, noMore, loadMore, ensureMessageVisible } = useMessageWindowPagination(
|
|
164
|
+
safeMessages,
|
|
165
|
+
currentSessionId,
|
|
166
|
+
paginationEnabled,
|
|
167
|
+
);
|
|
143
168
|
const renderedItemsKey = useMemo(() => visibleMessages.map((message) => message.id).join('|'), [visibleMessages]);
|
|
144
169
|
|
|
145
170
|
React.useEffect(() => {
|