@blueking/chat-x 0.0.48-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.
Files changed (70) hide show
  1. package/dist/ag-ui/types/constants.d.ts +1 -0
  2. package/dist/ag-ui/types/file.d.ts +13 -0
  3. package/dist/ag-ui/types/index.d.ts +1 -0
  4. package/dist/ag-ui/types/messages.d.ts +4 -0
  5. package/dist/components/chat-message/assistant-message/assistant-message.vue.d.ts +12 -1
  6. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-file-card.vue.d.ts +12 -0
  7. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/artifact-preview-host.vue.d.ts +11 -0
  8. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/preview-strategy.d.ts +8 -0
  9. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/code-preview.vue.d.ts +8 -0
  10. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/html-preview.vue.d.ts +6 -0
  11. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/image-preview.vue.d.ts +7 -0
  12. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/markdown-preview.vue.d.ts +6 -0
  13. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/txt-preview.vue.d.ts +6 -0
  14. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/renderers/url-iframe-preview.vue.d.ts +6 -0
  15. package/dist/components/chat-message/assistant-message/message-artifacts/artifact-preview/use-artifact-preview-loader.d.ts +22 -0
  16. package/dist/components/chat-message/assistant-message/message-artifacts/file-artifact-panel.vue.d.ts +13 -0
  17. package/dist/components/chat-message/assistant-message/message-artifacts/message-artifacts.vue.d.ts +9 -0
  18. package/dist/components/chat-message/interrupt-message/user-question/use-user-question.d.ts +7 -2
  19. package/dist/components/chat-message/user-message/user-message.vue.d.ts +15 -2
  20. package/dist/components/file-icon/file-icon.vue.d.ts +7 -0
  21. package/dist/components/index.d.ts +3 -1
  22. package/dist/components/message-tools/message-time/format-message-time.d.ts +8 -0
  23. package/dist/components/message-tools/message-time/message-time.vue.d.ts +8 -0
  24. package/dist/components/message-tools/message-tools.vue.d.ts +11 -1
  25. package/dist/composables/index.d.ts +1 -0
  26. package/dist/composables/use-artifact-preview.d.ts +49 -0
  27. package/dist/composables/use-container-scroll.d.ts +9 -2
  28. package/dist/composables/use-custom-tab.d.ts +7 -3
  29. package/dist/composables/use-global-config.d.ts +3 -0
  30. package/dist/composables/use-message-group.d.ts +505 -0
  31. package/dist/icons/file-icons.d.ts +4 -0
  32. package/dist/icons/file.d.ts +6 -0
  33. package/dist/icons/index.d.ts +2 -0
  34. package/dist/icons/tools.d.ts +3 -0
  35. package/dist/index.css +1 -1
  36. package/dist/index.js +3203 -2368
  37. package/dist/index.js.map +1 -1
  38. package/dist/lang/lang.d.ts +9 -1
  39. package/dist/mcp/generated/docs/activity-message.md +122 -97
  40. package/dist/mcp/generated/docs/assistant-message.md +146 -75
  41. package/dist/mcp/generated/docs/chat-container.md +121 -38
  42. package/dist/mcp/generated/docs/chat-input.md +2 -10
  43. package/dist/mcp/generated/docs/constants.md +3 -1
  44. package/dist/mcp/generated/docs/content-render.md +2 -10
  45. package/dist/mcp/generated/docs/execution-summary.md +3 -3
  46. package/dist/mcp/generated/docs/file-artifact-panel.md +293 -0
  47. package/dist/mcp/generated/docs/file-icon.md +111 -0
  48. package/dist/mcp/generated/docs/info-message.md +29 -12
  49. package/dist/mcp/generated/docs/interrupt.md +1 -0
  50. package/dist/mcp/generated/docs/loading-message.md +36 -17
  51. package/dist/mcp/generated/docs/message-container.md +27 -23
  52. package/dist/mcp/generated/docs/message-render.md +47 -59
  53. package/dist/mcp/generated/docs/message-time.md +180 -0
  54. package/dist/mcp/generated/docs/message-tools.md +47 -12
  55. package/dist/mcp/generated/docs/messages.md +9 -0
  56. package/dist/mcp/generated/docs/reasoning-message.md +17 -20
  57. package/dist/mcp/generated/docs/tool-message.md +61 -45
  58. package/dist/mcp/generated/docs/toolcall-render.md +18 -10
  59. package/dist/mcp/generated/docs/use-artifact-preview.md +234 -0
  60. package/dist/mcp/generated/docs/use-container-scroll.md +6 -2
  61. package/dist/mcp/generated/docs/use-custom-tab.md +25 -8
  62. package/dist/mcp/generated/docs/use-global-config.md +15 -5
  63. package/dist/mcp/generated/docs/use-message-group.md +37 -5
  64. package/dist/mcp/generated/docs/user-message.md +191 -121
  65. package/dist/mcp/generated/docs/user-question-card.md +6 -3
  66. package/dist/mcp/generated/index.json +144 -18
  67. package/dist/mcp/index.js +0 -0
  68. package/dist/utils/file-type.d.ts +14 -0
  69. package/dist/utils/index.d.ts +1 -0
  70. package/package.json +20 -21
