@blueking/chat-x 0.0.49-beta.9 → 0.0.51-beta.1

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 (47) hide show
  1. package/dist/ag-ui/types/contents.d.ts +2 -0
  2. package/dist/ag-ui/types/messages.d.ts +3 -0
  3. package/dist/common/constants.d.ts +1 -1
  4. package/dist/components/ai-buttons/file-upload-btn/file-upload-btn.vue.d.ts +0 -2
  5. package/dist/components/chat-content/file-content/file-content.vue.d.ts +5 -2
  6. package/dist/components/chat-content/file-content/upload-file-item.vue.d.ts +12 -0
  7. package/dist/components/chat-content/file-content/upload-image-item.vue.d.ts +21 -0
  8. package/dist/components/chat-input/ai-slash-editor/ai-slash-editor.vue.d.ts +1 -1
  9. package/dist/components/chat-input/build-default-placeholder.d.ts +7 -0
  10. package/dist/components/chat-input/chat-input.vue.d.ts +1 -1
  11. package/dist/components/chat-message/message-container/message-container.vue.d.ts +1 -0
  12. package/dist/components/chat-message/message-render/message-render.vue.d.ts +2 -0
  13. package/dist/components/chat-message/user-message/user-message.vue.d.ts +2 -1
  14. package/dist/composables/use-custom-tab.d.ts +5 -3
  15. package/dist/composables/use-message-group.d.ts +78 -72
  16. package/dist/icons/execution.d.ts +6 -0
  17. package/dist/icons/tools.d.ts +3 -0
  18. package/dist/index.css +1 -1
  19. package/dist/index.js +3054 -2854
  20. package/dist/index.js.map +1 -1
  21. package/dist/lang/lang.d.ts +7 -7
  22. package/dist/mcp/generated/docs/ai-slash-input.md +2 -0
  23. package/dist/mcp/generated/docs/assistant-message.md +9 -7
  24. package/dist/mcp/generated/docs/chat-container.md +35 -31
  25. package/dist/mcp/generated/docs/chat-input.md +18 -12
  26. package/dist/mcp/generated/docs/cite-content.md +3 -3
  27. package/dist/mcp/generated/docs/desc-panel.md +32 -10
  28. package/dist/mcp/generated/docs/execution-summary.md +3 -3
  29. package/dist/mcp/generated/docs/file-artifact-panel.md +6 -4
  30. package/dist/mcp/generated/docs/file-content.md +89 -73
  31. package/dist/mcp/generated/docs/file-upload-btn.md +16 -18
  32. package/dist/mcp/generated/docs/message-container.md +1 -0
  33. package/dist/mcp/generated/docs/message-render.md +2 -1
  34. package/dist/mcp/generated/docs/messages.md +5 -0
  35. package/dist/mcp/generated/docs/toolcall-render.md +82 -43
  36. package/dist/mcp/generated/docs/use-artifact-preview.md +19 -17
  37. package/dist/mcp/generated/docs/use-custom-tab.md +12 -8
  38. package/dist/mcp/generated/docs/user-message.md +3 -0
  39. package/dist/mcp/generated/docs/user-question-card.md +2 -0
  40. package/dist/mcp/generated/index.json +7 -3
  41. package/dist/mcp/index.js +0 -0
  42. package/dist/types/input.d.ts +6 -0
  43. package/dist/utils/file.d.ts +7 -1
  44. package/dist/utils/index.d.ts +2 -0
  45. package/dist/utils/merge-tools-by-id.d.ts +6 -0
  46. package/dist/utils/upload-file.d.ts +35 -0
  47. package/package.json +21 -20
@@ -19,26 +19,34 @@
19
19
 
20
20
  > **能力域**:媒体文件
21
21
 
22
- 文件列表展示组件,支持图片缩略图预览、点击图片全屏预览(`ImagePreview`)、文档卡片展示(文件名/扩展名/文件大小)、图片加载失败占位和删除操作。
22
+ 文件列表展示组件,支持图片缩略图预览、点击图片全屏预览(`ImagePreview`)、文件卡片展示(类型图标 / 文件名 / 文件大小)、图片加载失败占位和删除操作。
23
+
24
+ 内部由两个子组件承载单项渲染:
25
+
26
+ | 子组件 | 源码位置 | 职责 |
27
+ | ------------------ | ------------------------------------------------------------------- | --------------------------------------------- |
28
+ | `UploadImageItem` | `src/components/chat-content/file-content/upload-image-item.vue` | 图片缩略图、加载失败占位、hover 删除徽标 |
29
+ | `UploadFileItem` | `src/components/chat-content/file-content/upload-file-item.vue` | 180px 文件卡片(`FileIcon` + 文件名 + 大小) |
23
30
 
24
31
  ## 渲染决策逻辑
25
32
 
26
- 每个文件按以下优先级决定渲染方式:
33
+ 先分组、再渲染。设计稿要求**图片始终排在文件前方**,两类各自成行:
27
34
 
28
35
  ```
29
- file.url 存在?
30
- ├── 是 → 图片模式(用 file.url 作为 <img src>,点击可全屏预览)
31
- │ 图片加载失败 → 错误占位(粉色背景 + 红色边框 + 灰色图标)
32
- └── 否 → 检查 mimeType 或 file.file?.type
33
- ├── 以 'image/' 开头 → 图片模式(用 getFilePreviewUrl(file.file) 作为 src,点击可全屏预览)
34
- └── 其他 → 文档卡片模式(图标 + 文件名 + 扩展名 + 大小)
36
+ splitUploadFiles(files) // 单次遍历
37
+ ├── 图片组(.ai-files-content-row.is-images)
38
+ │ 判定依据:mimeType 或 file.file?.type 以 'image/' 开头
39
+ │ src:file.url 优先,否则用本地 File 的 blob URL(按 key 缓存,移除 / 卸载时 revoke)
40
+ │ 加载失败 → 错误占位(粉色背景 + 红色边框 + 灰色图标),且不进入预览列表
41
+ └── 文件组(.ai-files-content-row.is-files)
42
+ 文件卡片:类型图标(FileIcon,按文件名解析扩展名)+ 文件名 + 文件大小
35
43
  ```
