@blueking/chat-x 0.0.49-beta.7 → 0.0.49-beta.9

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 (38) hide show
  1. package/dist/ag-ui/types/file.d.ts +1 -11
  2. package/dist/ag-ui/types/messages.d.ts +2 -0
  3. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/artifact-preview-host.vue.d.ts +5 -1
  4. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/preview-strategy.d.ts +4 -5
  5. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/code-preview.vue.d.ts +8 -0
  6. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/image-preview.vue.d.ts +7 -0
  7. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/use-artifact-preview-loader.d.ts +1 -0
  8. package/dist/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue.d.ts +1 -0
  9. package/dist/components/chat-message/user-message/user-message.vue.d.ts +1 -0
  10. package/dist/components/file-icon/file-icon.vue.d.ts +7 -0
  11. package/dist/components/index.d.ts +3 -1
  12. package/dist/components/message-tools/message-time/format-message-time.d.ts +8 -0
  13. package/dist/components/message-tools/message-time/message-time.vue.d.ts +8 -0
  14. package/dist/components/message-tools/message-tools.vue.d.ts +11 -1
  15. package/dist/composables/use-global-config.d.ts +3 -0
  16. package/dist/composables/use-message-group.d.ts +144 -72
  17. package/dist/icons/file-icons.d.ts +4 -0
  18. package/dist/icons/file.d.ts +0 -18
  19. package/dist/icons/index.d.ts +1 -0
  20. package/dist/index.css +1 -1
  21. package/dist/index.js +2239 -1982
  22. package/dist/index.js.map +1 -1
  23. package/dist/lang/lang.d.ts +2 -1
  24. package/dist/mcp/generated/docs/assistant-message.md +12 -8
  25. package/dist/mcp/generated/docs/chat-container.md +3 -1
  26. package/dist/mcp/generated/docs/file-artifact-panel.md +27 -11
  27. package/dist/mcp/generated/docs/file-icon.md +111 -0
  28. package/dist/mcp/generated/docs/message-container.md +2 -0
  29. package/dist/mcp/generated/docs/message-time.md +180 -0
  30. package/dist/mcp/generated/docs/message-tools.md +47 -12
  31. package/dist/mcp/generated/docs/messages.md +4 -0
  32. package/dist/mcp/generated/docs/use-global-config.md +15 -5
  33. package/dist/mcp/generated/docs/user-message.md +6 -0
  34. package/dist/mcp/generated/index.json +61 -5
  35. package/dist/utils/file-type.d.ts +14 -0
  36. package/dist/utils/index.d.ts +1 -0
  37. package/package.json +2 -2
  38. package/dist/components/chat-message/assistant-message/message-artifacts/file-icon.d.ts +0 -29