@@ -0,0 +1,234 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
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
+
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` 中创建,负责维护当前命中的文件 `outputId`;Consumer 在深层 `ArtifactFileCard` 中注入,用于点击卡片触发预览。
19
+
20
+ **职责边界**:
21
+
22
+ - **本 composable**:维护「命中文件」与「URL 解析」(每次重新取链 + 并发去重);打开侧栏 Tab(`addCustomTab`)由容器通过 `onOpen` 注入
23
+ - **不在本 composable**:聚合会话文件列表、渲染预览面板、按类型 fetch 正文 / iframe 展示 —— 分别由 `useMessageGroup.sessionArtifacts`、`FileArtifactPanel`、内部 `ArtifactPreviewHost` + `useArtifactPreviewLoader` 承担(预览重载键为 `outputId:type`)
24
+
25
+ ## 函数签名
26
+
27
+ ### useArtifactPreviewProvider
28
+
29
+ ```typescript
30
+ function useArtifactPreviewProvider(options: {
31
+ /** 读取业务侧异步取链回调(getter 保持对 props 变更敏感) */
32
+ getOnArtifactClick?: () => OnArtifactClick | undefined;
33
+ /** 命中文件后触发:由容器负责 addCustomTab + 展开侧栏 + 选中 Tab */
34
+ onOpen: (outputId: 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
+ ## 使用示例
59
+
60
+ ### Provider(ChatContainer)
61
+
62
+ 会话级文件产物由 [`useMessageGroup`](./use-message-group) 统一聚合(`sessionArtifacts`),Provider 只负责命中与打开侧栏 Tab:
63
+
64
+ ```typescript
65
+ import {
66
+ useArtifactPreviewProvider,
67
+ useCustomTabProvider,
68
+ useMessageGroup,
69
+ FILE_ARTIFACT_TAB_NAME,
70
+ } from '@blueking/chat-x';
71
+ import { t } from '@blueking/chat-x/lang';
72
+
73
+ const { addCustomTab, ensureCustomTab, removeCustomTab } = useCustomTabProvider({ /* ... */ });
74
+
75
+ // 会话级文件产物聚合已内聚在 useMessageGroup,直接消费
76
+ const { sessionArtifacts } = useMessageGroup({ keyword, messages, selectedUserMessages });
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
+
88
+ const { activeArtifactId, setActiveArtifactId } = useArtifactPreviewProvider({
89
+ getOnArtifactClick: () => props.onArtifactClick,
90
+ // 点击文件卡片:展开侧栏并选中「文件产物」Tab
91
+ onOpen: () => {
92
+ addCustomTab(FILE_ARTIFACT_TAB);
93
+ },
94
+ });
95
+
96
+ // 仅维护命中态:无产物清空,命中项失效时回落到第一个
97
+ watch(sessionArtifacts, list => {
98
+ if (!list.length) {
99
+ setActiveArtifactId('');
100
+ return;
101
+ }
102
+ if (!list.some(item => item.outputId === activeArtifactId.value)) {
103
+ setActiveArtifactId(list[0].outputId);
104
+ }
105
+ }, { immediate: true });
106
+ ```
107
+
108
+ ### Consumer(ArtifactFileCard)
109
+
110
+ ```typescript
111
+ import { useArtifactPreviewConsumer } from '@blueking/chat-x';
112
+
113
+ const artifactPreview = useArtifactPreviewConsumer();
114
+
115
+ // 有 Provider 时卡片可点击;无 Provider 时返回 undefined,卡片不可点击
116
+ const clickable = computed(() => !!props.onPreview || !!artifactPreview);
117
+
118
+ const handleCardClick = () => {
119
+ if (props.onPreview) {
120
+ props.onPreview(props.file);
121
+ return;
122
+ }
123
+ artifactPreview?.openPreview({ file: props.file });
124
+ };
125
+ ```
126
+
127
+ ### 侧栏列表内切换命中文件
128
+
129
+ ```typescript
130
+ // FileArtifactPanel 列表点击 → emit select(outputId) → 容器调用 setActiveArtifactId
131
+ // 右侧预览由面板内 ArtifactPreviewHost 消费 activeArtifact,自行取链并按类型渲染
132
+ <FileArtifactPanel
133
+ :active-id="activeArtifactId"
134
+ :artifacts="sessionArtifacts"
135
+ @select="setActiveArtifactId"
136
+ />
137
+ ```
138
+
139
+ ### 业务侧取链(ChatContainer `onArtifactClick`)
140
+
141
+ 文本类预览需要可 `fetch` 的 `download_url`;iframe 类需要 `preview_url`(一般为后台转好的 PDF):
142
+
143
+ ```typescript
144
+ const onArtifactClick = async (file: AIFileInfo) => {
145
+ const res = await api.getArtifactUrls(file.outputId);
146
+ return {
147
+ download_url: res.download_url,
148
+ preview_url: res.preview_url,
149
+ };
150
+ };
151
+ ```
152
+
153
+ ## 内置常量
154
+
155
+ | 常量名 | 值 | 说明 |
156
+ | ------------------------- | ------------------ | ----------------------------------------- |
157
+ | `FILE_ARTIFACT_TAB_NAME` | `'file-artifact'` | 「文件产物」侧栏 Tab 的固定标识,不可关闭 |
158
+ | `ARTIFACT_PREVIEW_TOKEN` | `Symbol` | provide/inject 注入 Token |
159
+
160
+ ## 返回值说明
161
+
162
+ | 属性/方法名 | 类型 | 说明 |
163
+ | ------------------- | ----------------------------------------- | -------------------------------------------------------------------- |
164
+ | activeArtifactId | `ShallowRef<string>` | 当前命中的文件 `outputId` |
165
+ | canResolveArtifactUrl | `ComputedRef<boolean>` | 是否具备异步取链能力(有 `onArtifactClick` 时为 true,下载按钮据此显隐) |
166
+ | openPreview | `(payload: OpenArtifactPreviewPayload) => void` | 由文件卡片触发:以 `file.outputId` 更新命中态、调用 `onOpen` |
167
+ | resolveArtifactUrls | `(file: AIFileInfo) => Promise<ArtifactUrlResult>` | 调用 `onArtifactClick` 取链;每次重新获取,不缓存;同文件并发去重 |
168
+ | setActiveArtifactId | `(id: string) => void` | 直接设置命中文件 `outputId`;侧栏列表内切换选中时使用 |
169
+
170
+ ## 类型定义
171
+
172
+ ```typescript
173
+ import type { AIFileInfo, ArtifactUrlResult, OnArtifactClick } from '@blueking/chat-x';
174
+
175
+ /** 打开预览时的入参 */
176
+ type OpenArtifactPreviewPayload = {
177
+ file: AIFileInfo;
178
+ };
179
+
180
+ /**
181
+ * 会话级文件产物:以 outputId 为会话内唯一键(同 outputId 视为同一文件)。
182
+ * 拍平去重后即为 AIFileInfo,此处用别名标明语义。
183
+ */
184
+ type SessionArtifact = AIFileInfo;
185
+
186
+ /** onArtifactClick 返回值(snake_case) */
187
+ type ArtifactUrlResult = {
188
+ download_url?: string;
189
+ preview_url?: string;
190
+ };
191
+ ```
192
+
193
+ ## 唯一键规则
194
+
195
+ 会话内以 **`outputId`** 作为文件产物唯一键:
196
+
197
+ - 同一 `outputId` 在多条 `AssistantMessage` 中出现时,`sessionArtifacts` 去重并保留**最后一次**出现的文件信息
198
+ - `activeArtifactId`、列表 `:key`、`select` 事件参数均使用 `outputId`
199
+ - 文件名可能重复,**不可**作为唯一键
200
+
201
+ ## 完整触发链路
202
+
203
+ ```
204
+ ArtifactFileCard(点击)
205
+ └─ useArtifactPreviewConsumer().openPreview({ file })
206
+ └─ useArtifactPreviewProvider(ChatContainer)
207
+ ├─ activeArtifactId = file.outputId
208
+ └─ onOpen(outputId) → addCustomTab(FILE_ARTIFACT_TAB_NAME) 展开并选中
209
+ └─ FileArtifactPanel(列表 + 下载头,@select → setActiveArtifactId)
210
+ └─ ArtifactPreviewHost(loader + 分类型 renderer)
211
+
212
+ 容器初始化
213
+ └─ ensureCustomTab(FILE_ARTIFACT_TAB_NAME) 常驻挂上(不展开侧栏);因 order:-1 排在首位,
214
+ 未主动切换过 Tab 时会成为默认选中面板;无产物时由面板展示整块空态
215
+
216
+ sessionArtifacts 变化
217
+ └─ 仅维护命中态(无产物清空,命中项失效回落第一个);不增删 Tab
218
+ ```
219
+
220
+ 分类型预览策略见 [FileArtifactPanel 预览机制](../components/message/file-artifact-panel#预览机制)。
221
+
222
+ ## 设计特点
223
+
224
+ - **职责单一**:composable 不直接调用 `useCustomTab`,侧栏 Tab 打开逻辑由 `onOpen` 注入;也不做正文 fetch / iframe 渲染
225
+ - **ShallowRef 优先**:`activeArtifactId` 使用 `shallowRef`,避免不必要的深层响应式开销
226
+ - **Consumer 兜底**:`useArtifactPreviewConsumer` 无 Provider 时返回 `undefined`,文件卡片在无容器上下文时自动不可点击
227
+ - **与 useCustomTab 协作**:点击卡片走 `addCustomTab`(展开 + 选中);容器初始化时走 `ensureCustomTab` 常驻挂载(不展开;因 `order: -1` 在未主动切换前会成为默认选中),无产物也不移除,由面板展示空态
228
+
229
+ ## 关联组件
230
+
231
+ - [ChatContainer](../components/setup/chat-container) — Provider 主场景,内置「文件产物」Tab
232
+ - [FileArtifactPanel](../components/message/file-artifact-panel) — 侧栏列表与预览 Host 挂载
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
- ├── toScrollBottom() → autoScrollEnabled=true + bottomRef.scrollIntoView('smooth')
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
  })))
