@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.
- package/dist/ag-ui/types/contents.d.ts +2 -0
- package/dist/ag-ui/types/messages.d.ts +3 -0
- package/dist/common/constants.d.ts +1 -1
- package/dist/components/ai-buttons/file-upload-btn/file-upload-btn.vue.d.ts +0 -2
- package/dist/components/chat-content/file-content/file-content.vue.d.ts +5 -2
- package/dist/components/chat-content/file-content/upload-file-item.vue.d.ts +12 -0
- package/dist/components/chat-content/file-content/upload-image-item.vue.d.ts +21 -0
- package/dist/components/chat-input/ai-slash-editor/ai-slash-editor.vue.d.ts +1 -1
- package/dist/components/chat-input/build-default-placeholder.d.ts +7 -0
- package/dist/components/chat-input/chat-input.vue.d.ts +1 -1
- package/dist/components/chat-message/message-container/message-container.vue.d.ts +1 -0
- package/dist/components/chat-message/message-render/message-render.vue.d.ts +2 -0
- package/dist/components/chat-message/user-message/user-message.vue.d.ts +2 -1
- package/dist/composables/use-custom-tab.d.ts +5 -3
- package/dist/composables/use-message-group.d.ts +78 -72
- package/dist/icons/execution.d.ts +6 -0
- package/dist/icons/tools.d.ts +3 -0
- package/dist/index.css +1 -1
- package/dist/index.js +3054 -2854
- package/dist/index.js.map +1 -1
- package/dist/lang/lang.d.ts +7 -7
- package/dist/mcp/generated/docs/ai-slash-input.md +2 -0
- package/dist/mcp/generated/docs/assistant-message.md +9 -7
- package/dist/mcp/generated/docs/chat-container.md +35 -31
- package/dist/mcp/generated/docs/chat-input.md +18 -12
- package/dist/mcp/generated/docs/cite-content.md +3 -3
- package/dist/mcp/generated/docs/desc-panel.md +32 -10
- package/dist/mcp/generated/docs/execution-summary.md +3 -3
- package/dist/mcp/generated/docs/file-artifact-panel.md +6 -4
- package/dist/mcp/generated/docs/file-content.md +89 -73
- package/dist/mcp/generated/docs/file-upload-btn.md +16 -18
- package/dist/mcp/generated/docs/message-container.md +1 -0
- package/dist/mcp/generated/docs/message-render.md +2 -1
- package/dist/mcp/generated/docs/messages.md +5 -0
- package/dist/mcp/generated/docs/toolcall-render.md +82 -43
- package/dist/mcp/generated/docs/use-artifact-preview.md +19 -17
- package/dist/mcp/generated/docs/use-custom-tab.md +12 -8
- package/dist/mcp/generated/docs/user-message.md +3 -0
- package/dist/mcp/generated/docs/user-question-card.md +2 -0
- package/dist/mcp/generated/index.json +7 -3
- package/dist/mcp/index.js +0 -0
- package/dist/types/input.d.ts +6 -0
- package/dist/utils/file.d.ts +7 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/merge-tools-by-id.d.ts +6 -0
- package/dist/utils/upload-file.d.ts +35 -0
- 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
|
-
|
|
30
|
-
├──
|
|
31
|
-
│
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
>
|
|
45
|
+
> **是否为图片只看 MIME,不看 `url`。** 解除上传类型限制后,任意文件上传成功都会拿到 `url`,若按 `url` 判断会把 PDF / DOC 渲染成破图。因此 `url` 只决定 `<img src>` 从哪里来,不参与图片判定。
|
|
38
46
|
|
|
39
|
-
##
|
|
47
|
+
## 基础用法(文件卡片)
|
|
40
48
|
|
|
41
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
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`
|
|
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
|
-
| 条件
|
|
218
|
-
|
|
|
219
|
-
| `file.url`
|
|
220
|
-
|
|
|
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
|
-
|
|
|
229
|
-
| 文件大小 |
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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">`
|
|
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
|
|
31
|
-
v-tippy: "
|
|
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
|
-
>
|
|
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
|
-
|
|
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` |
|
|
132
|
-
|
|
|
133
|
-
|
|
|
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
|
|
23
|
+
展示 AI 调用外部工具 / MCP / Skill 过程与结果的渲染组件。由**单行可折叠头部**和**详情面板**组成:头部是一段弱化的灰色文本(`#979ba5`),按调用类型给出前缀,进行中用文字渐变闪动表示、结束后在工具名右侧补一段状态与耗时;详情面板默认折叠。
|
|
24
24
|
|
|
25
25
|
## 组件结构
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
.ai-toolcall-render
|
|
29
|
-
├── .ai-toolcall-render-header
|
|
30
|
-
│ ├──
|
|
31
|
-
│ ├──
|
|
32
|
-
│ ├── .toolcall-header-title
|
|
33
|
-
│ └── .
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
│
|
|
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`
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| `complete` / `completed` / `success` | 调用成功 | `#ebfaf0` | `#a1e3ba` | `BkFlowSuccessIcon` |
|
|
87
|
-
| `error` | 调用失败 | `#fff0f0` | `#f8b4b4` | `BkFlowFailedIcon` |
|
|
88
|
-
| 其他 / `undefined` | 调用中 | —(无匹配 class) | — | — |
|
|
97
|
+
- **只有状态词着色**:`.toolcall-header-result` 只包住「成功 / 失败」,括号与耗时留在 `.toolcall-header-status` 内保持弱显示
|
|
98
|
+
- **括号写法**:头部渲染用半角括号并在两侧补 ` ` 撑开间距;`overflow-tips` 气泡里的纯文本用全角括号,形如 `调用工具 search(成功,耗时:1.2s)`
|
|
99
|
+
- **失败优先**:`toolMessage.error` 有值时即便 `status` 是成功态也判为失败
|
|
89
100
|
|
|
90
|
-
|
|
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`
|
|
133
|
+
`function.name` 为空字符串时,自动回退到 `toolCall.id` 作为标题。前缀、标题与状态段同处一个内联文本块 `.toolcall-header-text`,整体超出容器宽度时统一截断,并由 overflow-tips 展示完整文案。
|
|
104
134
|
|
|
105
135
|
## 折叠/展开详情面板
|
|
106
136
|
|
|
107
|
-
|
|
137
|
+
详情面板**默认折叠**,点击**整行头部**切换折叠状态(旧版仅箭头可点)。折叠状态由 `collapsed`(默认 `true`)和 `superCollapsed` 两个 `shallowRef` 管理,不暴露为 prop/v-model。
|
|
108
138
|
|
|
109
|
-
| 折叠状态 |
|
|
110
|
-
| ------------ |
|
|
111
|
-
| 折叠(默认) | `
|
|
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',
|
|
208
|
+
mcpName: 'bk-data-server',
|
|
175
209
|
},
|
|
176
210
|
};
|
|
177
|
-
// 头部显示:调用 MCP
|
|
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`
|
|
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` | — |
|
|
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
|
|
332
|
+
mcpName?: string; // MCP 服务名;有值时标题格式变为 "{mcpName} / {name}",缺省 type 时兼容判定为 MCP
|
|
333
|
+
type?: FunctionCallType; // 调用类型,决定头部前缀;不传按 mcpName 兼容判定
|
|
295
334
|
};
|
|
296
335
|
|
|
297
336
|
// ToolMessage —— 工具返回消息
|