@ant-design/agentic-ui 2.30.3 → 2.30.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.
@@ -212,9 +212,10 @@ function _ts_generator(thisArg, body) {
212
212
  };
213
213
  }
214
214
  }
215
- import { memo, useContext } from "react";
215
+ import { memo, useContext, useRef } from "react";
216
216
  import { ConfigProvider, Flex } from "antd";
217
217
  import clsx from "clsx";
218
+ import { nanoid } from "nanoid";
218
219
  import React from "react";
219
220
  import { BubbleAvatar } from "./Avatar";
220
221
  import { BubbleBeforeNode } from "./BubbleBeforeNode";
@@ -222,6 +223,7 @@ import { BubbleConfigContext } from "./BubbleConfigProvide";
222
223
  import { BubbleFileView } from "./FileView";
223
224
  import { BubbleMessageDisplay } from "./MessagesContent";
224
225
  import { MessagesContext } from "./MessagesContent/BubbleContext";
226
+ import { LOADING_FLAT } from "./MessagesContent";
225
227
  import { BubbleExtra } from "./MessagesContent/BubbleExtra";
226
228
  import { useStyle } from "./style";
227
229
  import { BubbleTitle } from "./Title";
@@ -269,13 +271,14 @@ var getTaskList = function getTaskList(originData) {
269
271
  * </AIBubble>
270
272
  * ```
271
273
  */ export var AIBubble = /*#__PURE__*/ memo(function(props) {
272
- var _props_readonly;
274
+ var _ref, _messageDisplayKeyRef_current, _props_readonly;
273
275
  var _props_originData, _props_originData1, _props_originData2, _props_originData3, _props_originData4, _props_originData5, _props_bubbleRenderConfig, _props_originData_fileMap, _props_originData6, _props_bubbleRenderConfig1, _props_styles, _props_bubbleRenderConfig2, _bubbleRenderConfig_render;
274
276
  var onAvatarClick = props.onAvatarClick, className = props.className, style = props.style, bubbleRenderConfig = props.bubbleRenderConfig, classNames = props.classNames, styles = props.styles, originData = props.originData, preMessage = props.preMessage;
275
277
  var _React_useState = _sliced_to_array(React.useState(false), 2), hidePadding = _React_useState[0], setHidePadding = _React_useState[1];
278
+ var messageDisplayKeyRef = useRef(null);
276
279
  var getPrefixCls = useContext(ConfigProvider.ConfigContext).getPrefixCls;
277
280
  var context = useContext(BubbleConfigContext);
278
- var _ref = context || {}, compact = _ref.compact, standalone = _ref.standalone, extraShowOnHover = _ref.extraShowOnHover;
281
+ var _ref1 = context || {}, compact = _ref1.compact, standalone = _ref1.standalone, extraShowOnHover = _ref1.extraShowOnHover;
279
282
  var prefixClass = getPrefixCls('agentic');
280
283
  var _useStyle = useStyle(prefixClass), wrapSSR = _useStyle.wrapSSR, hashId = _useStyle.hashId;
281
284
  var preMessageSameRole = isSameRoleAsPrevious(preMessage, originData);
@@ -300,14 +303,21 @@ var getTaskList = function getTaskList(originData) {
300
303
  prefixCls: "".concat(prefixClass, "-bubble-avatar"),
301
304
  style: styles === null || styles === void 0 ? void 0 : styles.bubbleListItemAvatarStyle
302
305
  }));
306
+ var id = props === null || props === void 0 ? void 0 : (_props_originData = props.originData) === null || _props_originData === void 0 ? void 0 : _props_originData.id;
307
+ if (id === undefined || id === LOADING_FLAT) {
308
+ if (!messageDisplayKeyRef.current) {
309
+ messageDisplayKeyRef.current = nanoid();
310
+ }
311
+ }
312
+ var messageDisplayKey = (_ref = (_messageDisplayKeyRef_current = messageDisplayKeyRef.current) !== null && _messageDisplayKeyRef_current !== void 0 ? _messageDisplayKeyRef_current : id) !== null && _ref !== void 0 ? _ref : nanoid();
303
313
  var messageContent = /*#__PURE__*/ React.createElement(BubbleMessageDisplay, {
304
314
  markdownRenderConfig: props.markdownRenderConfig,
305
315
  docListProps: props.docListProps,
306
316
  bubbleListRef: props.bubbleListRef,
307
317
  bubbleListItemExtraStyle: styles === null || styles === void 0 ? void 0 : styles.bubbleListItemExtraStyle,
308
318
  bubbleRef: props.bubbleRef,
309
- content: props === null || props === void 0 ? void 0 : (_props_originData = props.originData) === null || _props_originData === void 0 ? void 0 : _props_originData.content,
310
- key: props === null || props === void 0 ? void 0 : (_props_originData1 = props.originData) === null || _props_originData1 === void 0 ? void 0 : _props_originData1.id,
319
+ content: props === null || props === void 0 ? void 0 : (_props_originData1 = props.originData) === null || _props_originData1 === void 0 ? void 0 : _props_originData1.content,
320
+ key: messageDisplayKey,
311
321
  "data-id": props === null || props === void 0 ? void 0 : (_props_originData2 = props.originData) === null || _props_originData2 === void 0 ? void 0 : _props_originData2.id,
312
322
  avatar: props === null || props === void 0 ? void 0 : (_props_originData3 = props.originData) === null || _props_originData3 === void 0 ? void 0 : _props_originData3.meta,
313
323
  readonly: (_props_readonly = props.readonly) !== null && _props_readonly !== void 0 ? _props_readonly : false,
@@ -74,8 +74,9 @@ export var PureBubbleList = /*#__PURE__*/ React.memo(function(props) {
74
74
  }, [
75
75
  props.style
76
76
  ]);
77
- // 为 loading 项生成唯一的 key,使用 ref 缓存以确保稳定性
78
77
  var loadingKeysRef = useRef(new Map());
78
+ var loadingKeyByIndexRef = useRef(new Map());
79
+ var realIdToStableKeyRef = useRef(new Map());
79
80
  var listDom = useMemo(function() {
80
81
  var _props_lazy;
81
82
  var isLazyEnabled = (_props_lazy = props.lazy) === null || _props_lazy === void 0 ? void 0 : _props_lazy.enable;
@@ -88,15 +89,26 @@ export var PureBubbleList = /*#__PURE__*/ React.memo(function(props) {
88
89
  isLatest: isLast,
89
90
  isLast: isLast
90
91
  });
91
- // 如果 id 是 LOADING_FLAT,使用 uuid 作为 key
92
- // 使用 index 和 createAt 的组合作为缓存 key,确保同一项在重新渲染时保持相同的 key
93
- var itemKey = item.id;
92
+ var itemKey;
94
93
  if (item.id === LOADING_FLAT) {
95
94
  var cacheKey = "".concat(index, "-").concat(item.createAt || Date.now());
96
95
  if (!loadingKeysRef.current.has(cacheKey)) {
97
96
  loadingKeysRef.current.set(cacheKey, nanoid());
98
97
  }
99
98
  itemKey = loadingKeysRef.current.get(cacheKey);
99
+ loadingKeyByIndexRef.current.set(index, itemKey);
100
+ } else {
101
+ var realId = item.id;
102
+ var prevLoadingKey = loadingKeyByIndexRef.current.get(index);
103
+ if (prevLoadingKey) {
104
+ itemKey = prevLoadingKey;
105
+ realIdToStableKeyRef.current.set(realId, prevLoadingKey);
106
+ loadingKeyByIndexRef.current.delete(index);
107
+ } else if (realIdToStableKeyRef.current.has(realId)) {
108
+ itemKey = realIdToStableKeyRef.current.get(realId);
109
+ } else {
110
+ itemKey = realId;
111
+ }
100
112
  }
101
113
  var bubbleElement = /*#__PURE__*/ React.createElement(BubbleComponent, {
102
114
  key: itemKey,
@@ -161,8 +161,11 @@ import { useStyle } from "./style";
161
161
  JSON.stringify(props.style)
162
162
  ]);
163
163
  // 为 loading 项生成唯一的 key,使用 ref 缓存以确保稳定性
164
- // 使用 item 的唯一标识(index + createAt)作为缓存 key
165
164
  var loadingKeysRef = useRef(new Map());
165
+ // 记录每个 index 在上一轮是否是 loading,用于 loading→real 过渡时保持 key 稳定,避免闪动
166
+ var loadingKeyByIndexRef = useRef(new Map());
167
+ // 真实 id 映射到稳定 key(过渡后沿用),避免同一条消息因 id 变化导致 remount
168
+ var realIdToStableKeyRef = useRef(new Map());
166
169
  var bubbleListDom = useMemo(function() {
167
170
  var _props_lazy;
168
171
  var isLazyEnabled = (_props_lazy = props.lazy) === null || _props_lazy === void 0 ? void 0 : _props_lazy.enable;
@@ -174,15 +177,26 @@ import { useStyle } from "./style";
174
177
  // 保持向后兼容性,设置isLatest
175
178
  item.isLatest = isLast;
176
179
  item.isLast = isLast;
177
- // 如果 id 是 LOADING_FLAT,使用 uuid 作为 key
178
- // 使用 index 和 createAt 的组合作为缓存 key,确保同一项在重新渲染时保持相同的 key
179
- var itemKey = item.id;
180
+ var itemKey;
180
181
  if (item.id === LOADING_FLAT) {
181
182
  var cacheKey = "".concat(index, "-").concat(item.createAt || Date.now());
182
183
  if (!loadingKeysRef.current.has(cacheKey)) {
183
184
  loadingKeysRef.current.set(cacheKey, nanoid());
184
185
  }
185
186
  itemKey = loadingKeysRef.current.get(cacheKey);
187
+ loadingKeyByIndexRef.current.set(index, itemKey);
188
+ } else {
189
+ var realId = item.id;
190
+ var prevLoadingKey = loadingKeyByIndexRef.current.get(index);
191
+ if (prevLoadingKey) {
192
+ itemKey = prevLoadingKey;
193
+ realIdToStableKeyRef.current.set(realId, prevLoadingKey);
194
+ loadingKeyByIndexRef.current.delete(index);
195
+ } else if (realIdToStableKeyRef.current.has(realId)) {
196
+ itemKey = realIdToStableKeyRef.current.get(realId);
197
+ } else {
198
+ itemKey = realId;
199
+ }
186
200
  }
187
201
  var bubbleElement = /*#__PURE__*/ React.createElement(Bubble, {
188
202
  key: itemKey,
@@ -13,7 +13,8 @@ export interface AnimationTextProps {
13
13
  * 流式文字淡入动画组件。
14
14
  *
15
15
  * 采用 opacity + translateY(GPU 硬件加速),清爽流派。
16
- * 动画结束后通过 animationend 标记 done,仍用 span 包裹以保持布局稳定。
16
+ * 同一段流式前缀追加只触发**一次**入场动画;后续增量仅更新文案。内容被替换
17
+ * (非前缀增长)时重新播放入场。动画结束后仍用 span 包裹以保持布局稳定。
17
18
  */
18
19
  declare const AnimationText: React.NamedExoticComponent<AnimationTextProps>;
19
20
  export default AnimationText;
@@ -6,25 +6,6 @@ function _array_like_to_array(arr, len) {
6
6
  function _array_with_holes(arr) {
7
7
  if (Array.isArray(arr)) return arr;
8
8
  }
9
- function _array_without_holes(arr) {
10
- if (Array.isArray(arr)) return _array_like_to_array(arr);
11
- }
12
- function _define_property(obj, key, value) {
13
- if (key in obj) {
14
- Object.defineProperty(obj, key, {
15
- value: value,
16
- enumerable: true,
17
- configurable: true,
18
- writable: true
19
- });
20
- } else {
21
- obj[key] = value;
22
- }
23
- return obj;
24
- }
25
- function _iterable_to_array(iter) {
26
- if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
27
- }
28
9
  function _iterable_to_array_limit(arr, i) {
29
10
  var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
30
11
  if (_i == null) return;
@@ -52,54 +33,9 @@ function _iterable_to_array_limit(arr, i) {
52
33
  function _non_iterable_rest() {
53
34
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
54
35
  }
55
- function _non_iterable_spread() {
56
- throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57
- }
58
- function _object_spread(target) {
59
- for(var i = 1; i < arguments.length; i++){
60
- var source = arguments[i] != null ? arguments[i] : {};
61
- var ownKeys = Object.keys(source);
62
- if (typeof Object.getOwnPropertySymbols === "function") {
63
- ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
64
- return Object.getOwnPropertyDescriptor(source, sym).enumerable;
65
- }));
66
- }
67
- ownKeys.forEach(function(key) {
68
- _define_property(target, key, source[key]);
69
- });
70
- }
71
- return target;
72
- }
73
- function ownKeys(object, enumerableOnly) {
74
- var keys = Object.keys(object);
75
- if (Object.getOwnPropertySymbols) {
76
- var symbols = Object.getOwnPropertySymbols(object);
77
- if (enumerableOnly) {
78
- symbols = symbols.filter(function(sym) {
79
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
80
- });
81
- }
82
- keys.push.apply(keys, symbols);
83
- }
84
- return keys;
85
- }
86
- function _object_spread_props(target, source) {
87
- source = source != null ? source : {};
88
- if (Object.getOwnPropertyDescriptors) {
89
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
90
- } else {
91
- ownKeys(Object(source)).forEach(function(key) {
92
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
93
- });
94
- }
95
- return target;
96
- }
97
36
  function _sliced_to_array(arr, i) {
98
37
  return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
99
38
  }
100
- function _to_consumable_array(arr) {
101
- return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
102
- }
103
39
  function _unsupported_iterable_to_array(o, minLen) {
104
40
  if (!o) return;
105
41
  if (typeof o === "string") return _array_like_to_array(o, minLen);
@@ -134,11 +70,13 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
134
70
  * 流式文字淡入动画组件。
135
71
  *
136
72
  * 采用 opacity + translateY(GPU 硬件加速),清爽流派。
137
- * 动画结束后通过 animationend 标记 done,仍用 span 包裹以保持布局稳定。
73
+ * 同一段流式前缀追加只触发**一次**入场动画;后续增量仅更新文案。内容被替换
74
+ * (非前缀增长)时重新播放入场。动画结束后仍用 span 包裹以保持布局稳定。
138
75
  */ var AnimationText = /*#__PURE__*/ React.memo(function(param) {
139
76
  var children = param.children, animationConfig = param.animationConfig;
140
77
  var _ref = animationConfig || {}, _ref_fadeDuration = _ref.fadeDuration, fadeDuration = _ref_fadeDuration === void 0 ? 200 : _ref_fadeDuration, _ref_easing = _ref.easing, easing = _ref_easing === void 0 ? 'ease-out' : _ref_easing;
141
- var _useState = _sliced_to_array(useState([]), 2), chunks = _useState[0], setChunks = _useState[1];
78
+ var _useState = _sliced_to_array(useState(false), 2), animComplete = _useState[0], setAnimComplete = _useState[1];
79
+ var _useState1 = _sliced_to_array(useState(0), 2), animSession = _useState1[0], setAnimSession = _useState1[1];
142
80
  var prevTextRef = useRef('');
143
81
  var text = extractText(children);
144
82
  var hasElementContent = hasElementNode(children);
@@ -148,44 +86,28 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
148
86
  return;
149
87
  }
150
88
  if (text === prevTextRef.current) return;
151
- if (!prevTextRef.current || !text.startsWith(prevTextRef.current)) {
152
- setChunks([
153
- {
154
- key: '0',
155
- text: text,
156
- content: children,
157
- done: false
158
- }
159
- ]);
89
+ var prev = prevTextRef.current;
90
+ if (!prev) {
91
+ if (!text) return;
160
92
  prevTextRef.current = text;
93
+ setAnimComplete(false);
161
94
  return;
162
95
  }
163
- var prevLen = prevTextRef.current.length;
164
- var newText = text.slice(prevLen);
165
- var newKey = "anim-".concat(Date.now(), "-").concat(prevLen);
166
- setChunks(function(prev) {
167
- return _to_consumable_array(prev).concat([
168
- {
169
- key: newKey,
170
- text: newText,
171
- done: false
172
- }
173
- ]);
96
+ if (text.length > prev.length && text.startsWith(prev)) {
97
+ prevTextRef.current = text;
98
+ return;
99
+ }
100
+ setAnimComplete(false);
101
+ setAnimSession(function(s) {
102
+ return s + 1;
174
103
  });
175
104
  prevTextRef.current = text;
176
105
  }, [
177
106
  text,
178
- children,
179
107
  hasElementContent
180
108
  ]);
181
- var handleAnimationEnd = function handleAnimationEnd(key) {
182
- setChunks(function(prev) {
183
- return prev.map(function(c) {
184
- return c.key === key ? _object_spread_props(_object_spread({}, c), {
185
- done: true
186
- }) : c;
187
- });
188
- });
109
+ var handleAnimationEnd = function handleAnimationEnd() {
110
+ return setAnimComplete(true);
189
111
  };
190
112
  var animationStyle = useMemo(function() {
191
113
  return {
@@ -207,20 +129,13 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
207
129
  if (hasElementContent) {
208
130
  return /*#__PURE__*/ React.createElement(React.Fragment, null, children);
209
131
  }
210
- return /*#__PURE__*/ React.createElement(React.Fragment, null, chunks.map(function(chunk) {
211
- var _chunk_content;
212
- var rendered = (_chunk_content = chunk.content) !== null && _chunk_content !== void 0 ? _chunk_content : chunk.text;
213
- return chunk.done ? /*#__PURE__*/ React.createElement("span", {
214
- key: chunk.key,
215
- style: doneChunkStyle
216
- }, rendered) : /*#__PURE__*/ React.createElement("span", {
217
- key: chunk.key,
218
- style: animationStyle,
219
- onAnimationEnd: function onAnimationEnd() {
220
- return handleAnimationEnd(chunk.key);
221
- }
222
- }, rendered);
223
- }));
132
+ return animComplete ? /*#__PURE__*/ React.createElement("span", {
133
+ style: doneChunkStyle
134
+ }, children) : /*#__PURE__*/ React.createElement("span", {
135
+ key: animSession,
136
+ style: animationStyle,
137
+ onAnimationEnd: handleAnimationEnd
138
+ }, children);
224
139
  });
225
140
  AnimationText.displayName = 'AnimationText';
226
141
  export default AnimationText;
@@ -77,7 +77,8 @@ var DEFAULT_BACKGROUND_BATCH_MULTIPLIER = 10;
77
77
  speed: (_ref2 = options === null || options === void 0 ? void 0 : options.speed) !== null && _ref2 !== void 0 ? _ref2 : DEFAULT_SPEED,
78
78
  flushOnComplete: (_ref3 = options === null || options === void 0 ? void 0 : options.flushOnComplete) !== null && _ref3 !== void 0 ? _ref3 : true,
79
79
  backgroundInterval: (_ref4 = options === null || options === void 0 ? void 0 : options.backgroundInterval) !== null && _ref4 !== void 0 ? _ref4 : DEFAULT_BACKGROUND_INTERVAL,
80
- backgroundBatchMultiplier: (_ref5 = options === null || options === void 0 ? void 0 : options.backgroundBatchMultiplier) !== null && _ref5 !== void 0 ? _ref5 : DEFAULT_BACKGROUND_BATCH_MULTIPLIER
80
+ backgroundBatchMultiplier: (_ref5 = options === null || options === void 0 ? void 0 : options.backgroundBatchMultiplier) !== null && _ref5 !== void 0 ? _ref5 : DEFAULT_BACKGROUND_BATCH_MULTIPLIER,
81
+ animateTailChars: options === null || options === void 0 ? void 0 : options.animateTailChars
81
82
  };
82
83
  this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
83
84
  if (typeof document !== 'undefined') {
@@ -94,6 +95,13 @@ var DEFAULT_BACKGROUND_BATCH_MULTIPLIER = 10;
94
95
  this.onFlush(content);
95
96
  return;
96
97
  }
98
+ // 仅对末尾 N 字做动画:立即展示前面内容
99
+ var tail = this.options.animateTailChars;
100
+ if (tail !== undefined && tail > 0 && content.length > tail) {
101
+ var staticLength = content.length - tail;
102
+ this.displayedLength = Math.max(this.displayedLength, staticLength);
103
+ this.onFlush(content.slice(0, this.displayedLength));
104
+ }
97
105
  this.ensureTicking();
98
106
  }
99
107
  },
@@ -263,7 +263,15 @@ var SCHEMA_LANGUAGES = new Set([
263
263
  }, [
264
264
  plugins
265
265
  ]);
266
- // 字符队列管理
266
+ // 字符队列管理:流式时仅对最后 50 字做动画,避免每段逐字输出
267
+ var resolvedQueueOptions = useMemo(function() {
268
+ return streaming ? _object_spread({
269
+ animateTailChars: 50
270
+ }, queueOptions) : queueOptions;
271
+ }, [
272
+ streaming,
273
+ queueOptions
274
+ ]);
267
275
  useEffect(function() {
268
276
  if (!streaming) {
269
277
  // 非流式:直接展示全部内容
@@ -273,14 +281,14 @@ var SCHEMA_LANGUAGES = new Set([
273
281
  if (!queueRef.current) {
274
282
  queueRef.current = new CharacterQueue(function(displayed) {
275
283
  return setDisplayedContent(displayed);
276
- }, queueOptions);
284
+ }, resolvedQueueOptions);
277
285
  }
278
286
  queueRef.current.push(content || '');
279
287
  return undefined;
280
288
  }, [
281
289
  content,
282
290
  streaming,
283
- queueOptions
291
+ resolvedQueueOptions
284
292
  ]);
285
293
  // 流式完成时 flush 所有剩余内容
286
294
  useEffect(function() {
@@ -6,6 +6,12 @@ export interface CharacterQueueOptions {
6
6
  charsPerFrame?: number;
7
7
  /** 是否启用打字动画,默认 true(流式时) */
8
8
  animate?: boolean;
9
+ /**
10
+ * 仅对末尾 N 个字符做动画,前面内容立即展示。
11
+ * 设为 50 时,每次 push 只对最后 50 字逐字输出,避免整段逐字动画。
12
+ * 默认 undefined 表示整段动画(原有行为)。
13
+ */
14
+ animateTailChars?: number;
9
15
  /** 动画速度因子,1.0 为标准速度 */
10
16
  speed?: number;
11
17
  /** 内容完成后立即 flush 全部剩余 */
@@ -1079,45 +1079,62 @@ var extractLanguageFromClassName = function extractLanguageFromClassName(classNa
1079
1079
  return null;
1080
1080
  }
1081
1081
  };
1082
+ /** 流式未闭合围栏内,空行后的下一行是否仍像围栏内代码(JSON 等)延续 */ var lineLooksLikeFenceCodeContinuation = function lineLooksLikeFenceCodeContinuation(rawLine) {
1083
+ var t = rawLine.trimStart();
1084
+ if (!t) {
1085
+ return true;
1086
+ }
1087
+ if (t.startsWith('```') || t.startsWith('~~~')) {
1088
+ return true;
1089
+ }
1090
+ var c = t[0];
1091
+ return c === '{' || c === '}' || c === '[' || c === ']' || c === '"' || c === "'" || c === '`' || c === '-' || c === ',' || c >= '0' && c <= '9';
1092
+ };
1082
1093
  /**
1083
1094
  * 将 markdown 按块(双换行)拆分,尊重代码围栏边界。
1084
1095
  * 返回的每个块是一个独立的 markdown 片段,可单独解析。
1085
- */ var splitMarkdownBlocks = function splitMarkdownBlocks(content) {
1096
+ *
1097
+ * 流式且围栏未闭合时,inFence 会一直为 true,段间 `\n\n` 在按行拆分时只出现一个空行,
1098
+ * 无法用「连续两个空行」检测。此时在空行处前瞻下一行:若不像围栏内代码延续则虚拟闭合围栏,
1099
+ * 使末块能变为非末块且缓存键与先前一致。
1100
+ */ var splitMarkdownBlocks = function splitMarkdownBlocks(content, streaming) {
1086
1101
  var lines = content.split('\n');
1087
1102
  var blocks = [];
1088
1103
  var current = [];
1089
1104
  var inFence = false;
1090
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1091
- try {
1092
- for(var _iterator = lines[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1093
- var line = _step.value;
1094
- var trimmed = line.trimStart();
1095
- if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) {
1096
- inFence = !inFence;
1097
- }
1098
- if (!inFence && line === '' && current.length > 0) {
1099
- var prev = current[current.length - 1];
1100
- if (prev === '') {
1101
- blocks.push(current.join('\n'));
1102
- current = [];
1103
- continue;
1105
+ for(var i = 0; i < lines.length; i += 1){
1106
+ var line = lines[i];
1107
+ var trimmed = line.trimStart();
1108
+ if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) {
1109
+ inFence = !inFence;
1110
+ }
1111
+ if (streaming && inFence && line === '' && current.length > 0) {
1112
+ var prev = current[current.length - 1];
1113
+ var nextLine = lines[i + 1];
1114
+ var doubleBlankInsideFence = prev === '';
1115
+ var blankThenNonCodeLine = prev !== '' && nextLine !== undefined && nextLine.trim() !== '' && !lineLooksLikeFenceCodeContinuation(nextLine);
1116
+ if (doubleBlankInsideFence || blankThenNonCodeLine) {
1117
+ var body = current;
1118
+ while(body.length > 0 && body[body.length - 1] === ''){
1119
+ body = body.slice(0, -1);
1120
+ }
1121
+ if (body.length > 0) {
1122
+ blocks.push(body.join('\n'));
1104
1123
  }
1124
+ current = [];
1125
+ inFence = false;
1126
+ continue;
1105
1127
  }
1106
- current.push(line);
1107
1128
  }
1108
- } catch (err) {
1109
- _didIteratorError = true;
1110
- _iteratorError = err;
1111
- } finally{
1112
- try {
1113
- if (!_iteratorNormalCompletion && _iterator.return != null) {
1114
- _iterator.return();
1115
- }
1116
- } finally{
1117
- if (_didIteratorError) {
1118
- throw _iteratorError;
1129
+ if (!inFence && line === '' && current.length > 0) {
1130
+ var prev1 = current[current.length - 1];
1131
+ if (prev1 === '') {
1132
+ blocks.push(current.join('\n'));
1133
+ current = [];
1134
+ continue;
1119
1135
  }
1120
1136
  }
1137
+ current.push(line);
1121
1138
  }
1122
1139
  if (current.length > 0) {
1123
1140
  blocks.push(current.join('\n'));
@@ -1152,6 +1169,7 @@ export var useMarkdownToReact = function useMarkdownToReact(content, options) {
1152
1169
  var processorRef = useRef(null);
1153
1170
  var blockCacheRef = useRef(new Map());
1154
1171
  var lastBlockRef = useRef(null);
1172
+ var prevContentRef = useRef('');
1155
1173
  var processor = useMemo(function() {
1156
1174
  var p = createHastProcessor(options === null || options === void 0 ? void 0 : options.remarkPlugins, options === null || options === void 0 ? void 0 : options.htmlConfig);
1157
1175
  processorRef.current = p;
@@ -1171,22 +1189,33 @@ export var useMarkdownToReact = function useMarkdownToReact(content, options) {
1171
1189
  options === null || options === void 0 ? void 0 : options.linkConfig
1172
1190
  ]);
1173
1191
  return useMemo(function() {
1174
- if (!content) return null;
1192
+ if (!content) {
1193
+ prevContentRef.current = '';
1194
+ return null;
1195
+ }
1196
+ var prevContent = prevContentRef.current;
1197
+ if (prevContent && content !== prevContent && !content.startsWith(prevContent)) {
1198
+ blockCacheRef.current = new Map();
1199
+ lastBlockRef.current = null;
1200
+ }
1201
+ prevContentRef.current = content;
1175
1202
  try {
1176
1203
  var preprocessed = content.replace(new RegExp(JINJA_DOLLAR_PLACEHOLDER, 'g'), '$');
1177
- var blocks = splitMarkdownBlocks(preprocessed);
1204
+ var blocks = splitMarkdownBlocks(preprocessed, options === null || options === void 0 ? void 0 : options.streaming);
1178
1205
  if (blocks.length === 0) return null;
1179
1206
  var cache = blockCacheRef.current;
1180
1207
  var newCache = new Map();
1181
1208
  var elements = [];
1209
+ var KEY_PREFIX_LEN = 64;
1182
1210
  for(var i = 0; i < blocks.length; i++){
1183
1211
  var block = blocks[i];
1184
1212
  var isLast = i === blocks.length - 1;
1185
1213
  // 用 index + 内容前 64 字符作 key,保持稳定性:
1186
- // 相同位置 + 相同内容开头 相同 key → React 不 unmount
1187
- // 流式场景下,最后一个块内容频繁变化,若 key 随内容变会导致反复 unmount/remount
1188
- // ChartBlockRenderer 等会重复展示 Loading,故对末块使用稳定 key
1189
- var stableKey = isLast && (options === null || options === void 0 ? void 0 : options.streaming) ? "b".concat(i, "-last") : "b".concat(i, "-").concat(block.slice(0, 64));
1214
+ // - 末块在流式中节流时,用 lastBlockRef.source 的切片作 key,避免每次追加字符导致 key 变化
1215
+ // - 末块变为非末块时,必须与先前 key 一致,否则 ChartBlockRenderer 等会 unmount/remount 闪烁
1216
+ // - 因此统一用「实际展示内容」的 slice key,节流时用 lastBlockRef.source
1217
+ var contentForKey = isLast && (options === null || options === void 0 ? void 0 : options.streaming) && lastBlockRef.current && !shouldReparseLastBlock(lastBlockRef.current.source, block, options === null || options === void 0 ? void 0 : options.streaming) ? lastBlockRef.current.source.slice(0, KEY_PREFIX_LEN) : block.slice(0, KEY_PREFIX_LEN);
1218
+ var stableKey = "b".concat(i, "-").concat(contentForKey);
1190
1219
  if (!isLast) {
1191
1220
  var cached = cache.get(block);
1192
1221
  if (cached && cached.source === block) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ant-design/agentic-ui",
3
- "version": "2.30.3",
3
+ "version": "2.30.5",
4
4
  "description": "面向智能体的 UI 组件库,提供多步推理可视化、工具调用展示、任务执行协同等 Agentic UI 能力",
5
5
  "repository": "git@github.com:ant-design/agentic-ui.git",
6
6
  "license": "MIT",