@@ -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 在切换时拉取数据、可选 collapsed 注入受控折叠态 ref。 ensureCustomTab 只挂载/合并元信息,不展开侧栏、不主动切换选中;addCustomTab 会展开并选中。 未被主动切换过时,选中态默认跟随 Tab 栏首位(order 最小),如常驻的「文件产物」。 useCustomTabConsumer 在后代注入同一套 API,常用于侧栏动态节点详情等。EXECUTION_TAB_NAME 标识默认「执行情况」Tab。 ChatContainer 侧栏集成 Provider 与 Tab UI。
5
5
 
6
6
  ### 关联组件
7
7
  - **chat-container** — Provider 与侧栏 Tab 主场景
@@ -21,6 +21,8 @@ Provider/Consumer 模式的自定义 Tab 管理,用于 `ChatContainer` 侧边
21
21
 
22
22
  ```typescript
23
23
  function useCustomTabProvider<T extends Record<string, unknown>>(options: {
24
+ // 侧栏折叠态;由容器传入受控 ref(如 ChatContainer 的 v-model:asideCollapsed),缺省内部自持
25
+ collapsed?: Ref<boolean>;
24
26
  // 执行情况 Tab 是否展示,缺省 true;传 getter 以保持响应式
25
27
  executionTabVisible?: () => boolean | undefined;
26
28
  onTabChange?: (tab: CustomTab<T>) => void;
@@ -28,8 +30,9 @@ function useCustomTabProvider<T extends Record<string, unknown>>(options: {
28
30
  tabs: ShallowRef<CustomTab<T>[]>;
29
31
  displayTabs: ComputedRef<CustomTab<T>[]>;
30
32
  selectedTab: Ref<CustomTab<T>>;
31
- isCollapse: ShallowRef<boolean>;
33
+ isCollapse: Ref<boolean>;
32
34
  addCustomTab: (tab: CustomTab<T>) => void;
35
+ ensureCustomTab: (tab: CustomTab<T>) => void;
33
36
  removeCustomTab: (tabName: string) => void;
34
37
  selectCustomTab: (tab: CustomTab<T>) => void;
35
38
  resetCustomTab: () => void;
@@ -46,6 +49,7 @@ function useCustomTabConsumer<T extends Record<string, unknown>>():
46
49
  displayTabs: ComputedRef<CustomTab<T>[]>;
47
50
  selectedTab: ShallowRef<CustomTab<T> | null>;
48
51
  addCustomTab: (tab: CustomTab<T>) => void;
52
+ ensureCustomTab: (tab: CustomTab<T>) => void;
49
53
  removeCustomTab: (tabName: string) => void;
50
54
  selectCustomTab: (tab: CustomTab<T>) => void;
51
55
  resetCustomTab: () => void;
@@ -59,7 +63,7 @@ function useCustomTabConsumer<T extends Record<string, unknown>>():
59
63
  ```typescript