36
44
 
37
- > **注意**:`file.url` 存在时**无论文件 MIME 类型是什么**都会走图片模式。若要将 PDF 等非图片文件显示为文档卡片,确保不设置 `url` 字段(或设为 `undefined`)。
45
+ > **是否为图片只看 MIME,不看 `url`。** 解除上传类型限制后,任意文件上传成功都会拿到 `url`,若按 `url` 判断会把 PDF / DOC 渲染成破图。因此 `url` 只决定 `<img src>` 从哪里来,不参与图片判定。
38
46
 
39
- ## 基础用法(文档文件)
47
+ ## 基础用法(文件卡片)
40
48
 
41
- 无 `url` 字段、MIME 类型非 `image/*` 的文件,渲染为文档卡片(文档图标 + 文件名 + 扩展名 + 大小):
49
+ MIME 类型非 `image/*` 的文件,渲染为固定宽 180px 的文件卡片(类型图标 + 文件名 + 大小)。类型图标与文件产物侧栏共用 `FileIcon` 的扩展名映射,`pdf` / `py` / `docx` 等各有专属图标,未登记的扩展名回退兜底图标:
42
50
 
43
51
  ```vue
44
52
  <template>
@@ -64,11 +72,11 @@ file.url 存在?
64
72
  </script>
65
73
  ```
66
74
 
67
- **渲染效果**(悬停文件卡片,右上角出现删除按钮)
75
+ **渲染效果**(悬停文件卡片,底色加深并在右上角出现删除徽标)
68
76
 
69
77
  ## 图片文件预览
70
78
 
71
- 设置了 `url` 字段时,渲染为 48×48 的图片缩略图(`cursor: zoom-in`)。点击图片可打开全屏预览(内部集成 `ImagePreview` 组件),支持缩放、旋转、下载等操作:
79
+ MIME 为 `image/*` 时渲染为图片缩略图(`cursor: zoom-in`)。点击图片可打开全屏预览(内部集成 `ImagePreview` 组件),支持缩放、旋转、下载等操作:
72
80
 
73
81
  ```vue
74
82
  <script setup lang="ts">
@@ -77,9 +85,9 @@ file.url 存在?
77
85
 
78
86
  const imageFiles = ref<Partial<UploadFile>[]>([
79
87
  {
80
- url: 'https://example.com/cat.jpg', // 有 url → 图片模式
88
+ url: 'https://example.com/cat.jpg',
81
89
  filename: 'cat.jpg',
82
- mimeType: 'image/jpeg',
90
+ mimeType: 'image/jpeg', // 图片判定依据
83
91
  file: new File([''], 'cat.jpg', { type: 'image/jpeg' }),
84
92
  },
85
93
  {
@@ -120,9 +128,9 @@ file.url 存在?
120
128
 
121
129
  `<img>` 触发 `onerror` 时,切换为粉色背景 + 红色边框的错误占位:
122
130
 
123
- ## 混合文件(图片 + 文档)
131
+ ## 混合文件(图片 + 文件)
124
132
 
125
- 同一列表中可同时包含图片和文档文件,横向 flex 布局、换行排列:
133
+ 同一列表可同时包含图片和其他文件,组件内部自动把图片排在前一行、文件排在后一行,各行内部横向排列并按需换行:
126
134
 
127
135
  ```vue
128
136
  <script setup lang="ts">
@@ -148,19 +156,20 @@ file.url 存在?
148
156
 
149
157
  ## 仅有 filename(无 File 对象)
150
158
 
151
- 从服务端恢复的历史文件没有 `File` 对象时,仍可渲染文档卡片,但**文件大小不显示**,扩展名从 `filename` 或 `mimeType` 推断:
159
+ 从服务端恢复的历史文件没有 `File` 对象时仍可渲染文件卡片,类型图标从 `filename` 推断。文件大小取 `size` 字段;未下发 `size` 时大小节点不渲染:
152
160
 
153
161
  ```vue
154
162
  <script setup lang="ts">
155
163
  const remoteFiles = [
156
- // 无 file 对象,文件大小显示为空
164
+ // 无 file 对象,未带 size,文件大小不渲染
157
165
  { filename: 'server-report.pdf', mimeType: 'application/pdf' },
158
- { filename: 'config.json', mimeType: 'application/json' },
166
+ // 带 size 时正常显示「1.00M」
167
+ { filename: 'config.json', mimeType: 'application/json', size: 1024 * 1024 },
159
168
  ];
160
169
  </script>
161
170
  ```
162
171
 
163
- **渲染效果**(大小区域为空)
172
+ **渲染效果**(未带 `size` 的项无大小行)
164
173
 
165
174
  ## 在 ChatInput 中使用
166
175
 
@@ -199,10 +208,20 @@ file.url 存在?
199
208
 
200
209
  ### Props
201
210
 
