@blueking/chat-x 0.0.49-beta.1 → 0.0.49-beta.2

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 (35) hide show
  1. package/dist/ag-ui/types/constants.d.ts +1 -0
  2. package/dist/ag-ui/types/file.d.ts +1 -1
  3. package/dist/components/chat-message/assistant-message/assistant-message.vue.d.ts +12 -1
  4. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-file-card.vue.d.ts +0 -2
  5. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/artifact-preview-host.vue.d.ts +1 -2
  6. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/use-artifact-preview-loader.d.ts +4 -2
  7. package/dist/components/chat-message/assistant-message/message-artifacts/message-artifacts.vue.d.ts +0 -1
  8. package/dist/components/chat-message/interrupt-message/user-question/use-user-question.d.ts +7 -2
  9. package/dist/components/chat-message/user-message/user-message.vue.d.ts +14 -2
  10. package/dist/composables/use-artifact-preview.d.ts +17 -20
  11. package/dist/composables/use-custom-tab.d.ts +2 -0
  12. package/dist/composables/use-message-group.d.ts +1 -2
  13. package/dist/index.css +1 -1
  14. package/dist/index.js +514 -469
  15. package/dist/index.js.map +1 -1
  16. package/dist/lang/lang.d.ts +3 -1
  17. package/dist/mcp/generated/docs/assistant-message.md +1 -1
  18. package/dist/mcp/generated/docs/chat-container.md +7 -6
  19. package/dist/mcp/generated/docs/chat-input.md +2 -10
  20. package/dist/mcp/generated/docs/constants.md +3 -1
  21. package/dist/mcp/generated/docs/content-render.md +2 -10
  22. package/dist/mcp/generated/docs/file-artifact-panel.md +32 -36
  23. package/dist/mcp/generated/docs/info-message.md +6 -5
  24. package/dist/mcp/generated/docs/interrupt.md +1 -0
  25. package/dist/mcp/generated/docs/message-container.md +4 -21
  26. package/dist/mcp/generated/docs/message-render.md +4 -27
  27. package/dist/mcp/generated/docs/messages.md +5 -0
  28. package/dist/mcp/generated/docs/reasoning-message.md +2 -9
  29. package/dist/mcp/generated/docs/toolcall-render.md +8 -8
  30. package/dist/mcp/generated/docs/use-artifact-preview.md +45 -50
  31. package/dist/mcp/generated/docs/use-custom-tab.md +18 -5
  32. package/dist/mcp/generated/docs/use-message-group.md +13 -8
  33. package/dist/mcp/generated/docs/user-question-card.md +6 -3
  34. package/dist/mcp/generated/index.json +7 -7
  35. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  <!-- AI SUMMARY -->
2
2
  ## 快速了解
3
3
 
4
- useArtifactPreviewProvider 维护 activeArtifactIdopenPreview 命中文件并触发 onOpen 打开侧栏 Tab; 并通过 getOnArtifactClick 封装 resolveArtifactUrls(按 outputId 缓存 download_url / preview_url); useArtifactPreviewConsumer 在后代注入同一套 API。buildArtifactId messageUid#index#outputId 生成唯一 id。 正文加载与分类型渲染不在本 composable,由 FileArtifactPanel 内 ArtifactPreviewHost 完成。 FILE_ARTIFACT_TAB_NAME 标识固定「文件产物」Tab。
4
+ useArtifactPreviewProvider 维护 activeArtifactId(值为 outputId),openPreview 命中文件并触发 onOpen 打开侧栏 Tab; 并通过 getOnArtifactClick 封装 resolveArtifactUrls(TTL 8 分钟缓存;常规只传 file,force 可强制刷新,并发去重); 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` 中创建,负责维护当前命中的文件 id;Consumer 在深层 `ArtifactFileCard` 中注入,用于点击卡片触发预览。
18
+ Provider/Consumer 模式的文件产物预览状态管理。Provider 在 `ChatContainer` 中创建,负责维护当前命中的文件 `outputId`;Consumer 在深层 `ArtifactFileCard` 中注入,用于点击卡片触发预览。
19
19
 
20
20
  **职责边界**:
21
21
 
22
- - **本 composable**:维护「命中文件」与「URL 解析缓存」;打开侧栏 Tab(`addCustomTab`)由容器通过 `onOpen` 注入
23
- - **不在本 composable**:聚合会话文件列表、渲染预览面板、按类型 fetch 正文 / iframe 展示 —— 分别由 `useMessageGroup.sessionArtifacts`、`FileArtifactPanel`、内部 `ArtifactPreviewHost` + `useArtifactPreviewLoader` 承担
22
+ - **本 composable**:维护「命中文件」与「URL 解析」(TTL 缓存 + 并发去重;常规 `resolveArtifactUrls(file)`,重试可 `force` 刷新);打开侧栏 Tab(`addCustomTab`)由容器通过 `onOpen` 注入
23
+ - **不在本 composable**:聚合会话文件列表、渲染预览面板、按类型 fetch 正文 / iframe 展示 —— 分别由 `useMessageGroup.sessionArtifacts`、`FileArtifactPanel`、内部 `ArtifactPreviewHost` + `useArtifactPreviewLoader` 承担(预览重载键为 `outputId:type`)
24
24
 
25
25
  ## 函数签名
26
26
 
@@ -31,12 +31,12 @@ function useArtifactPreviewProvider(options: {
31
31
  /** 读取业务侧异步取链回调(getter 保持对 props 变更敏感) */
32
32
  getOnArtifactClick?: () => OnArtifactClick | undefined;
33
33
  /** 命中文件后触发:由容器负责 addCustomTab + 展开侧栏 + 选中 Tab */
34
- onOpen: (artifactId: string) => void;
34
+ onOpen: (outputId: string) => void;
35
35
  }): {
36
36
  activeArtifactId: ShallowRef<string>;
37
37
  canResolveArtifactUrl: ComputedRef<boolean>;
38
38
  openPreview: (payload: OpenArtifactPreviewPayload) => void;
39
- resolveArtifactUrls: (file: AIFileInfo) => Promise<ArtifactUrlResult>;
39
+ resolveArtifactUrls: (file: AIFileInfo, options?: { force?: boolean }) => Promise<ArtifactUrlResult>;
40
40
  setActiveArtifactId: (id: string) => void;
41
41
  };
42
42
  ```
