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

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 (55) hide show
  1. package/README.md +42 -2
  2. package/dist/cjs/components/XAdkChatbot/index.js +240 -68
  3. package/dist/cjs/components/XAdkChatbot/index.js.map +3 -3
  4. package/dist/cjs/components/XAdkChatbot/styles.d.ts +1 -0
  5. package/dist/cjs/components/XAdkChatbot/styles.js +34 -6
  6. package/dist/cjs/components/XAdkChatbot/styles.js.map +2 -2
  7. package/dist/cjs/components/XAdkProvider/compound/Messages.d.ts +7 -1
  8. package/dist/cjs/components/XAdkProvider/compound/Messages.js +10 -0
  9. package/dist/cjs/components/XAdkProvider/compound/Messages.js.map +2 -2
  10. package/dist/cjs/components/XAdkSender/index.js +69 -5
  11. package/dist/cjs/components/XAdkSender/index.js.map +3 -3
  12. package/dist/cjs/components/XAdkThoughtChain/index.js +7 -3
  13. package/dist/cjs/components/XAdkThoughtChain/index.js.map +2 -2
  14. package/dist/cjs/components/XAdkThoughtChain/styles.d.ts +1 -1
  15. package/dist/cjs/components/XAdkThoughtChain/styles.js +25 -32
  16. package/dist/cjs/components/XAdkThoughtChain/styles.js.map +2 -2
  17. package/dist/cjs/components/XAiChatbot/index.js.map +2 -2
  18. package/dist/cjs/components/XAiProvider/index.js.map +2 -2
  19. package/dist/cjs/hooks/useADKMessageQueue.d.ts +4 -2
  20. package/dist/cjs/hooks/useADKMessageQueue.js +13 -11
  21. package/dist/cjs/hooks/useADKMessageQueue.js.map +2 -2
  22. package/dist/cjs/types/XAdkChatbot.d.ts +36 -0
  23. package/dist/cjs/types/XAdkChatbot.js.map +1 -1
  24. package/dist/cjs/types/XAdkSender.d.ts +20 -1
  25. package/dist/cjs/types/XAdkSender.js.map +1 -1
  26. package/dist/cjs/types/antdx.d.ts +4 -1
  27. package/dist/cjs/types/antdx.js.map +1 -1
  28. package/dist/esm/components/XAdkChatbot/index.js +210 -33
  29. package/dist/esm/components/XAdkChatbot/index.js.map +1 -1
  30. package/dist/esm/components/XAdkChatbot/styles.d.ts +1 -0
  31. package/dist/esm/components/XAdkChatbot/styles.js +30 -29
  32. package/dist/esm/components/XAdkChatbot/styles.js.map +1 -1
  33. package/dist/esm/components/XAdkProvider/compound/Messages.d.ts +7 -1
  34. package/dist/esm/components/XAdkProvider/compound/Messages.js +11 -1
  35. package/dist/esm/components/XAdkProvider/compound/Messages.js.map +1 -1
  36. package/dist/esm/components/XAdkSender/index.js +94 -16
  37. package/dist/esm/components/XAdkSender/index.js.map +1 -1
  38. package/dist/esm/components/XAdkThoughtChain/index.js +7 -7
  39. package/dist/esm/components/XAdkThoughtChain/index.js.map +1 -1
  40. package/dist/esm/components/XAdkThoughtChain/styles.d.ts +1 -1
  41. package/dist/esm/components/XAdkThoughtChain/styles.js +14 -14
  42. package/dist/esm/components/XAdkThoughtChain/styles.js.map +1 -1
  43. package/dist/esm/components/XAiChatbot/index.js.map +1 -1
  44. package/dist/esm/components/XAiProvider/index.js.map +1 -1
  45. package/dist/esm/hooks/useADKMessageQueue.d.ts +4 -2
  46. package/dist/esm/hooks/useADKMessageQueue.js +12 -10
  47. package/dist/esm/hooks/useADKMessageQueue.js.map +1 -1
  48. package/dist/esm/types/XAdkChatbot.d.ts +36 -0
  49. package/dist/esm/types/XAdkChatbot.js.map +1 -1
  50. package/dist/esm/types/XAdkSender.d.ts +20 -1
  51. package/dist/esm/types/XAdkSender.js.map +1 -1
  52. package/dist/esm/types/antdx.d.ts +4 -1
  53. package/dist/esm/types/antdx.js.map +1 -1
  54. package/dist/umd/chat-sdk.min.js +1 -1
  55. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/types/XAdkSender.ts"],