60
64
  import { useCustomTabProvider, EXECUTION_TAB_NAME } from '@blueking/chat-x';
61
65
 
62
- const { tabs, selectedTab, isCollapse, addCustomTab, removeCustomTab, selectCustomTab, resetCustomTab } =
66
+ const { tabs, selectedTab, isCollapse, addCustomTab, ensureCustomTab, removeCustomTab, selectCustomTab, resetCustomTab } =
63
67
  useCustomTabProvider({
64
68
  onTabChange: async tab => {
65
69
  // Tab 切换时加载数据
@@ -76,7 +80,7 @@ import { useCustomTabConsumer } from '@blueking/chat-x';
76
80
 
77
81
  const tabManager = useCustomTabConsumer();
78
82
 
79
- // 添加一个自定义 Tab
83
+ // 添加一个自定义 Tab(展开侧栏并选中)
80
84
  tabManager?.addCustomTab({
81
85
  name: 'node-detail-123',
82
86
  label: '节点详情',
@@ -86,6 +90,14 @@ tabManager?.addCustomTab({
86
90
  },
87
91
  });
88
92
 
93
+ // 仅确保 Tab 存在(不展开、不切换选中)—— 如常驻挂上文件产物 Tab
94
+ tabManager?.ensureCustomTab({
95
+ name: 'file-artifact',
96
+ label: '文件产物',
97
+ closable: false,
98
+ order: -1,
99
+ });
100
+
89
101
  // 移除 Tab
90
102
  tabManager?.removeCustomTab('node-detail-123');
91
103
  ```
@@ -104,9 +116,10 @@ tabManager?.removeCustomTab('node-detail-123');
104
116
  | --------------- | --------------------------- | ------------------------------------------------- |
105
117
  | tabs | `ShallowRef<CustomTab[]>` | 所有 Tab 列表(含默认的执行情况 Tab,保留隐藏项) |
106
118
  | displayTabs | `ComputedRef<CustomTab[]>` | Tab 栏实际展示列表:过滤 `visible === false`,按 `order` 升序稳定排序 |
107
- | selectedTab | `Ref<CustomTab>` | 当前选中的 Tab;选中项被隐藏时自动回退到首个可见 Tab |
108
- | isCollapse | `ShallowRef<boolean>` | 侧边栏折叠状态;`addCustomTab` 时自动设为 `false` |
109
- | addCustomTab | `(tab: CustomTab) => void` | 添加 Tab;同名 Tab 合并更新(可改 `order` / `visible` / `label`) |
119
+ | selectedTab | `Ref<CustomTab>` | 当前选中的 Tab;未被主动切换过时跟随 Tab 栏首位,选中项被隐藏时自动回退到首个可见 Tab |
120
+ | isCollapse | `Ref<boolean>` | 侧边栏折叠状态;传入 `collapsed` 时即该受控 ref(读写都作用于外部),否则为内部状态;`addCustomTab` 时自动设为 `false` |
121
+ | addCustomTab | `(tab: CustomTab) => void` | 添加/合并 Tab,**展开侧栏并选中**目标 Tab |
122
+ | ensureCustomTab | `(tab: CustomTab) => void` | 添加/合并 Tab,**不展开、不主动切换选中**;用于常驻挂载(如文件产物) |
110
123
  | removeCustomTab | `(tabName: string) => void` | 移除指定 Tab |
111
124
  | selectCustomTab | `(tab: CustomTab) => void` | 切换到指定 Tab,触发 `onTabChange` 回调 |
112
125
  | resetCustomTab | `() => void` | 重置为仅保留「执行情况」Tab、折叠侧栏并选中默认 Tab;`ChatContainer` 在卸载时调用,避免残留自定义 Tab |
@@ -136,7 +149,11 @@ interface CustomTab<T = Record<string, unknown>> {
136
149
  - 排序为稳定排序,`order` 相同的 Tab 保持插入先后顺序
137
150
  - 选中的 Tab 被配置隐藏时,内容不再渲染,自动切换到首个可见 Tab
138
151
  - `addCustomTab` 同时展开侧边栏(`isCollapse = false`)并在 `nextTick` 后自动选中目标 Tab;同名 Tab 合并更新
152
+ - `ensureCustomTab` 与 `addCustomTab` 共用合并逻辑,但不改 `isCollapse`、自身不切换 `selectedTab`;适合「常驻挂载文件产物 Tab」等场景
153
+ - 默认选中跟随 Tab 栏首位:只要没调用过 `selectCustomTab` / `addCustomTab`,挂上更靠前(`order` 更小)的 Tab 就会成为选中项,因此常驻的「文件产物」(`order: -1`)是侧栏默认面板;一旦主动切换过便不再跟随。`resetCustomTab` 会重置该标记
154
+ - 折叠态支持受控:传入 `collapsed` 后 Provider 不再自持状态,内部展开动作直接写回该 ref,容器即可通过 `v-model` 把状态交给外部判断
139
155
 
140
156
  ## 关联组件
141
157
 
142
- - [ChatContainer](../components/setup/chat-container) — 侧栏 Tab 与自定义面板
158
+ - [ChatContainer](../components/setup/chat-container) — 侧栏 Tab 与自定义面板
159
+ - [useArtifactPreview](./use-artifact-preview) — 文件产物 Tab:初始化 `ensureCustomTab` 常驻挂载,点击卡片时 `addCustomTab`
@@ -1,10 +1,11 @@
1
1
  <!-- AI SUMMARY -->
2
2
  ## 快速了解
3
3
 
4
- useGlobalConfig 接收 GlobalConfig(含 size?: ComputedRef<AiSizeMode>、supportUpload: ComputedRef<boolean>),以 GLOBAL_CONFIG_TOKEN provide 给后代; injectGlobalConfig 在子组件中取出配置,无 Provider 时返回 undefined。ChatContainer 在 setup 中调用 useGlobalConfig 注入 size 与 supportUpload; 后代组件可通过 injectGlobalConfig 读取配置;字号主题主要通过根节点 data-ai-size 与 CSS 变量生效。
4
+ useGlobalConfig 接收 GlobalConfig(含 size?: ComputedRef<AiSizeMode>、supportUpload: ComputedRef<boolean>、timezone?: ComputedRef<string | undefined>),以 GLOBAL_CONFIG_TOKEN provide 给后代; injectGlobalConfig 在子组件中取出配置,无 Provider 时返回 undefined。ChatContainer 在 setup 中调用 useGlobalConfig 注入 size、supportUpload 与 timezone; 后代组件可通过 injectGlobalConfig 读取配置;字号主题主要通过根节点 data-ai-size 与 CSS 变量生效。
5
5
 
6
6
  ### 关联组件
7
7
  - **chat-container** — 根容器调用 useGlobalConfig 注入 supportUpload
8
+ - **message-time** — 消息时间组件读取 timezone
8
9
 
9
10
  ---
10
11
  <!-- FULL DOC -->
@@ -13,7 +14,7 @@ useGlobalConfig 接收 GlobalConfig(含 size?: ComputedRef<AiSizeMode>、suppo
13
14
 
14
15
  > **分类**:composable
15
16
 
16
- 在聊天容器根组件与子组件之间通过 Vue `provide` / `inject` 共享**全局展示相关配置**(当前包括字号主题档位 `size`、是否支持上传 `supportUpload`)。与 Teleport 插槽 ID 无关。
17
+ 在聊天容器根组件与子组件之间通过 Vue `provide` / `inject` 共享**全局展示相关配置**(当前包括字号主题档位 `size`、是否支持上传 `supportUpload`、消息时间时区 `timezone`)。与 Teleport 插槽 ID 无关。
17
18
 
18
19
  > 字号主题主要通过 `ChatContainer` 根节点的 `data-ai-size` 与 CSS 变量(`--ai-font-size` 等)生效;`GlobalConfig.size` 供后代在逻辑层读取当前档位,样式层无需逐组件传参。
19
20
 
@@ -25,10 +26,11 @@ ChatContainer(根)
25
26
  ├── useGlobalConfig({
26
27
  │ size: computed(() => props.size ?? 'small'),
27
28
  │ supportUpload: computed(() => props.supportUpload ?? false),
29
+ │ timezone: computed(() => props.timezone),
28
30
  │ })
29
- │ └── provide(GLOBAL_CONFIG_TOKEN, { size, supportUpload })
31
+ │ └── provide(GLOBAL_CONFIG_TOKEN, { size, supportUpload, timezone })
30
32
  │
31
- └── MessageContainer → … → UserMessage 等
33
+ └── MessageContainer → … → UserMessage / MessageTime 等
32
34
 
33
35
  UserMessage(后代)
34
36
  ├── injectGlobalConfig()
@@ -51,11 +53,14 @@ UserMessage(后代)
51
53
  const props = defineProps<{
52
54
  size?: 'normal' | 'small';
53
55
  supportUpload?: boolean;
56
+ timezone?: string;
54
57
  }>();
55
58
 
56
59
  useGlobalConfig({
57
60
  size: computed(() => props.size ?? 'small'),
58
61
  supportUpload: computed(() => props.supportUpload ?? false),
62
+ // 无默认值:未配置时由 MessageTime 回退到浏览器时区
63
+ timezone: computed(() => props.timezone),
59
64
  });
60
65
  </script>
61
66
  ```
@@ -88,11 +93,13 @@ export type AiSizeMode = 'normal' | 'small';
88
93
  export type GlobalConfig = {
89
94
  size?: ComputedRef<AiSizeMode>;
90
95
  supportUpload: ComputedRef<boolean>;
96
+ timezone?: ComputedRef<string | undefined>;
91
97
  };
92
98
 
93
99
  export function useGlobalConfig(options: GlobalConfig): {
94
100
  size?: ComputedRef<AiSizeMode>;
95
101
  supportUpload: ComputedRef<boolean>;
102
+ timezone?: ComputedRef<string | undefined>;
96
103
  };
97
104
 
98
105
  export function injectGlobalConfig(): GlobalConfig | undefined;
@@ -110,6 +117,7 @@ export function injectGlobalConfig(): GlobalConfig | undefined;
110
117
  | --------------- | ------------------------------------------------------------------------ |
111
118
  | `size` | 可选。字号主题档位 `normal`(14px)/ `small`(12px),与 `ChatContainer.size` 对齐 |
112
119
  | `supportUpload` | 是否支持上传,与根容器 `ChatContainer` 的 `supportUpload` 等展示策略对齐 |
120
+ | `timezone` | 可选。消息时间展示所用的 IANA 时区名,与 `ChatContainer.timezone` 对齐;未配置时 `MessageTime` 按浏览器时区展示 |
113
121
 
114
122
  ### `useGlobalConfig(options)`
115
123
 
@@ -117,6 +125,7 @@ export function injectGlobalConfig(): GlobalConfig | undefined;
117
125
  | ----------------------- | ------------------------------------------------------------------------------------- |
118
126
  | `options.size` | 可选。字号主题档位,建议使用 `computed(() => props.size ?? 'small')` 与根 props 同步 |
119
127
  | `options.supportUpload` | 是否支持上传,建议使用 `computed(() => props.supportUpload ?? false)` 与根 props 同步 |
128
+ | `options.timezone` | 可选。消息时间时区,建议使用 `computed(() => props.timezone)` 与根 props 同步;不设默认值,交由 `MessageTime` 回退浏览器时区 |
120
129
 
121
130
  - 调用后立即 `provide(GLOBAL_CONFIG_TOKEN, options)`。
122
131
  - 必须在具有组件实例上下文的 `setup` 中调用(与 Vue `provide` 要求一致)。
@@ -136,5 +145,6 @@ export function injectGlobalConfig(): GlobalConfig | undefined;
136
145
 
137
146
  ## 关联组件
138
147
 
139
- - [ChatContainer](../components/setup/chat-container) — 调用 `useGlobalConfig` 注入 `size` 与 `supportUpload`
148
+ - [ChatContainer](../components/setup/chat-container) — 调用 `useGlobalConfig` 注入 `size`、`supportUpload` 与 `timezone`
149
+ - [MessageTime](../components/feedback/message-time) — 读取 `timezone` 展示消息时间
140
150
  - [主题配置](../theme/theme) — `data-ai-size` 与 CSS 变量说明
@@ -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>;
@@ -70,15 +71,18 @@ role=user role=tool 其他 role
70
71
  `role: 'tool'` 消息不会独立渲染,而是通过 `toolCallId` 注入到对应 AssistantMessage 的 `toolCall.toolMessage` 字段:
71
72
 
72
73
  ```typescript
73
- const toolMessage = messages.find(
74
+ const assistantToolMessage = messages.find(
74
75
  m => m.role === 'assistant' && m.toolCalls?.some(t => t.id === message.toolCallId),
75
- );
76
- if (toolMessage) {
77
- const toolCall = toolMessage.toolCalls?.find(t => t.id === message.toolCallId);
76
+ ) as AssistantMessage | undefined;
77
+ if (assistantToolMessage) {
78
+ const toolCall = assistantToolMessage.toolCalls?.find(t => t.id === message.toolCallId);
78
79
  if (toolCall) {
79
80
  toolCall.toolMessage = message;
80
81
  }
81
- // 同步 assistant 状态(错误等)
82
+ // error 时强制 assistant 为 Error;否则保留原 status,空值兜底 Complete
83
+ assistantToolMessage.status = message.error
84
+ ? MessageStatus.Error
85
+ : assistantToolMessage.status || MessageStatus.Complete;
82
86
  }
83
87
  ```
84
88
 
@@ -116,6 +120,33 @@ const isExecutionMessage = (m: Message): boolean => {
116
120
  | toolCall | `function.name`、`mcpName`、`description`、`arguments`、`id` |
117
121
  | flow_agent | 各任务 `task_name`、各节点 `name` |
118
122
 
123
+ ## sessionArtifacts 会话级文件产物
124
+
125
+ `sessionArtifacts` 拍平当前会话所有 `AssistantMessage.property.artifacts`,供 `ChatContainer` 侧栏「文件产物」Tab 聚合预览。以 **`outputId`** 为会话内唯一键去重(同 `outputId` 视为同一文件),保留最后一次出现的文件信息,列表顺序与「最后一次出现」的相对顺序一致:
126
+
127
+ ```typescript
128
+ const sessionArtifacts = computed(() => {
129
+ // delete + set:同 key 覆盖内容,并把该项挪到 Map 末尾,保证「最后出现」顺序
130
+ const byOutputId = new Map();
131
+ for (const message of messages.value) {
132
+ if (message.role !== MessageRole.Assistant) continue;
133
+ const artifacts = message.property?.artifacts;
134
+ if (!artifacts?.length) continue;
135
+ for (const file of artifacts) {
136
+ if (byOutputId.has(file.outputId)) {
137
+ byOutputId.delete(file.outputId);
138
+ }
139
+ byOutputId.set(file.outputId, file);
140
+ }
141
+ }
142
+ return Array.from(byOutputId.values());
143
+ });
144
+ ```
145
+
146
+ > `SessionArtifact` 即为 `AIFileInfo` 别名;文件名可能重复,不可作唯一键。
147
+
148
+ 预览命中与取链见 [useArtifactPreview](./use-artifact-preview);侧栏列表与分类型预览(`ArtifactPreviewHost`)见 [FileArtifactPanel](../components/message/file-artifact-panel)。
149
+
119
150
  ## 待审批统计
120
151
 
121
152
  `useMessageGroup` 会统计消息列表中处于待审批状态的 AI Dev 审批中断:
@@ -186,6 +217,7 @@ const {
186
217
  | ---------------- | ----------------------------- | --------------------------------------------------------------------------- |
187
218
  | messageGroups | `Ref<MessageGroup[]>` | 完整消息分组列表 |
188
219
  | executionGroups | `ComputedRef<MessageGroup[]>` | 仅包含执行类消息的分组(工具调用 + FlowAgent),自动提取 `userMessageTitle` |
220
+ | sessionArtifacts | `ComputedRef<SessionArtifact[]>` | 拍平会话所有 AssistantMessage 文件产物,按 `outputId` 去重(保留最后一次) |
189
221
  | pendingApprovalCount | `ComputedRef<number>` | 当前消息中待审批 AI Dev 审批中断的数量 |
190
222
  | pendingApprovalTipText | `ComputedRef<string>` | 待审批阻塞发送提示文案;无待审批时为空字符串 |
191
223
  | isShareMode | `ShallowRef<boolean>` | 是否处于分享模式 |