@@ -50,18 +50,11 @@ function useArtifactPreviewConsumer():
50
50
  activeArtifactId: Ref<string>;
51
51
  canResolveArtifactUrl: ComputedRef<boolean>;
52
52
  openPreview: (payload: OpenArtifactPreviewPayload) => void;
53
- resolveArtifactUrls: (file: AIFileInfo) => Promise<ArtifactUrlResult>;
53
+ resolveArtifactUrls: (file: AIFileInfo, options?: { force?: boolean }) => Promise<ArtifactUrlResult>;
54
54
  setActiveArtifactId: (id: string) => void;
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,13 +70,14 @@ 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
 
85
78
  const { activeArtifactId, setActiveArtifactId } = useArtifactPreviewProvider({
86
79
  getOnArtifactClick: () => props.onArtifactClick,
80
+ // 点击文件卡片:展开侧栏并选中「文件产物」Tab
87
81
  onOpen: () => {
88
82
  addCustomTab({
89
83
  closable: false,
@@ -94,13 +88,23 @@ const { activeArtifactId, setActiveArtifactId } = useArtifactPreviewProvider({
94
88
  },
95
89
  });
96
90
 
97
- // 无文件产物时清理 Tab 与命中态
91
+ // 有产物时静默挂上 Tab(不抢焦点);无产物时清理
98
92
  watch(sessionArtifacts, list => {
99
93
  if (!list.length) {
100
94
  removeCustomTab(FILE_ARTIFACT_TAB_NAME);
101
95
  setActiveArtifactId('');
96
+ return;
102
97
  }
103
- });
98
+ ensureCustomTab({
99
+ closable: false,
100
+ label: t('文件产物'),
101
+ name: FILE_ARTIFACT_TAB_NAME,
102
+ order: -1,
103
+ });
104
+ if (!list.some(item => item.outputId === activeArtifactId.value)) {
105
+ setActiveArtifactId(list[0].outputId);
106
+ }
107
+ }, { immediate: true });
104
108
  ```
105
109
 
106
110
  ### Consumer(ArtifactFileCard)
@@ -118,18 +122,14 @@ const handleCardClick = () => {
118
122
  props.onPreview(props.file);
119
123
  return;
120
124
  }
121
- artifactPreview?.openPreview({
122
- file: props.file,
123
- index: props.index ?? 0,
124
- messageUid: props.messageUid ?? '',
125
- });
125
+ artifactPreview?.openPreview({ file: props.file });
126
126
  };
127
127
  ```
128
128
 
129
129
  ### 侧栏列表内切换命中文件
130
130
 
131
131
  ```typescript
132
- // FileArtifactPanel 列表点击 → emit select → 容器调用 setActiveArtifactId
132
+ // FileArtifactPanel 列表点击 → emit select(outputId) → 容器调用 setActiveArtifactId
133
133
  // 右侧预览由面板内 ArtifactPreviewHost 消费 activeArtifact,自行取链并按类型渲染
134
134
  <FileArtifactPanel
135
135
  :active-id="activeArtifactId"
@@ -163,32 +163,27 @@ const onArtifactClick = async (file: AIFileInfo) => {
163
163
 
164
164
  | 属性/方法名 | 类型 | 说明 |
165
165
  | ------------------- | ----------------------------------------- | -------------------------------------------------------------------- |
166
- | activeArtifactId | `ShallowRef<string>` | 当前命中的文件 id(`messageUid#index#outputId`) |
166
+ | activeArtifactId | `ShallowRef<string>` | 当前命中的文件 `outputId` |
167
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;侧栏列表内切换选中时使用 |
168
+ | openPreview | `(payload: OpenArtifactPreviewPayload) => void` | 由文件卡片触发:以 `file.outputId` 更新命中态、调用 `onOpen` |
169
+ | resolveArtifactUrls | `(file: AIFileInfo, options?: { force?: boolean }) => Promise<ArtifactUrlResult>` | 调用 `onArtifactClick` 取链;成功结果按 `outputId` 缓存 8 分钟;**常规调用只传 `file`**,仅预览重试 / 强刷传 `{ force: true }`(不要传 `undefined` 作为第二参);并发去重 |
170
+ | setActiveArtifactId | `(id: string) => void` | 直接设置命中文件 `outputId`;侧栏列表内切换选中时使用 |
171
171
 
172
172
  ## 类型定义
173
173
 
174
174
  ```typescript
175
175
  import type { AIFileInfo, ArtifactUrlResult, OnArtifactClick } from '@blueking/chat-x';
176
176
 
177
- /** 打开预览时的入参:文件 + 消息内下标 + 所属消息 uid */
177
+ /** 打开预览时的入参 */
178
178
  type OpenArtifactPreviewPayload = {
179
179
  file: AIFileInfo;
180
- index: number;
181
- messageUid: string;
182
180
  };
183
181
 
184
182
  /**
185
- * 会话级文件产物:在 AIFileInfo 基础上补充命中所需的唯一 id 与所属消息。
186
- * 同一会话可能出现多个 AssistantMessage + 同名文件,文件名不可作唯一键。
183
+ * 会话级文件产物:以 outputId 为会话内唯一键(同 outputId 视为同一文件)。
184
+ * 拍平去重后即为 AIFileInfo,此处用别名标明语义。
187
185
  */
188
- type SessionArtifact = AIFileInfo & {
189
- artifactId: string;
190
- messageUid: string;
191
- };
186
+ type SessionArtifact = AIFileInfo;
192
187
 
193
188
  /** onArtifactClick 返回值(snake_case) */
194
189
  type ArtifactUrlResult = {
@@ -197,28 +192,27 @@ type ArtifactUrlResult = {
197
192
  };
198
193
  ```
199
194
 
200
- ## 唯一 id 规则
195
+ ## 唯一键规则
201
196
 
202
- 同一会话可能存在多个 `AssistantMessage`,且不同消息里可能有同名文件,因此**文件名不可作为唯一键**。Provider 侧聚合与 Consumer 侧透传必须使用同一 `buildArtifactId` 规则:
197
+ 会话内以 **`outputId`** 作为文件产物唯一键:
203
198
 
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`
199
+ - 同一 `outputId` 在多条 `AssistantMessage` 中出现时,`sessionArtifacts` 去重并保留**最后一次**出现的文件信息
200
+ - `activeArtifactId`、列表 `:key`、`select` 事件参数均使用 `outputId`
201
+ - 文件名可能重复,**不可**作为唯一键
211
202
 
212
203
  ## 完整触发链路
213
204
 
214
205
  ```
215
206
  ArtifactFileCard(点击)
216
- └─ useArtifactPreviewConsumer().openPreview({ file, index, messageUid })
207
+ └─ useArtifactPreviewConsumer().openPreview({ file })
217
208
  └─ useArtifactPreviewProvider(ChatContainer)
218
- ├─ activeArtifactId = buildArtifactId(...)
219
- └─ onOpen(artifactId) → addCustomTab(FILE_ARTIFACT_TAB_NAME)
209
+ ├─ activeArtifactId = file.outputId
210
+ └─ onOpen(outputId) → addCustomTab(FILE_ARTIFACT_TAB_NAME) 展开并选中
220
211
  └─ FileArtifactPanel(列表 + 下载头,@select → setActiveArtifactId)
221
212
  └─ ArtifactPreviewHost(loader + 分类型 renderer)
213
+
214
+ sessionArtifacts 变化(有产物)
215
+ └─ ensureCustomTab(FILE_ARTIFACT_TAB_NAME) 静默挂上,不抢当前选中(如执行情况)
222
216
  ```
223
217
 
224
218
  分类型预览策略见 [FileArtifactPanel 预览机制](../components/message/file-artifact-panel#预览机制)。
@@ -228,10 +222,11 @@ ArtifactFileCard(点击)
228
222
  - **职责单一**:composable 不直接调用 `useCustomTab`,侧栏 Tab 打开逻辑由 `onOpen` 注入;也不做正文 fetch / iframe 渲染
229
223
  - **ShallowRef 优先**:`activeArtifactId` 使用 `shallowRef`,避免不必要的深层响应式开销
230
224
  - **Consumer 兜底**:`useArtifactPreviewConsumer` 无 Provider 时返回 `undefined`,文件卡片在无容器上下文时自动不可点击
231
- - **与 useCustomTab 协作**:「文件产物」Tab 通过 `addCustomTab` 按需添加(`order: -1`、`closable: false`),会话无文件产物时由容器 `removeCustomTab` 清理
225
+ - **与 useCustomTab 协作**:点击卡片走 `addCustomTab`(展开 + 选中);会话已有产物时走 `ensureCustomTab`(只挂载,不抢焦点);无文件产物时由容器 `removeCustomTab` 清理
232
226
 
233
227
  ## 关联组件
234
228
 
235
229
  - [ChatContainer](../components/setup/chat-container) — Provider 主场景,内置「文件产物」Tab
236
230
  - [FileArtifactPanel](../components/message/file-artifact-panel) — 侧栏列表与预览 Host 挂载
237
- - [AssistantMessage](../components/message/assistant-message) — 文件产物来源(`property.artifacts`)
231
+ - [AssistantMessage](../components/message/assistant-message) — 文件产物来源(`property.artifacts`)
232
+ - [useCustomTab](./use-custom-tab) — `addCustomTab` / `ensureCustomTab` 分工
@@ -1,7 +1,7 @@
1
1
  <!-- AI SUMMARY -->
2
2
  ## 快速了解
3
3
 
4
- useCustomTabProvider 返回 tabs、selectedTab、isCollapse 及 add/remove/selectCustomTab,并通过 provide 共享;可选 onTabChange 在切换时拉取数据。 useCustomTabConsumer 在后代注入同一套 API,常用于侧栏动态节点详情等。EXECUTION_TAB_NAME 标识默认「执行情况」Tab。 ChatContainer 侧栏集成 Provider 与 Tab UI。
4
+ useCustomTabProvider 返回 tabs、selectedTab、isCollapse 及 add/ensure/remove/selectCustomTab,并通过 provide 共享;可选 onTabChange 在切换时拉取数据。 ensureCustomTab 只挂载/合并元信息,不展开侧栏、不切换选中;addCustomTab 会展开并选中。 useCustomTabConsumer 在后代注入同一套 API,常用于侧栏动态节点详情等。EXECUTION_TAB_NAME 标识默认「执行情况」Tab。 ChatContainer 侧栏集成 Provider 与 Tab UI。
5
5
 
6
6
  ### 关联组件
7
7
  - **chat-container** — Provider 与侧栏 Tab 主场景
@@ -30,6 +30,7 @@ function useCustomTabProvider<T extends Record<string, unknown>>(options: {
30
30
  selectedTab: Ref<CustomTab<T>>;
31
31
  isCollapse: ShallowRef<boolean>;
32
32
  addCustomTab: (tab: CustomTab<T>) => void;
33
+ ensureCustomTab: (tab: CustomTab<T>) => void;
33
34
  removeCustomTab: (tabName: string) => void;
34
35
  selectCustomTab: (tab: CustomTab<T>) => void;
35
36
  resetCustomTab: () => void;
@@ -46,6 +47,7 @@ function useCustomTabConsumer<T extends Record<string, unknown>>():
46
47
  displayTabs: ComputedRef<CustomTab<T>[]>;
47
48
  selectedTab: ShallowRef<CustomTab<T> | null>;
48
49
  addCustomTab: (tab: CustomTab<T>) => void;
50
+ ensureCustomTab: (tab: CustomTab<T>) => void;
49
51
  removeCustomTab: (tabName: string) => void;
50
52
  selectCustomTab: (tab: CustomTab<T>) => void;
51
53
  resetCustomTab: () => void;
@@ -59,7 +61,7 @@ function useCustomTabConsumer<T extends Record<string, unknown>>():
59
61
  ```typescript
60
62
  import { useCustomTabProvider, EXECUTION_TAB_NAME } from '@blueking/chat-x';
61
63
 
62
- const { tabs, selectedTab, isCollapse, addCustomTab, removeCustomTab, selectCustomTab, resetCustomTab } =
64
+ const { tabs, selectedTab, isCollapse, addCustomTab, ensureCustomTab, removeCustomTab, selectCustomTab, resetCustomTab } =
63
65
  useCustomTabProvider({
64
66
  onTabChange: async tab => {
65
67
  // Tab 切换时加载数据
@@ -76,7 +78,7 @@ import { useCustomTabConsumer } from '@blueking/chat-x';
76
78
 
77
79
  const tabManager = useCustomTabConsumer();
78
80
 
79
- // 添加一个自定义 Tab
81
+ // 添加一个自定义 Tab(展开侧栏并选中)
80
82
  tabManager?.addCustomTab({
81
83
  name: 'node-detail-123',
82
84
  label: '节点详情',
@@ -86,6 +88,14 @@ tabManager?.addCustomTab({
86
88
  },
87
89
  });
88
90
 
91
+ // 仅确保 Tab 存在(不展开、不切换选中)—— 如侧栏已因执行情况打开时同步挂上文件产物
92
+ tabManager?.ensureCustomTab({
93
+ name: 'file-artifact',
94
+ label: '文件产物',
95
+ closable: false,
96
+ order: -1,
97
+ });
98
+
89
99
  // 移除 Tab
90
100
  tabManager?.removeCustomTab('node-detail-123');
91
101
  ```
@@ -106,7 +116,8 @@ tabManager?.removeCustomTab('node-detail-123');
106
116
  | displayTabs | `ComputedRef<CustomTab[]>` | Tab 栏实际展示列表:过滤 `visible === false`,按 `order` 升序稳定排序 |
107
117
  | selectedTab | `Ref<CustomTab>` | 当前选中的 Tab;选中项被隐藏时自动回退到首个可见 Tab |
108
118
  | isCollapse | `ShallowRef<boolean>` | 侧边栏折叠状态;`addCustomTab` 时自动设为 `false` |
109
- | addCustomTab | `(tab: CustomTab) => void` | 添加 Tab;同名 Tab 合并更新(可改 `order` / `visible` / `label`) |
119
+ | addCustomTab | `(tab: CustomTab) => void` | 添加/合并 Tab,**展开侧栏并选中**目标 Tab |
120
+ | ensureCustomTab | `(tab: CustomTab) => void` | 添加/合并 Tab,**不展开、不切换选中**;用于静默挂载(如文件产物) |
110
121
  | removeCustomTab | `(tabName: string) => void` | 移除指定 Tab |
111
122
  | selectCustomTab | `(tab: CustomTab) => void` | 切换到指定 Tab,触发 `onTabChange` 回调 |
112
123
  | resetCustomTab | `() => void` | 重置为仅保留「执行情况」Tab、折叠侧栏并选中默认 Tab;`ChatContainer` 在卸载时调用,避免残留自定义 Tab |
@@ -136,7 +147,9 @@ interface CustomTab<T = Record<string, unknown>> {
136
147
  - 排序为稳定排序,`order` 相同的 Tab 保持插入先后顺序
137
148
  - 选中的 Tab 被配置隐藏时,内容不再渲染,自动切换到首个可见 Tab
138
149
  - `addCustomTab` 同时展开侧边栏(`isCollapse = false`)并在 `nextTick` 后自动选中目标 Tab;同名 Tab 合并更新
150
+ - `ensureCustomTab` 与 `addCustomTab` 共用合并逻辑,但不改 `isCollapse`、不切换 `selectedTab`;适合「侧栏已打开时同步挂上文件产物」等场景
139
151
 
140
152
  ## 关联组件
141
153
 
142
- - [ChatContainer](../components/setup/chat-container) — 侧栏 Tab 与自定义面板
154
+ - [ChatContainer](../components/setup/chat-container) — 侧栏 Tab 与自定义面板
155
+ - [useArtifactPreview](./use-artifact-preview) — 文件产物 Tab:有产物时 `ensureCustomTab`,点击卡片时 `addCustomTab`
@@ -119,24 +119,29 @@ const isExecutionMessage = (m: Message): boolean => {
119
119
 
120
120
  ## sessionArtifacts 会话级文件产物
121
121
 
122
- `sessionArtifacts` 拍平当前会话所有 `AssistantMessage.property.artifacts`,供 `ChatContainer` 侧栏「文件产物」Tab 聚合预览。由于同一会话可能出现「多个 AssistantMessage + 同名文件」,文件名不可作为唯一键,统一用 `messageUid`(回退 `String(id)`)+ 消息内下标 + `outputId` 通过 [`buildArtifactId`](./use-artifact-preview) 生成全局唯一 `artifactId`:
122
+ `sessionArtifacts` 拍平当前会话所有 `AssistantMessage.property.artifacts`,供 `ChatContainer` 侧栏「文件产物」Tab 聚合预览。以 **`outputId`** 为会话内唯一键去重(同 `outputId` 视为同一文件),保留最后一次出现的文件信息,列表顺序与「最后一次出现」的相对顺序一致:
123
123
 
124
124
  ```typescript
125
125
  const sessionArtifacts = computed(() => {
126
- const list = [];
126
+ // delete + set:同 key 覆盖内容,并把该项挪到 Map 末尾,保证「最后出现」顺序
127
+ const byOutputId = new Map();
127
128
  for (const message of messages.value) {
128
129
  if (message.role !== MessageRole.Assistant) continue;
129
130
  const artifacts = message.property?.artifacts;
130
131
  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
- });
132
+ for (const file of artifacts) {
133
+ if (byOutputId.has(file.outputId)) {
134
+ byOutputId.delete(file.outputId);
135
+ }
136
+ byOutputId.set(file.outputId, file);
137
+ }
135
138
  }
136
- return list;
139
+ return Array.from(byOutputId.values());
137
140
  });
138
141
  ```
139
142
 
143
+ > `SessionArtifact` 即为 `AIFileInfo` 别名;文件名可能重复,不可作唯一键。
144
+
140
145
  预览命中与取链见 [useArtifactPreview](./use-artifact-preview);侧栏列表与分类型预览(`ArtifactPreviewHost`)见 [FileArtifactPanel](../components/message/file-artifact-panel)。
141
146
 
142
147
  ## 待审批统计
@@ -209,7 +214,7 @@ const {
209
214
  | ---------------- | ----------------------------- | --------------------------------------------------------------------------- |
210
215
  | messageGroups | `Ref<MessageGroup[]>` | 完整消息分组列表 |
211
216
  | executionGroups | `ComputedRef<MessageGroup[]>` | 仅包含执行类消息的分组(工具调用 + FlowAgent),自动提取 `userMessageTitle` |
212
- | sessionArtifacts | `ComputedRef<SessionArtifact[]>` | 拍平会话所有 AssistantMessage 文件产物,含全局唯一 `artifactId` |
217
+ | sessionArtifacts | `ComputedRef<SessionArtifact[]>` | 拍平会话所有 AssistantMessage 文件产物,按 `outputId` 去重(保留最后一次) |
213
218
  | pendingApprovalCount | `ComputedRef<number>` | 当前消息中待审批 AI Dev 审批中断的数量 |
214
219
  | pendingApprovalTipText | `ComputedRef<string>` | 待审批阻塞发送提示文案;无待审批时为空字符串 |
215
220
  | isShareMode | `ShallowRef<boolean>` | 是否处于分享模式 |
@@ -1,7 +1,7 @@
1
1
  <!-- AI SUMMARY -->
2
2
  ## 快速了解
3
3
 
4
- 渲染 UserQuestion 中断的待回答面板,支持单选、多选、Others 与跳过。 源码位置:src/components/chat-message/interrupt-message/user-question/user-question-card.vue。
4
+ 渲染 UserQuestion 中断的待回答面板:一次只展示一题,标题栏提供题号切换, Footer 展示已完成进度;支持单选自动跳下一题、多选/Others、完成与跳过。 源码位置:src/components/chat-message/interrupt-message/user-question/user-question-card.vue。
5
5
 
6
6
  ### 关联组件
7
7
  - **interrupt-message** — outcome.success 时挂载 UserQuestionAnsweredCard 回显回答
@@ -16,7 +16,7 @@
16
16
 
17
17
  - **源码位置**:`src/components/chat-message/interrupt-message/user-question/user-question-card.vue`
18
18
  - **能力域**:Agent 能力
19
- - **能力说明**:渲染 UserQuestion 中断的待回答面板,支持单选、多选、Others 与跳过。
19
+ - **能力说明**:渲染 UserQuestion 中断的待回答面板;一次一题分页切换,支持单选/多选、Others、跳过与已完成进度。
20
20
 
21
21
  > **能力域**:Agent 能力
22
22
 
@@ -24,6 +24,9 @@
24
24
 
25
25
  ## 交互能力
26
26
 
27
+ - **一次一题**:正文只展示当前题;标题栏右侧提供 `< 当前题 / 总题数 >` 切换定位(单题为 `1 / 1`),首末题对应箭头禁用。
28
+ - **自动跳转**:单选预设选项从未答变为有效答时,自动跳到下一题(最后一题停留);多选与 Others 不自动跳,靠箭头切换。
29
+ - **已完成进度**:Footer 左侧展示「已完成 N 题」;右侧为「跳过」+「完成」。
27
30
  - **单选 / 多选**:每道题通过 `multiSelect` 控制选择行为;未传时不展示单选/多选标签,默认仍按单选处理。
28
31
  - **Others 自由输入**:默认 [UserQuestionChoice](/components/agent/user-question-choice) 为每道题追加 `label: 'others'` 输入项,输入文本写入 `answer[].description`。
29
32
  - **自定义作答形态**:通过 `#question` slot 可替换默认选择题,渲染任意表单;作答有效时调用 `setAnswer` 回传 `UserQuestionAnswerItem`,无效时传 `undefined`。
@@ -142,7 +145,7 @@ const payload = {
142
145
  />
143
146
  ```
144
147
 
145
- - 卡片内「完成 / 跳过」→ `onInterruptResume(payload, interrupt)`
148
+ - 卡片内「跳过 / 完成」→ `onInterruptResume(payload, interrupt)`(Footer 右置,跳过在前、完成在后)
146
149
  - 输入框直接发送 → `onSendMessage(content, docSchema, { interrupt, payload })`,其中 `payload` 由 `buildSkipResumePayload(interrupt)` 生成(`status: 'cancelled'`)
147
150
 
148
151
  ## 工具函数 buildSkipResumePayload
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "2.0.0",
3
- "generatedAt": "2026-07-29T07:29:01.699Z",
3
+ "generatedAt": "2026-08-06T04:13:41.238Z",
4
4
  "domains": {
5
5
  "setup": {
6
6
  "label": "对话搭建",
@@ -362,8 +362,8 @@
362
362
  "name": "UserQuestionCard 用户问题中断",
363
363
  "slug": "user-question-card",
364
364
  "kind": "component",
365
- "description": "渲染 UserQuestion 中断的待回答面板,支持单选、多选、Others 与跳过。",
366
- "aiSummary": "渲染 UserQuestion 中断的待回答面板,支持单选、多选、Others 与跳过。 源码位置:src/components/chat-message/interrupt-message/user-question/user-question-card.vue。",
365
+ "description": "渲染 UserQuestion 中断的待回答面板;一次一题分页切换,支持单选/多选、Others、跳过与已完成进度。",
366
+ "aiSummary": "渲染 UserQuestion 中断的待回答面板:一次只展示一题,标题栏提供题号切换, Footer 展示已完成进度;支持单选自动跳下一题、多选/Others、完成与跳过。 源码位置:src/components/chat-message/interrupt-message/user-question/user-question-card.vue。",
367
367
  "relatedComponents": [
368
368
  {
369
369
  "slug": "interrupt-message",
@@ -1115,7 +1115,7 @@
1115
1115
  "slug": "file-artifact-panel",
1116
1116
  "kind": "component",
1117
1117
  "description": "汇总当前会话全部文件产物,支持搜索、选中与分类型预览,挂载在 ChatContainer 侧栏「文件产物」Tab。",
1118
- "aiSummary": "汇总当前会话所有 AssistantMessage 的 artifacts,支持关键词搜索、列表选中与下载; 预览区委托 ArtifactPreviewHost:按类型走 text_from_download(html / markdown / md / txt / json) 或 preview_url_iframe(其余类型);download_url / preview_url 经 onArtifactClick 异步获取。 源码位置:src/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue。",
1118
+ "aiSummary": "汇总当前会话所有 AssistantMessage 的 artifacts(按 outputId 去重),支持关键词搜索、列表选中与下载; 预览区委托 ArtifactPreviewHost:按类型走 text_from_download(html / markdown / md / txt / json) 或 preview_url_iframe(其余类型);download_url / preview_url 经 onArtifactClick 异步获取(TTL 缓存,重试 force); 预览重载键为 outputId:type;常规 resolveArtifactUrls 只传 file。 源码位置:src/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue。",
1119
1119
  "relatedComponents": [
1120
1120
  {
1121
1121
  "slug": "assistant-message",
@@ -1586,7 +1586,7 @@
1586
1586
  "slug": "use-artifact-preview",
1587
1587
  "kind": "composable",
1588
1588
  "description": "Provider/Consumer 模式的文件产物预览状态管理,用于 ChatContainer 侧栏「文件产物」Tab 的命中与切换。 Provider 在 ChatContainer 中创建,Consumer 在深层文件卡片中注入使用。",
1589
- "aiSummary": "useArtifactPreviewProvider 维护 activeArtifactIdopenPreview 命中文件并触发 onOpen 打开侧栏 Tab; 并通过 getOnArtifactClick 封装 resolveArtifactUrls(按 outputId 缓存 download_url / preview_url); useArtifactPreviewConsumer 在后代注入同一套 API。buildArtifactId messageUid#index#outputId 生成唯一 id。 正文加载与分类型渲染不在本 composable,由 FileArtifactPanel 内 ArtifactPreviewHost 完成。 FILE_ARTIFACT_TAB_NAME 标识固定「文件产物」Tab。",
1589
+ "aiSummary": "useArtifactPreviewProvider 维护 activeArtifactId(值为 outputId),openPreview 命中文件并触发 onOpen 打开侧栏 Tab; 并通过 getOnArtifactClick 封装 resolveArtifactUrls(TTL 8 分钟缓存;常规只传 file,force 可强制刷新,并发去重); useArtifactPreviewConsumer 在后代注入同一套 API。SessionArtifact AIFileInfo,会话内以 outputId 为唯一键。 正文加载与分类型渲染不在本 composable,由 FileArtifactPanel 内 ArtifactPreviewHost 完成(重载键 outputId:type)。 FILE_ARTIFACT_TAB_NAME 标识固定「文件产物」Tab。",
1590
1590
  "relatedComponents": [
1591
1591
  {
1592
1592
  "slug": "chat-container",
@@ -1666,7 +1666,7 @@
1666
1666
  "slug": "use-custom-tab",
1667
1667
  "kind": "composable",
1668
1668
  "description": "Provider/Consumer 模式的自定义 Tab 管理,用于 `ChatContainer` 侧边栏的 Tab 动态管理。Provider 在 `ChatContainer` 中创建,Consumer 在任意后代组件中注入使用。",
1669
- "aiSummary": "useCustomTabProvider 返回 tabs、selectedTab、isCollapse 及 add/remove/selectCustomTab,并通过 provide 共享;可选 onTabChange 在切换时拉取数据。 useCustomTabConsumer 在后代注入同一套 API,常用于侧栏动态节点详情等。EXECUTION_TAB_NAME 标识默认「执行情况」Tab。 ChatContainer 侧栏集成 Provider 与 Tab UI。",
1669
+ "aiSummary": "useCustomTabProvider 返回 tabs、selectedTab、isCollapse 及 add/ensure/remove/selectCustomTab,并通过 provide 共享;可选 onTabChange 在切换时拉取数据。 ensureCustomTab 只挂载/合并元信息,不展开侧栏、不切换选中;addCustomTab 会展开并选中。 useCustomTabConsumer 在后代注入同一套 API,常用于侧栏动态节点详情等。EXECUTION_TAB_NAME 标识默认「执行情况」Tab。 ChatContainer 侧栏集成 Provider 与 Tab UI。",
1670
1670
  "relatedComponents": [
1671
1671
  {
1672
1672
  "slug": "chat-container",
@@ -1787,7 +1787,7 @@
1787
1787
  "slug": "constants",
1788
1788
  "kind": "type",
1789
1789
  "description": "`@blueking/chat-x` 导出的常量和枚举类型。",
1790
- "aiSummary": "汇总 MessageRole、MessageStatus(含 Fetching 请求中)、MessageContentType、MessageToolsStatus、MessageState、Z-Index 与 CONST_MESSAGE_TOOLS 等导出常量。 用于构造消息、配置 MessageContainer 工具栏与输入态,以及层级与默认快捷指令。与类型 messages 配套使用。",
1790
+ "aiSummary": "汇总 MessageRole、MessageStatus(含 Fetching 请求中、Complete/Completed 完成态兼容)、MessageContentType、MessageToolsStatus、MessageState、Z-Index 与 CONST_MESSAGE_TOOLS 等导出常量。 用于构造消息、配置 MessageContainer 工具栏与输入态,以及层级与默认快捷指令。与类型 messages 配套使用。",
1791
1791
  "relatedComponents": [
1792
1792
  {
1793
1793
  "slug": "message-tools",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueking/chat-x",
3
- "version": "0.0.49-beta.1",
3
+ "version": "0.0.49-beta.2",
4
4
  "description": "蓝鲸智云 AI Chat 组件库 —— 遵循 AG-UI,为 AI Agent 和人类开发者共同设计的对话 UI 组件库。",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -79,7 +79,7 @@
79
79
  "vitepress": "2.0.0-alpha.16",
80
80
  "vitest": "^4.0.18",
81
81
  "vue-tsc": "^3.1.4",
82
- "@blueking/chat-helper": "0.0.12-beta.11"
82
+ "@blueking/chat-helper": "0.0.12-beta.14"
83
83
  },
84
84
  "scripts": {
85
85
  "dev": "vite --config vite.config.ts",