@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.
Files changed (66) hide show
  1. package/components/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Actions.tsx +2 -5
  2. package/components/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Card.tsx +1 -4
  3. package/components/AgentScopeRuntimeWebUI/core/Chat/Input/index.tsx +47 -148
  4. package/components/AgentScopeRuntimeWebUI/core/Chat/MessageList/UserMessageAnchors/index.tsx +12 -2
  5. package/components/AgentScopeRuntimeWebUI/core/Chat/MessageList/index.tsx +57 -32
  6. package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.tsx +122 -265
  7. package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.tsx +56 -49
  8. package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.tsx +9 -17
  9. package/components/AgentScopeRuntimeWebUI/core/Chat/index.tsx +3 -42
  10. package/components/AgentScopeRuntimeWebUI/core/Chat/styles.tsx +3 -324
  11. package/components/AgentScopeRuntimeWebUI/core/ChatAnywhere/index.tsx +1 -1
  12. package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.tsx +8 -34
  13. package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereInputContext.tsx +11 -4
  14. package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereOptionsContext.tsx +2 -13
  15. package/components/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.tsx +15 -82
  16. package/components/AgentScopeRuntimeWebUI/core/types/IChatAnywhere.ts +8 -153
  17. package/components/AgentScopeRuntimeWebUI/core/types/ISessions.tsx +1 -5
  18. package/components/AgentScopeRuntimeWebUI/starter/index.tsx +13 -99
  19. package/components/Bubble/style/list.ts +5 -0
  20. package/components/Sender/index.tsx +2 -11
  21. package/lib/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Actions.js +4 -6
  22. package/lib/AgentScopeRuntimeWebUI/core/AgentScopeRuntime/Response/Card.js +2 -3
  23. package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/index.d.ts +1 -9
  24. package/lib/AgentScopeRuntimeWebUI/core/Chat/Input/index.js +19 -116
  25. package/lib/AgentScopeRuntimeWebUI/core/Chat/MessageList/UserMessageAnchors/index.js +13 -2
  26. package/lib/AgentScopeRuntimeWebUI/core/Chat/MessageList/index.js +67 -49
  27. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.d.ts +2 -16
  28. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatController.js +148 -260
  29. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.d.ts +1 -1
  30. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatRequest.js +89 -91
  31. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.d.ts +3 -3
  32. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useChatSessionHandler.js +12 -33
  33. package/lib/AgentScopeRuntimeWebUI/core/Chat/index.js +2 -33
  34. package/lib/AgentScopeRuntimeWebUI/core/Chat/styles.js +1 -161
  35. package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.d.ts +1 -23
  36. package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereI18nContext.js +8 -32
  37. package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereInputContext.js +8 -4
  38. package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereOptionsContext.js +2 -13
  39. package/lib/AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.js +21 -110
  40. package/lib/AgentScopeRuntimeWebUI/core/types/IChatAnywhere.d.ts +2 -138
  41. package/lib/AgentScopeRuntimeWebUI/core/types/ISessions.d.ts +0 -4
  42. package/lib/AgentScopeRuntimeWebUI/starter/index.js +20 -144
  43. package/lib/Bubble/style/list.js +3 -1
  44. package/lib/Sender/index.d.ts +0 -5
  45. package/lib/Sender/index.js +5 -8
  46. package/package.json +1 -2
  47. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.tsx +0 -321
  48. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/__tests__/inputQueue.scenarios.zh-CN.md +0 -59
  49. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/__tests__/inputQueue.test.ts +0 -801
  50. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.ts +0 -514
  51. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.ts +0 -415
  52. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.ts +0 -102
  53. package/components/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.ts +0 -87
  54. package/components/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.tsx +0 -1477
  55. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.d.ts +0 -15
  56. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/Panel.js +0 -304
  57. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.d.ts +0 -17
  58. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/background.js +0 -653
  59. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.d.ts +0 -102
  60. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/index.js +0 -254
  61. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.d.ts +0 -17
  62. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/session.js +0 -39
  63. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.d.ts +0 -5
  64. package/lib/AgentScopeRuntimeWebUI/core/Chat/InputQueue/storage.js +0 -108
  65. package/lib/AgentScopeRuntimeWebUI/core/Chat/hooks/useInputQueueController.d.ts +0 -59
  66. 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 defaultActions = [
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
- {nickNode && <span>{nickNode}</span>}
46
+ {nick && <span>{nick as string}</span>}
50
47
  </Flex>}