202
- | 属性名 | 类型 | 默认值 | 必填 | 说明 |
203
- | -------- | ----------------------- | ------- | ---- | ------------------------------- |
204
- | files | `Partial<UploadFile>[]` | - | ✅ | 文件列表 |
205
- | readonly | `boolean` | `false` | - | 只读模式,`true` 时隐藏删除按钮 |
211
+ | 属性名 | 类型 | 默认值 | 必填 | 说明 |
212
+ | -------- | ----------------------- | --------- | ---- | ------------------------------------------- |
213
+ | files | `Partial<UploadFile>[]` | - | ✅ | 文件列表 |
214
+ | readonly | `boolean` | `false` | - | 只读模式,`true` 时隐藏删除徽标与 hover 态 |
215
+ | variant | `'input' \| 'message'` | `'input'` | - | 展示形态,见下方「展示形态」 |
216
+
217
+ ### 展示形态(variant)
218
+
219
+ 只影响图片缩略图的圆角描边与整体对齐,文件卡片两种形态一致。图片尺寸规则两种形态相同:**定高 48px,宽度按原图比例,并在 48~120px 之间夹取**(竖图不至于过窄,长图不会撑破容器),超出部分由 `object-fit: cover` 裁切。
220
+
221
+ | variant | 使用场景 | 图片圆角 / 描边 | 对齐 |
222
+ | ----------- | ------------------ | --------------- | ------ |
223
+ | `'input'` | 输入框内待发送态 | 8px / `#f0f1f5` | 左对齐 |
224
+ | `'message'` | 用户消息内已发送态 | 4px / `#eaebf0` | 右对齐 |
206
225
 
207
226
  ### Events
208
227
 
@@ -214,19 +233,21 @@ file.url 存在?
214
233
 
215
234
  ### 图片模式
216
235
 
217
- | 条件 | 图片 src | 点击行为 |
218
- | ---------------------------------- | -------------------------------- | -------------- |
219
- | `file.url` 有值(优先) | `file.url` | 打开全屏预览 |
220
- | MIME 以 `image/` 开头(无 url 时) | `URL.createObjectURL(file.file)` | 打开全屏预览 |
221
- | 图片加载失败 | 错误占位 | 不进入预览列表 |
236
+ | 条件 | 图片 src | 点击行为 |
237
+ | ------------------- | ------------------------------------------------- | -------------- |
238
+ | `file.url` 有值 | `file.url` | 打开全屏预览 |
239
+ | 无 url、有 `File` | `URL.createObjectURL(file.file)`(按 key 缓存) | 打开全屏预览 |
240
+ | 图片加载失败 | 错误占位 | 不进入预览列表 |
241
+
242
+ blob URL 按附件 key 缓存,同一文件重复渲染不会重复创建;文件被移除或组件卸载时统一 `revokeObjectURL`。
222
243
 
223
- ### 文档卡片模式
244
+ ### 文件卡片模式
224
245
 
225
- | 字段 | 取值优先级 |
226
- | -------- | ------------------------------------------------------------------------------------------------------------ |
227
- | 文件名 | `file.filename` → `file.file?.name` |
228
- | 扩展名 | 有 `file.file`:取文件名最后一段 `.xxx` 或 MIME 后缀<br>无 `file.file`:取 `filename` 后缀 → `mimeType` 后缀 |
229
- | 文件大小 | 仅当有 `file.file`(`File` 对象)时显示,否则为空 |
246
+ | 字段 | 取值优先级 |
247
+ | -------- | --------------------------------------------- |
248
+ | 文件名 | `file.filename` → `file.file?.name` |
249
+ | 类型图标 | 由文件名解析扩展名,交给 `FileIcon` 映射 |
250
+ | 文件大小 | `file.file?.size` → `file.size`;都没有则不渲染 |
230
251
 
231
252
  ## 类型定义
232
253
 
