@ai-group/chat-sdk 3.8.0-alpha.1 → 3.8.0-alpha.2

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 (54) hide show
  1. package/dist/cjs/components/XAdkChatbot/index.js +240 -68
  2. package/dist/cjs/components/XAdkChatbot/index.js.map +3 -3
  3. package/dist/cjs/components/XAdkChatbot/styles.d.ts +1 -0
  4. package/dist/cjs/components/XAdkChatbot/styles.js +34 -6
  5. package/dist/cjs/components/XAdkChatbot/styles.js.map +2 -2
  6. package/dist/cjs/components/XAdkProvider/compound/Messages.d.ts +7 -1
  7. package/dist/cjs/components/XAdkProvider/compound/Messages.js +10 -0
  8. package/dist/cjs/components/XAdkProvider/compound/Messages.js.map +2 -2
  9. package/dist/cjs/components/XAdkSender/index.js +53 -5
  10. package/dist/cjs/components/XAdkSender/index.js.map +3 -3
  11. package/dist/cjs/components/XAdkThoughtChain/index.js +7 -3
  12. package/dist/cjs/components/XAdkThoughtChain/index.js.map +2 -2
  13. package/dist/cjs/components/XAdkThoughtChain/styles.d.ts +1 -1
  14. package/dist/cjs/components/XAdkThoughtChain/styles.js +25 -32
  15. package/dist/cjs/components/XAdkThoughtChain/styles.js.map +2 -2
  16. package/dist/cjs/components/XAiChatbot/index.js.map +2 -2
  17. package/dist/cjs/components/XAiProvider/index.js.map +2 -2
  18. package/dist/cjs/hooks/useADKMessageQueue.d.ts +4 -2
  19. package/dist/cjs/hooks/useADKMessageQueue.js +13 -11
  20. package/dist/cjs/hooks/useADKMessageQueue.js.map +2 -2
  21. package/dist/cjs/types/XAdkChatbot.d.ts +36 -0
  22. package/dist/cjs/types/XAdkChatbot.js.map +1 -1
  23. package/dist/cjs/types/XAdkSender.d.ts +8 -1
  24. package/dist/cjs/types/XAdkSender.js.map +1 -1
  25. package/dist/cjs/types/antdx.d.ts +4 -1
  26. package/dist/cjs/types/antdx.js.map +1 -1
  27. package/dist/esm/components/XAdkChatbot/index.js +210 -33
  28. package/dist/esm/components/XAdkChatbot/index.js.map +1 -1
  29. package/dist/esm/components/XAdkChatbot/styles.d.ts +1 -0
  30. package/dist/esm/components/XAdkChatbot/styles.js +30 -29
  31. package/dist/esm/components/XAdkChatbot/styles.js.map +1 -1
  32. package/dist/esm/components/XAdkProvider/compound/Messages.d.ts +7 -1
  33. package/dist/esm/components/XAdkProvider/compound/Messages.js +11 -1
  34. package/dist/esm/components/XAdkProvider/compound/Messages.js.map +1 -1
  35. package/dist/esm/components/XAdkSender/index.js +80 -16
  36. package/dist/esm/components/XAdkSender/index.js.map +1 -1
  37. package/dist/esm/components/XAdkThoughtChain/index.js +7 -7
  38. package/dist/esm/components/XAdkThoughtChain/index.js.map +1 -1
  39. package/dist/esm/components/XAdkThoughtChain/styles.d.ts +1 -1
  40. package/dist/esm/components/XAdkThoughtChain/styles.js +14 -14
  41. package/dist/esm/components/XAdkThoughtChain/styles.js.map +1 -1
  42. package/dist/esm/components/XAiChatbot/index.js.map +1 -1
  43. package/dist/esm/components/XAiProvider/index.js.map +1 -1
  44. package/dist/esm/hooks/useADKMessageQueue.d.ts +4 -2
  45. package/dist/esm/hooks/useADKMessageQueue.js +12 -10
  46. package/dist/esm/hooks/useADKMessageQueue.js.map +1 -1
  47. package/dist/esm/types/XAdkChatbot.d.ts +36 -0
  48. package/dist/esm/types/XAdkChatbot.js.map +1 -1
  49. package/dist/esm/types/XAdkSender.d.ts +8 -1
  50. package/dist/esm/types/XAdkSender.js.map +1 -1
  51. package/dist/esm/types/antdx.d.ts +4 -1
  52. package/dist/esm/types/antdx.js.map +1 -1
  53. package/dist/umd/chat-sdk.min.js +1 -1
  54. package/package.json +1 -1
@@ -57,7 +57,10 @@ export type BaseNode = React.ReactNode | false;
57
57
  export type NodeRender = (oriNode: React.ReactNode, info: {
58
58
  components: ActionsComponents;
59
59
  }) => BaseNode;