4
- "sourcesContent": ["// types/XAdkSender.ts\nimport React from \"react\";\n\n// ===================== 快捷键/Mention 相关类型 =====================\n\n/** 快捷键触发的列表项 */\nexport interface QuickListOption {\n id: string;\n label: string;\n description?: string;\n icon?: React.ReactNode;\n /** 选中后的行为。insert 会写入 Mention;command 仅执行回调。默认 insert。 */\n action?: \"insert\" | \"command\";\n /** 选中当前选项时执行,可用于 command 类型的业务动作。 */\n onSelect?: (option: QuickListOption) => void;\n /** 分组名(用于聚合列表,子分组通过下划线分割) */\n group?: string;\n [key: string]: any;\n}\n\n/** 快捷键配置 */\nexport interface QuickKeyConfig {\n /** 触发字符,如 '@', '#', '$' */\n key: string;\n /** 选中后写入 MentionTag.type 的语义类型,不传时回退为 key */\n mentionType?: string;\n /** @deprecated 请使用 mentionType。 */\n tagType?: string;\n /** 列表标题 */\n title?: string;\n /** 列表数据 */\n options: QuickListOption[];\n /** 自定义列表项渲染 */\n renderItem?: (option: QuickListOption) => React.ReactNode;\n /** 选中回调 */\n onSelect?: (option: QuickListOption) => void;\n}\n\n/** 输入框内的资源引用。SDK 只理解通用 Mention,业务语义可放入 data 或可选语义字段。 */\nexport interface MentionTag {\n id: string;\n label: string;\n /** Mention 类型,如 'knowledge', 'skill', 'plugin', 'agent' */\n type: string;\n /** 资源 URI,如 skill://skillNo、kb://kb/knNo/dir/docs/2024 */\n uri?: string;\n /** 业务资源 ID,如 skillNo、knowledgeNo */\n targetId?: string;\n /** 展示名;不传时回退 label */\n displayName?: string;\n /** 提交为纯文本/Markdown/XML 时使用的业务值,不传时回退 uri/targetId/label */\n value?: string;\n /** 触发字符,如 '@', '#', '$',用于业务方区分来源 */\n trigger?: string;\n color?: string;\n icon?: React.ReactNode;\n /** 业务扩展上下文,例如知识库目录 scope */\n scope?: any;\n /** 原始数据 */\n data?: any;\n}\n\n/** @deprecated 请使用 MentionTag。 */\nexport type InputTag = MentionTag;\n\nexport interface InlineTagRenderInfo {\n onRemove: () => void;\n}\n\nexport type XAdkSenderSubmitFormat = \"text\" | \"structured\" | \"markdown\" | \"xml\";\n\nexport type XAdkSenderSubmitPart =\n | { type: \"text\"; text: string }\n | { type: \"mention\"; mention: MentionTag };\n\n/** @deprecated 旧 tag part,仅用于兼容 defaultInputParts 历史传入。 */\nexport type XAdkSenderLegacyTagPart = { type: \"tag\"; tag: MentionTag };\n\n/** 输入框文本/Mention 有序片段,表示用户正在编辑的输入内容 */\nexport type XAdkSenderInputPart =\n | XAdkSenderSubmitPart\n | XAdkSenderLegacyTagPart;\n\nexport interface XAdkSenderFormatMentionContext {\n format: Exclude<XAdkSenderSubmitFormat, \"structured\">;\n index: number;\n}\n\n/** @deprecated 请使用 XAdkSenderFormatMentionContext。 */\nexport type XAdkSenderFormatTagContext = XAdkSenderFormatMentionContext;\n\nexport interface XAdkSenderSubmitData<TPayload = unknown> {\n /** 根据 submitFormat 生成的最终内容,默认是纯文本 */\n text: string;\n /** 去掉标签后的用户原始输入文本 */\n rawText: string;\n /** 当前提交格式 */\n format: XAdkSenderSubmitFormat;\n /** 文本与 Mention 的有序片段,inlineTags 模式下可还原资源引用在输入框中的位置 */\n parts: XAdkSenderSubmitPart[];\n files: ServerFile[];\n /** 当前 Mention 列表 */\n mentions?: MentionTag[];\n /** @deprecated 请使用 mentions。 */\n tags?: MentionTag[];\n /** 业务方通过 transformSubmitData 派生出的自定义提交结构 */\n payload?: TPayload;\n}\n\n// ===================== 上传相关类型 =====================\n\nexport interface UploadFileResult {\n fileName?: string;\n fileId?: string | number;\n tempUrl?: string;\n url?: string;\n fileUrl?: string;\n fileType?: string;\n fileSize?: number;\n mimeType?: string;\n [key: string]: any;\n}\n\nexport interface UploadSuccessResponse {\n data?: UploadFileResult | UploadFileResult[];\n [key: string]: any;\n}\n\ninterface uploadRequestProps {\n (options: {\n file: File;\n onProgress?: (e: { percent: number }) => void;\n onSuccess?: (\n response: UploadFileResult | UploadFileResult[] | UploadSuccessResponse,\n ) => void;\n onError?: (error: Error) => void;\n }): Promise<void> | void;\n}\n\nexport type UploadRequestFn = uploadRequestProps;\n\nexport interface ServerFile {\n fileName: string;\n fileId: string | number;\n tempUrl: string;\n type: string;\n size?: number;\n mimeType: string;\n raw?: UploadFileResult;\n}\n\nexport interface LocalFile {\n id: string;\n uid: string;\n name: string;\n file: File;\n size: number;\n type: string;\n progress: number;\n status: \"pending\" | \"uploading\" | \"success\" | \"error\";\n // 服务器返回字段\n fileId?: string | number;\n tempUrl?: string;\n response?: any;\n errorMessage?: string; // 上传错误信息\n}\n\nexport type FileValidator = (\n file: File,\n context: { files: LocalFile[] },\n) => string | null;\n\nexport interface ActionsComponents {\n SendButton: React.ComponentType<any>;\n ClearButton: React.ComponentType<any>;\n}\n\nexport type SlotRenderFunction = (\n oriNode: React.ReactNode,\n info: { components: ActionsComponents },\n) => React.ReactNode | false;\n\n// 上传相关\nexport interface UploaderCoreProps {\n uploadRequest?: uploadRequestProps;\n\n maxFileSize?: number;\n allowedFileTypes?: string[];\n maxFiles?: number;\n\n validators?: FileValidator[];\n\n // 数据层回调\n onFilesChange?: (files: LocalFile[]) => void;\n onUploadSuccess?: (file: LocalFile) => void;\n onUploadError?: (file: LocalFile, error: Error) => void;\n}\n\n// UI 相关\nexport interface SenderUIProps {\n clearBtnShow?: boolean;\n /**\n * 是否启用 Sender 自身的拖拽上传。\n * 拖拽产生的文件通过 onFileSelect 回调交给业务/外层处理;\n * 默认 false,由外层(如 DefaultLayout)管理拖拽时无需开启。\n */\n draggable?: boolean;\n /**\n * 拖拽文件回调:当 draggable 为 true 且用户向输入区拖拽文件时触发。\n * 业务/外层在此回调中决定如何处理文件(通常调用 senderRef.addFiles 注入)。\n */\n onFileSelect?: (files: File[]) => void;\n loading?: boolean;\n disabled?: boolean; // 只读状态\n placeholder?: string;\n /** 受控:输入框的值 */\n value?: string;\n /** 非受控:输入框默认值 */\n defaultValue?: string;\n onClear?: () => void;\n onChange?: (value: string) => void;\n onSubmit?: (data: XAdkSenderSubmitData) => void;\n onStop?: () => void;\n\n /** 自定义发送按钮渲染 */\n sendButtonRender?: (info: {\n loading: boolean;\n disabled: boolean;\n /** 当前是否可以发送(文本非空或有已上传的文件) */\n canSend: boolean;\n onSend: () => void;\n onStop?: () => void;\n }) => React.ReactNode;\n\n // UI插槽功能\n /** 后缀内容,默认展示操作按钮,设为 false 时不显示 */\n suffix?: React.ReactNode | false | SlotRenderFunction;\n /** 输入主体内部的头部内容 */\n header?: React.ReactNode | false | SlotRenderFunction;\n /** 前缀内容 */\n prefix?: React.ReactNode | false | SlotRenderFunction;\n /** 底部内容 */\n footer?: React.ReactNode | false | SlotRenderFunction;\n /** 底部左侧拓展区域,不传时 footer 保持原有居中布局 */\n footerLeft?: React.ReactNode | false | SlotRenderFunction;\n\n // 快捷键/Mention 功能\n /** 快捷键配置列表 */\n quickKeys?: QuickKeyConfig[];\n /** 是否把 Mention 以内联 token 形式展示在输入区内;默认 false 保持历史 textarea 行为 */\n inlineTags?: boolean;\n /** 自定义内联 Mention 渲染 */\n renderMention?: (\n mention: MentionTag,\n info: InlineTagRenderInfo,\n ) => React.ReactNode;\n /** @deprecated 请使用 renderMention。 */\n renderTag?: (\n mention: MentionTag,\n info: InlineTagRenderInfo,\n ) => React.ReactNode;\n /** 受控:Mention 列表 */\n mentions?: MentionTag[];\n /** @deprecated 请使用 mentions。 */\n tags?: MentionTag[];\n /** 非受控:默认 Mention 列表 */\n defaultMentions?: MentionTag[];\n /** @deprecated 请使用 defaultMentions。 */\n defaultTags?: MentionTag[];\n /** 非受控:默认输入内容片段,用于初始化内联 Mention 输入区 */\n defaultInputParts?: XAdkSenderInputPart[];\n /** Mention 变化回调 */\n onMentionsChange?: (mentions: MentionTag[]) => void;\n /** @deprecated 请使用 onMentionsChange。 */\n onTagsChange?: (mentions: MentionTag[]) => void;\n /** 快捷键触发回调(业务方可在此处理搜索等逻辑) */\n onQuickKeyTrigger?: (key: string, keyword: string) => void;\n /**\n * 提交内容格式:\n * - text: 默认,输出文字 + Mention 文本(如 @生图技能)\n * - structured: 保留 parts/mentions/files 结构化数据,text 为 rawText\n * - markdown/xml: 输出对应标记文本\n */\n submitFormat?: XAdkSenderSubmitFormat;\n /** 自定义 Mention 在 text/markdown/xml 格式中的序列化结果 */\n formatMention?: (\n mention: MentionTag,\n context: XAdkSenderFormatMentionContext,\n ) => string;\n /** @deprecated 请使用 formatMention。 */\n formatTag?: (\n mention: MentionTag,\n context: XAdkSenderFormatMentionContext,\n ) => string;\n /**\n * 基于 SDK 标准提交结构派生业务 payload。\n * SDK 不理解 knowledge/skill/agent 等业务字段,只负责把 payload 原样放入 onSubmit 数据中。\n */\n transformSubmitData?: (\n data: Omit<XAdkSenderSubmitData, \"payload\">,\n ) => unknown;\n}\n\nexport type XAdkSenderProps = SenderUIProps & UploaderCoreProps;\n\nexport interface XAdkSenderHandle {\n addFiles: (files: File[]) => void;\n /** 向当前光标位置插入一个内联 Mention;未启用 inlineTags 时会退化为追加 Mention。 */\n insertMention: (mention: MentionTag) => void;\n /** @deprecated 请使用 insertMention。 */\n insertTag: (mention: MentionTag) => void;\n /** 聚焦输入区 */\n focus: () => void;\n}\n"],
4
+ "sourcesContent": ["// types/XAdkSender.ts\nimport React from \"react\";\n\n// ===================== 快捷键/Mention 相关类型 =====================\n\n/** 快捷键触发的列表项 */\nexport interface QuickListOption {\n id: string;\n label: string;\n description?: string;\n icon?: React.ReactNode;\n /** 选中后的行为。insert 会写入 Mention;command 仅执行回调。默认 insert。 */\n action?: \"insert\" | \"command\";\n /** 选中当前选项时执行,可用于 command 类型的业务动作。 */\n onSelect?: (option: QuickListOption) => void;\n /** 分组名(用于聚合列表,子分组通过下划线分割) */\n group?: string;\n [key: string]: any;\n}\n\n/** 快捷键配置 */\nexport interface QuickKeyConfig {\n /** 触发字符,如 '@', '#', '$' */\n key: string;\n /** 选中后写入 MentionTag.type 的语义类型,不传时回退为 key */\n mentionType?: string;\n /** @deprecated 请使用 mentionType。 */\n tagType?: string;\n /** 列表标题 */\n title?: string;\n /** 列表数据 */\n options: QuickListOption[];\n /** 自定义列表项渲染 */\n renderItem?: (option: QuickListOption) => React.ReactNode;\n /** 选中回调 */\n onSelect?: (option: QuickListOption) => void;\n}\n\n/** 输入框内的资源引用。SDK 只理解通用 Mention,业务语义可放入 data 或可选语义字段。 */\nexport interface MentionTag {\n id: string;\n label: string;\n /** Mention 类型,如 'knowledge', 'skill', 'plugin', 'agent' */\n type: string;\n /** 资源 URI,如 skill://skillNo、kb://kb/knNo/dir/docs/2024 */\n uri?: string;\n /** 业务资源 ID,如 skillNo、knowledgeNo */\n targetId?: string;\n /** 展示名;不传时回退 label */\n displayName?: string;\n /** 提交为纯文本/Markdown/XML 时使用的业务值,不传时回退 uri/targetId/label */\n value?: string;\n /** 触发字符,如 '@', '#', '$',用于业务方区分来源 */\n trigger?: string;\n color?: string;\n icon?: React.ReactNode;\n /** 业务扩展上下文,例如知识库目录 scope */\n scope?: any;\n /** 原始数据 */\n data?: any;\n}\n\n/** @deprecated 请使用 MentionTag。 */\nexport type InputTag = MentionTag;\n\nexport interface InlineTagRenderInfo {\n onRemove: () => void;\n}\n\nexport type XAdkSenderSubmitFormat = \"text\" | \"structured\" | \"markdown\" | \"xml\";\n\nexport type XAdkSenderSubmitPart =\n | { type: \"text\"; text: string }\n | { type: \"mention\"; mention: MentionTag };\n\n/** @deprecated 旧 tag part,仅用于兼容 defaultInputParts 历史传入。 */\nexport type XAdkSenderLegacyTagPart = { type: \"tag\"; tag: MentionTag };\n\n/** 输入框文本/Mention 有序片段,表示用户正在编辑的输入内容 */\nexport type XAdkSenderInputPart =\n | XAdkSenderSubmitPart\n | XAdkSenderLegacyTagPart;\n\nexport interface XAdkSenderFormatMentionContext {\n format: Exclude<XAdkSenderSubmitFormat, \"structured\">;\n index: number;\n}\n\n/** @deprecated 请使用 XAdkSenderFormatMentionContext。 */\nexport type XAdkSenderFormatTagContext = XAdkSenderFormatMentionContext;\n\nexport interface XAdkSenderSubmitData<TPayload = unknown> {\n /** 根据 submitFormat 生成的最终内容,默认是纯文本 */\n text: string;\n /** 去掉标签后的用户原始输入文本 */\n rawText: string;\n /** 当前提交格式 */\n format: XAdkSenderSubmitFormat;\n /** 文本与 Mention 的有序片段,inlineTags 模式下可还原资源引用在输入框中的位置 */\n parts: XAdkSenderSubmitPart[];\n files: ServerFile[];\n /** 当前 Mention 列表 */\n mentions?: MentionTag[];\n /** @deprecated 请使用 mentions。 */\n tags?: MentionTag[];\n /** 业务方通过 transformSubmitData 派生出的自定义提交结构 */\n payload?: TPayload;\n}\n\n// ===================== 上传相关类型 =====================\n\nexport interface UploadFileResult {\n fileName?: string;\n fileId?: string | number;\n tempUrl?: string;\n url?: string;\n fileUrl?: string;\n fileType?: string;\n fileSize?: number;\n mimeType?: string;\n [key: string]: any;\n}\n\nexport interface UploadSuccessResponse {\n data?: UploadFileResult | UploadFileResult[];\n [key: string]: any;\n}\n\ninterface uploadRequestProps {\n (options: {\n file: File;\n onProgress?: (e: { percent: number }) => void;\n onSuccess?: (\n response: UploadFileResult | UploadFileResult[] | UploadSuccessResponse,\n ) => void;\n onError?: (error: Error) => void;\n }): Promise<void> | void;\n}\n\nexport type UploadRequestFn = uploadRequestProps;\n\nexport interface ServerFile {\n fileName: string;\n fileId: string | number;\n tempUrl: string;\n type: string;\n size?: number;\n mimeType: string;\n raw?: UploadFileResult;\n}\n\nexport interface LocalFile {\n id: string;\n uid: string;\n name: string;\n file: File;\n size: number;\n type: string;\n progress: number;\n status: \"pending\" | \"uploading\" | \"success\" | \"error\";\n // 服务器返回字段\n fileId?: string | number;\n tempUrl?: string;\n response?: any;\n errorMessage?: string; // 上传错误信息\n}\n\nexport type FileValidator = (\n file: File,\n context: { files: LocalFile[] },\n) => string | null;\n\nexport interface ActionsComponents {\n SendButton: React.ComponentType<any>;\n ClearButton: React.ComponentType<any>;\n}\n\nexport type SlotRenderFunction = (\n oriNode: React.ReactNode,\n info: { components: ActionsComponents },\n) => React.ReactNode | false;\n\n// 上传相关\nexport interface UploaderCoreProps {\n uploadRequest?: uploadRequestProps;\n\n maxFileSize?: number;\n allowedFileTypes?: string[];\n maxFiles?: number;\n\n validators?: FileValidator[];\n\n // 数据层回调\n onFilesChange?: (files: LocalFile[]) => void;\n onUploadSuccess?: (file: LocalFile) => void;\n onUploadError?: (file: LocalFile, error: Error) => void;\n}\n\n// UI 相关\nexport interface SenderUIProps {\n clearBtnShow?: boolean;\n /**\n * 是否启用 Sender 自身的拖拽上传。\n * 拖拽产生的文件通过 onFileSelect 回调交给业务/外层处理;\n * 默认 false,由外层(如 DefaultLayout)管理拖拽时无需开启。\n */\n draggable?: boolean;\n /**\n * 拖拽文件回调:当 draggable 为 true 且用户向输入区拖拽文件时触发。\n * 业务/外层在此回调中决定如何处理文件(通常调用 senderRef.addFiles 注入)。\n */\n onFileSelect?: (files: File[]) => void;\n /**\n * 是否启用 Sender 自身的粘贴上传。\n * 粘贴产生的文件通过 onFilePaste 回调交给业务/外层处理;\n * 默认 true,粘贴是常用能力,开箱即用。设为 false 可完全关闭。\n */\n pasteable?: boolean;\n /**\n * 粘贴文件回调:当 pasteable 为 true 且用户向输入区粘贴文件(含截图)时触发。\n * 业务/外层在此回调中决定如何处理文件(通常调用 senderRef.addFiles 注入)。\n * 注意:纯文本粘贴不会触发本回调,输入框照常接收文本。\n */\n onFilePaste?: (files: File[]) => void;\n loading?: boolean;\n disabled?: boolean; // 只读状态\n placeholder?: string;\n /** 受控:输入框的值 */\n value?: string;\n /** 非受控:输入框默认值 */\n defaultValue?: string;\n onClear?: () => void;\n /** 清除按钮的二次确认文案;未传时沿用 SDK 默认文案。 */\n clearConfirm?: {\n title?: React.ReactNode;\n okText?: React.ReactNode;\n cancelText?: React.ReactNode;\n };\n onChange?: (value: string) => void;\n onSubmit?: (data: XAdkSenderSubmitData) => void;\n onStop?: () => void;\n\n /** 自定义发送按钮渲染 */\n sendButtonRender?: (info: {\n loading: boolean;\n disabled: boolean;\n /** 当前是否可以发送(文本非空或有已上传的文件) */\n canSend: boolean;\n onSend: () => void;\n onStop?: () => void;\n }) => React.ReactNode;\n\n // UI插槽功能\n /** 后缀内容,默认展示操作按钮,设为 false 时不显示 */\n suffix?: React.ReactNode | false | SlotRenderFunction;\n /** 输入主体内部的头部内容 */\n header?: React.ReactNode | false | SlotRenderFunction;\n /** 前缀内容 */\n prefix?: React.ReactNode | false | SlotRenderFunction;\n /** 底部内容 */\n footer?: React.ReactNode | false | SlotRenderFunction;\n /** 底部左侧拓展区域,不传时 footer 保持原有居中布局 */\n footerLeft?: React.ReactNode | false | SlotRenderFunction;\n\n // 快捷键/Mention 功能\n /** 快捷键配置列表 */\n quickKeys?: QuickKeyConfig[];\n /** 是否把 Mention 以内联 token 形式展示在输入区内;默认 false 保持历史 textarea 行为 */\n inlineTags?: boolean;\n /** 自定义内联 Mention 渲染 */\n renderMention?: (\n mention: MentionTag,\n info: InlineTagRenderInfo,\n ) => React.ReactNode;\n /** @deprecated 请使用 renderMention。 */\n renderTag?: (\n mention: MentionTag,\n info: InlineTagRenderInfo,\n ) => React.ReactNode;\n /** 受控:Mention 列表 */\n mentions?: MentionTag[];\n /** @deprecated 请使用 mentions。 */\n tags?: MentionTag[];\n /** 非受控:默认 Mention 列表 */\n defaultMentions?: MentionTag[];\n /** @deprecated 请使用 defaultMentions。 */\n defaultTags?: MentionTag[];\n /** 非受控:默认输入内容片段,用于初始化内联 Mention 输入区 */\n defaultInputParts?: XAdkSenderInputPart[];\n /** Mention 变化回调 */\n onMentionsChange?: (mentions: MentionTag[]) => void;\n /** @deprecated 请使用 onMentionsChange。 */\n onTagsChange?: (mentions: MentionTag[]) => void;\n /** 快捷键触发回调(业务方可在此处理搜索等逻辑) */\n onQuickKeyTrigger?: (key: string, keyword: string) => void;\n /**\n * 提交内容格式:\n * - text: 默认,输出文字 + Mention 文本(如 @生图技能)\n * - structured: 保留 parts/mentions/files 结构化数据,text 为 rawText\n * - markdown/xml: 输出对应标记文本\n */\n submitFormat?: XAdkSenderSubmitFormat;\n /** 自定义 Mention 在 text/markdown/xml 格式中的序列化结果 */\n formatMention?: (\n mention: MentionTag,\n context: XAdkSenderFormatMentionContext,\n ) => string;\n /** @deprecated 请使用 formatMention。 */\n formatTag?: (\n mention: MentionTag,\n context: XAdkSenderFormatMentionContext,\n ) => string;\n /**\n * 基于 SDK 标准提交结构派生业务 payload。\n * SDK 不理解 knowledge/skill/agent 等业务字段,只负责把 payload 原样放入 onSubmit 数据中。\n */\n transformSubmitData?: (\n data: Omit<XAdkSenderSubmitData, \"payload\">,\n ) => unknown;\n}\n\nexport type XAdkSenderProps = SenderUIProps & UploaderCoreProps;\n\nexport interface XAdkSenderHandle {\n /** 添加本地文件或已上传文件;后者直接进入附件列表,不重复上传。 */\n addFiles: (files: Array<File | ServerFile>) => void;\n /** 向当前光标位置插入一个内联 Mention;未启用 inlineTags 时会退化为追加 Mention。 */\n insertMention: (mention: MentionTag) => void;\n /** @deprecated 请使用 insertMention。 */\n insertTag: (mention: MentionTag) => void;\n /** 聚焦输入区 */\n focus: () => void;\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -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
  })]