@@ -249,48 +270,43 @@ type UploadFile = BinaryInputContent & {
249
270
  // 二进制内容基础类型
250
271
  interface BinaryInputContent {
251
272
  type: 'binary';
252
- url?: string; // 文件访问地址,存在时强制走图片模式
253
- filename?: string; // 文件名(用于文档卡片)
254
- mimeType?: string; // MIME 类型(用于图片判断和扩展名推断)
273
+ url?: string; // 文件访问地址,只决定 <img src> 来源,不参与图片判定
274
+ filename?: string; // 文件名(文件卡片展示 + 类型图标解析)
275
+ mimeType?: string; // MIME 类型(图片判定依据)
276
+ size?: number; // 文件字节数,发送时由原始 File 写入
255
277
  }
256
278
  ```
257
279
 
258
- ## 工具函数(内部使用)
259
-
260
- ```typescript
261
- // 判断是否走图片模式
262
- // ⚠️ file.url 存在时直接返回 true,不判断 MIME 类型
263
- const isImage = (file: Partial<UploadFile>): boolean => {
264
- if (file.url) return true;
265
- return isImageFile(file.mimeType || file.file?.type);
266
- };
267
-
268
- // 判断 MIME 类型是否为图片
269
- const isImageFile = (mimeType?: string): boolean => {
270
- if (!mimeType) return false;
271
- return mimeType.startsWith('image/');
272
- };
273
-
274
- // 获取 File 对象的临时预览 URL(无 url 时的图片模式备选)
275
- const getFilePreviewUrl = (file?: File): string => {
276
- if (!file) return '';
277
- return URL.createObjectURL(file);
278
- };
280
+ ## 工具函数(`src/utils/upload-file.ts`)
279
281
 
280
- // 获取文件扩展名
281
- const getFileExtension = (file?: File): string => {
282
- if (!file) return '';
283
- return file.name.split('.').pop() || file.type?.split('/').pop() || '';
284
- };
282
+ 组件内的取值与分组逻辑都收敛在这里,`ChatInput`、`UserMessage` 共用同一套判定:
285
283
 
286
- // 格式化文件大小(需要 File 对象)
287
- const formatFileSize = (file?: File): string => {
288
- if (!file) return '';
289
- const size = file.size;
290
- const units = ['B', 'KB', 'M', 'GB'];
291
- const index = Math.floor(Math.log2(size || 1) / 10);
292
- return `${(size / Math.pow(1024, index)).toFixed(2)} ${units[index]}`;
293
- };
284
+ ```typescript
285
+ import {
286
+ getFileIdentity,
287
+ getUploadFileKey,
288
+ getUploadFileName,
289
+ getUploadFileSize,
290
+ isUploadImageFile,
291
+ splitUploadFiles,
292
+ } from '@blueking/chat-x';
293
+
294
+ // File 身份:文件名 + 大小 + 修改时间,用于去重与列表 key
295
+ getFileIdentity(file); // 'report.pdf_2048_1700000000000'
296
+
297
+ // 附件稳定 key:待发送态用 File 身份(上传成功回填 url 后不变),已发送态退回 url / 文件名
298
+ getUploadFileKey({ file }); // 'report.pdf_2048_1700000000000'
299
+ getUploadFileKey({ url: 'https://x/a.pdf' }); // 'https://x/a.pdf'
300
+
301
+ // 是否按图片渲染:只看 MIME,有 url 也不例外
302
+ isUploadImageFile({ mimeType: 'application/pdf', url: 'https://x/a.pdf' }); // false
303
+
304
+ // 文件名 / 字节数取值优先级
305
+ getUploadFileName({ filename: 'remote.pdf', file }); // 'remote.pdf'
306
+ getUploadFileSize({ size: 2048 }); // 2048
307
+
308
+ // 单次遍历分出图片组与其他文件组(图片在前)
309
+ splitUploadFiles(files); // { imageFiles, otherFiles }
294
310
  ```
295
311
 
296
312
  ## 使用场景
@@ -19,16 +19,18 @@
19
19
 
20
20
  > **能力域**:输入交互
21
21
 
22
- 聊天输入框内置的文件上传触发按钮,点击后弹出系统文件选择框。内部包含隐藏的 `<input type="file">` 与可见的图标按钮;在按钮层对**单文件**做大小与空文件过滤,**已选文件个数上限**由上层(如 `ChatInput`)统一校验并提示,避免按钮与输入区各弹一条错误提示。
22
+ 聊天输入框内置的文件上传触发按钮,点击后弹出系统文件选择框。内部包含隐藏的 `<input type="file">` 与可见的图标按钮;**不限制文件类型**,在按钮层只对**单文件**做大小与空文件过滤,**已选文件个数上限**由上层(如 `ChatInput`)统一校验并提示,避免按钮与输入区各弹一条错误提示。
23
23
 
24
24
  ## 组件结构
25
25
 
26
26
  ```
27
27
  .ai-file-upload-btn(display: flex,align-items: center)
28
28
  ├── input[type="file"](.file-upload-btn-input,display: none,multiple,:accept)
29
+ │ accept 缺省时不下发该属性,系统选择框不过滤任何类型
29
30
  │ 触发后走 handleFileInputChange → 校验 → emit upload → target.value = ''
30
- └── span.ai-shortcut-btn.file-upload-btn-icon(24×24px,color: #979ba5,hover: cursor: pointer)
31
- v-tippy: "上传图片, 最多支持上传 3 个, 最大支持 2.4MB"(theme: ai-chat-box,offset: [0, 16],可通过 tippyOptions 扩展)
31
+ └── span.ai-shortcut-btn.file-upload-btn-icon(热区 32×32px / 圆角 8px;图标字号跟随 --ai-icon-size-sm:small=16px、normal=18px;color: #979ba5;hover: #f0f1f5)
32
+ v-tippy: "上传文件,最多支持 {count} 个,单个最大 {size}MB"
33
+ ({count} / {size} 由 MAX_UPLOAD_FILES 与 MAX_UPLOAD_FILE_SIZE 运行时填充,theme: ai-chat-box,offset: [0, 16],可通过 tippyOptions 扩展)
32
34
  @click → fileInputRef.click()
33
35
  └── <slot> 默认:FileUploadIcon
34
36
  ```
@@ -61,7 +63,7 @@
61
63
 
62
64
  > `multiple` prop 声明存在但当前模板中 `input` 的 `multiple` 属性为**硬编码**(非 `:multiple="multiple"` 绑定),始终允许多选,该 prop 暂时无实际效果。
63
65
 
64
- > **`maxFiles` prop**:当前版本在按钮内**不参与截断或计数校验**,仅作类型与文档预留;列表最多几个文件由上层(如 `ChatInput` 的 `MAX_UPLOAD_FILES`)控制。详见 [ChatInput 文件上传](/components/input/chat-input#file-upload)。
66
+ > **文件类型不做限制**:组件不再默认 `accept="image/*"`,任意类型文件都可选择。若业务需要收窄,显式传入 `accept`。个数上限由上层(如 `ChatInput` 的 `MAX_UPLOAD_FILES`)控制,详见 [ChatInput 文件上传](/components/input/chat-input#file-upload)。
65
67
 
66
68
  ## 基础用法
67
69
 
@@ -84,11 +86,14 @@
84
86
 
85
87
  ## 限制文件类型
86
88
 
87
- 通过 `accept` 属性控制系统文件选择框的过滤条件,遵循 `<input type="file">` 的 `accept` 规范:
89
+ 默认**不限制**文件类型。需要收窄时通过 `accept` 属性控制系统文件选择框的过滤条件,遵循 `<input type="file">` 的 `accept` 规范:
88
90
 
89
91
  ```vue
90
92
  <template>
91
- <!-- 仅图片(默认) -->
93
+ <!-- 不限制类型(默认,不下发 accept) -->
94
+ <FileUploadBtn @upload="handleUpload" />
95
+
96
+ <!-- 仅图片 -->
92
97
  <FileUploadBtn
93
98
  accept="image/*"
94
99
  @upload="handleUpload"
@@ -99,12 +104,6 @@
99
104
  accept=".pdf,.doc,.docx,.xlsx,.pptx"
100
105
  @upload="handleUpload"
101
106
  />
102
-
103
- <!-- 不限制类型 -->
104
- <FileUploadBtn
105
- accept="*/*"
106
- @upload="handleUpload"
107
- />
108
107
  </template>
109
108
  ```
110
109
 
@@ -126,12 +125,11 @@
126
125
 
127
126
  ### Props
128
127
 
129
- | 属性名 | 类型 | 默认值 | 说明 |
130
- | ------------ | -------------- | ----------- | -------------------------------------------------------------------- |
131
- | accept | `string` | `'image/*'` | 文件选择框过滤类型,遵循 `<input accept>` 规范 |
132
- | maxFiles | `number` | `3` | 预留字段,**当前不在按钮内生效**;个数上限见 `ChatInput` 与全局常量 |
133
- | multiple | `boolean` | `true` | 声明属性(当前版本未实际绑定到 input,始终多选) |
134
- | tippyOptions | `AITippyProps` | — | 扩展 tooltip 配置,会与内置配置合并 |
128
+ | 属性名 | 类型 | 默认值 | 说明 |
129
+ | ------------ | -------------- | ------ | -------------------------------------------------------------------------- |
130
+ | accept | `string` | — | 文件选择框过滤类型,遵循 `<input accept>` 规范;缺省时不下发,不限制类型 |
131
+ | multiple | `boolean` | `true` | 声明属性(当前版本未实际绑定到 input,始终多选) |
132
+ | tippyOptions | `AITippyProps` | — | 扩展 tooltip 配置,会与内置配置合并 |
135
133
 
136
134
  ### Events
137
135
 
@@ -568,6 +568,7 @@ AI 回复状态为 `error` 时,消息以错误样式展示:
568
568
  | messageStatus | `MessageStatus` | — | 当前整体消息状态,控制底部「停止生成」按钮显示;`ChatContainer` 会结合末尾 Loading 占位推导 `fetching` 等再传入 |
569
569
  | messageTools | `IToolBtn[]` | — | AI 消息左侧工具(复制/引用等)的自定义配置;按 `id` 与内置 `CONST_MESSAGE_TOOLS` 合并(覆盖同 id、追加新 id、`hidden` 过滤),详见「自定义消息工具栏」 |
570
570
  | updateTools | `IToolBtn[]` | — | AI 消息右侧反馈工具(点赞/踩/删除等)的自定义配置;按 `id` 与内置 `CONST_UPDATE_TOOLS` 合并,规则同上 |
571
+ | userMessageTools | `IToolBtn[]` | — | 自定义用户消息工具组,透传给 `MessageRender` → `UserMessage`;按 id 与 `CONST_USER_MESSAGE_TOOLS` 合并,`{ id, hidden: true }` 可隐藏 |
571
572
  | messageToolsStatus | `MessageToolsStatus` | — | 工具栏状态,透传给 `MessageTools` 和 `MessageRender` |
572
573
  | messageToolsTippyOptions | `AITippyProps` | — | 透传给 `MessageTools` 和 `MessageRender`(进而透传给 `UserMessage` 的工具栏)的 Tippy 配置,用于自定义 tooltip 挂载点、位置等(如 `appendTo`、`placement`、`zIndex`) |
573
574
  | enableSelection | `boolean` | `false` | 是否启用多选模式 |
@@ -281,6 +281,7 @@ h(ContentRender, { content: message.content || '', status: message.status }, /*
281
281
  | 属性名 | 类型 | 默认值 | 说明 |
282
282
  | ------------------ | -------------------------------------------------------------------------- | ------ | ------------------------------------------------- |
283
283
  | message | `Partial<Message>` | — | **必填**,消息对象,`role` 字段决定渲染哪个子组件 |
284
+ | messageTools | `IToolBtn[]` | — | 自定义用户消息工具组;**仅转发给 `UserMessage`** |
284
285
  | messageToolsStatus | `MessageToolsStatus` | — | 工具按钮状态;**仅转发给 `UserMessage`** |
285
286
  | onAction | `(tool: IToolBtn) => Promise<string[] \| void>` | — | 工具操作回调;**仅转发给 `UserMessage`** |
286
287
  | onInputConfirm | `(content: UserMessage['content'], docSchema: TagSchema) => Promise<void>` | — | 用户编辑确认回调;**仅转发给 `UserMessage`** |
@@ -300,7 +301,7 @@ h(ContentRender, { content: message.content || '', status: message.status }, /*
300
301
 
301
302
  | `MessageRole` | 渲染组件 | prop 路由 | 说明 |
302
303
  | ------------- | ------------------ | ------------------------------------------------------------------------------------------------------- | --------------------- |
303
- | `user` | `UserMessage` | `message` + `onAction` + `onInputConfirm` + `onShortcutConfirm` + `messageToolsStatus` + `tippyOptions` | 用户发送的消息 |
304
+ | `user` | `UserMessage` | `message` + `onAction` + `onInputConfirm` + `onShortcutConfirm` + `messageTools` + `messageToolsStatus` + `tippyOptions` | 用户发送的消息 |
304
305
  | `assistant` | `AssistantMessage` | `message` + `default slot` | AI 助手回复消息 |
305
306
  | `info` | `InfoMessage` | `message` | 系统信息 / 会话分隔符 |
306
307
  | `reasoning` | `ReasoningMessage` | `message` | AI 思考过程(可折叠) |
@@ -215,11 +215,16 @@ type ToolCall = {
215
215
  function: FunctionCall;
216
216
  };
217
217
 
218
+ // 调用类型:不填等价于 function
219
+ type FunctionCallType = 'function' | 'mcp' | 'skill';
220
+
218
221
  type FunctionCall = {
219
222
  name: string;
220
223
  arguments: string;
221
224
  description?: string;
222
225
  mcpName?: string;
226
+ // 决定 ToolcallRender 头部前缀(调用工具 / 调用 MCP / 读取 Skill)
227
+ type?: FunctionCallType;
223
228
  };
224
229
 
225
230
  // 示例
@@ -20,29 +20,29 @@
20
20
 
21
21
  > **能力域**:Agent 能力
22
22
 
23
- 展示 AI 调用外部工具/函数过程与结果的渲染组件。由**可折叠头部**和**详情面板**组成,根据 `status` 自动切换颜色和状态文案,支持 MCP 调用识别和内联结果展示。
23
+ 展示 AI 调用外部工具 / MCP / Skill 过程与结果的渲染组件。由**单行可折叠头部**和**详情面板**组成:头部是一段弱化的灰色文本(`#979ba5`),按调用类型给出前缀,进行中用文字渐变闪动表示、结束后在工具名右侧补一段状态与耗时;详情面板默认折叠。
24
24
 
25
25
  ## 组件结构
26
26
 
27
27
  ```
28
- .ai-toolcall-render
29
- ├── .ai-toolcall-render-header(高 40px,class="toolcall-status-{status}")
30
- │ ├── ArrowRightIcon(14×14px,点击切换折叠;默认 rotate(0deg),展开后 rotate(90deg))
31
- │ ├── "调用工具:" / "调用 MCP:"(有 mcpName 时)
32
- │ ├── .toolcall-header-title(工具名,overflow-tips 溢出截断)
33
- │ └── .toolcall-status-title
34
- │ ├── Loading(pending / streaming)
35
- │ ├── BkFlowSuccessIcon(success / complete / completed)
36
- │ ├── BkFlowFailedIcon(error)
37
- │ ├── 状态文案(调用中 / 调用成功 / 调用失败)
38
- │ └── .toolcall-duration(耗时,如 "(1.2s)")
28
+ .ai-toolcall-render(font-size: 12px,line-height: 20px)
29
+ ├── .ai-toolcall-render-header(单行,整行可点击切换折叠;class="is-expanded" 表示已展开)
30
+ │ ├── ToolCallIcon(.ai-toolcall-icon,16×16px)
31
+ │ ├── .toolcall-header-text(内联文本块,溢出截断 + overflow-tips)
32
+ │ │ ├── .toolcall-header-title(前缀 + HighlightKeyword(工具名))
33
+ │ │ │ └── .is-loading(进行中,渐变光带闪动)
34
+ │ │ └── .toolcall-header-status(v-if 有状态词;括号与耗时为弱显示)
35
+ │ │ └── .toolcall-header-result(.is-success #2caf5e / .is-error #ea3636)
36
+ │ └── ChevronRightIcon(.ai-chevron-right-icon,10×10px;v-if 非进行中,展开时 rotate(90deg))
39
37
  │
40
- └── .ai-toolcall-render-content(v-show,默认折叠)
38
+ └── .ai-toolcall-render-content(v-show,默认折叠,子项 gap: 8px)
41
39
  ├── DescPanel(title="描述",desc=function.description)← 始终渲染
42
40
  ├── DescPanel(title="参数",desc=function.arguments)← 始终渲染
43
41
  └── ToolMessage(v-if="toolCall?.toolMessage")← 有结果时渲染
44
42
  ```
45
43
 
44
+ > **头部悬停/展开反馈**:头部默认 `#979ba5`;`:hover` 或 `.is-expanded` 时,`ToolCallIcon` 与非闪动态的工具名变为 `#313238`,展开态的箭头同样变为 `#313238`。头部不再有背景色与边框(旧版的 `$toolcallStatusMap` 状态底色已随重构移除)。
45
+
46
46
  ## 基础用法
47
47
 
48
48
  ```vue
@@ -78,19 +78,49 @@
78
78
 
79
79
  ## 调用状态
80
80
 
81
- `status` prop 同时控制头部的 CSS class(`toolcall-status-{status}`)、背景/边框颜色、状态文案和状态图标:
81
+ 组件内部把 `status` 归一为**成功 / 失败 / 进行中**三态,不再逐个 status 匹配底色:
82
+
83
+ ```typescript
84
+ isSuccess = [complete, completed, success].includes(status);
85
+ isError = status === 'error' || !!toolCall?.toolMessage?.error; // toolMessage.error 可独立判定失败
86
+ isPending = !isSuccess && !isError; // 其余(含 pending / streaming / stop / undefined)统一视为进行中
87
+ ```
88
+
89
+ | 归一状态 | 命中条件 | 头部渲染 |
90
+ | -------- | ---------------------------------------------------- | -------------------------------------------------------------- |
91
+ | 进行中 | 非成功且非失败(含未传 `status`) | 前缀替换为「正在调用」(Skill 为「正在读取」),标题带 `is-loading` 闪动,无状态段、无箭头 |
92
+ | 成功 | `complete` / `completed` / `success` | 状态段 `( 成功 )`,状态词 `#2caf5e` |
93
+ | 失败 | `status === 'error'` **或** `toolMessage.error` 为真 | 状态段 `( 失败 )`,状态词 `#ea3636` |
94
+
95
+ 关于状态段的三个细节:
82
96
 
83
- | `status` | 状态文案 | 背景色 | 边框色 | 状态图标 |
84
- | ------------------------------------- | -------- | ----------------- | --------- | ------------------- |
85
- | `pending` / `streaming` | 调用中 | `#fafbfd` | `#dcdee5` | `Loading` |
86
- | `complete` / `completed` / `success` | 调用成功 | `#ebfaf0` | `#a1e3ba` | `BkFlowSuccessIcon` |
87
- | `error` | 调用失败 | `#fff0f0` | `#f8b4b4` | `BkFlowFailedIcon` |
88
- | 其他 / `undefined` | 调用中 | —(无匹配 class) | — | — |
97
+ - **只有状态词着色**:`.toolcall-header-result` 只包住「成功 / 失败」,括号与耗时留在 `.toolcall-header-status` 内保持弱显示
98
+ - **括号写法**:头部渲染用半角括号并在两侧补 `&nbsp;` 撑开间距;`overflow-tips` 气泡里的纯文本用全角括号,形如 `调用工具 search(成功,耗时:1.2s)`
99
+ - **失败优先**:`toolMessage.error` 有值时即便 `status` 是成功态也判为失败
89
100
 
90
- > **说明**:`statusTitle` 将 `Completed`(`completed`)与 `Complete` / `Success` 一并视为成功;主题 `$toolcallStatusMap` 同步提供 `completed` 色值。`default` 与 `case Pending` 共享「调用中」文案,`streaming` 与未知 status 命中 `default`。状态图标互斥:`pending` / `streaming` 显示 `Loading`;`success` / `complete` / `completed` 显示 `BkFlowSuccessIcon`;`error` 显示 `BkFlowFailedIcon`。
101
+ 进行中的「文字 loading」由 CSS 实现:`.toolcall-header-title.is-loading` 用 `linear-gradient` + `background-clip: text` 让一条光带以 `1.8s linear infinite` 循环扫过文字;`prefers-reduced-motion: reduce` 下自动关闭动画。
91
102
 
92
103
  **三种状态对比**
93
104
 
105
+ ## 调用类型前缀
106
+
107
+ 非进行中态的前缀由 `function.type` 决定;进行中态工具 / MCP 显示「正在调用」,Skill 显示「正在读取」:
108
+
109
+ | `function.type` | 前缀 | 说明 |
110
+ | ------------------------ | ----------- | ---------------------------------------- |
111
+ | `'function'` / 不传 | 调用工具 | 普通函数调用 |
112
+ | `'mcp'` | 调用 MCP | MCP 调用,通常同时带 `mcpName` |
113
+ | `'skill'` | 读取 Skill | Skill 读取 |
114
+
115
+ ```typescript
116
+ const callType = fn?.type ?? (fn?.mcpName ? 'mcp' : 'function');
117
+ ```
118
+
119
+ - **旧数据兼容**:未下发 `type` 时,有 `mcpName` 仍按 MCP 判定,历史消息展示不变
120
+ - **`type` 优先**:显式 `type: 'function'` 不会被 `mcpName` 覆盖回 MCP,但标题仍是 `{mcpName} / {name}`
121
+
122
+ **三种前缀对比**
123
+
94
124
  ## 工具标题(toolTitle)
95
125
 
96
126
  头部标题的计算规则:
@@ -100,16 +130,20 @@
100
130
  无 mcpName → function.name || toolCall.id
101
131
  ```
102
132
 
103
- `function.name` 为空字符串时,自动回退到 `toolCall.id` 作为标题。标题超出容器宽度时截断并配备 overflow-tips。
133
+ `function.name` 为空字符串时,自动回退到 `toolCall.id` 作为标题。前缀、标题与状态段同处一个内联文本块 `.toolcall-header-text`,整体超出容器宽度时统一截断,并由 overflow-tips 展示完整文案。
104
134
 
105
135
  ## 折叠/展开详情面板
106
136
 
107
- 详情面板**默认折叠**,点击头部左侧的 **箭头图标**(14×14px 可点区域,非整行)切换折叠状态。折叠状态由 `collapsed`(默认 `true`)和 `superCollapsed` 两个 `shallowRef` 共同管理,不暴露为 prop/v-model。
137
+ 详情面板**默认折叠**,点击**整行头部**切换折叠状态(旧版仅箭头可点)。折叠状态由 `collapsed`(默认 `true`)和 `superCollapsed` 两个 `shallowRef` 管理,不暴露为 prop/v-model。
108
138
 
109
- | 折叠状态 | 箭头旋转角度 | 详情面板 |
110
- | ------------ | ----------------------- | ------------- |
111
- | 折叠(默认) | `rotate(0deg)`(朝右) | `v-show` 隐藏 |
112
- | 展开 | `rotate(90deg)`(朝下) | 可见 |
139
+ | 折叠状态 | 箭头 | 头部 class | 详情面板 |
140
+ | ------------ | ----------------------------------- | ------------- | ------------- |
141
+ | 折叠(默认) | `ChevronRightIcon` 朝右 | — | `v-show` 隐藏 |
142
+ | 展开 | `rotate(90deg)` 朝下,颜色 `#313238` | `is-expanded` | 可见 |
143
+
144
+ > **进行中不渲染箭头**:`isPending` 为真时箭头隐藏(此时通常还没有结果可看),但头部点击仍会切换详情面板。
145
+ >
146
+ > **关键词联动**:接入 `useKeywordMatch` 后,若上层正在搜索关键词且用户未手动点过头部(`superCollapsed` 为 `null`),面板会按「命中关键词则展开」自动切换;用户一旦点击,`superCollapsed` 接管并固定为手动选择的状态。
113
147
 
114
148
  详情面板由三块区域构成:
115
149
 
@@ -133,6 +167,8 @@ durationDisplay = formatDuration(props.duration || toolCall?.toolMessage?.durati
133
167
  | 未传 `duration`,toolMessage 有 `duration` | 使用 `toolMessage.duration` |
134
168
  | 两者均无 | 不显示耗时 |
135
169
 
170
+ 耗时不再单独占一个元素,而是拼进状态段:有耗时时渲染为 `( 成功,耗时:1.2s )`,无耗时时只保留 `( 成功 )`。进行中态没有状态段,因此也不展示耗时。
171
+
136
172
  ```vue
137
173
  <!-- 方式一:直接传 duration prop(优先) -->
138
174
  <ToolcallRender :tool-call="toolCall" status="complete" :duration="1200" />
@@ -156,32 +192,28 @@ const toolCallWithDuration: ToolCall = {
156
192
  };
157
193
  ```
158
194
 
159
- ## MCP 工具调用
195
+ ## MCP 调用
160
196
 
161
- `function.mcpName` 有值时:
162
-
163
- - 头部文案从 **"调用工具:"** 自动切换为 **"调用 MCP:"**
164
- - 标题格式变为 `{mcpName} / {functionName}`
197
+ `function.type` 为 `'mcp'`(或旧数据仅有 `mcpName`)时,前缀为「调用 MCP」,标题格式变为 `{mcpName} / {functionName}`:
165
198
 
166
199
  ```typescript
167
200
  const mcpToolCall: ToolCall = {
168
201
  id: 'call_mcp_1',
169
202
  type: 'function',
170
203
  function: {
204
+ type: 'mcp', // ← 前缀显示「调用 MCP」;缺省时有 mcpName 也会兼容判定为 MCP
171
205
  name: 'query_table',
172
206
  arguments: JSON.stringify({ table: 'events', limit: 50 }),
173
207
  description: '通过 MCP 协议查询蓝鲸数据平台中的事件数据',
174
- mcpName: 'bk-data-server', // ← 有值时头部显示"调用 MCP:"
208
+ mcpName: 'bk-data-server',
175
209
  },
176
210
  };
177
- // 头部显示:调用 MCP: bk-data-server / query_table
211
+ // 头部显示:调用 MCP bk-data-server / query_table(成功,耗时:830ms)
178
212
  ```
179
213
 
180
- **渲染效果**
181
-
182
214
  ## 调用失败
183
215
 
184
- `toolMessage.error` 有值且 `content` 为空时,`ToolMessage` 内部展示错误信息(由 `content || error` 决定)。`status` 设为 `error` 控制头部红色样式:
216
+ `toolMessage.error` 有值且 `content` 为空时,`ToolMessage` 内部展示错误信息(由 `content || error` 决定)。头部状态词是否红色由 `status === 'error'` **或** `toolMessage.error` 任一命中决定,因此下例即使不传 `status` 也会显示失败:
185
217
 
186
218
  ```typescript
187
219
  const failedToolCall: ToolCall = {
@@ -257,15 +289,17 @@ const assistantMessage = {
257
289
  </template>
258
290
  ```
259
291
 
292
+ > **多条工具调用**:`AssistantMessage` 把 `toolCalls` 包在 `.ai-assistant-message-toolcalls` 容器内,条目之间固定 `8px` 间距,不受消息区 `12px` 间距影响。
293
+
260
294
  ## API
261
295
 
262
296
  ### Props
263
297
 
264
- | 属性名 | 类型 | 默认值 | 说明 |
265
- | -------- | --------------- | ------ | --------------------------------------------------------------------------- |
266
- | toolCall | `ToolCall` | — | 工具调用信息对象 |
267
- | status | `MessageStatus` | — | 调用状态,控制头部颜色和状态文案;未传时 `default` 分支显示"调用中" |
268
- | duration | `number` | — | 调用耗时(毫秒),优先于 `toolCall.toolMessage?.duration`;均无时不显示耗时 |
298
+ | 属性名 | 类型 | 默认值 | 说明 |
299
+ | -------- | --------------- | ------ | ----------------------------------------------------------------------------------- |
300
+ | toolCall | `ToolCall` | — | 工具调用信息对象 |
301
+ | status | `MessageStatus` | — | 调用状态,归一为成功 / 失败 / 进行中三态;未传时按进行中渲染(「正在调用」+ 闪动) |
302
+ | duration | `number` | — | 调用耗时(毫秒),优先于 `toolCall.toolMessage?.duration`;均无时状态段不展示耗时 |
269
303
 
270
304
  ## 类型定义
271
305
 
@@ -275,6 +309,7 @@ import {
275
309
  MessageContentType,
276
310
  type ToolCall,
277
311
  type FunctionCall,
312
+ type FunctionCallType,
278
313
  type ToolMessage,
279
314
  } from '@blueking/chat-x';
280
315
 
@@ -286,12 +321,16 @@ type ToolCall = {
286
321
  toolMessage?: Partial<ToolMessage>; // 有值时在详情面板底部内联渲染 ToolMessage
287
322
  };
288
323
 
324
+ // FunctionCallType —— 调用类型
325
+ type FunctionCallType = 'function' | 'mcp' | 'skill';
326
+
289
327
  // FunctionCall —— 函数调用描述
290
328
  type FunctionCall = {
291
329
  name: string; // 函数名;为空时标题 fallback 为 toolCall.id
292
330
  arguments: string; // 调用参数(通常为 JSON 字符串)
293
331
  description?: string; // 工具描述;为空时"描述"区块保留但内容为空白
294
- mcpName?: string; // MCP 服务名;有值时头部改为"调用 MCP:",标题格式变为 "{mcpName} / {name}"
332
+ mcpName?: string; // MCP 服务名;有值时标题格式变为 "{mcpName} / {name}",缺省 type 时兼容判定为 MCP
333
+ type?: FunctionCallType; // 调用类型,决定头部前缀;不传按 mcpName 兼容判定
295
334
  };
296
335
 
297
336
  // ToolMessage —— 工具返回消息