51
48
  {props.contentPrepend ?? null}
52
49
  {
@@ -1,36 +1,21 @@
1
- import {
2
- ChatInput,
3
- Disclaimer,
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 { useCallback, type ReactNode } from 'react';
9
- import { useChatAnywhereInput } from '../../Context/ChatAnywhereInputContext';
10
- import { useChatAnywhereOptions } from '../../Context/ChatAnywhereOptionsContext';
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((v) => v.sender);
33
- const inputContext = useChatAnywhereInput((v) => v);
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 data = getSubmittableData();
97
- if (!data) return;
98
-
99
- if (
100
- props.queue &&
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
- }, [props.onCancel]);
152
-
153
- return (
154
- <div className={prefixCls} onKeyDownCapture={handleKeyDownCapture}>
155
- <div className={`${prefixCls}-wrapper`}>
156
- {beforeUI}
157
- {props.queue?.panel}
158
- <ChatInput
159
- loading={inputContext.loading}
160
- disabled={inputContext.disabled}
161
- placeholder={placeholder}
162
- value={content}
163
- prefix={
164
- <>
165
- {uploadIconButton}
166
- {prefix}
167
- </>
168
- }
169
- header={uploadFileListHeader}
170
- onChange={setContent}
171
- maxLength={maxLength}
172
- showCharacterCount={showCharacterCount}
173
- characterCountRender={characterCountRender}
174
- actionAffix={actionAffix}
175
- onSubmit={handleSubmit}
176
- onCancel={handleCancel}
177
- allowSubmitWhenLoading={!!props.queue}
178
- allowSpeech={allowSpeech}
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
  }
@@ -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 = items.slice().reverse();
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, items]);
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 = 10;
14
- const ANCHOR_JUMP_WINDOW_BEFORE = 24;
15
- const ANCHOR_JUMP_WINDOW_AFTER = 36;
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: PAGE_SIZE };
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: Math.max(0, historyIndex - ANCHOR_JUMP_WINDOW_BEFORE),
30
- end: Math.min(historyLength, historyIndex + ANCHOR_JUMP_WINDOW_AFTER),
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
- * Simulated backend pagination:
40
- * - History messages loaded with the session are revealed page by page.
41
- * - New messages produced in the current session are always shown in full.
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 useSimulatedMessagePagination(
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(INITIAL_HISTORY_RANGE);
65
- }, [sessionId, setHistoryRange]);
69
+ setHistoryRange(enabled
70
+ ? INITIAL_HISTORY_RANGE
71
+ : { start: 0, end: allMessages.length });
72
+ }, [allMessages.length, enabled, sessionId, setHistoryRange]);
66
73
 
67
- const historyMessages = useMemo(
68
- () => allMessages.filter((m) => m.history),
69
- [allMessages],
70
- );
71
- const newMessages = useMemo(
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
- const visibleHistory = historyMessages.slice(historyRange.start, historyRange.end);
77
- const noMore = historyRange.end >= historyMessages.length;
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
- () => [...newMessages, ...visibleHistory],
82
- [newMessages, visibleHistory],
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(historyMessages.length, prev.end + PAGE_SIZE),
105
+ end: Math.min(allMessages.length, prev.end + PAGE_SIZE),
92
106
  }));
93
107
  });
94
108
  resolve();
95
109
  }, 300);
96
110
  });
97
- }, [historyMessages.length, updateHistoryRange]);
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 = historyMessages.findIndex((message) => message.id === messageId);
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, historyMessages.length);
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
- }, [historyMessages, setHistoryRange]);
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 userMessageAnchorsOptions = useChatAnywhereOptions(v => v.theme?.bubbleList?.userMessageAnchors);
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 } = useSimulatedMessagePagination(safeMessages, currentSessionId);
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(() => {