@blueking/chat-x 0.0.48-beta.1 → 0.0.49-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/file.d.ts +23 -0
- package/dist/ag-ui/types/index.d.ts +1 -0
- package/dist/ag-ui/types/messages.d.ts +2 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-file-card.vue.d.ts +14 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/artifact-preview-host.vue.d.ts +8 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/preview-strategy.d.ts +9 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/html-preview.vue.d.ts +6 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/markdown-preview.vue.d.ts +6 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/txt-preview.vue.d.ts +6 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/url-iframe-preview.vue.d.ts +6 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/use-artifact-preview-loader.d.ts +21 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue.d.ts +12 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/file-icon.d.ts +29 -0
- package/dist/components/chat-message/assistant-message/message-artifacts/message-artifacts.vue.d.ts +10 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/use-artifact-preview.d.ts +59 -0
- package/dist/composables/use-message-group.d.ts +434 -0
- package/dist/icons/file.d.ts +24 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.js +2083 -1600
- package/dist/index.js.map +1 -1
- package/dist/lang/lang.d.ts +6 -1
- package/dist/mcp/generated/docs/activity-message.md +122 -97
- package/dist/mcp/generated/docs/assistant-message.md +125 -62
- package/dist/mcp/generated/docs/chat-container.md +94 -16
- package/dist/mcp/generated/docs/file-artifact-panel.md +279 -0
- package/dist/mcp/generated/docs/info-message.md +23 -7
- package/dist/mcp/generated/docs/loading-message.md +36 -17
- package/dist/mcp/generated/docs/message-render.md +44 -33
- package/dist/mcp/generated/docs/reasoning-message.md +15 -11
- package/dist/mcp/generated/docs/tool-message.md +61 -45
- package/dist/mcp/generated/docs/use-artifact-preview.md +237 -0
- package/dist/mcp/generated/docs/use-message-group.md +24 -0
- package/dist/mcp/generated/docs/user-message.md +185 -121
- package/dist/mcp/generated/index.json +79 -9
- package/dist/mcp/index.js +0 -0
- package/package.json +20 -21
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- AI SUMMARY -->
|
|
2
2
|
## 快速了解
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
渲染工具返回内容;DescPanel 解析 JSON 为 key-value,嵌套值 JSON.stringify 后经 HighlightKeyword 展示。 源码位置:src/components/chat-message/tool-message/tool-message.vue。
|
|
5
5
|
|
|
6
6
|
### 关联组件
|
|
7
7
|
- **assistant-message** — 结果常作为 assistant 消息中 toolCall.toolMessage 内联展示
|
|
@@ -12,51 +12,57 @@
|
|
|
12
12
|
<!-- FULL DOC -->
|
|
13
13
|
|
|
14
14
|
# ToolMessage 工具消息
|
|
15
|
+
|
|
15
16
|
## 源码事实
|
|
16
17
|
|
|
17
18
|
- **源码位置**:`src/components/chat-message/tool-message/tool-message.vue`
|
|
18
19
|
- **能力域**:消息系统
|
|
19
|
-
- **能力说明**:渲染工具返回内容,JSON 场景交给 DescPanel 展示。
|
|
20
|
+
- **能力说明**:渲染工具返回内容,JSON 场景交给 DescPanel + HighlightKeyword 展示。
|
|
20
21
|
|
|
21
|
-
>
|
|
22
|
+
> **导出说明**:`ToolMessage` **未**从包入口导出(入口同名是 TS interface)。消费方经 `MessageRender` / `ToolcallRender` 使用。下文 `ToolMessageComp` 为文档站内部示例。
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
工具执行结果展示组件。内部通过 `DescPanel` 渲染,标题固定为「返回内容」,可解析 JSON 为 key-value 列表。
|
|
24
25
|
|
|
25
|
-
>
|
|
26
|
+
> **通常不需要直接使用**。`ToolcallRender` 在 `toolCall.toolMessage` 有值时内联渲染;`role: 'tool'` 也可由 `MessageRender` 渲染。
|
|
26
27
|
|
|
27
28
|
## 渲染架构
|
|
28
29
|
|
|
29
30
|
```
|
|
30
31
|
ToolMessage
|
|
31
|
-
└── DescPanel(desc=
|
|
32
|
-
├── JSON.parse
|
|
33
|
-
│ └── key-value
|
|
34
|
-
│
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
└── DescPanel(desc = content || (typeof error === 'string' ? error : undefined),title="返回内容")
|
|
33
|
+
├── JSON.parse 成功且结果为 object/array(排除 null)
|
|
34
|
+
│ └── key-value 列表
|
|
35
|
+
│ · key / value 均经 HighlightKeyword 展示
|
|
36
|
+
│ · 嵌套 object/array:JSON.stringify 后展示(无 overflow-tips)
|
|
37
|
+
└── 其他(parse 失败 / 标量)
|
|
38
|
+
└── HighlightKeyword 纯文本
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
## 基础用法
|
|
40
42
|
|
|
41
43
|
```vue
|
|
42
44
|
<template>
|
|
43
|
-
<
|
|
44
|
-
:content="content"
|
|
45
|
-
tool-call-id="call_1"
|
|
46
|
-
:duration="850"
|
|
47
|
-
/>
|
|
45
|
+
<MessageRender :message="message" />
|
|
48
46
|
</template>
|
|
49
47
|
|
|
50
48
|
<script setup lang="ts">
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
import { MessageRender, MessageRole, MessageStatus } from '@blueking/chat-x';
|
|
50
|
+
|
|
51
|
+
const message = {
|
|
52
|
+
id: 't1',
|
|
53
|
+
messageId: 't1',
|
|
54
|
+
role: MessageRole.Tool,
|
|
55
|
+
status: MessageStatus.Complete,
|
|
56
|
+
toolCallId: 'call_1',
|
|
57
|
+
duration: 850,
|
|
58
|
+
content: JSON.stringify({
|
|
59
|
+
city: '北京',
|
|
60
|
+
temperature: 22,
|
|
61
|
+
weather: '晴',
|
|
62
|
+
humidity: '45%',
|
|
63
|
+
wind: '东北风 3 级',
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
60
66
|
</script>
|
|
61
67
|
```
|
|
62
68
|
|
|
@@ -70,18 +76,22 @@ ToolMessage
|
|
|
70
76
|
|
|
71
77
|
```vue
|
|
72
78
|
<template>
|
|
73
|
-
<
|
|
74
|
-
content=""
|
|
75
|
-
:error="error"
|
|
76
|
-
tool-call-id="call_3"
|
|
77
|
-
:duration="5000"
|
|
78
|
-
/>
|
|
79
|
+
<MessageRender :message="message" />
|
|
79
80
|
</template>
|
|
80
81
|
|
|
81
82
|
<script setup lang="ts">
|
|
82
|
-
import {
|
|
83
|
-
|
|
84
|
-
const
|
|
83
|
+
import { MessageRender, MessageRole, MessageStatus } from '@blueking/chat-x';
|
|
84
|
+
|
|
85
|
+
const message = {
|
|
86
|
+
id: 't3',
|
|
87
|
+
messageId: 't3',
|
|
88
|
+
role: MessageRole.Tool,
|
|
89
|
+
status: MessageStatus.Error,
|
|
90
|
+
toolCallId: 'call_3',
|
|
91
|
+
duration: 5000,
|
|
92
|
+
content: '',
|
|
93
|
+
error: 'Connection timeout: database server is unreachable (timeout: 5000ms)',
|
|
94
|
+
};
|
|
85
95
|
</script>
|
|
86
96
|
```
|
|
87
97
|
|
|
@@ -98,25 +108,27 @@ ToolMessage
|
|
|
98
108
|
| 解析失败(非法 JSON) | 捕获异常,返回原字符串 | 纯文本 |
|
|
99
109
|
| `content` 和 `error` 均为空 | `''` → 解析失败 | 空内容区 |
|
|
100
110
|
|
|
101
|
-
> **注意**:JSON
|
|
111
|
+
> **注意**:JSON 数组 `typeof [] === 'object'`,会以 `0:`、`1:`、`2:` 为键渲染为列表,而非整段纯文本。
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
**嵌套对象 / 数组值**:`DescPanel` 用 `JSON.stringify(value)` 转成字符串后交给 `HighlightKeyword`,**不再**使用 overflow-tips。
|
|
104
114
|
|
|
105
115
|
```typescript
|
|
106
|
-
//
|
|
116
|
+
// key-value 列表
|
|
107
117
|
const jsonObject = '{"city":"北京","temperature":22}';
|
|
108
118
|
|
|
109
|
-
//
|
|
119
|
+
// index-keyed 列表(0: item1, 1: item2)
|
|
110
120
|
const jsonArray = '["item1","item2","item3"]';
|
|
111
121
|
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
const jsonBool = 'true';
|
|
122
|
+
// 嵌套值 stringify 展示
|
|
123
|
+
const nested = '{"result":"success","data":{"city":"北京","meta":{"humidity":45}}}';
|
|
115
124
|
|
|
116
|
-
//
|
|
125
|
+
// 标量 / 非法 JSON → 纯文本
|
|
126
|
+
const jsonNumber = '42';
|
|
117
127
|
const plainText = '查询成功,共返回 10 条记录。';
|
|
118
128
|
```
|
|
119
129
|
|
|
130
|
+
**嵌套 JSON 渲染效果**
|
|
131
|
+
|
|
120
132
|
## 与 ToolcallRender 的关系
|
|
121
133
|
|
|
122
134
|
`ToolcallRender` 在详情面板展开时,若 `toolCall.toolMessage` 有值,会在底部内联渲染 `ToolMessage`:
|
|
@@ -186,8 +198,8 @@ const messages = [
|
|
|
186
198
|
|
|
187
199
|
| 属性名 | 类型 | 说明 |
|
|
188
200
|
| ---------- | ------------------ | ------------------------------------------------------------------------------- |
|
|
189
|
-
| content | `string`
|
|
190
|
-
| error | `string`
|
|
201
|
+
| content | `string` | 工具执行返回内容;与 `error` 通过 `\|\|` 决定优先级,**truthy 时 error 被忽略** |
|
|
202
|
+
| error | `boolean \| string` | 类型上可为 boolean;**仅当为 `string` 且 content 为 falsy 时**才会展示 |
|
|
191
203
|
| toolCallId | `string` | 关联的工具调用 ID(透传,组件内不使用) |
|
|
192
204
|
| duration | `number` | 工具执行耗时(毫秒,透传,组件内不使用) |
|
|
193
205
|
| status | `MessageStatus` | 消息状态(透传,组件内不使用) |
|
|
@@ -210,11 +222,15 @@ interface ToolMessage {
|
|
|
210
222
|
content: string;
|
|
211
223
|
toolCallId: string; // 关联的 ToolCall.id
|
|
212
224
|
duration: number; // 工具执行耗时(毫秒)
|
|
213
|
-
error?: string; //
|
|
225
|
+
error?: boolean | string; // 仅 string 会展示在 DescPanel
|
|
214
226
|
name?: string;
|
|
215
227
|
}
|
|
216
228
|
```
|
|
217
229
|
|
|
230
|
+
### Events / Slots / Expose
|
|
231
|
+
|
|
232
|
+
无。
|
|
233
|
+
|
|
218
234
|
## 关联组件
|
|
219
235
|
|
|
220
236
|
- [AssistantMessage](/components/message/assistant-message) — toolCall.toolMessage 内联场景
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<!-- AI SUMMARY -->
|
|
2
|
+
## 快速了解
|
|
3
|
+
|
|
4
|
+
useArtifactPreviewProvider 维护 activeArtifactId,openPreview 命中文件并触发 onOpen 打开侧栏 Tab; 并通过 getOnArtifactClick 封装 resolveArtifactUrls(按 outputId 缓存 download_url / preview_url); useArtifactPreviewConsumer 在后代注入同一套 API。buildArtifactId 用 messageUid#index#outputId 生成唯一 id。 正文加载与分类型渲染不在本 composable,由 FileArtifactPanel 内 ArtifactPreviewHost 完成。 FILE_ARTIFACT_TAB_NAME 标识固定「文件产物」Tab。
|
|
5
|
+
|
|
6
|
+
### 关联组件
|
|
7
|
+
- **chat-container** — Provider 主场景,聚合 sessionArtifacts 并挂载 FileArtifactPanel
|
|
8
|
+
- **file-artifact-panel** — 侧栏面板消费 activeArtifactId 与 setActiveArtifactId;预览加载在面板内 Host
|
|
9
|
+
- **assistant-message** — 文件产物来源 property.artifacts
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
<!-- FULL DOC -->
|
|
13
|
+
|
|
14
|
+
# useArtifactPreview 文件产物预览
|
|
15
|
+
|
|
16
|
+
> **分类**:composable
|
|
17
|
+
|
|
18
|
+
Provider/Consumer 模式的文件产物预览状态管理。Provider 在 `ChatContainer` 中创建,负责维护当前命中的文件 id;Consumer 在深层 `ArtifactFileCard` 中注入,用于点击卡片触发预览。
|
|
19
|
+
|
|
20
|
+
**职责边界**:
|
|
21
|
+
|
|
22
|
+
- **本 composable**:维护「命中文件」与「URL 解析缓存」;打开侧栏 Tab(`addCustomTab`)由容器通过 `onOpen` 注入
|
|
23
|
+
- **不在本 composable**:聚合会话文件列表、渲染预览面板、按类型 fetch 正文 / iframe 展示 —— 分别由 `useMessageGroup.sessionArtifacts`、`FileArtifactPanel`、内部 `ArtifactPreviewHost` + `useArtifactPreviewLoader` 承担
|
|
24
|
+
|
|
25
|
+
## 函数签名
|
|
26
|
+
|
|
27
|
+
### useArtifactPreviewProvider
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
function useArtifactPreviewProvider(options: {
|
|
31
|
+
/** 读取业务侧异步取链回调(getter 保持对 props 变更敏感) */
|
|
32
|
+
getOnArtifactClick?: () => OnArtifactClick | undefined;
|
|
33
|
+
/** 命中文件后触发:由容器负责 addCustomTab + 展开侧栏 + 选中 Tab */
|
|
34
|
+
onOpen: (artifactId: string) => void;
|
|
35
|
+
}): {
|
|
36
|
+
activeArtifactId: ShallowRef<string>;
|
|
37
|
+
canResolveArtifactUrl: ComputedRef<boolean>;
|
|
38
|
+
openPreview: (payload: OpenArtifactPreviewPayload) => void;
|
|
39
|
+
resolveArtifactUrls: (file: AIFileInfo) => Promise<ArtifactUrlResult>;
|
|
40
|
+
setActiveArtifactId: (id: string) => void;
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### useArtifactPreviewConsumer
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
function useArtifactPreviewConsumer():
|
|
48
|
+
| undefined
|
|
49
|
+
| {
|
|
50
|
+
activeArtifactId: Ref<string>;
|
|
51
|
+
canResolveArtifactUrl: ComputedRef<boolean>;
|
|
52
|
+
openPreview: (payload: OpenArtifactPreviewPayload) => void;
|
|
53
|
+
resolveArtifactUrls: (file: AIFileInfo) => Promise<ArtifactUrlResult>;
|
|
54
|
+
setActiveArtifactId: (id: string) => void;
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### buildArtifactId
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
function buildArtifactId(messageUid: string, index: number, outputId: string): string;
|
|
62
|
+
// => `${messageUid}#${index}#${outputId}`
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 使用示例
|
|
66
|
+
|
|
67
|
+
### Provider(ChatContainer)
|
|
68
|
+
|
|
69
|
+
会话级文件产物由 [`useMessageGroup`](./use-message-group) 统一聚合(`sessionArtifacts`),Provider 只负责命中与打开侧栏 Tab:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import {
|
|
73
|
+
useArtifactPreviewProvider,
|
|
74
|
+
useCustomTabProvider,
|
|
75
|
+
useMessageGroup,
|
|
76
|
+
FILE_ARTIFACT_TAB_NAME,
|
|
77
|
+
} from '@blueking/chat-x';
|
|
78
|
+
import { t } from '@blueking/chat-x/lang';
|
|
79
|
+
|
|
80
|
+
const { addCustomTab, removeCustomTab } = useCustomTabProvider({ /* ... */ });
|
|
81
|
+
|
|
82
|
+
// 会话级文件产物聚合已内聚在 useMessageGroup,直接消费
|
|
83
|
+
const { sessionArtifacts } = useMessageGroup({ keyword, messages, selectedUserMessages });
|
|
84
|
+
|
|
85
|
+
const { activeArtifactId, setActiveArtifactId } = useArtifactPreviewProvider({
|
|
86
|
+
getOnArtifactClick: () => props.onArtifactClick,
|
|
87
|
+
onOpen: () => {
|
|
88
|
+
addCustomTab({
|
|
89
|
+
closable: false,
|
|
90
|
+
label: t('文件产物'),
|
|
91
|
+
name: FILE_ARTIFACT_TAB_NAME,
|
|
92
|
+
order: -1,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 无文件产物时清理 Tab 与命中态
|
|
98
|
+
watch(sessionArtifacts, list => {
|
|
99
|
+
if (!list.length) {
|
|
100
|
+
removeCustomTab(FILE_ARTIFACT_TAB_NAME);
|
|
101
|
+
setActiveArtifactId('');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Consumer(ArtifactFileCard)
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { useArtifactPreviewConsumer } from '@blueking/chat-x';
|
|
110
|
+
|
|
111
|
+
const artifactPreview = useArtifactPreviewConsumer();
|
|
112
|
+
|
|
113
|
+
// 有 Provider 时卡片可点击;无 Provider 时返回 undefined,卡片不可点击
|
|
114
|
+
const clickable = computed(() => !!props.onPreview || !!artifactPreview);
|
|
115
|
+
|
|
116
|
+
const handleCardClick = () => {
|
|
117
|
+
if (props.onPreview) {
|
|
118
|
+
props.onPreview(props.file);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
artifactPreview?.openPreview({
|
|
122
|
+
file: props.file,
|
|
123
|
+
index: props.index ?? 0,
|
|
124
|
+
messageUid: props.messageUid ?? '',
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 侧栏列表内切换命中文件
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// FileArtifactPanel 列表点击 → emit select → 容器调用 setActiveArtifactId
|
|
133
|
+
// 右侧预览由面板内 ArtifactPreviewHost 消费 activeArtifact,自行取链并按类型渲染
|
|
134
|
+
<FileArtifactPanel
|
|
135
|
+
:active-id="activeArtifactId"
|
|
136
|
+
:artifacts="sessionArtifacts"
|
|
137
|
+
@select="setActiveArtifactId"
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 业务侧取链(ChatContainer `onArtifactClick`)
|
|
142
|
+
|
|
143
|
+
文本类预览需要可 `fetch` 的 `download_url`;iframe 类需要 `preview_url`(一般为后台转好的 PDF):
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const onArtifactClick = async (file: AIFileInfo) => {
|
|
147
|
+
const res = await api.getArtifactUrls(file.outputId);
|
|
148
|
+
return {
|
|
149
|
+
download_url: res.download_url,
|
|
150
|
+
preview_url: res.preview_url,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 内置常量
|
|
156
|
+
|
|
157
|
+
| 常量名 | 值 | 说明 |
|
|
158
|
+
| ------------------------- | ------------------ | ----------------------------------------- |
|
|
159
|
+
| `FILE_ARTIFACT_TAB_NAME` | `'file-artifact'` | 「文件产物」侧栏 Tab 的固定标识,不可关闭 |
|
|
160
|
+
| `ARTIFACT_PREVIEW_TOKEN` | `Symbol` | provide/inject 注入 Token |
|
|
161
|
+
|
|
162
|
+
## 返回值说明
|
|
163
|
+
|
|
164
|
+
| 属性/方法名 | 类型 | 说明 |
|
|
165
|
+
| ------------------- | ----------------------------------------- | -------------------------------------------------------------------- |
|
|
166
|
+
| activeArtifactId | `ShallowRef<string>` | 当前命中的文件 id(`messageUid#index#outputId`) |
|
|
167
|
+
| canResolveArtifactUrl | `ComputedRef<boolean>` | 是否具备异步取链能力(有 `onArtifactClick` 时为 true,下载按钮据此显隐) |
|
|
168
|
+
| openPreview | `(payload: OpenArtifactPreviewPayload) => void` | 由文件卡片触发:计算 id、更新命中态、调用 `onOpen` |
|
|
169
|
+
| resolveArtifactUrls | `(file: AIFileInfo) => Promise<ArtifactUrlResult>` | 调用 `onArtifactClick` 并按 `outputId` 缓存结果;并发请求去重 |
|
|
170
|
+
| setActiveArtifactId | `(id: string) => void` | 直接设置命中文件 id;侧栏列表内切换选中时使用 |
|
|
171
|
+
|
|
172
|
+
## 类型定义
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import type { AIFileInfo, ArtifactUrlResult, OnArtifactClick } from '@blueking/chat-x';
|
|
176
|
+
|
|
177
|
+
/** 打开预览时的入参:文件 + 消息内下标 + 所属消息 uid */
|
|
178
|
+
type OpenArtifactPreviewPayload = {
|
|
179
|
+
file: AIFileInfo;
|
|
180
|
+
index: number;
|
|
181
|
+
messageUid: string;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 会话级文件产物:在 AIFileInfo 基础上补充命中所需的唯一 id 与所属消息。
|
|
186
|
+
* 同一会话可能出现多个 AssistantMessage + 同名文件,文件名不可作唯一键。
|
|
187
|
+
*/
|
|
188
|
+
type SessionArtifact = AIFileInfo & {
|
|
189
|
+
artifactId: string;
|
|
190
|
+
messageUid: string;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/** onArtifactClick 返回值(snake_case) */
|
|
194
|
+
type ArtifactUrlResult = {
|
|
195
|
+
download_url?: string;
|
|
196
|
+
preview_url?: string;
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 唯一 id 规则
|
|
201
|
+
|
|
202
|
+
同一会话可能存在多个 `AssistantMessage`,且不同消息里可能有同名文件,因此**文件名不可作为唯一键**。Provider 侧聚合与 Consumer 侧透传必须使用同一 `buildArtifactId` 规则:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
buildArtifactId('msg-a', 2, 'output-9'); // => 'msg-a#2#output-9'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- `messageUid`:所属 `AssistantMessage` 的 `uid`(回退 `String(id)`)
|
|
209
|
+
- `index`:文件在所属消息 `property.artifacts` 数组中的下标
|
|
210
|
+
- `outputId`:文件自身的 `AIFileInfo.outputId`
|
|
211
|
+
|
|
212
|
+
## 完整触发链路
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
ArtifactFileCard(点击)
|
|
216
|
+
└─ useArtifactPreviewConsumer().openPreview({ file, index, messageUid })
|
|
217
|
+
└─ useArtifactPreviewProvider(ChatContainer)
|
|
218
|
+
├─ activeArtifactId = buildArtifactId(...)
|
|
219
|
+
└─ onOpen(artifactId) → addCustomTab(FILE_ARTIFACT_TAB_NAME)
|
|
220
|
+
└─ FileArtifactPanel(列表 + 下载头,@select → setActiveArtifactId)
|
|
221
|
+
└─ ArtifactPreviewHost(loader + 分类型 renderer)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
分类型预览策略见 [FileArtifactPanel 预览机制](../components/message/file-artifact-panel#预览机制)。
|
|
225
|
+
|
|
226
|
+
## 设计特点
|
|
227
|
+
|
|
228
|
+
- **职责单一**:composable 不直接调用 `useCustomTab`,侧栏 Tab 打开逻辑由 `onOpen` 注入;也不做正文 fetch / iframe 渲染
|
|
229
|
+
- **ShallowRef 优先**:`activeArtifactId` 使用 `shallowRef`,避免不必要的深层响应式开销
|
|
230
|
+
- **Consumer 兜底**:`useArtifactPreviewConsumer` 无 Provider 时返回 `undefined`,文件卡片在无容器上下文时自动不可点击
|
|
231
|
+
- **与 useCustomTab 协作**:「文件产物」Tab 通过 `addCustomTab` 按需添加(`order: -1`、`closable: false`),会话无文件产物时由容器 `removeCustomTab` 清理
|
|
232
|
+
|
|
233
|
+
## 关联组件
|
|
234
|
+
|
|
235
|
+
- [ChatContainer](../components/setup/chat-container) — Provider 主场景,内置「文件产物」Tab
|
|
236
|
+
- [FileArtifactPanel](../components/message/file-artifact-panel) — 侧栏列表与预览 Host 挂载
|
|
237
|
+
- [AssistantMessage](../components/message/assistant-message) — 文件产物来源(`property.artifacts`)
|
|
@@ -28,6 +28,7 @@ function useMessageGroup(options: {
|
|
|
28
28
|
}): {
|
|
29
29
|
messageGroups: Ref<MessageGroup[]>;
|
|
30
30
|
executionGroups: ComputedRef<MessageGroup[]>;
|
|
31
|
+
sessionArtifacts: ComputedRef<SessionArtifact[]>;
|
|
31
32
|
pendingApprovalCount: ComputedRef<number>;
|
|
32
33
|
pendingApprovalTipText: ComputedRef<string>;
|
|
33
34
|
isShareMode: ShallowRef<boolean>;
|
|
@@ -116,6 +117,28 @@ const isExecutionMessage = (m: Message): boolean => {
|
|
|
116
117
|
| toolCall | `function.name`、`mcpName`、`description`、`arguments`、`id` |
|
|
117
118
|
| flow_agent | 各任务 `task_name`、各节点 `name` |
|
|
118
119
|
|
|
120
|
+
## sessionArtifacts 会话级文件产物
|
|
121
|
+
|
|
122
|
+
`sessionArtifacts` 拍平当前会话所有 `AssistantMessage.property.artifacts`,供 `ChatContainer` 侧栏「文件产物」Tab 聚合预览。由于同一会话可能出现「多个 AssistantMessage + 同名文件」,文件名不可作为唯一键,统一用 `messageUid`(回退 `String(id)`)+ 消息内下标 + `outputId` 通过 [`buildArtifactId`](./use-artifact-preview) 生成全局唯一 `artifactId`:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const sessionArtifacts = computed(() => {
|
|
126
|
+
const list = [];
|
|
127
|
+
for (const message of messages.value) {
|
|
128
|
+
if (message.role !== MessageRole.Assistant) continue;
|
|
129
|
+
const artifacts = message.property?.artifacts;
|
|
130
|
+
if (!artifacts?.length) continue;
|
|
131
|
+
const messageUid = message.uid ?? String(message.id);
|
|
132
|
+
artifacts.forEach((file, index) => {
|
|
133
|
+
list.push({ ...file, artifactId: buildArtifactId(messageUid, index, file.outputId), messageUid });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return list;
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
预览命中与取链见 [useArtifactPreview](./use-artifact-preview);侧栏列表与分类型预览(`ArtifactPreviewHost`)见 [FileArtifactPanel](../components/message/file-artifact-panel)。
|
|
141
|
+
|
|
119
142
|
## 待审批统计
|
|
120
143
|
|
|
121
144
|
`useMessageGroup` 会统计消息列表中处于待审批状态的 AI Dev 审批中断:
|
|
@@ -186,6 +209,7 @@ const {
|
|
|
186
209
|
| ---------------- | ----------------------------- | --------------------------------------------------------------------------- |
|
|
187
210
|
| messageGroups | `Ref<MessageGroup[]>` | 完整消息分组列表 |
|
|
188
211
|
| executionGroups | `ComputedRef<MessageGroup[]>` | 仅包含执行类消息的分组(工具调用 + FlowAgent),自动提取 `userMessageTitle` |
|
|
212
|
+
| sessionArtifacts | `ComputedRef<SessionArtifact[]>` | 拍平会话所有 AssistantMessage 文件产物,含全局唯一 `artifactId` |
|
|
189
213
|
| pendingApprovalCount | `ComputedRef<number>` | 当前消息中待审批 AI Dev 审批中断的数量 |
|
|
190
214
|
| pendingApprovalTipText | `ComputedRef<string>` | 待审批阻塞发送提示文案;无待审批时为空字符串 |
|
|
191
215
|
| isShareMode | `ShallowRef<boolean>` | 是否处于分享模式 |
|