@@ -37,6 +37,7 @@ export declare const lang: {
37
37
  readonly 停止生成: "Stop generating";
38
38
  readonly 正在停止: "Stopping";
39
39
  readonly 耗时: "Duration";
40
+ readonly 昨天: "Yesterday";
40
41
  readonly 参数: "Parameters";
41
42
  readonly 描述: "Description";
42
43
  readonly 执行情况: "Execution Status";
@@ -156,4 +157,4 @@ export declare const lang: {
156
157
  readonly 预览加载失败: "Failed to load preview";
157
158
  readonly 暂无可预览的文件: "No file to preview";
158
159
  };
159
- export declare const t: (key: keyof typeof lang) => "Send" | "Stop" | "Ask AI" | "Copy" | "Share" | "Like" | "Unsatisfied" | "Delete" | "Quote" | "Regenerate" | "Regenerating will clear the content below" | "Submit" | "Cancel" | "Preview Content" | "Jump to Detail" | "Call Tool:" | "Calling..." | "Call Success" | "Call Failed" | "Tell us your thoughts" | "What makes you satisfied?" | "What makes you dissatisfied?" | "Return Content" | "Edit" | "Deep Thinking" | "Quick Thinking" | "Image to Text" | "Loading image..." | "Failed to load image" | "Thinking..." | "Thinking Completed" | "Thinking Failed" | "Copy Success" | "Copy Failed" | "Return to bottom" | "Stop generating" | "Stopping" | "Duration" | "Parameters" | "Description" | "Execution Status" | "Running" | "Success" | "Failed" | "Pending" | "To Be Executed" | "Details" | "Retry" | "Retrying" | "Skipping" | "The task is retrying and cannot be skipped" | "The task is skipping and cannot be retried" | "Node" | "Node Config" | "Node Output" | "Basic Info" | "Flow Template" | "Node Name" | "Step Name" | "Execution Plan" | "Optional" | "Failure Handler" | "Timeout Control" | "Yes" | "No" | "Input Params" | "Output Params" | "Param Name" | "Param Value" | "Name" | "Structured Output" | "Manual Skip" | "No Data" | "Call MCP:" | "More" | "Algorithm Plan Review Ticket" | "Reviewing" | "Approving" | "Abandoned" | "Approved" | "Cancelled" | "Expired" | "Rejected" | "Revoked" | "Ticket No." | "Submitted At" | "Current Handler" | "None" | "View Ticket Detail" | "Copy Ticket" | "Copy Ticket Link" | "Cancel Approval" | "Approval Cancelled" | "Refresh Ticket Status" | "This ticket has been rejected and cannot be cancelled" | "This ticket has been approved and cannot be cancelled" | "This ticket has been cancelled, no need to click again" | "Approval cannot be cancelled in the current status" | "There are {count} pending approval tickets in the current conversation. To continue, cancel approval first." | "Unsupported interrupt message" | "Searching" | "Search Completed" | "Upload File" | "Select model" | "Search models" | "Requesting..." | "Cancel satisfied" | "Cancel dissatisfied" | "Confirm delete this answer?" | "This operation cannot be undone. Please proceed with caution!" | "Preview" | "Zoom Out" | "Zoom In" | "Rotate" | "Download" | "Sorry, image loading failed. Please try reloading." | "Reset" | "Reload" | "W" | "H" | "Upload Image" | "Search keyword" | "Select date" | "Locate in Chat" | "Select All" | "Confirm" | "Upload Image, up to 3 images supported, max 2.4MB each" | "Hello, I am BlueKing AI Bot" | "Clear Search" | "Search Result is Empty" | "Valid Evidence" | "Full Screen" | "Exit Full Screen" | "Please choose to continue" | "Continue" | "Received: " | "Done" | "Skip" | "Single" | "Multiple" | "Others" | "Completed" | "question(s)" | "Answers" | "Replied" | "Please enter..." | "File Artifacts" | "File List" | "Search file keyword" | "Failed to load preview" | "No file to preview" | "发送" | "停止" | "问问小鲸" | "复制" | "分享" | "点赞" | "不满意" | "删除" | "引用" | "重新生成" | "重新生成将清空下文内容" | "提交" | "取消" | "预览内容" | "跳转详情" | "调用工具:" | "调用中" | "调用成功" | "调用失败" | "说出您的想法" | "什么原因让你满意?" | "什么原因让你不满意?" | "返回内容" | "编辑" | "深度思考" | "快速思考" | "图生文" | "图片加载中..." | "图片加载失败" | "思考中" | "已思考完成" | "思考失败" | "复制成功" | "复制失败" | "返回底部" | "停止生成" | "正在停止" | "耗时" | "参数" | "描述" | "执行情况" | "执行中" | "成功" | "失败" | "挂起" | "待执行" | "详情" | "重试" | "重试中" | "跳过中" | "任务正在重试中,不可跳过" | "任务正在跳过中,不可重试" | "节点" | "节点配置" | "节点输出" | "基础信息" | "流程模板" | "节点名称" | "步骤名称" | "执行方案" | "是否可选" | "失败处理" | "超时控制" | "是" | "否" | "输入参数" | "输出参数" | "参数名" | "参数值" | "名称" | "变量说明" | "结构化输出" | "手动跳过" | "暂无数据" | "调用 MCP:" | "更多" | "算法方案评审单" | "评审中" | "审批中" | "已废弃" | "已批准" | "已通过" | "已取消" | "已过期" | "已拒绝" | "已撤销" | "单据编号" | "提交时间" | "当前处理人" | "无" | "查看单据详情" | "复制单据" | "复制单据链接" | "取消审批" | "已取消审批" | "刷新单据状态" | "单据已取消审批" | "该单据已被拒绝,无法取消" | "该单据已通过,无法取消" | "单据已取消,无需重复点击" | "当前状态无法取消审批" | "当前会话有 {count} 个待审批单,如需继续,请先取消审批" | "暂不支持的中断消息" | "检索中" | "检索完成" | "上传文件" | "选择模型" | "搜索模型关键字" | "请求中..." | "取消满意" | "取消不满意" | "确认删除该回答?" | "删除操作无法撤回,请谨慎操作!" | "预览" | "缩小" | "放大" | "旋转" | "下载" | "抱歉,图片加载失败,可尝试重新加载" | "重置" | "重新加载" | "宽" | "高" | "上传图片" | "搜索 关键字" | "选择日期" | "在对话中定位" | "全选" | "确定" | "上传图片, 最多支持上传 3 个, 最大支持 2.4MB" | "你好,我是小鲸" | "清空搜索" | "搜索结果为空" | "有效证据" | "全屏" | "退出全屏" | "请选择以继续" | "继续" | "收到信息:" | "待审批" | "已审批" | "完成" | "跳过" | "单选" | "多选" | "其他" | "已完成" | "题" | "回答内容" | "已回复" | "请输入..." | "文件产物" | "文件列表" | "搜索文件关键字" | "预览加载失败" | "暂无可预览的文件";
160
+ export declare const t: (key: keyof typeof lang) => "Send" | "Stop" | "Ask AI" | "Copy" | "Share" | "Like" | "Unsatisfied" | "Delete" | "Quote" | "Regenerate" | "Regenerating will clear the content below" | "Submit" | "Cancel" | "Preview Content" | "Jump to Detail" | "Call Tool:" | "Calling..." | "Call Success" | "Call Failed" | "Tell us your thoughts" | "What makes you satisfied?" | "What makes you dissatisfied?" | "Return Content" | "Edit" | "Deep Thinking" | "Quick Thinking" | "Image to Text" | "Loading image..." | "Failed to load image" | "Thinking..." | "Thinking Completed" | "Thinking Failed" | "Copy Success" | "Copy Failed" | "Return to bottom" | "Stop generating" | "Stopping" | "Duration" | "Yesterday" | "Parameters" | "Description" | "Execution Status" | "Running" | "Success" | "Failed" | "Pending" | "To Be Executed" | "Details" | "Retry" | "Retrying" | "Skipping" | "The task is retrying and cannot be skipped" | "The task is skipping and cannot be retried" | "Node" | "Node Config" | "Node Output" | "Basic Info" | "Flow Template" | "Node Name" | "Step Name" | "Execution Plan" | "Optional" | "Failure Handler" | "Timeout Control" | "Yes" | "No" | "Input Params" | "Output Params" | "Param Name" | "Param Value" | "Name" | "Structured Output" | "Manual Skip" | "No Data" | "Call MCP:" | "More" | "Algorithm Plan Review Ticket" | "Reviewing" | "Approving" | "Abandoned" | "Approved" | "Cancelled" | "Expired" | "Rejected" | "Revoked" | "Ticket No." | "Submitted At" | "Current Handler" | "None" | "View Ticket Detail" | "Copy Ticket" | "Copy Ticket Link" | "Cancel Approval" | "Approval Cancelled" | "Refresh Ticket Status" | "This ticket has been rejected and cannot be cancelled" | "This ticket has been approved and cannot be cancelled" | "This ticket has been cancelled, no need to click again" | "Approval cannot be cancelled in the current status" | "There are {count} pending approval tickets in the current conversation. To continue, cancel approval first." | "Unsupported interrupt message" | "Searching" | "Search Completed" | "Upload File" | "Select model" | "Search models" | "Requesting..." | "Cancel satisfied" | "Cancel dissatisfied" | "Confirm delete this answer?" | "This operation cannot be undone. Please proceed with caution!" | "Preview" | "Zoom Out" | "Zoom In" | "Rotate" | "Download" | "Sorry, image loading failed. Please try reloading." | "Reset" | "Reload" | "W" | "H" | "Upload Image" | "Search keyword" | "Select date" | "Locate in Chat" | "Select All" | "Confirm" | "Upload Image, up to 3 images supported, max 2.4MB each" | "Hello, I am BlueKing AI Bot" | "Clear Search" | "Search Result is Empty" | "Valid Evidence" | "Full Screen" | "Exit Full Screen" | "Please choose to continue" | "Continue" | "Received: " | "Done" | "Skip" | "Single" | "Multiple" | "Others" | "Completed" | "question(s)" | "Answers" | "Replied" | "Please enter..." | "File Artifacts" | "File List" | "Search file keyword" | "Failed to load preview" | "No file to preview" | "发送" | "停止" | "问问小鲸" | "复制" | "分享" | "点赞" | "不满意" | "删除" | "引用" | "重新生成" | "重新生成将清空下文内容" | "提交" | "取消" | "预览内容" | "跳转详情" | "调用工具:" | "调用中" | "调用成功" | "调用失败" | "说出您的想法" | "什么原因让你满意?" | "什么原因让你不满意?" | "返回内容" | "编辑" | "深度思考" | "快速思考" | "图生文" | "图片加载中..." | "图片加载失败" | "思考中" | "已思考完成" | "思考失败" | "复制成功" | "复制失败" | "返回底部" | "停止生成" | "正在停止" | "耗时" | "昨天" | "参数" | "描述" | "执行情况" | "执行中" | "成功" | "失败" | "挂起" | "待执行" | "详情" | "重试" | "重试中" | "跳过中" | "任务正在重试中,不可跳过" | "任务正在跳过中,不可重试" | "节点" | "节点配置" | "节点输出" | "基础信息" | "流程模板" | "节点名称" | "步骤名称" | "执行方案" | "是否可选" | "失败处理" | "超时控制" | "是" | "否" | "输入参数" | "输出参数" | "参数名" | "参数值" | "名称" | "变量说明" | "结构化输出" | "手动跳过" | "暂无数据" | "调用 MCP:" | "更多" | "算法方案评审单" | "评审中" | "审批中" | "已废弃" | "已批准" | "已通过" | "已取消" | "已过期" | "已拒绝" | "已撤销" | "单据编号" | "提交时间" | "当前处理人" | "无" | "查看单据详情" | "复制单据" | "复制单据链接" | "取消审批" | "已取消审批" | "刷新单据状态" | "单据已取消审批" | "该单据已被拒绝,无法取消" | "该单据已通过,无法取消" | "单据已取消,无需重复点击" | "当前状态无法取消审批" | "当前会话有 {count} 个待审批单,如需继续,请先取消审批" | "暂不支持的中断消息" | "检索中" | "检索完成" | "上传文件" | "选择模型" | "搜索模型关键字" | "请求中..." | "取消满意" | "取消不满意" | "确认删除该回答?" | "删除操作无法撤回,请谨慎操作!" | "预览" | "缩小" | "放大" | "旋转" | "下载" | "抱歉,图片加载失败,可尝试重新加载" | "重置" | "重新加载" | "宽" | "高" | "上传图片" | "搜索 关键字" | "选择日期" | "在对话中定位" | "全选" | "确定" | "上传图片, 最多支持上传 3 个, 最大支持 2.4MB" | "你好,我是小鲸" | "清空搜索" | "搜索结果为空" | "有效证据" | "全屏" | "退出全屏" | "请选择以继续" | "继续" | "收到信息:" | "待审批" | "已审批" | "完成" | "跳过" | "单选" | "多选" | "其他" | "已完成" | "题" | "回答内容" | "已回复" | "请输入..." | "文件产物" | "文件列表" | "搜索文件关键字" | "预览加载失败" | "暂无可预览的文件";
@@ -378,14 +378,18 @@ AI 可在一次回复中发起多个工具调用,组件依次渲染:
378
378
 
379
379
  `AIFileInfo` 仅含元信息(`name` / `outputId` / `size` / `type`);`download_url` / `preview_url` 由容器 `onArtifactClick` 异步获取。命中唯一文件依赖 `messageUid = uid ?? String(id)` + 卡片下标 + `outputId`。
380
380
 
381
- 侧栏预览由面板内 `ArtifactPreviewHost` 按类型分派(详见面板文档「预览机制」):
382
-
383
- | type | 预览依赖 | 渲染 |
384
- | ---- | -------- | ---- |
385
- | `html` / `markdown` / `md` / `txt` / `json` | `download_url` | 正文直渲染(srcdoc / MarkdownContent / `<pre>`) |
386
- | `pdf` / `jpg` | `preview_url` | iframe(一般为后台转好的 PDF) |
387
-
388
- `md` `markdown` 等价(见 `AIFileType.Md` / `AIFileType.Markdown`)。预览重载、重试与取链约定见 [FileArtifactPanel 预览机制](/components/message/file-artifact-panel#预览机制)。
381
+ 侧栏预览由面板内 `ArtifactPreviewHost` 按**文件分类**分派(详见面板文档「预览机制」):
382
+
383
+ | 分类 | 典型 type | 预览依赖 | 渲染 |
384
+ | ---- | --------- | -------- | ---- |
385
+ | 源码 / 配置 | `py` / `ts` / `json` / `yaml` / `Dockerfile` | `download_url` | highlight.js 高亮 |
386
+ | Markdown | `md` / `markdown` | `download_url` | MarkdownContent 富文本 |
387
+ | HTML | `html` / `htm` | `download_url` | `<iframe srcdoc>` 真实渲染 |
388
+ | 纯文本 | `txt` / `rst` | `download_url` | `<pre>` |
389
+ | 图片 | `png` / `jpg` / `svg` | `preview_url` | `<img>` |
390
+ | 其余(含未知类型) | `pdf` / `docx` / `xlsx` | `preview_url` | iframe(一般为后台转好的 PDF) |
391
+
392
+ `type` 为扩展名字符串(大小写不敏感),缺省时回退文件名推断;`md` 与 `markdown` 等价。预览重载、重试与取链约定见 [FileArtifactPanel 预览机制](/components/message/file-artifact-panel#预览机制)。
389
393
 
390
394
  ```vue
391
395
  <template>
@@ -31,6 +31,7 @@
31
31
  - **模型选择**:透传 `models`、`v-model:selectedModel` 与 `@modelChange` 至 `ChatInput`,传入 `models` 后在发送按钮左侧展示 [ModelSelector](/components/input/model-selector)
32
32
  - **分享模式 / 渲染模式**:内置多选分享;`renderMode` 经 Provider 下传。`Share` 态开放侧栏只读查看,隐藏底部输入与「重试 / 跳过」等交互
33
33
  - **字号主题**:`size` 为 `small`(默认 12px)/ `normal`(14px);根节点 `data-ai-size`,浮层同步 `document.body.dataset.aiSize`
34
+ - **消息时间时区**:`timezone` 经 `useGlobalConfig` 下传给 `MessageTime`,统一整个会话的时间展示时区;未配置时按浏览器时区
34
35
  - **空状态欢迎页**:无消息时展示 Banner、`welcomeTitle`(默认「你好,我是小鲸」)与 `openingRemark`
35
36
 
36
37
  ## 组件结构
@@ -708,6 +709,7 @@ ChatContainer 的 Props 继承自 `ChatInputProps` 和 `MessageContainerProps`
708
709
  | placement | `'left' \| 'right'` | `'left'` | 侧边栏位置 |
709
710
  | resizeProps | `{ disabled?: boolean; initialDivide?: number \| string; max?: number; min?: number }` | — | 透传给内部 `ResizeLayout`;与默认 `collapsible: false`、`immediate: true`、`min: 400` 合并;`placement` 始终取自本组件。**数字型** `initialDivide` 还会作为内部侧栏宽度初值(驱动 `--resize-main-width`,并在展开时作为 `collapseChange` 的 `width`);百分比等字符串则回退为 `400` |
710
711
  | size | `'normal' \| 'small'` | `'small'` | 字号主题:`small` 12px / `normal` 14px;根节点设置 `data-ai-size` 并注入 `useGlobalConfig` |
712
+ | timezone | `string` | — | 消息时间展示所用的 IANA 时区名(如 `Asia/Shanghai`);经 `useGlobalConfig` 注入,未配置时按浏览器时区展示,详见 [MessageTime](/components/feedback/message-time) |
711
713
  | welcomeTitle | `string` | — | 欢迎页标题;未传时默认展示「你好,我是小鲸」 |
712
714
  | onCustomTabChange | `(tab: CustomTab) => Promise<any>` | — | 自定义 Tab 切换回调,返回值作为 Tab 组件 props |
713
715
  | onArtifactClick | `(file: AIFileInfo) => Promise<{ download_url?: string; preview_url?: string }>` | — | 异步获取下载 / 预览链接(每次调用重新获取,无缓存;同文件并发去重)。文本类预览依赖 `download_url`,iframe 类依赖 `preview_url`;未传则隐藏下载、预览无数据 |
@@ -865,5 +867,5 @@ interface Shortcut {
865
867
  - [SelectionFooter](/components/input/selection-footer) — 多选操作栏
866
868
  - [ToolBtn](/components/feedback/tool-btn) — 侧栏全屏按钮
867
869
  - [useFullScreen](/composables/use-full-screen) — 侧栏全屏控制
868
- - [useGlobalConfig](/composables/use-global-config) — 注入 `size` 与 `supportUpload`
870
+ - [useGlobalConfig](/composables/use-global-config) — 注入 `size`、`supportUpload` 与 `timezone`
869
871
  - [主题配置](/theme/theme) — 字号主题 CSS 变量
@@ -1,7 +1,7 @@
1
1
  <!-- AI SUMMARY -->
2
2
  ## 快速了解
3
3
 
4
- 汇总当前会话所有 AssistantMessage 的 artifacts(按 outputId 去重),支持关键词搜索、列表选中与下载; 预览区委托 ArtifactPreviewHost:按类型走 text_from_download(html / markdown / md / txt / json) preview_url_iframe(其余类型);download_url / preview_url 经 onArtifactClick 每次异步获取(无 URL 缓存,并发去重); 预览重载键为 outputId:type;重试再次调用 load() 重新取链。 源码位置:src/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue。
4
+ 汇总当前会话所有 AssistantMessage 的 artifacts(按 outputId 去重),支持关键词搜索、列表选中与下载; 预览区委托 ArtifactPreviewHost:由 resolveFileKind 把扩展名归入 code / markdown / html / text / image / binary 六类, 前四类走 text_from_download 拉正文(code 交给 highlight.js 高亮),image / binary 走 preview_url; download_url / preview_url 经 onArtifactClick 每次异步获取(无 URL 缓存,并发去重); 预览重载键为 outputId:type;重试再次调用 load() 重新取链。 源码位置:src/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue。
5
5
 
6
6
  ### 关联组件
7
7
  - **assistant-message** — 文件产物来源于 AssistantMessage.property.artifacts
@@ -183,16 +183,25 @@ sessionArtifacts 有产物时
183
183
 
184
184
  ## 预览机制
185
185
 
186
- 预览由内部 `getArtifactPreviewStrategy(type)` 决定 **加载方式** 与 **渲染器**;面板不直接写死类型分支。
186
+ 预览分两步:先由 `resolveFileKind(type, name)`(`src/utils/file-type.ts`)把扩展名归入六个**分类**,再由 `getArtifactPreviewStrategy` 查表得到 **加载方式** 与 **渲染器**。面板与 Host 都不写死具体扩展名分支,后台新增文件类型时只需在分类表里补一行。
187
187
 
188
- | 文件类型 | load | 取链字段 | renderer |
189
- | -------- | ---- | -------- | -------- |
190
- | `html` | `text_from_download` | `download_url` → `fetch` 正文 | `HtmlPreview`(`<iframe srcdoc>`) |
191
- | `markdown` / `md` | `text_from_download` | 同上 | `MarkdownPreview`(`MarkdownContent`) |
192
- | `txt` / `json` | `text_from_download` | 同上 | `TxtPreview`(`<pre>`) |
193
- | 其余(如 `pdf` / `jpg`) | `preview_url_iframe` | `preview_url` | `UrlIframePreview`(`<iframe src>`,一般为后台转好的 PDF) |
188
+ | 分类 | 覆盖扩展名 | load | 取链字段 | renderer |
189
+ | ---- | ---------- | ---- | -------- | -------- |
190
+ | `code` | `py` `js` `mjs` `cjs` `ts` `tsx` `jsx` `vue` `go` `rs` `rb` `java` `kt` `swift` `c` `h` `cpp` `hpp` `cs` `php` `lua` `r` `scala` `dart` `sh` `bash` `zsh` `ps1` `sql` `css` `scss` `less` `json` `jsonc` `yaml` `yml` `toml` `ini` `cfg` `conf` `env` `xml` `tex` `Dockerfile` `Makefile` `gitignore` `dockerignore` `editorconfig` | `text_from_download` | `download_url` → `fetch` 正文 | `CodePreview`(highlight.js 高亮) |
191
+ | `markdown` | `md` `markdown` | `text_from_download` | 同上 | `MarkdownPreview`(`MarkdownContent`) |
192
+ | `html` | `html` `htm` | `text_from_download` | 同上 | `HtmlPreview`(`<iframe srcdoc>`) |
193
+ | `text` | `txt` `rst` | `text_from_download` | 同上 | `TxtPreview`(`<pre>`) |
194
+ | `image` | `png` `jpg` `jpeg` `svg` | `preview_url` | `preview_url` | `ImagePreview`(`<img>`,`object-fit: contain`) |
195
+ | `binary` | `pdf` `docx` `xlsx` `xlsm` `xls` `pptx` `csv` `tsv`,以及**所有未登记的扩展名** | `preview_url` | `preview_url` | `UrlIframePreview`(`<iframe src>`,一般为后台转好的 PDF) |
194
196
 
195
- > `md`(`AIFileType.Md`)为后台扩展名别名,与 `markdown`(`AIFileType.Markdown`)等价,共用 Markdown 直渲染。
197
+ 关于类型解析:
198
+
199
+ - `AIFileInfo.type` 为**扩展名字符串**(如 `'pdf'` / `'py'`)或无扩展名的文件名(如 `'Dockerfile'`),大小写不敏感
200
+ - `type` 缺省时回退 `name` 推断;`报告.final.xlsx` 取 `xlsx`,`.gitignore` 取 `gitignore`
201
+ - `md` 为后台扩展名别名,与 `markdown` 等价,共用 Markdown 直渲染
202
+ - 未登记的扩展名一律落入 `binary` 走后台预览,前端不会因为新类型报错
203
+
204
+ `CodePreview` 的语言由扩展名映射到 highlight.js(`vue → xml`、`tsx → typescript`、`env / cfg / conf → ini` 等,其余交给 hljs 自身别名表,识别不了则按 `plaintext` 转义输出)。单文件超过 300KB 时跳过高亮直接转义,避免同步解析阻塞主线程。
196
205
 
197
206
  ### 加载态
198
207
 
@@ -219,15 +228,19 @@ message-artifacts/
219
228
  ├── file-artifact-panel.vue # 列表 + 下载头 + 挂载 Host
220
229
  └── artifact-preview/
221
230
  ├── artifact-preview-host.vue # 状态机 UI + 分派 renderer
222
- ├── preview-strategy.ts # getArtifactPreviewStrategy
231
+ ├── preview-strategy.ts # getArtifactPreviewStrategy(分类 → 策略查表)
223
232
  ├── use-artifact-preview-loader.ts
224
233
  └── renderers/
234
+ ├── code-preview.vue
225
235
  ├── html-preview.vue
236
+ ├── image-preview.vue
226
237
  ├── markdown-preview.vue
227
238
  ├── txt-preview.vue
228
239
  └── url-iframe-preview.vue
229
240
  ```
230
241
 
242
+ 分类表与扩展名归一化在 `src/utils/file-type.ts`(导出 `AIFileKind` / `resolveFileKind` / `normalizeFileExtension`),与[文件图标](/components/helper/file-icon)共用同一份解析入口。
243
+
231
244
  ## API
232
245
 
233
246
  ### Props
@@ -259,10 +272,13 @@ type AIFileInfo = {
259
272
  name: string;
260
273
  outputId: string;
261
274
  size: number;
262
- type: AIFileType;
275
+ /** 扩展名(如 'pdf' / 'py')或无扩展名文件名(如 'Dockerfile'),大小写不敏感 */
276
+ type: string;
263
277
  };
264
278
  ```
265
279
 
280
+ > **破坏性变更**:原 `AIFileType` 枚举已移除,`AIFileInfo.type` 改为 `string`。此前写 `type: AIFileType.Pdf` 的代码改为 `type: 'pdf'` 即可;枚举成员的值与新字符串一一对应,运行时数据无需迁移。
281
+
266
282
  ## 关联 Composable
267
283
 
268
284
  预览**命中与取链**由 [useArtifactPreview](/composables/use-artifact-preview) 提供(Provider / Consumer + `ARTIFACT_PREVIEW_TOKEN`)。**正文加载与渲染**由内部 `useArtifactPreviewLoader` + `ArtifactPreviewHost` 完成,不在该 composable 内。
@@ -0,0 +1,111 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ 按扩展名渲染文件类型图标:内联 svg,尺寸用 1em 跟随外层 font-size; 扩展名解析走 normalizeFileExtension(优先 fileType,缺省回退 fileName,大小写不敏感, 支持 Dockerfile / .gitignore 这类无扩展名或点号开头的文件);未登记类型返回 unknown 兜底图标。 源码位置:src/components/file-icon/file-icon.vue,图标注册表在 src/icons/file-icons.ts。
5
+
6
+ ### 关联组件
7
+ - **file-artifact-panel** — 文件产物列表与预览头使用该组件展示类型图标
8
+ - **assistant-message** — 消息内的文件产物卡片使用该组件
9
+
10
+ ---
11
+ <!-- FULL DOC -->
12
+
13
+ # FileIcon 文件类型图标
14
+
15
+ > **能力域**:辅助能力
16
+
17
+ `FileIcon` 按文件扩展名渲染对应的类型图标,用于文件列表、附件卡片、产物预览头等需要「一眼看出这是什么文件」的场景。图标以内联 svg 形式打进包内,不产生额外网络请求,也不需要消费方配置静态资源路径。
18
+
19
+ ## 源码事实
20
+
21
+ - **源码位置**:`src/components/file-icon/file-icon.vue`
22
+ - **图标注册表**:`src/icons/file-icons.ts`(导出 `getFileIconSvg` / `UNKNOWN_FILE_ICON_SVG`)
23
+ - **扩展名解析**:`src/utils/file-type.ts` 的 `normalizeFileExtension`
24
+
25
+ ## 核心能力
26
+
27
+ - **扩展名驱动**:优先取 `fileType`,缺省时回退 `fileName` 推断;大小写不敏感
28
+ - **特殊文件名**:`Dockerfile` / `Makefile` 这类无扩展名文件,以及 `.gitignore` / `.editorconfig` 这类点号开头的文件都能正确命中
29
+ - **多扩展名共用图标**:如 `xlsx` / `xls` / `csv` / `tsv` 共用表格图标,`tsx` / `jsx` 共用 React 图标
30
+ - **兜底不报错**:未登记的扩展名返回 `unknown` 图标,后台新增文件类型时前端不会缺图
31
+ - **尺寸自适应**:内部 svg 固定为 `1em`,直接用外层 `font-size` 控制大小
32
+
33
+ ## 基础用法
34
+
35
+ ```vue
36
+ <template>
37
+ <FileIcon file-type="pdf" />
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { FileIcon } from '@blueking/chat-x';
42
+ </script>
43
+ ```
44
+
45
+ **渲染效果**
46
+
47
+ ## 从文件名推断
48
+
49
+ 后台未下发 `type` 时传 `fileName` 即可,组件会取最后一段扩展名:
50
+
51
+ ```vue
52
+ <template>
53
+ <!-- 取 xlsx -->
54
+ <FileIcon file-name="季度报告.final.xlsx" />
55
+ <!-- 取 gitignore -->
56
+ <FileIcon file-name=".gitignore" />
57
+ </template>
58
+ ```
59
+
60
+ 两者同时传入时以 `fileType` 优先。
61
+
62
+ ## 控制尺寸
63
+
64
+ 图标宽高为 `1em`,用外层 `font-size` 控制即可,无需改 svg:
65
+
66
+ ```vue
67
+ <template>
68
+ <span style="font-size: 16px"><FileIcon file-type="py" /></span>
69
+ <span style="font-size: 32px"><FileIcon file-type="py" /></span>
70
+ </template>
71
+ ```
72
+
73
+ ## API
74
+
75
+ ### Props
76
+
77
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
78
+ | -------- | -------- | ---- | ----------- | -------------------------------------------------------- |
79
+ | fileType | `string` | 否 | `undefined` | 文件类型:扩展名(如 `pdf` / `py`)或无扩展名文件名(如 `Dockerfile`) |
80
+ | fileName | `string` | 否 | `undefined` | 文件名,`fileType` 缺省时用于推断扩展名 |
81
+
82
+ 两者都不传时渲染兜底图标。
83
+
84
+ ### Emits / Slots / Expose
85
+
86
+ - 无。
87
+
88
+ ## 图标覆盖范围
89
+
90
+ | 图标 | 覆盖扩展名 |
91
+ | ---- | ---------- |
92
+ | 文档类 | `pptx` / `docx` / `pdf` / `txt` / `rst` / `md` / `markdown` / `tex` |
93
+ | 表格类 | `xlsx` / `xlsm` / `xls` / `csv` / `tsv` |
94
+ | 前端 | `html` / `htm` / `css` / `scss` / `less` / `js` / `mjs` / `cjs` / `ts` / `tsx` / `jsx` / `vue` / `xml` |
95
+ | 后端 / 系统 | `py` / `go` / `rs` / `rb` / `java` / `kt` / `swift` / `c` / `h` / `cpp` / `hpp` / `cs` / `php` / `lua` / `r` / `scala` / `dart` / `sql` / `sh` / `bash` / `zsh` / `ps1` |
96
+ | 配置 | `json` / `jsonc` / `yaml` / `yml` / `toml` / `ini` / `cfg` / `conf` / `env` / `editorconfig` / `Makefile` |
97
+ | 工具链 | `Dockerfile` / `dockerignore` / `gitignore` |
98
+ | 图片 | `png` / `jpg` / `jpeg` / `svg` |
99
+ | 兜底 | 以上之外的所有类型 |
100
+
101
+ 新增类型时在 `src/icons/file-icons.ts` 的 `FILE_ICON_GROUPS` 里补一行即可;对应 svg 需先放进 `src/svgs/` 并按需 `?raw` 引入(只引实际用到的,避免把整个图标库打进产物)。
102
+
103
+ ## 使用建议
104
+
105
+ - 图标颜色由 svg 自带,不继承 `currentColor`,不要试图用 `color` 覆盖
106
+ - 需要与文件名同行展示时,给父容器设 `display: flex` + `gap`,组件本身已是 `inline-flex` 且 `flex-shrink: 0`
107
+
108
+ ## 关联组件
109
+
110
+ - [FileArtifactPanel](../message/file-artifact-panel.md) — 文件产物列表与预览头使用该组件。
111
+ - [AssistantMessage](../message/assistant-message.md) — 消息内文件产物卡片的图标来源。
@@ -124,6 +124,7 @@
124
124
  - `renderMode === RenderMode.Share`(分享预览模式)
125
125
  - 消息组的 `pause` 为 `true`(来源于 `message.property?.extra?.pause`)
126
126
  - 多选模式(`enableSelection`)开启且消息组不是 Loading 类型
127
+ - AI 消息组的时间通过 `MessageTools` 的 `#append` 插槽渲染在工具图标右侧,取值为组内**最后一条**带 `createdAt` 的消息(即本轮回答完成时间);组内 `reasoning` / `activity` 等子消息不单独展示时间,全组都没有 `createdAt` 时不展示
127
128
  - `renderMode === RenderMode.Test` 时,工具栏会过滤掉「分享」按钮,其余正常
128
129
  - `renderMode === RenderMode.Share` 时,`message-group-messages` 自动添加 `message-group-enabled-selection` 类名(与 `enableSelection: true` 一致的多选视觉效果)
129
130
  - Loading 消息组的 `type` 是 `MessageRole.Loading`,不显示工具栏和多选 Checkbox
@@ -641,6 +642,7 @@ enum MessageToolsStatus {
641
642
  ## 关联组件
642
643
 
643
644
  - [MessageRender](/components/message/message-render) — 按组渲染每条消息时委托使用
645
+ - [MessageTime](/components/feedback/message-time) — AI 消息组工具栏右侧的时间
644
646
  - [InterruptMessage 中断消息](/components/agent/interrupt-message) — `role: 'interrupt'` 的渲染与 `onInterruptResume` 透传
645
647
  - [ChatInput](/components/input/chat-input) — 常与输入区组合构成完整对话界面
646
648
  - [LoadingMessage](/components/message/loading-message) — 末尾为用户消息时自动追加加载组
@@ -0,0 +1,180 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ 按 createdAt 展示消息时间,四档格式:今天 `12:00`、昨天 `昨天 12:00`、今年内更早 `3-12 12:00`、非今年 `2025-3-12 12:00`; 时区取 props.timezone,未传时回退 injectGlobalConfig().timezone(由 ChatContainer 的 timezone prop 注入),都没有则用浏览器时区; 无值或非法时间不渲染任何 DOM。通常通过 MessageTools 的 prepend / append 插槽使用。 源码位置:src/components/message-tools/message-time/message-time.vue。
5
+
6
+ ### 关联组件
7
+ - **message-tools** — 通过 prepend / append 插槽嵌入工具栏
8
+ - **user-message** — 用户消息在工具栏左侧展示时间
9
+ - **message-container** — AI 消息组在工具栏右侧展示本轮回答时间
10
+
11
+ ---
12
+ <!-- FULL DOC -->
13
+
14
+ # MessageTime 消息时间
15
+
16
+ ## 源码事实
17
+
18
+ - **源码位置**:`src/components/message-tools/message-time/message-time.vue`
19
+ - **格式化工具**:`src/components/message-tools/message-time/format-message-time.ts`
20
+ - **能力域**:工具与反馈
21
+ - **能力说明**:按「今天 / 昨天 / 今年内 / 跨年」四档格式展示消息创建时间。
22
+
23
+ > **能力域**:工具与反馈
24
+
25
+ 展示单条消息(或一组 AI 回答)的创建时间。组件本身只负责格式化与渲染一段文本,**位置由使用方决定**——项目内通过 `MessageTools` 的 `prepend` / `append` 插槽嵌入工具栏。
26
+
27
+ ## 格式规则
28
+
29
+ 时间按与「今天」的日历日差值分四档,同一档内时分固定 `HH:mm`(24 小时制,补零),月日**不补零**:
30
+
31
+ | 档位 | 判定条件 | 输出示例 |
32
+ | ------------ | ---------------------- | --------------- |
33
+ | 今天 | 与今天同一日历日 | `12:00` |
34
+ | 昨天 | 与今天相差 1 个日历日 | `昨天 12:00` |
35
+ | 今年内更早 | 相差 ≥ 2 天且年份相同 | `3-12 12:00` |
36
+ | 非今年 | 年份不同 | `2025-3-12 12:00` |
37
+
38
+ - 分档与展示取**同一时区**的日历日:既避免「昨天 23:59」与「今天 00:01」因毫秒差不足一天被判成同一天,也避免按浏览器时区分档、按配置时区显示时分导致的错位
39
+ - `昨天` 走 `t('昨天')` 国际化,英文环境输出 `Yesterday 12:00`
40
+
41
+ ## 基础用法
42
+
43
+ `createdAt` 接受 ISO 字符串或毫秒时间戳:
44
+
45
+ ```vue
46
+ <template>
47
+ <MessageTime :created-at="message.createdAt" />
48
+ </template>
49
+
50
+ <script setup lang="ts">
51
+ import { MessageTime } from '@blueking/chat-x';
52
+
53
+ const message = {
54
+ id: '1',
55
+ messageId: '1',
56
+ role: 'user',
57
+ content: '你好',
58
+ status: 'completed',
59
+ createdAt: '2026-08-17T04:00:00.000Z',
60
+ };
61
+ </script>
62
+ ```
63
+
64
+ > **无值不渲染**:`createdAt` 为空、为空字符串或无法解析成合法时间时,组件不渲染任何 DOM(`v-if`),使用方无需额外判空。
65
+
66
+ ## 时区配置
67
+
68
+ 时区按以下优先级取值,均未配置时使用**浏览器时区**:
69
+
70
+ ```
71
+ props.timezone
72
+ └─ 未传 → injectGlobalConfig()?.timezone(由 ChatContainer 的 timezone prop 注入)
73
+ └─ 未配置 → 浏览器时区
74
+ ```
75
+
76
+ ```vue
77
+ <template>
78
+ <!-- 整个会话统一按北京时间展示 -->
79
+ <ChatContainer
80
+ :messages="messages"
81
+ timezone="Asia/Shanghai"
82
+ />
83
+ </template>
84
+ ```
85
+
86
+ ```vue
87
+ <template>
88
+ <!-- 单个实例覆盖全局配置 -->
89
+ <MessageTime
90
+ :created-at="message.createdAt"
91
+ timezone="UTC"
92
+ />
93
+ </template>
94
+ ```
95
+
96
+ - 取值为 [IANA 时区名](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)(如 `Asia/Shanghai`、`UTC`、`America/New_York`)
97
+ - 传入非法时区名时回退到浏览器时区,不会导致渲染失败
98
+ - 同一时区的 `Intl.DateTimeFormat` 实例内部有缓存,长会话中不会为每条消息重复构造
99
+
100
+ ## 在消息工具栏中的使用
101
+
102
+ `MessageTools` 提供 `prepend`(工具图标左侧)与 `append`(工具图标右侧)两个插槽,项目内的时间位置即由此决定:
103
+
104
+ | 场景 | 插槽 | 时间取值 |
105
+ | ------------ | --------- | -------------------------------------------- |
106
+ | 用户消息 | `prepend` | 该条消息的 `createdAt` |
107
+ | AI 消息组 | `append` | 组内**最后一条**带 `createdAt` 的消息,即本轮回答完成时间 |
108
+
109
+ ```vue
110
+ <template>
111
+ <MessageTools :on-action="handleAction">
112
+ <template #append>
113
+ <MessageTime :created-at="createdAt" />
114
+ </template>
115
+ </MessageTools>
116
+ </template>
117
+
118
+ <script setup lang="ts">
119
+ import { MessageTime, MessageTools } from '@blueking/chat-x';
120
+ </script>
121
+ ```
122
+
123
+ > 组内 `reasoning` / `activity` 等子消息不单独展示时间,一个 AI 回答组只显示一次。
124
+
125
+ ## API
126
+
127
+ ### Props
128
+
129
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
130
+ | --------- | ------------------ | ---- | ------ | -------------------------------------------------------------------- |
131
+ | createdAt | `number \| string` | 否 | — | 消息创建时间,ISO 字符串或毫秒时间戳;无值或非法时不渲染 |
132
+ | timezone | `string` | 否 | — | IANA 时区名;优先于全局配置,两者都未配置时按浏览器时区展示 |
133
+
134
+ ### Events / Slots / Expose
135
+
136
+ 无。
137
+
138
+ ### 全局配置依赖
139
+
140
+ 通过 `injectGlobalConfig()` 读取 `timezone`。祖先需已调用 `useGlobalConfig()`(通常由 `ChatContainer` 的 `timezone` prop 注册);无 Provider 时按浏览器时区展示。
141
+
142
+ ## 类型定义
143
+
144
+ ```typescript
145
+ export type MessageTimeProps = {
146
+ createdAt?: number | string;
147
+ timezone?: string;
148
+ };
149
+
150
+ // 独立可用的格式化函数(组件内部使用,未从包入口导出)
151
+ declare const formatMessageTime: (createdAt?: number | string, timezone?: string) => string;
152
+ ```
153
+
154
+ ## 样式说明
155
+
156
+ ```scss
157
+ .ai-message-time {
158
+ flex: none;
159
+ font-size: var(--ai-font-size, 12px);
160
+ line-height: 16px;
161
+ color: $color-text-secondary;
162
+ white-space: nowrap;
163
+ }
164
+ ```
165
+
166
+ - 字号跟随 `--ai-font-size`(`ChatContainer` 的 `size` 档位),颜色使用次要文本语义色
167
+ - `flex: none` + `nowrap` 保证在工具栏 flex 布局中不被压缩换行
168
+
169
+ ## 注意事项
170
+
171
+ 1. **时间来源**:`createdAt` 由消息层(`chat-helper`)写入 `BaseMessage`,组件不参与取数
172
+ 2. **不做相对时间**:不提供「几分钟前」这类相对描述,四档格式固定
173
+ 3. **空值语义**:不渲染而非渲染占位,工具栏侧的 `prepend` / `append` 包裹容器会因 `:empty` 收起,不留多余间距
174
+
175
+ ## 关联组件
176
+
177
+ - [MessageTools](/components/feedback/message-tools) — 通过 `prepend` / `append` 插槽嵌入
178
+ - [UserMessage](/components/message/user-message) — 用户消息时间位置
179
+ - [MessageContainer](/components/setup/message-container) — AI 消息组时间位置
180
+ - [useGlobalConfig](/composables/use-global-config) — `timezone` 全局配置
@@ -1,12 +1,13 @@
1
1
  <!-- AI SUMMARY -->
2
2
  ## 快速了解
3
3
 
4
- 消息悬浮工具栏,组合复制、删除、反馈等工具按钮。 源码位置:src/components/message-tools/message-tools.vue。
4
+ 消息悬浮工具栏,组合复制、删除、反馈等工具按钮;提供 prepend / append 两端插槽承载消息时间等附加内容。 源码位置:src/components/message-tools/message-tools.vue。
5
5
 
6
6
  ### 关联组件
7
7
  - **tool-btn** — 普通工具项由 ToolBtn 渲染
8
8
  - **user-feedback** — like/unlike 时弹出反馈表单
9
9
  - **delete-tool** — id 为 delete 时替换为带确认的删除按钮
10
+ - **message-time** — 通过 prepend / append 插槽嵌入消息时间
10
11
 
11
12
  ---
12
13
  <!-- FULL DOC -->
@@ -20,22 +21,22 @@
20
21
 
21
22
  > **能力域**:工具与反馈
22
23
 
23
- AI 消息的操作工具栏组件,由**左侧消息工具区**和**右侧更新工具区**两部分组成,中间以分隔线分隔。仅 `like` / `unlike` 按钮会弹出反馈表单(`UserFeedback`),其余按钮直接触发 `onAction`。
24
+ AI 消息的操作工具栏组件,由**左侧消息工具区**和**右侧更新工具区**两部分组成,中间以分隔线分隔;两端另有 `prepend` / `append` 插槽用于挂载消息时间等附加内容。仅 `like` / `unlike` 按钮会弹出反馈表单(`UserFeedback`),其余按钮直接触发 `onAction`。
24
25
 
25
26
  ## 组件结构
26
27
 
27
28
  ```
28
- ┌─────────────────────────────────────────────────────┐
29
- │ .ai-message-tools-container
30
- │ ┌─────────────────────┐ │ ┌───────────────────┐
31
- │ │ messageTools │ │ │ updateTools │ │
32
- │ │ copy cite rebuild… │ │ │ like unlike del │ │
33
- │ └─────────────────────┘ │ └───────────────────┘
34
- 左侧区域 分隔线 右侧区域 │
35
- └─────────────────────────────────────────────────────┘
29
+ ┌───────────────────────────────────────────────────────────────────────┐
30
+ │ .ai-message-tools-container(display: flex,gap: 4px)
31
+ ┌────────┐ ┌─────────────────────┐ │ ┌───────────────┐ ┌───────┐
32
+ │#prependmessageTools │ │ │ updateTools │#append│
33
+ │ │ copy cite rebuild… │ │ │ like unlike │ │
34
+ └────────┘ └─────────────────────┘ │ └───────────────┘ └───────┘
35
+ 插槽区 左侧区域 分隔线 右侧区域 插槽区
36
+ └───────────────────────────────────────────────────────────────────────┘
36
37
  ```
37
38
 
38
- - 分隔线(`.ai-divider`)仅在 `updateTools` 非空时显示
39
+ - 分隔线(`.ai-divider`)与**右侧更新工具区**均仅在 `updateTools` 非空时渲染
39
40
  - `updateTools` 中 `id` 为 `like` / `unlike` 的按钮被 `Tippy` 弹窗包裹,点击后展示 `UserFeedback` 反馈表单
40
41
  - `updateTools` 中 `id` 为 `delete` 的按钮使用 `DeleteTool` 组件,点击后展示**确认删除弹窗**(含"删除"/"取消"按钮),确认后触发 `onAction`
41
42
  - `updateTools` 中其他按钮直接触发 `onAction`,不弹表单
@@ -224,6 +225,32 @@ const CONST_UPDATE_TOOLS = [
224
225
 
225
226
  **渲染效果**
226
227
 
228
+ ## 两端插槽(prepend / append)
229
+
230
+ `prepend`(工具图标左侧)与 `append`(工具图标右侧)用于在工具栏两端挂载附加内容,项目内用于放置 [MessageTime 消息时间](/components/feedback/message-time):
231
+
232
+ | 场景 | 使用插槽 | 效果 |
233
+ | --------- | --------- | --------------------------------------- |
234
+ | 用户消息 | `prepend` | 时间显示在工具图标左侧 |
235
+ | AI 消息组 | `append` | 时间显示在工具图标右侧 |
236
+
237
+ ```vue
238
+ <template>
239
+ <MessageTools :on-action="handleAction">
240
+ <template #append>
241
+ <MessageTime :created-at="createdAt" />
242
+ </template>
243
+ </MessageTools>
244
+ </template>
245
+
246
+ <script setup lang="ts">
247
+ import { MessageTime, MessageTools } from '@blueking/chat-x';
248
+ </script>
249
+ ```
250
+
251
+ - 插槽包裹容器(`.ai-message-tools-prepend` / `.ai-message-tools-append`)仅在对应插槽传入时渲染
252
+ - 插槽内容为空时(如消息无 `createdAt`,`MessageTime` 不渲染任何 DOM),包裹容器命中 `:empty` 被收起,不会留下多余间距
253
+
227
254
  ## 工具栏状态控制
228
255
 
229
256
  `messageToolsStatus` 控制工具栏整体状态:
@@ -357,6 +384,13 @@ const CONST_UPDATE_TOOLS = [
357
384
  | -------- | ------------------------------------------------------------- | ------------------------------------------ |
358
385
  | feedback | `(tool: IToolBtn, reasonList: string[], otherReason: string)` | 用户在反馈表单点击"提交"后触发(not 取消) |
359
386
 
387
+ ### Slots
388
+
389
+ | 插槽名 | 作用域参数 | 说明 |
390
+ | ------- | ---------- | ------------------------------------------------ |
391
+ | prepend | — | 工具图标左侧的附加内容,如用户消息的时间 |
392
+ | append | — | 工具图标右侧的附加内容,如 AI 消息组的时间 |
393
+
360
394
  ## 类型定义
361
395
 
362
396
  ```typescript
@@ -382,4 +416,5 @@ enum MessageToolsStatus {
382
416
 
383
417
  - [ToolBtn](/components/feedback/tool-btn) — 单项工具按钮
384
418
  - [UserFeedback](/components/feedback/user-feedback) — 点赞/踩反馈面板
385
- - [DeleteTool](/components/feedback/delete-tool) — 删除二次确认
419
+ - [DeleteTool](/components/feedback/delete-tool) — 删除二次确认
420
+ - [MessageTime](/components/feedback/message-time) — 两端插槽内的消息时间
@@ -54,6 +54,10 @@ interface BaseMessage<T extends MessageType, C = string> {
54
54
  // 消息名称(可选)
55
55
  name?: string;
56
56
 
57
+ // 可选:消息创建时间,ISO 字符串或毫秒时间戳,由消息层(chat-helper)写入
58
+ // 用于 MessageTime 展示;缺省时不展示时间
59
+ createdAt?: number | string;
60
+
57
61
  // 消息属性(可选)
58
62
  property?: {
59
63
  extra?: {