@blueking/chat-x 0.0.49-beta.1 → 0.0.49-beta.10
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/constants.d.ts +1 -0
- package/dist/ag-ui/types/file.d.ts +1 -11
- package/dist/ag-ui/types/messages.d.ts +2 -0
- package/dist/components/chat-message/assistant-message/assistant-message.vue.d.ts +12 -1
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-file-card.vue.d.ts +0 -2
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/artifact-preview-host.vue.d.ts +6 -3
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/preview-strategy.d.ts +4 -5
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/code-preview.vue.d.ts +8 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/image-preview.vue.d.ts +7 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/use-artifact-preview-loader.d.ts +1 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue.d.ts +1 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/message-artifacts.vue.d.ts +0 -1
- package/dist/components/chat-message/interrupt-message/user-question/use-user-question.d.ts +7 -2
- package/dist/components/chat-message/user-message/user-message.vue.d.ts +15 -2
- package/dist/components/file-icon/file-icon.vue.d.ts +7 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/message-tools/message-time/format-message-time.d.ts +8 -0
- package/dist/components/message-tools/message-time/message-time.vue.d.ts +8 -0
- package/dist/components/message-tools/message-tools.vue.d.ts +11 -1
- package/dist/composables/use-artifact-preview.d.ts +8 -18
- package/dist/composables/use-container-scroll.d.ts +9 -2
- package/dist/composables/use-custom-tab.d.ts +7 -3
- package/dist/composables/use-global-config.d.ts +3 -0
- package/dist/composables/use-message-group.d.ts +145 -74
- package/dist/icons/file-icons.d.ts +4 -0
- package/dist/icons/file.d.ts +0 -18
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/tools.d.ts +3 -0
- package/dist/index.css +1 -1
- package/dist/index.js +2939 -2587
- package/dist/index.js.map +1 -1
- package/dist/lang/lang.d.ts +4 -1
- package/dist/mcp/generated/docs/assistant-message.md +28 -20
- package/dist/mcp/generated/docs/chat-container.md +36 -31
- package/dist/mcp/generated/docs/chat-input.md +2 -10
- package/dist/mcp/generated/docs/constants.md +3 -1
- package/dist/mcp/generated/docs/content-render.md +2 -10
- package/dist/mcp/generated/docs/execution-summary.md +3 -3
- package/dist/mcp/generated/docs/file-artifact-panel.md +60 -46
- package/dist/mcp/generated/docs/file-icon.md +111 -0
- package/dist/mcp/generated/docs/info-message.md +6 -5
- package/dist/mcp/generated/docs/interrupt.md +1 -0
- package/dist/mcp/generated/docs/message-container.md +27 -23
- package/dist/mcp/generated/docs/message-render.md +4 -27
- package/dist/mcp/generated/docs/message-time.md +180 -0
- package/dist/mcp/generated/docs/message-tools.md +47 -12
- package/dist/mcp/generated/docs/messages.md +9 -0
- package/dist/mcp/generated/docs/reasoning-message.md +2 -9
- package/dist/mcp/generated/docs/toolcall-render.md +18 -10
- package/dist/mcp/generated/docs/use-artifact-preview.md +52 -55
- package/dist/mcp/generated/docs/use-container-scroll.md +6 -2
- package/dist/mcp/generated/docs/use-custom-tab.md +25 -8
- package/dist/mcp/generated/docs/use-global-config.md +15 -5
- package/dist/mcp/generated/docs/use-message-group.md +21 -13
- package/dist/mcp/generated/docs/user-message.md +6 -0
- package/dist/mcp/generated/docs/user-question-card.md +6 -3
- package/dist/mcp/generated/index.json +68 -12
- package/dist/utils/file-type.d.ts +14 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +2 -2
- package/dist/components/chat-message/assistant-message/message-artifacts/file-icon.d.ts +0 -29
|
@@ -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
|
-
|
|
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
|
|
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… │ │ │
|
|
33
|
-
│ └─────────────────────┘ │
|
|
34
|
-
│
|
|
35
|
-
|
|
29
|
+
┌───────────────────────────────────────────────────────────────────────┐
|
|
30
|
+
│ .ai-message-tools-container(display: flex,gap: 4px) │
|
|
31
|
+
│ ┌────────┐ ┌─────────────────────┐ │ ┌───────────────┐ ┌───────┐ │
|
|
32
|
+
│ │#prepend│ │ messageTools │ │ │ updateTools │ │#append│ │
|
|
33
|
+
│ │ │ │ copy cite rebuild… │ │ │ like unlike… │ │ │ │
|
|
34
|
+
│ └────────┘ └─────────────────────┘ │ └───────────────┘ └───────┘ │
|
|
35
|
+
│ 插槽区 左侧区域 分隔线 右侧区域 插槽区 │
|
|
36
|
+
└───────────────────────────────────────────────────────────────────────┘
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
- 分隔线(`.ai-divider
|
|
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?: {
|
|
@@ -144,6 +148,9 @@ enum MessageStatus {
|
|
|
144
148
|
// 已完成
|
|
145
149
|
Complete = 'complete',
|
|
146
150
|
|
|
151
|
+
// 已完成(与 Complete 同义,兼容协议/后端返回的 completed)
|
|
152
|
+
Completed = 'completed',
|
|
153
|
+
|
|
147
154
|
// 已禁用
|
|
148
155
|
Disabled = 'disabled',
|
|
149
156
|
|
|
@@ -170,6 +177,8 @@ enum MessageStatus {
|
|
|
170
177
|
}
|
|
171
178
|
```
|
|
172
179
|
|
|
180
|
+
完整取值与说明见 [常量枚举 · MessageStatus](./constants#messagestatus)。
|
|
181
|
+
|
|
173
182
|
## 具体消息类型
|
|
174
183
|
|
|
175
184
|
### UserMessage
|
|
@@ -224,17 +224,10 @@ interface ReasoningMessage {
|
|
|
224
224
|
duration?: number; // 推理耗时(毫秒)
|
|
225
225
|
name?: string;
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
enum MessageStatus {
|
|
229
|
-
Pending = 'pending',
|
|
230
|
-
Streaming = 'streaming',
|
|
231
|
-
Complete = 'complete',
|
|
232
|
-
Success = 'success',
|
|
233
|
-
Error = 'error',
|
|
234
|
-
Stop = 'stop',
|
|
235
|
-
}
|
|
236
227
|
```
|
|
237
228
|
|
|
229
|
+
> `MessageStatus` 完整取值见 [常量枚举](../../types/constants)。本组件完成态识别 `complete` / `success`(与标题文案表一致)。
|
|
230
|
+
|
|
238
231
|
## 关联组件
|
|
239
232
|
|
|
240
233
|
- [MessageRender](/components/message/message-render) — reasoning 角色由其实例化
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
│ ├── "调用工具:" / "调用 MCP:"(有 mcpName 时)
|
|
32
32
|
│ ├── .toolcall-header-title(工具名,overflow-tips 溢出截断)
|
|
33
33
|
│ └── .toolcall-status-title
|
|
34
|
-
│ ├── Loading
|
|
34
|
+
│ ├── Loading(pending / streaming)
|
|
35
|
+
│ ├── BkFlowSuccessIcon(success / complete / completed)
|
|
36
|
+
│ ├── BkFlowFailedIcon(error)
|
|
35
37
|
│ ├── 状态文案(调用中 / 调用成功 / 调用失败)
|
|
36
38
|
│ └── .toolcall-duration(耗时,如 "(1.2s)")
|
|
37
39
|
│
|
|
@@ -76,16 +78,16 @@
|
|
|
76
78
|
|
|
77
79
|
## 调用状态
|
|
78
80
|
|
|
79
|
-
`status` prop 同时控制头部的 CSS class(`toolcall-status-{status}
|
|
81
|
+
`status` prop 同时控制头部的 CSS class(`toolcall-status-{status}`)、背景/边框颜色、状态文案和状态图标:
|
|
80
82
|
|
|
81
|
-
| `status`
|
|
82
|
-
|
|
|
83
|
-
| `pending` / `streaming`
|
|
84
|
-
| `complete` / `success` | 调用成功 | `#ebfaf0` | `#a1e3ba` |
|
|
85
|
-
| `error`
|
|
86
|
-
| 其他 / `undefined`
|
|
83
|
+
| `status` | 状态文案 | 背景色 | 边框色 | 状态图标 |
|
|
84
|
+
| ------------------------------------- | -------- | ----------------- | --------- | ------------------- |
|
|
85
|
+
| `pending` / `streaming` | 调用中 | `#fafbfd` | `#dcdee5` | `Loading` |
|
|
86
|
+
| `complete` / `completed` / `success` | 调用成功 | `#ebfaf0` | `#a1e3ba` | `BkFlowSuccessIcon` |
|
|
87
|
+
| `error` | 调用失败 | `#fff0f0` | `#f8b4b4` | `BkFlowFailedIcon` |
|
|
88
|
+
| 其他 / `undefined` | 调用中 | —(无匹配 class) | — | — |
|
|
87
89
|
|
|
88
|
-
> **说明**:`statusTitle`
|
|
90
|
+
> **说明**:`statusTitle` 将 `Completed`(`completed`)与 `Complete` / `Success` 一并视为成功;主题 `$toolcallStatusMap` 同步提供 `completed` 色值。`default` 与 `case Pending` 共享「调用中」文案,`streaming` 与未知 status 命中 `default`。状态图标互斥:`pending` / `streaming` 显示 `Loading`;`success` / `complete` / `completed` 显示 `BkFlowSuccessIcon`;`error` 显示 `BkFlowFailedIcon`。
|
|
89
91
|
|
|
90
92
|
**三种状态对比**
|
|
91
93
|
|
|
@@ -244,7 +246,13 @@ const assistantMessage = {
|
|
|
244
246
|
v-for="toolCall in assistantMessage.toolCalls"
|
|
245
247
|
:key="toolCall.id"
|
|
246
248
|
:tool-call="toolCall"
|
|
247
|
-
:status="
|
|
249
|
+
:status="
|
|
250
|
+
!toolCall.toolMessage
|
|
251
|
+
? MessageStatus.Pending
|
|
252
|
+
: toolCall.toolMessage.error
|
|
253
|
+
? MessageStatus.Error
|
|
254
|
+
: (toolCall.toolMessage.status ?? assistantMessage.status)
|
|
255
|
+
"
|
|
248
256
|
/>
|
|
249
257
|
</template>
|
|
250
258
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- AI SUMMARY -->
|
|
2
2
|
## 快速了解
|
|
3
3
|
|
|
4
|
-
useArtifactPreviewProvider 维护 activeArtifactId
|
|
4
|
+
useArtifactPreviewProvider 维护 activeArtifactId(值为 outputId),openPreview 命中文件并触发 onOpen 打开侧栏 Tab; 并通过 getOnArtifactClick 封装 resolveArtifactUrls(每次重新取链,并发去重); useArtifactPreviewConsumer 在后代注入同一套 API。SessionArtifact 即 AIFileInfo,会话内以 outputId 为唯一键。 正文加载与分类型渲染不在本 composable,由 FileArtifactPanel 内 ArtifactPreviewHost 完成(重载键 outputId:type)。 FILE_ARTIFACT_TAB_NAME 标识固定「文件产物」Tab。
|
|
5
5
|
|
|
6
6
|
### 关联组件
|
|
7
7
|
- **chat-container** — Provider 主场景,聚合 sessionArtifacts 并挂载 FileArtifactPanel
|
|
@@ -15,12 +15,12 @@ useArtifactPreviewProvider 维护 activeArtifactId,openPreview 命中文件并
|
|
|
15
15
|
|
|
16
16
|
> **分类**:composable
|
|
17
17
|
|
|
18
|
-
Provider/Consumer 模式的文件产物预览状态管理。Provider 在 `ChatContainer` 中创建,负责维护当前命中的文件
|
|
18
|
+
Provider/Consumer 模式的文件产物预览状态管理。Provider 在 `ChatContainer` 中创建,负责维护当前命中的文件 `outputId`;Consumer 在深层 `ArtifactFileCard` 中注入,用于点击卡片触发预览。
|
|
19
19
|
|
|
20
20
|
**职责边界**:
|
|
21
21
|
|
|
22
|
-
- **本 composable**:维护「命中文件」与「URL
|
|
23
|
-
- **不在本 composable**:聚合会话文件列表、渲染预览面板、按类型 fetch 正文 / iframe 展示 —— 分别由 `useMessageGroup.sessionArtifacts`、`FileArtifactPanel`、内部 `ArtifactPreviewHost` + `useArtifactPreviewLoader`
|
|
22
|
+
- **本 composable**:维护「命中文件」与「URL 解析」(每次重新取链 + 并发去重);打开侧栏 Tab(`addCustomTab`)由容器通过 `onOpen` 注入
|
|
23
|
+
- **不在本 composable**:聚合会话文件列表、渲染预览面板、按类型 fetch 正文 / iframe 展示 —— 分别由 `useMessageGroup.sessionArtifacts`、`FileArtifactPanel`、内部 `ArtifactPreviewHost` + `useArtifactPreviewLoader` 承担(预览重载键为 `outputId:type`)
|
|
24
24
|
|
|
25
25
|
## 函数签名
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ function useArtifactPreviewProvider(options: {
|
|
|
31
31
|
/** 读取业务侧异步取链回调(getter 保持对 props 变更敏感) */
|
|
32
32
|
getOnArtifactClick?: () => OnArtifactClick | undefined;
|
|
33
33
|
/** 命中文件后触发:由容器负责 addCustomTab + 展开侧栏 + 选中 Tab */
|
|
34
|
-
onOpen: (
|
|
34
|
+
onOpen: (outputId: string) => void;
|
|
35
35
|
}): {
|
|
36
36
|
activeArtifactId: ShallowRef<string>;
|
|
37
37
|
canResolveArtifactUrl: ComputedRef<boolean>;
|
|
@@ -55,13 +55,6 @@ function useArtifactPreviewConsumer():
|
|
|
55
55
|
};
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
### buildArtifactId
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
function buildArtifactId(messageUid: string, index: number, outputId: string): string;
|
|
62
|
-
// => `${messageUid}#${index}#${outputId}`
|
|
63
|
-
```
|
|
64
|
-
|
|
65
58
|
## 使用示例
|
|
66
59
|
|
|
67
60
|
### Provider(ChatContainer)
|
|
@@ -77,30 +70,39 @@ import {
|
|
|
77
70
|
} from '@blueking/chat-x';
|
|
78
71
|
import { t } from '@blueking/chat-x/lang';
|
|
79
72
|
|
|
80
|
-
const { addCustomTab, removeCustomTab } = useCustomTabProvider({ /* ... */ });
|
|
73
|
+
const { addCustomTab, ensureCustomTab, removeCustomTab } = useCustomTabProvider({ /* ... */ });
|
|
81
74
|
|
|
82
75
|
// 会话级文件产物聚合已内聚在 useMessageGroup,直接消费
|
|
83
76
|
const { sessionArtifacts } = useMessageGroup({ keyword, messages, selectedUserMessages });
|
|
84
77
|
|
|
78
|
+
const FILE_ARTIFACT_TAB = {
|
|
79
|
+
closable: false,
|
|
80
|
+
label: t('文件产物'),
|
|
81
|
+
name: FILE_ARTIFACT_TAB_NAME,
|
|
82
|
+
order: -1,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// 常驻挂载:不随产物有无增删,无产物时由面板展示空态
|
|
86
|
+
ensureCustomTab(FILE_ARTIFACT_TAB);
|
|
87
|
+
|
|
85
88
|
const { activeArtifactId, setActiveArtifactId } = useArtifactPreviewProvider({
|
|
86
89
|
getOnArtifactClick: () => props.onArtifactClick,
|
|
90
|
+
// 点击文件卡片:展开侧栏并选中「文件产物」Tab
|
|
87
91
|
onOpen: () => {
|
|
88
|
-
addCustomTab(
|
|
89
|
-
closable: false,
|
|
90
|
-
label: t('文件产物'),
|
|
91
|
-
name: FILE_ARTIFACT_TAB_NAME,
|
|
92
|
-
order: -1,
|
|
93
|
-
});
|
|
92
|
+
addCustomTab(FILE_ARTIFACT_TAB);
|
|
94
93
|
},
|
|
95
94
|
});
|
|
96
95
|
|
|
97
|
-
//
|
|
96
|
+
// 仅维护命中态:无产物清空,命中项失效时回落到第一个
|
|
98
97
|
watch(sessionArtifacts, list => {
|
|
99
98
|
if (!list.length) {
|
|
100
|
-
removeCustomTab(FILE_ARTIFACT_TAB_NAME);
|
|
101
99
|
setActiveArtifactId('');
|
|
100
|
+
return;
|
|
102
101
|
}
|
|
103
|
-
|
|
102
|
+
if (!list.some(item => item.outputId === activeArtifactId.value)) {
|
|
103
|
+
setActiveArtifactId(list[0].outputId);
|
|
104
|
+
}
|
|
105
|
+
}, { immediate: true });
|
|
104
106
|
```
|
|
105
107
|
|
|
106
108
|
### Consumer(ArtifactFileCard)
|
|
@@ -118,18 +120,14 @@ const handleCardClick = () => {
|
|
|
118
120
|
props.onPreview(props.file);
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
|
-
artifactPreview?.openPreview({
|
|
122
|
-
file: props.file,
|
|
123
|
-
index: props.index ?? 0,
|
|
124
|
-
messageUid: props.messageUid ?? '',
|
|
125
|
-
});
|
|
123
|
+
artifactPreview?.openPreview({ file: props.file });
|
|
126
124
|
};
|
|
127
125
|
```
|
|
128
126
|
|
|
129
127
|
### 侧栏列表内切换命中文件
|
|
130
128
|
|
|
131
129
|
```typescript
|
|
132
|
-
// FileArtifactPanel 列表点击 → emit select → 容器调用 setActiveArtifactId
|
|
130
|
+
// FileArtifactPanel 列表点击 → emit select(outputId) → 容器调用 setActiveArtifactId
|
|
133
131
|
// 右侧预览由面板内 ArtifactPreviewHost 消费 activeArtifact,自行取链并按类型渲染
|
|
134
132
|
<FileArtifactPanel
|
|
135
133
|
:active-id="activeArtifactId"
|
|
@@ -163,32 +161,27 @@ const onArtifactClick = async (file: AIFileInfo) => {
|
|
|
163
161
|
|
|
164
162
|
| 属性/方法名 | 类型 | 说明 |
|
|
165
163
|
| ------------------- | ----------------------------------------- | -------------------------------------------------------------------- |
|
|
166
|
-
| activeArtifactId | `ShallowRef<string>` | 当前命中的文件
|
|
164
|
+
| activeArtifactId | `ShallowRef<string>` | 当前命中的文件 `outputId` |
|
|
167
165
|
| canResolveArtifactUrl | `ComputedRef<boolean>` | 是否具备异步取链能力(有 `onArtifactClick` 时为 true,下载按钮据此显隐) |
|
|
168
|
-
| openPreview | `(payload: OpenArtifactPreviewPayload) => void` |
|
|
169
|
-
| resolveArtifactUrls | `(file: AIFileInfo) => Promise<ArtifactUrlResult>` | 调用 `onArtifactClick`
|
|
170
|
-
| setActiveArtifactId | `(id: string) => void` | 直接设置命中文件
|
|
166
|
+
| openPreview | `(payload: OpenArtifactPreviewPayload) => void` | 由文件卡片触发:以 `file.outputId` 更新命中态、调用 `onOpen` |
|
|
167
|
+
| resolveArtifactUrls | `(file: AIFileInfo) => Promise<ArtifactUrlResult>` | 调用 `onArtifactClick` 取链;每次重新获取,不缓存;同文件并发去重 |
|
|
168
|
+
| setActiveArtifactId | `(id: string) => void` | 直接设置命中文件 `outputId`;侧栏列表内切换选中时使用 |
|
|
171
169
|
|
|
172
170
|
## 类型定义
|
|
173
171
|
|
|
174
172
|
```typescript
|
|
175
173
|
import type { AIFileInfo, ArtifactUrlResult, OnArtifactClick } from '@blueking/chat-x';
|
|
176
174
|
|
|
177
|
-
/**
|
|
175
|
+
/** 打开预览时的入参 */
|
|
178
176
|
type OpenArtifactPreviewPayload = {
|
|
179
177
|
file: AIFileInfo;
|
|
180
|
-
index: number;
|
|
181
|
-
messageUid: string;
|
|
182
178
|
};
|
|
183
179
|
|
|
184
180
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
181
|
+
* 会话级文件产物:以 outputId 为会话内唯一键(同 outputId 视为同一文件)。
|
|
182
|
+
* 拍平去重后即为 AIFileInfo,此处用别名标明语义。
|
|
187
183
|
*/
|
|
188
|
-
type SessionArtifact = AIFileInfo
|
|
189
|
-
artifactId: string;
|
|
190
|
-
messageUid: string;
|
|
191
|
-
};
|
|
184
|
+
type SessionArtifact = AIFileInfo;
|
|
192
185
|
|
|
193
186
|
/** onArtifactClick 返回值(snake_case) */
|
|
194
187
|
type ArtifactUrlResult = {
|
|
@@ -197,28 +190,31 @@ type ArtifactUrlResult = {
|
|
|
197
190
|
};
|
|
198
191
|
```
|
|
199
192
|
|
|
200
|
-
##
|
|
201
|
-
|
|
202
|
-
同一会话可能存在多个 `AssistantMessage`,且不同消息里可能有同名文件,因此**文件名不可作为唯一键**。Provider 侧聚合与 Consumer 侧透传必须使用同一 `buildArtifactId` 规则:
|
|
193
|
+
## 唯一键规则
|
|
203
194
|
|
|
204
|
-
|
|
205
|
-
buildArtifactId('msg-a', 2, 'output-9'); // => 'msg-a#2#output-9'
|
|
206
|
-
```
|
|
195
|
+
会话内以 **`outputId`** 作为文件产物唯一键:
|
|
207
196
|
|
|
208
|
-
- `
|
|
209
|
-
- `
|
|
210
|
-
-
|
|
197
|
+
- 同一 `outputId` 在多条 `AssistantMessage` 中出现时,`sessionArtifacts` 去重并保留**最后一次**出现的文件信息
|
|
198
|
+
- `activeArtifactId`、列表 `:key`、`select` 事件参数均使用 `outputId`
|
|
199
|
+
- 文件名可能重复,**不可**作为唯一键
|
|
211
200
|
|
|
212
201
|
## 完整触发链路
|
|
213
202
|
|
|
214
203
|
```
|
|
215
204
|
ArtifactFileCard(点击)
|
|
216
|
-
└─ useArtifactPreviewConsumer().openPreview({ file
|
|
205
|
+
└─ useArtifactPreviewConsumer().openPreview({ file })
|
|
217
206
|
└─ useArtifactPreviewProvider(ChatContainer)
|
|
218
|
-
├─ activeArtifactId =
|
|
219
|
-
└─ onOpen(
|
|
207
|
+
├─ activeArtifactId = file.outputId
|
|
208
|
+
└─ onOpen(outputId) → addCustomTab(FILE_ARTIFACT_TAB_NAME) 展开并选中
|
|
220
209
|
└─ FileArtifactPanel(列表 + 下载头,@select → setActiveArtifactId)
|
|
221
210
|
└─ ArtifactPreviewHost(loader + 分类型 renderer)
|
|
211
|
+
|
|
212
|
+
容器初始化
|
|
213
|
+
└─ ensureCustomTab(FILE_ARTIFACT_TAB_NAME) 常驻挂上(不展开侧栏);因 order:-1 排在首位,
|
|
214
|
+
未主动切换过 Tab 时会成为默认选中面板;无产物时由面板展示整块空态
|
|
215
|
+
|
|
216
|
+
sessionArtifacts 变化
|
|
217
|
+
└─ 仅维护命中态(无产物清空,命中项失效回落第一个);不增删 Tab
|
|
222
218
|
```
|
|
223
219
|
|
|
224
220
|
分类型预览策略见 [FileArtifactPanel 预览机制](../components/message/file-artifact-panel#预览机制)。
|
|
@@ -228,10 +224,11 @@ ArtifactFileCard(点击)
|
|
|
228
224
|
- **职责单一**:composable 不直接调用 `useCustomTab`,侧栏 Tab 打开逻辑由 `onOpen` 注入;也不做正文 fetch / iframe 渲染
|
|
229
225
|
- **ShallowRef 优先**:`activeArtifactId` 使用 `shallowRef`,避免不必要的深层响应式开销
|
|
230
226
|
- **Consumer 兜底**:`useArtifactPreviewConsumer` 无 Provider 时返回 `undefined`,文件卡片在无容器上下文时自动不可点击
|
|
231
|
-
- **与 useCustomTab
|
|
227
|
+
- **与 useCustomTab 协作**:点击卡片走 `addCustomTab`(展开 + 选中);容器初始化时走 `ensureCustomTab` 常驻挂载(不展开;因 `order: -1` 在未主动切换前会成为默认选中),无产物也不移除,由面板展示空态
|
|
232
228
|
|
|
233
229
|
## 关联组件
|
|
234
230
|
|
|
235
231
|
- [ChatContainer](../components/setup/chat-container) — Provider 主场景,内置「文件产物」Tab
|
|
236
232
|
- [FileArtifactPanel](../components/message/file-artifact-panel) — 侧栏列表与预览 Host 挂载
|
|
237
|
-
- [AssistantMessage](../components/message/assistant-message) — 文件产物来源(`property.artifacts`)
|
|
233
|
+
- [AssistantMessage](../components/message/assistant-message) — 文件产物来源(`property.artifacts`)
|
|
234
|
+
- [useCustomTab](./use-custom-tab) — `addCustomTab` / `ensureCustomTab` 分工
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- AI SUMMARY -->
|
|
2
2
|
## 快速了解
|
|
3
3
|
|
|
4
|
-
useContainerScrollProvider 在滚动容器与底部锚点上绑定 IntersectionObserver、scroll、wheel,提供 isScrollBottom、scrollBottomHeight、autoScrollEnabled、toScrollBottom/toScrollTop 及防抖「返回底部」按钮状态。 useContainerScrollConsumer 通过 inject 在子组件中获取同一套控制,无需 props 透传。 典型用于流式输出时仅在用户位于底部时自动滚底。MessageContainer 与 ScrollBtn 配合使用。
|
|
4
|
+
useContainerScrollProvider 在滚动容器与底部锚点上绑定 IntersectionObserver、scroll、wheel,提供 isScrollBottom、scrollBottomHeight、autoScrollEnabled、jumpToBottom、toScrollBottom/toScrollTop 及防抖「返回底部」按钮状态。 toScrollBottom 缺省按距底部距离自动选择行为:超过 INSTANT_SCROLL_DISTANCE(600px)时瞬时贴底,否则平滑滚动,避免切换会话时出现长距离平滑滚动动画。 useContainerScrollConsumer 通过 inject 在子组件中获取同一套控制,无需 props 透传。 典型用于流式输出时仅在用户位于底部时自动滚底。MessageContainer 与 ScrollBtn 配合使用。
|
|
5
5
|
|
|
6
6
|
### 关联组件
|
|
7
7
|
- **message-container** — Provider 挂载于消息列表滚动区域
|
|
@@ -35,7 +35,10 @@ useContainerScrollProvider(containerRef, bottomRef)
|
|
|
35
35
|
├── wheel 事件(passive)→ deltaY < 0 时 autoScrollEnabled=false
|
|
36
36
|
│ (用户向上滚动时暂停自动滚动)
|
|
37
37
|
│
|
|
38
|
-
├──
|
|
38
|
+
├── jumpToBottom() → autoScrollEnabled=true + container.scrollTop = scrollHeight(瞬时)
|
|
39
|
+
├── toScrollBottom(behavior?) → autoScrollEnabled=true;
|
|
40
|
+
│ behavior 缺省时:距底部 > INSTANT_SCROLL_DISTANCE(600) → jumpToBottom()
|
|
41
|
+
│ 否则 / 显式 'smooth' → bottomRef.scrollIntoView({ behavior:'smooth', block:'end' })
|
|
39
42
|
├── toScrollTop() → containerRef.scrollTo({ top:0, behavior:'smooth' })
|
|
40
43
|
│
|
|
41
44
|
└── provide(CONTAINER_SCROLL_TOKEN, computed(() => ({
|
|
@@ -43,6 +46,7 @@ useContainerScrollProvider(containerRef, bottomRef)
|
|
|
43
46
|
isScrollBottom, // ShallowRef<boolean>(保持响应式)
|
|
44
47
|
scrollBottomHeight, // ShallowRef<number>(保持响应式)
|
|
45
48
|
debouncedShowScrollBottomBtn, // customRef,防抖显示返回底部按钮
|
|
49
|
+
jumpToBottom,
|
|
46
50
|
toScrollBottom,
|
|
47
51
|
toScrollTop,
|
|
48
52
|
})))
|