60
- export interface SenderProps extends Partial<Pick<TextareaProps, 'placeholder' | 'onKeyUp' | 'onFocus' | 'onBlur'>> {
60
+ export interface SenderProps extends Omit<Partial<Pick<TextareaProps, 'placeholder' | 'onKeyUp' | 'onFocus' | 'onBlur'>>, 'onKeyUp' | 'onFocus' | 'onBlur'> {
61
+ onKeyUp?: React.KeyboardEventHandler<HTMLTextAreaElement>;
62
+ onFocus?: React.FocusEventHandler<HTMLTextAreaElement>;
63
+ onBlur?: React.FocusEventHandler<HTMLTextAreaElement>;
61
64
  prefixCls?: string;
62
65
  defaultValue?: string;
63
66
  value?: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/types/antdx.ts"],
4
- "sourcesContent": ["/**\n * @ant-design/x 类型本地声明\n * 从 @ant-design/x 源码提取的公共 API 类型,减少外部依赖\n */\nimport type React from 'react';\nimport type { UploadFile, MenuProps, InputProps } from 'antd';\n\n// ==================== Conversations ====================\n\nexport interface ConversationItemType\n extends Omit<React.HTMLAttributes<HTMLLIElement>, 'onClick'> {\n key: string;\n label?: React.ReactNode;\n group?: string;\n icon?: React.ReactNode;\n disabled?: boolean;\n [key: string]: any;\n}\n\nexport interface DividerItemType {\n type: 'divider';\n key?: string;\n dashed?: boolean;\n}\n\nexport type ItemType = ConversationItemType | DividerItemType;\n\ntype ConversationsSemanticType = 'root' | 'creation' | 'group' | 'item';\n\nexport interface ConversationsProps extends React.HTMLAttributes<HTMLUListElement> {\n items?: ItemType[];\n activeKey?: ConversationItemType['key'];\n defaultActiveKey?: ConversationItemType['key'];\n onActiveChange?: (value: ConversationItemType['key'], item?: ItemType) => void;\n menu?:\n | (MenuProps & {\n trigger?:\n | React.ReactNode\n | ((\n conversation: ConversationItemType,\n info: { originNode: React.ReactNode },\n ) => React.ReactNode);\n getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;\n })\n | ((\n value: ConversationItemType,\n ) => MenuProps & {\n trigger?:\n | React.ReactNode\n | ((\n conversation: ConversationItemType,\n info: { originNode: React.ReactNode },\n ) => React.ReactNode);\n getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;\n });\n groupable?: boolean | Record<string, any>;\n styles?: Partial<Record<ConversationsSemanticType, React.CSSProperties>>;\n classNames?: Partial<Record<ConversationsSemanticType, string>>;\n prefixCls?: string;\n rootClassName?: string;\n}\n\n// ==================== Sender ====================\n\ntype TextareaProps = InputProps;\n\ntype SubmitType = 'enter' | 'shiftEnter';\n\ntype SenderSemanticType = 'root' | 'prefix' | 'input' | 'suffix' | 'footer' | 'switch' | 'content';\n\nexport interface SenderComponents {\n input?: React.ComponentType<any>;\n}\n\nexport type ActionsComponents = {\n SendButton: React.ComponentType<any>;\n ClearButton: React.ComponentType<any>;\n LoadingButton: React.ComponentType<any>;\n SpeechButton: React.ComponentType<any>;\n};\n\nexport type BaseNode = React.ReactNode | false;\nexport type NodeRender = (\n oriNode: React.ReactNode,\n info: { components: ActionsComponents },\n) => BaseNode;\n\nexport interface SenderProps\n extends Partial<Pick<TextareaProps, 'placeholder' | 'onKeyUp' | 'onFocus' | 'onBlur'>> {\n prefixCls?: string;\n defaultValue?: string;\n value?: string;\n loading?: boolean;\n readOnly?: boolean;\n submitType?: SubmitType;\n disabled?: boolean;\n onSubmit?: (message: string, ...args: any[]) => void;\n onChange?: (value: string, ...args: any[]) => void;\n onCancel?: VoidFunction;\n onKeyDown?: (event: React.KeyboardEvent) => void | false;\n onPaste?: React.ClipboardEventHandler<HTMLElement>;\n onPasteFile?: (files: FileList) => void;\n components?: SenderComponents;\n classNames?: Partial<Record<SenderSemanticType, string>>;\n styles?: Partial<Record<SenderSemanticType, React.CSSProperties>>;\n rootClassName?: string;\n style?: React.CSSProperties;\n className?: string;\n allowSpeech?: any;\n prefix?: BaseNode | NodeRender;\n footer?: BaseNode | NodeRender;\n suffix?: BaseNode | NodeRender;\n header?: BaseNode | NodeRender;\n autoSize?: boolean | { minRows?: number; maxRows?: number };\n slotConfig?: Readonly<any[]>;\n skill?: any;\n}\n\n// ==================== Prompts ====================\n\nexport interface BasePromptsItemType {\n key: string;\n icon?: React.ReactNode;\n label?: React.ReactNode;\n description?: React.ReactNode;\n disabled?: boolean;\n}\n\nexport interface PromptsItemType extends BasePromptsItemType {\n children?: BasePromptsItemType[];\n}\n\ntype PromptsSemanticType =\n | 'root'\n | 'list'\n | 'item'\n | 'itemContent'\n | 'title'\n | 'subList'\n | 'subItem';\n\nexport interface PromptsProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick' | 'title'> {\n items?: PromptsItemType[];\n title?: React.ReactNode;\n onClick?: (info: { data: PromptsItemType }) => void;\n styles?: Partial<Record<PromptsSemanticType, React.CSSProperties>>;\n classNames?: Partial<Record<PromptsSemanticType, string>>;\n prefixCls?: string;\n rootClassName?: string;\n vertical?: boolean;\n wrap?: boolean;\n}\n\n// ==================== Attachments ====================\n\nexport interface Attachment<T = any>\n extends UploadFile<T> {\n description?: React.ReactNode;\n cardType?: 'audio' | 'video' | 'image' | 'file';\n}\n\nexport interface AttachmentsRef {\n nativeElement?: HTMLDivElement | null;\n fileNativeElement?: HTMLInputElement | null;\n upload: (file: File) => void;\n select: (options?: { accept?: string; multiple?: boolean }) => void;\n}\n\n// ==================== Think ====================\n\ntype ThinkSemanticType = 'root' | 'header' | 'content' | 'icon';\n\nexport interface ThinkProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n prefixCls?: string;\n style?: React.CSSProperties;\n styles?: Partial<Record<ThinkSemanticType, React.CSSProperties>>;\n className?: string;\n classNames?: Partial<Record<ThinkSemanticType, string>>;\n rootClassName?: string;\n title?: React.ReactNode;\n icon?: React.ReactNode;\n loading?: boolean | React.ReactNode;\n defaultExpanded?: boolean;\n expanded?: boolean;\n onExpand?: (expand: boolean) => void;\n blink?: boolean;\n destroyOnHidden?: boolean;\n}\n"],
4
+ "sourcesContent": ["/**\n * @ant-design/x 类型本地声明\n * 从 @ant-design/x 源码提取的公共 API 类型,减少外部依赖\n */\nimport type React from 'react';\nimport type { UploadFile, MenuProps, InputProps } from 'antd';\n\n// ==================== Conversations ====================\n\nexport interface ConversationItemType\n extends Omit<React.HTMLAttributes<HTMLLIElement>, 'onClick'> {\n key: string;\n label?: React.ReactNode;\n group?: string;\n icon?: React.ReactNode;\n disabled?: boolean;\n [key: string]: any;\n}\n\nexport interface DividerItemType {\n type: 'divider';\n key?: string;\n dashed?: boolean;\n}\n\nexport type ItemType = ConversationItemType | DividerItemType;\n\ntype ConversationsSemanticType = 'root' | 'creation' | 'group' | 'item';\n\nexport interface ConversationsProps extends React.HTMLAttributes<HTMLUListElement> {\n items?: ItemType[];\n activeKey?: ConversationItemType['key'];\n defaultActiveKey?: ConversationItemType['key'];\n onActiveChange?: (value: ConversationItemType['key'], item?: ItemType) => void;\n menu?:\n | (MenuProps & {\n trigger?:\n | React.ReactNode\n | ((\n conversation: ConversationItemType,\n info: { originNode: React.ReactNode },\n ) => React.ReactNode);\n getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;\n })\n | ((\n value: ConversationItemType,\n ) => MenuProps & {\n trigger?:\n | React.ReactNode\n | ((\n conversation: ConversationItemType,\n info: { originNode: React.ReactNode },\n ) => React.ReactNode);\n getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;\n });\n groupable?: boolean | Record<string, any>;\n styles?: Partial<Record<ConversationsSemanticType, React.CSSProperties>>;\n classNames?: Partial<Record<ConversationsSemanticType, string>>;\n prefixCls?: string;\n rootClassName?: string;\n}\n\n// ==================== Sender ====================\n\ntype TextareaProps = InputProps;\n\ntype SubmitType = 'enter' | 'shiftEnter';\n\ntype SenderSemanticType = 'root' | 'prefix' | 'input' | 'suffix' | 'footer' | 'switch' | 'content';\n\nexport interface SenderComponents {\n input?: React.ComponentType<any>;\n}\n\nexport type ActionsComponents = {\n SendButton: React.ComponentType<any>;\n ClearButton: React.ComponentType<any>;\n LoadingButton: React.ComponentType<any>;\n SpeechButton: React.ComponentType<any>;\n};\n\nexport type BaseNode = React.ReactNode | false;\nexport type NodeRender = (\n oriNode: React.ReactNode,\n info: { components: ActionsComponents },\n) => BaseNode;\n\nexport interface SenderProps\n extends Omit<Partial<Pick<TextareaProps, 'placeholder' | 'onKeyUp' | 'onFocus' | 'onBlur'>>, 'onKeyUp' | 'onFocus' | 'onBlur'> {\n onKeyUp?: React.KeyboardEventHandler<HTMLTextAreaElement>;\n onFocus?: React.FocusEventHandler<HTMLTextAreaElement>;\n onBlur?: React.FocusEventHandler<HTMLTextAreaElement>;\n prefixCls?: string;\n defaultValue?: string;\n value?: string;\n loading?: boolean;\n readOnly?: boolean;\n submitType?: SubmitType;\n disabled?: boolean;\n onSubmit?: (message: string, ...args: any[]) => void;\n onChange?: (value: string, ...args: any[]) => void;\n onCancel?: VoidFunction;\n onKeyDown?: (event: React.KeyboardEvent) => void | false;\n onPaste?: React.ClipboardEventHandler<HTMLElement>;\n onPasteFile?: (files: FileList) => void;\n components?: SenderComponents;\n classNames?: Partial<Record<SenderSemanticType, string>>;\n styles?: Partial<Record<SenderSemanticType, React.CSSProperties>>;\n rootClassName?: string;\n style?: React.CSSProperties;\n className?: string;\n allowSpeech?: any;\n prefix?: BaseNode | NodeRender;\n footer?: BaseNode | NodeRender;\n suffix?: BaseNode | NodeRender;\n header?: BaseNode | NodeRender;\n autoSize?: boolean | { minRows?: number; maxRows?: number };\n slotConfig?: Readonly<any[]>;\n skill?: any;\n}\n\n// ==================== Prompts ====================\n\nexport interface BasePromptsItemType {\n key: string;\n icon?: React.ReactNode;\n label?: React.ReactNode;\n description?: React.ReactNode;\n disabled?: boolean;\n}\n\nexport interface PromptsItemType extends BasePromptsItemType {\n children?: BasePromptsItemType[];\n}\n\ntype PromptsSemanticType =\n | 'root'\n | 'list'\n | 'item'\n | 'itemContent'\n | 'title'\n | 'subList'\n | 'subItem';\n\nexport interface PromptsProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick' | 'title'> {\n items?: PromptsItemType[];\n title?: React.ReactNode;\n onClick?: (info: { data: PromptsItemType }) => void;\n styles?: Partial<Record<PromptsSemanticType, React.CSSProperties>>;\n classNames?: Partial<Record<PromptsSemanticType, string>>;\n prefixCls?: string;\n rootClassName?: string;\n vertical?: boolean;\n wrap?: boolean;\n}\n\n// ==================== Attachments ====================\n\nexport interface Attachment<T = any>\n extends UploadFile<T> {\n description?: React.ReactNode;\n cardType?: 'audio' | 'video' | 'image' | 'file';\n}\n\nexport interface AttachmentsRef {\n nativeElement?: HTMLDivElement | null;\n fileNativeElement?: HTMLInputElement | null;\n upload: (file: File) => void;\n select: (options?: { accept?: string; multiple?: boolean }) => void;\n}\n\n// ==================== Think ====================\n\ntype ThinkSemanticType = 'root' | 'header' | 'content' | 'icon';\n\nexport interface ThinkProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n prefixCls?: string;\n style?: React.CSSProperties;\n styles?: Partial<Record<ThinkSemanticType, React.CSSProperties>>;\n className?: string;\n classNames?: Partial<Record<ThinkSemanticType, string>>;\n rootClassName?: string;\n title?: React.ReactNode;\n icon?: React.ReactNode;\n loading?: boolean | React.ReactNode;\n defaultExpanded?: boolean;\n expanded?: boolean;\n onExpand?: (expand: boolean) => void;\n blink?: boolean;\n destroyOnHidden?: boolean;\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
2
  import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
3
- import { useMemo, useCallback, useEffect, useRef, useImperativeHandle, forwardRef } from "react";
3
+ import { useMemo, useCallback, useEffect, useLayoutEffect, useRef, useImperativeHandle, forwardRef, Fragment } from "react";
4
4
  import { Button, Flex, Tooltip, message as antdMessage } from "antd";
5
5
  import clsx from "clsx";
6
6
  import { SwapRightOutlined, ReloadOutlined, CopyOutlined, InfoCircleOutlined } from "@ant-design/icons";
@@ -30,12 +30,16 @@ var scrollThreshold = 10;
30
30
  * - ✅ 欢迎页面 (agentName/agentIcon/description)
31
31
  */
32
32
  var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
33
- var _mergedStrategies$par;
33
+ var _mergedStrategies$par, _history$direction;
34
34
  var _ref$loading = _ref.loading,
35
35
  loading = _ref$loading === void 0 ? false : _ref$loading,
36
36
  prologue = _ref.prologue,
37
37
  suggestions = _ref.suggestions,
38
38
  messages = _ref.messages,
39
+ renderBeforeGroup = _ref.renderBeforeGroup,
40
+ renderAfterGroup = _ref.renderAfterGroup,
41
+ renderGroup = _ref.renderGroup,
42
+ history = _ref.history,
39
43
  showFnCallDetail = _ref.showFnCallDetail,
40
44
  onConfirm = _ref.onConfirm,
41
45
  onSuggest = _ref.onSuggest,
@@ -76,6 +80,98 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
76
80
  var prevInitializedRef = useRef(false);
77
81
  var prevSessionIdRef = useRef(sessionId);
78
82
  var previousMessageCountRef = useRef(messages.length);
83
+ var historyRef = useRef(history);
84
+ historyRef.current = history;
85
+ var messagesRef = useRef(messages);
86
+ messagesRef.current = messages;
87
+ var historyRequestRef = useRef(false);
88
+ var historyRequestIdRef = useRef(0);
89
+ var historyRequestControllerRef = useRef(null);
90
+ var historyRefreshRef = useRef(false);
91
+ var historyRefreshIdRef = useRef(0);
92
+ var historyRefreshControllerRef = useRef(null);
93
+ var prependAnchorRef = useRef(null);
94
+ var skipNextAutoScrollRef = useRef(false);
95
+ var prependExpiryRef = useRef(null);
96
+ var loadMoreHistory = useCallback(function () {
97
+ var _pagination$direction;
98
+ var pagination = historyRef.current;
99
+ var el = listRef.current;
100
+ if (!(pagination !== null && pagination !== void 0 && pagination.hasMore) || pagination.loading || historyRequestRef.current || !el) return;
101
+ var direction = (_pagination$direction = pagination.direction) !== null && _pagination$direction !== void 0 ? _pagination$direction : "top";
102
+ if (direction === "top") {
103
+ var _currentMessages$;
104
+ if (prependExpiryRef.current) clearTimeout(prependExpiryRef.current);
105
+ var currentMessages = messagesRef.current;
106
+ prependAnchorRef.current = {
107
+ sessionId: prevSessionIdRef.current,
108
+ firstMessageId: (_currentMessages$ = currentMessages[0]) === null || _currentMessages$ === void 0 ? void 0 : _currentMessages$.id,
109
+ messageCount: currentMessages.length,
110
+ scrollTop: el.scrollTop,
111
+ scrollHeight: el.scrollHeight
112
+ };
113
+ }
114
+ historyRequestRef.current = true;
115
+ var requestId = ++historyRequestIdRef.current;
116
+ var controller = new AbortController();
117
+ historyRequestControllerRef.current = controller;
118
+ void Promise.resolve().then(function () {
119
+ return pagination.onLoadMore({
120
+ signal: controller.signal
121
+ });
122
+ }).catch(function () {
123
+ if (requestId !== historyRequestIdRef.current || controller.signal.aborted) return;
124
+ prependAnchorRef.current = null;
125
+ antdMessage.error("加载历史消息失败,请重试");
126
+ }).finally(function () {
127
+ if (requestId === historyRequestIdRef.current) {
128
+ historyRequestRef.current = false;
129
+ historyRequestControllerRef.current = null;
130
+ // 没有新页时解除滚动锚点,避免后续正常消息被误认为 prepend。
131
+ prependExpiryRef.current = setTimeout(function () {
132
+ prependAnchorRef.current = null;
133
+ prependExpiryRef.current = null;
134
+ }, 500);
135
+ }
136
+ });
137
+ }, []);
138
+ var refreshHistory = useCallback(function () {
139
+ var pagination = historyRef.current;
140
+ if (!(pagination !== null && pagination !== void 0 && pagination.onRefresh) || pagination.refreshing || historyRefreshRef.current) return;
141
+ historyRefreshRef.current = true;
142
+ var requestId = ++historyRefreshIdRef.current;
143
+ var controller = new AbortController();
144
+ historyRefreshControllerRef.current = controller;
145
+ void Promise.resolve().then(function () {
146
+ var _pagination$onRefresh;
147
+ return (_pagination$onRefresh = pagination.onRefresh) === null || _pagination$onRefresh === void 0 ? void 0 : _pagination$onRefresh.call(pagination, {
148
+ signal: controller.signal
149
+ });
150
+ }).catch(function () {
151
+ if (!controller.signal.aborted) antdMessage.error("刷新历史消息失败,请重试");
152
+ }).finally(function () {
153
+ if (requestId === historyRefreshIdRef.current) {
154
+ historyRefreshRef.current = false;
155
+ historyRefreshControllerRef.current = null;
156
+ }
157
+ });
158
+ }, []);
159
+ useLayoutEffect(function () {
160
+ var _messages$;
161
+ var anchor = prependAnchorRef.current;
162
+ var el = listRef.current;
163
+ if (!anchor || !el) return;
164
+ if (anchor.sessionId !== sessionId) {
165
+ prependAnchorRef.current = null;
166
+ return;
167
+ }
168
+ if (messages.length <= anchor.messageCount || ((_messages$ = messages[0]) === null || _messages$ === void 0 ? void 0 : _messages$.id) === anchor.firstMessageId) return;
169
+ el.scrollTop = anchor.scrollTop + el.scrollHeight - anchor.scrollHeight;
170
+ skipNextAutoScrollRef.current = true;
171
+ prependAnchorRef.current = null;
172
+ if (prependExpiryRef.current) clearTimeout(prependExpiryRef.current);
173
+ prependExpiryRef.current = null;
174
+ }, [messages, sessionId]);
79
175
 
80
176
  // 暴露命令式 API
81
177
  var scrollToBottom = useCallback(function () {
@@ -174,18 +270,40 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
174
270
 
175
271
  // 会话切换时重置滚动状态,允许重新触发初始化置底
176
272
  useEffect(function () {
273
+ var _historyRequestContro, _historyRefreshContro;
177
274
  if (sessionId === undefined) return; // 未传 sessionId 不处理
178
275
  if (prevSessionIdRef.current === sessionId) return; // 同一会话不处理
179
276
  prevSessionIdRef.current = sessionId;
180
277
  prevInitializedRef.current = false;
181
278
  userHasScrolledRef.current = false;
279
+ (_historyRequestContro = historyRequestControllerRef.current) === null || _historyRequestContro === void 0 || _historyRequestContro.abort();
280
+ (_historyRefreshContro = historyRefreshControllerRef.current) === null || _historyRefreshContro === void 0 || _historyRefreshContro.abort();
281
+ prependAnchorRef.current = null;
282
+ historyRequestRef.current = false;
283
+ historyRequestIdRef.current += 1;
284
+ historyRefreshRef.current = false;
285
+ historyRefreshIdRef.current += 1;
286
+ if (prependExpiryRef.current) clearTimeout(prependExpiryRef.current);
287
+ prependExpiryRef.current = null;
182
288
  }, [sessionId]);
289
+ useEffect(function () {
290
+ return function () {
291
+ var _historyRequestContro2, _historyRefreshContro2;
292
+ (_historyRequestContro2 = historyRequestControllerRef.current) === null || _historyRequestContro2 === void 0 || _historyRequestContro2.abort();
293
+ (_historyRefreshContro2 = historyRefreshControllerRef.current) === null || _historyRefreshContro2 === void 0 || _historyRefreshContro2.abort();
294
+ if (prependExpiryRef.current) clearTimeout(prependExpiryRef.current);
295
+ };
296
+ }, []);
183
297
 
184
298
  // 新增用户消息代表一次主动发送,应无条件恢复跟随并置底。
185
299
  // 之后的流式回复继续沿用“用户未主动上滚时跟随”的规则。
186
300
  useEffect(function () {
187
301
  var previousCount = previousMessageCountRef.current;
188
302
  previousMessageCountRef.current = messages.length;
303
+ if (skipNextAutoScrollRef.current || prependAnchorRef.current) {
304
+ skipNextAutoScrollRef.current = false;
305
+ return;
306
+ }
189
307
  var hasNewUserMessage = messages.slice(previousCount).some(function (message) {
190
308
  return message.role === "user";
191
309
  });
@@ -234,8 +352,15 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
234
352
  if (isAtBottom) {
235
353
  userHasScrolledRef.current = false;
236
354
  }
355
+ var pagination = historyRef.current;
356
+ if (pagination !== null && pagination !== void 0 && pagination.hasMore) {
357
+ var _pagination$threshold, _pagination$direction2;
358
+ var threshold = (_pagination$threshold = pagination.threshold) !== null && _pagination$threshold !== void 0 ? _pagination$threshold : 80;
359
+ var atLoadEdge = ((_pagination$direction2 = pagination.direction) !== null && _pagination$direction2 !== void 0 ? _pagination$direction2 : "top") === "top" ? currentTop <= threshold : el.scrollHeight - currentTop - el.clientHeight <= threshold;
360
+ if (atLoadEdge) loadMoreHistory();
361
+ }
237
362
  lastScrollTopRef.current = currentTop;
238
- }, []);
363
+ }, [loadMoreHistory]);
239
364
  useEffect(function () {
240
365
  var listElement = listRef.current;
241
366
  if (!listElement) return;
@@ -245,6 +370,13 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
245
370
  };
246
371
  }, [handleScroll]);
247
372
 
373
+ // 首屏内容不足一屏时没有滚动事件,继续按需加载直到内容可滚动或数据耗尽。
374
+ useEffect(function () {
375
+ if (!initialized || !(history !== null && history !== void 0 && history.hasMore) || history.loading) return;
376
+ var el = listRef.current;
377
+ if (el && el.scrollHeight <= el.clientHeight + 1) loadMoreHistory();
378
+ }, [history === null || history === void 0 ? void 0 : history.hasMore, history === null || history === void 0 ? void 0 : history.loading, initialized, loadMoreHistory, messages]);
379
+
248
380
  // ========== 消息分组逻辑 ==========
249
381
  var chatGroups = useMemo(function () {
250
382
  if (!enableGrouping) {
@@ -570,12 +702,25 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
570
702
  }, [enableProcessParsing, parseOptions, parseProcessMessage, showFnCallDetail, onConfirm, loading, showRetry, showCopy, showLog, onRetry, onCopy, onShowLog, actions, actionsExtra, onFileClick, onResourceLinkClick, renderResourceLink, mergedMarkdownExtensions, resourceLinkSchemes, styles, resolveToolKind, mergedStrategies]);
571
703
 
572
704
  // ========== 渲染用户消息组 ==========
573
- var renderUserGroup = useCallback(function (group) {
574
- return /*#__PURE__*/_jsx("div", {
705
+ var renderUserGroup = useCallback(function (group, nextGroup) {
706
+ var _userActions, _group$msgs;
707
+ var compactWithNextText = group.msgs.some(function (message) {
708
+ var _message$fileData;
709
+ return Boolean((_message$fileData = message.fileData) === null || _message$fileData === void 0 ? void 0 : _message$fileData.length);
710
+ }) && group.msgs.every(function (message) {
711
+ var _message$text;
712
+ return !((_message$text = message.text) !== null && _message$text !== void 0 && _message$text.trim());
713
+ }) && (nextGroup === null || nextGroup === void 0 ? void 0 : nextGroup.role) === "user" && nextGroup.msgs.some(function (message) {
714
+ var _message$text2;
715
+ return Boolean((_message$text2 = message.text) === null || _message$text2 === void 0 ? void 0 : _message$text2.trim());
716
+ });
717
+ return /*#__PURE__*/_jsxs("div", {
575
718
  className: styles.userMsg,
576
719
  "data-message-id": group.id,
577
- children: group.msgs.map(function (m, i) {
578
- var _userActions;
720
+ style: compactWithNextText ? {
721
+ marginBottom: 8
722
+ } : undefined,
723
+ children: [group.msgs.map(function (m, i) {
579
724
  return /*#__PURE__*/_jsxs("div", {
580
725
  className: styles.userContainer,
581
726
  children: [m.fileData && m.fileData.length > 0 && /*#__PURE__*/_jsx(_Fragment, {
@@ -584,7 +729,7 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
584
729
  file: file,
585
730
  align: "left",
586
731
  style: {
587
- marginBottom: "16px"
732
+ marginBottom: compactWithNextText && i === group.msgs.length - 1 && index === m.fileData.length - 1 ? 0 : 16
588
733
  },
589
734
  onClick: onFileClick
590
735
  }, index);
@@ -611,30 +756,36 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
611
756
  renderResourceLink: renderResourceLink,
612
757
  markdownExtensions: mergedMarkdownExtensions
613
758
  })
614
- }), showUserActions && /*#__PURE__*/_jsx("div", {
615
- className: "x-user-actions ".concat(styles.userActions),
616
- children: (_userActions = userActions === null || userActions === void 0 ? void 0 : userActions({
617
- message: group
618
- })) !== null && _userActions !== void 0 ? _userActions : /*#__PURE__*/_jsxs(_Fragment, {
619
- children: [/*#__PURE__*/_jsx("span", {
620
- className: styles.userTime,
621
- children: formatTimestamp(m.timestamp)
622
- }), /*#__PURE__*/_jsx(Tooltip, {
623
- title: "\u590D\u5236\u5185\u5BB9",
624
- children: /*#__PURE__*/_jsx(CopyOutlined, {
625
- className: styles.userActionIcon,
626
- onClick: function onClick() {
627
- var card = document.querySelector("[data-message-id=\"".concat(group.id, "\"] [data-xadk-user-card]"));
628
- var selected = getSelectionText(card);
629
- copy(selected || m.text || "");
630
- antdMessage.success("复制成功");
631
- }
632
- })
633
- })]
634
- })
635
759
  })]
636
760
  }, m.id || i);
637
- })
761
+ }), showUserActions && group.msgs.some(function (message) {
762
+ var _message$text3;
763
+ return Boolean((_message$text3 = message.text) === null || _message$text3 === void 0 ? void 0 : _message$text3.trim());
764
+ }) && /*#__PURE__*/_jsx("div", {
765
+ className: "x-user-actions ".concat(styles.userActions),
766
+ children: (_userActions = userActions === null || userActions === void 0 ? void 0 : userActions({
767
+ message: group
768
+ })) !== null && _userActions !== void 0 ? _userActions : /*#__PURE__*/_jsxs(_Fragment, {
769
+ children: [/*#__PURE__*/_jsx("span", {
770
+ className: styles.userTime,
771
+ children: formatTimestamp((_group$msgs = group.msgs[group.msgs.length - 1]) === null || _group$msgs === void 0 ? void 0 : _group$msgs.timestamp)
772
+ }), /*#__PURE__*/_jsx(Tooltip, {
773
+ title: "\u590D\u5236\u5185\u5BB9",
774
+ children: /*#__PURE__*/_jsx(CopyOutlined, {
775
+ className: styles.userActionIcon,
776
+ onClick: function onClick() {
777
+ var container = document.querySelector("[data-message-id=\"".concat(group.id, "\"]"));
778
+ var selected = getSelectionText(container);
779
+ var fullText = group.msgs.map(function (message) {
780
+ return message.text || "";
781
+ }).join("");
782
+ copy(selected || fullText);
783
+ antdMessage.success("复制成功");
784
+ }
785
+ })
786
+ })]
787
+ })
788
+ })]
638
789
  }, group.id);
639
790
  }, [styles, onFileClick, onResourceLinkClick, renderResourceLink, mergedMarkdownExtensions, resourceLinkSchemes, showUserActions, userActions]);
640
791
 
@@ -676,6 +827,20 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
676
827
 
677
828
  // 空状态判断
678
829
  var isEmpty = messages.length === 0 && !prologue && (!suggestions || suggestions.length === 0);
830
+ var historyControls = history && (history.hasMore || history.loading || history.onRefresh) && /*#__PURE__*/_jsxs("div", {
831
+ className: styles.historyControls,
832
+ children: [history.onRefresh && /*#__PURE__*/_jsx("button", {
833
+ type: "button",
834
+ onClick: refreshHistory,
835
+ disabled: history.refreshing,
836
+ children: history.refreshing ? "正在刷新..." : "刷新"
837
+ }), (history.hasMore || history.loading) && /*#__PURE__*/_jsx("button", {
838
+ type: "button",
839
+ onClick: loadMoreHistory,
840
+ disabled: history.loading,
841
+ children: history.loading ? "正在加载..." : "加载更多"
842
+ })]
843
+ });
679
844
  return /*#__PURE__*/_jsxs("div", {
680
845
  className: clsx(styles.wrapper, className),
681
846
  style: style,
@@ -688,7 +853,7 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
688
853
  style: isEmpty ? {
689
854
  flex: 0
690
855
  } : undefined,
691
- children: [prologue && /*#__PURE__*/_jsx("div", {
856
+ children: [((_history$direction = history === null || history === void 0 ? void 0 : history.direction) !== null && _history$direction !== void 0 ? _history$direction : "top") === "top" && historyControls, prologue && /*#__PURE__*/_jsx("div", {
692
857
  className: styles.prologue,
693
858
  children: /*#__PURE__*/_jsx(MarkdownRender, {
694
859
  text: prologue,
@@ -699,8 +864,20 @@ var XAdkChatbot = /*#__PURE__*/forwardRef(function (_ref, ref) {
699
864
  markdownExtensions: mergedMarkdownExtensions
700
865
  })
701
866
  }), chatGroups.map(function (group, idx) {
702
- return group.role === "user" ? renderUserGroup(group) : renderBotGroup(group, idx === chatGroups.length - 1, renderFunctionCall);
703
- }), renderSuggestions(), /*#__PURE__*/_jsx("div", {
867
+ var context = {
868
+ group: group,
869
+ index: idx,
870
+ previousGroup: chatGroups[idx - 1],
871
+ nextGroup: chatGroups[idx + 1]
872
+ };
873
+ var defaultNode = group.role === "user" ? renderUserGroup(group, chatGroups[idx + 1]) : renderBotGroup(group, idx === chatGroups.length - 1, renderFunctionCall);
874
+ var customNode = renderGroup === null || renderGroup === void 0 ? void 0 : renderGroup(_objectSpread(_objectSpread({}, context), {}, {
875
+ defaultNode: defaultNode
876
+ }));
877
+ return /*#__PURE__*/_jsxs(Fragment, {
878
+ children: [renderBeforeGroup === null || renderBeforeGroup === void 0 ? void 0 : renderBeforeGroup(context), customNode === undefined ? defaultNode : customNode, renderAfterGroup === null || renderAfterGroup === void 0 ? void 0 : renderAfterGroup(context)]
879
+ }, group.id);
880
+ }), (history === null || history === void 0 ? void 0 : history.direction) === "bottom" && historyControls, renderSuggestions(), /*#__PURE__*/_jsx("div", {
704
881
  ref: messagesEndRef
705
882
  })]
706
883
  })]