@blueking/chat-x 0.0.34 → 0.0.36

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.
@@ -151,12 +151,12 @@ ActivityMessage 展示活动类消息:知识检索(knowledge_rag)、引用
151
151
 
152
152
  ## FlowAgent 执行情况模式
153
153
 
154
- `activityType` 设为 `MessageContentType.FlowAgent`(`'flow_agent'`),`content` 传入 `BkFlowMessageContent` 对象。用于展示蓝鲸标准运维(BkFlow)任务的执行状态、节点列表和统计信息。
154
+ `activityType` 设为 `MessageContentType.FlowAgent`(`'flow_agent'`),`content` 传入 `BkFlowMessageContent` 任务数组。用于展示一个或多个蓝鲸标准运维(BkFlow)任务的执行状态、节点列表和统计信息。
155
155
 
156
156
  ### 核心交互
157
157
 
158
- - **标题栏**:显示「执行情况」+ 各状态计数(执行中 / 成功 / 失败 / 挂起),颜色区分
159
- - **任务组**:可折叠/展开的任务行,带状态图标和总耗时
158
+ - **标题栏**:显示「执行情况」+ 所有任务聚合后的各状态计数(执行中 / 成功 / 失败 / 挂起),颜色区分
159
+ - **任务组**:逐个展示任务行,带状态图标和总耗时
160
160
  - **节点列表**:每个节点显示状态圆点、名称和耗时;hover 时出现「详情」按钮
161
161
  - **节点详情**:点击「详情」按钮会通过 `useCustomTabConsumer` 在 `ChatContainer` 侧边栏新增自定义 Tab,展示节点配置(基础信息、输入参数、输出参数)
162
162
 
@@ -171,7 +171,7 @@ FlowAgentContent(activityType = 'flow_agent')
171
171
  │ ├── Loading / ArrowIcon(随 status 切换)
172
172
  │ └── 执行情况:执行中 N / 成功 N / 失败 N / 挂起 N
173
173
  └── #default
174
- └── TaskGroup
174
+ └── TaskGroup × N
175
175
  ├── TaskHeader(可折叠/展开)
176
176
  │ ├── 状态图标(running=Loading / success / failed / suspended)
177
177
  │ ├── task_name(HighlightKeyword 支持搜索高亮)
@@ -204,54 +204,56 @@ FlowAgentContent(activityType = 'flow_agent')
204
204
  const collapsed = ref(false);
205
205
  const status = ref(MessageStatus.Complete);
206
206
 
207
- const flowContent: BkFlowMessageContent = {
208
- task_id: 100,
209
- task_name: '数据清洗流程',
210
- task_state: 'FINISHED',
211
- task_outputs: [],
212
- statistics: {
213
- total: 3,
214
- state_counts: { FINISHED: 2, FAILED: 1 },
215
- },
216
- nodes: {
217
- node1: {
218
- id: 'node1',
219
- name: '数据拉取',
220
- state: 'FINISHED',
221
- elapsed_time: 12,
222
- start_time: '2025-01-01 10:00:00',
223
- finish_time: '2025-01-01 10:00:12',
224
- loop: 1,
225
- retry: 0,
226
- skip: false,
227
- type: 'ServiceActivity',
228
- },
229
- node2: {
230
- id: 'node2',
231
- name: '数据转换',
232
- state: 'FINISHED',
233
- elapsed_time: 45,
234
- start_time: '2025-01-01 10:00:12',
235
- finish_time: '2025-01-01 10:00:57',
236
- loop: 1,
237
- retry: 0,
238
- skip: false,
239
- type: 'ServiceActivity',
207
+ const flowContent: BkFlowMessageContent = [
208
+ {
209
+ task_id: 100,
210
+ task_name: '数据清洗流程',
211
+ task_state: 'FINISHED',
212
+ task_outputs: [],
213
+ statistics: {
214
+ total: 3,
215
+ state_counts: { FINISHED: 2, FAILED: 1 },
240
216
  },
241
- node3: {
242
- id: 'node3',
243
- name: '结果写入',
244
- state: 'FAILED',
245
- elapsed_time: 3,
246
- start_time: '2025-01-01 10:00:57',
247
- finish_time: '2025-01-01 10:01:00',
248
- loop: 1,
249
- retry: 0,
250
- skip: false,
251
- type: 'ServiceActivity',
217
+ nodes: {
218
+ node1: {
219
+ id: 'node1',
220
+ name: '数据拉取',
221
+ state: 'FINISHED',
222
+ elapsed_time: 12,
223
+ start_time: '2025-01-01 10:00:00',
224
+ finish_time: '2025-01-01 10:00:12',
225
+ loop: 1,
226
+ retry: 0,
227
+ skip: false,
228
+ type: 'ServiceActivity',
229
+ },
230
+ node2: {
231
+ id: 'node2',
232
+ name: '数据转换',
233
+ state: 'FINISHED',
234
+ elapsed_time: 45,
235
+ start_time: '2025-01-01 10:00:12',
236
+ finish_time: '2025-01-01 10:00:57',
237
+ loop: 1,
238
+ retry: 0,
239
+ skip: false,
240
+ type: 'ServiceActivity',
241
+ },
242
+ node3: {
243
+ id: 'node3',
244
+ name: '结果写入',
245
+ state: 'FAILED',
246
+ elapsed_time: 3,
247
+ start_time: '2025-01-01 10:00:57',
248
+ finish_time: '2025-01-01 10:01:00',
249
+ loop: 1,
250
+ retry: 0,
251
+ skip: false,
252
+ type: 'ServiceActivity',
253
+ },
252
254
  },
253
255
  },
254
- };
256
+ ];
255
257
  </script>
256
258
  ```
257
259
 
@@ -264,16 +266,18 @@ const messages = [
264
266
  role: 'activity',
265
267
  activityType: MessageContentType.FlowAgent, // 'flow_agent'
266
268
  status: MessageStatus.Streaming,
267
- content: {
268
- task_id: 100,
269
- task_name: '数据清洗流程',
270
- task_state: 'RUNNING',
271
- task_outputs: [],
272
- statistics: { total: 3, state_counts: { RUNNING: 1, FINISHED: 2 } },
273
- nodes: {
274
- /* ... */
269
+ content: [
270
+ {
271
+ task_id: 100,
272
+ task_name: '数据清洗流程',
273
+ task_state: 'RUNNING',
274
+ task_outputs: [],
275
+ statistics: { total: 3, state_counts: { RUNNING: 1, FINISHED: 2 } },
276
+ nodes: {
277
+ /* ... */
278
+ },
275
279
  },
276
- },
280
+ ],
277
281
  },
278
282
  ];
279
283
  ```
@@ -301,14 +305,16 @@ const messages = [
301
305
  ### 相关类型定义
302
306
 
303
307
  ```typescript
304
- import { MessageContentType, type BkFlowMessageContent, type BkFlowNode } from '@blueking/chat-x';
308
+ import { MessageContentType, type BkFlowMessageContent, type BkFlowNode, type BkFlowTask } from '@blueking/chat-x';
309
+
310
+ type BkFlowMessageContent = BkFlowTask[];
305
311
 
306
- type BkFlowMessageContent = {
312
+ type BkFlowTask = {
307
313
  nodes: Record<string, BkFlowNode>;
308
314
  statistics: { state_counts: Record<string, number>; total: number };
309
315
  task_id: number;
310
316
  task_name: string;
311
- task_outputs: unknown[];
317
+ task_outputs: unknown;
312
318
  task_state: string;
313
319
  };
314
320
 
@@ -23,8 +23,8 @@ AI 消息内容渲染的核心原子组件,集成代码高亮、LaTeX 公式
23
23
  ## 组件结构与渲染流程
24
24
 
25
25
  ```
26
- props.content → completeMarkdownSyntax → md.parse groupTokens → groupedTokens
27
-
26
+ props.content → completeMarkdownSyntax → md.parse(html: true)→ groupTokens → groupedTokens
27
+
28
28
  div.ai-markdown-content(contain: layout style)
29
29
 
30
30
  status === 'error' → CommonErrorContent(:content)
@@ -39,12 +39,21 @@ props.content → completeMarkdownSyntax → md.parse → groupTokens → groupe
39
39
  ↓ ↓ ↓ ↓
40
40
  MermaidContent LatexContent CodeContent VNodeRenderer
41
41
  @mounted @mounted @mounted @vue:mounted
42
-
43
- └──── handleTokenMounted(throttle 100ms)→ containerScroll.toScrollBottom()
42
+
43
+ └──── handleTokenMounted(throttle 100ms) │
44
+ → containerScroll.toScrollBottom() │
45
+ sanitize(DOMPurify + sanitizeCSS)
46
+ sanitizeHtmlFragment(流式片段净化)
44
47
  ```
45
48
 
46
49
  `VNodeRenderer` 的 `options` 中包含与当前 `MarkdownIt` 实例一致的 `mditOptions`(即 `md.options`),以便 `tokensToVNodes` 调用 `renderer.rules` 时第三参与 markdown-it 原生规则签名一致。
47
50
 
51
+ `MarkdownIt` 构造时开启 `html: true`,允许行内 HTML 标签(如 `<font>`、`<div>`)通过 markdown-it 解析为 `html_inline` / `html_block` token。
52
+
53
+ 两组净化函数各司其职:
54
+ - `sanitize`:DOMPurify 全局过滤后,再对 `style` 属性值进行 CSS 属性白名单校验(`sanitizeCSS`),用于最终渲染的完整 HTML。
55
+ - `sanitizeHtmlFragment`:轻量级片段净化,**不自动闭合标签**,专用于流式渲染中拆分到多个 token 的 HTML 标签场景。
56
+
48
57
  ### Token 分组(groupTokens)
49
58
 
50
59
  `groupTokens` 使用栈将扁平 Token 数组转为分组数组,每组对应一个顶层 DOM 节点(段落、标题、列表、代码块等):
@@ -189,15 +198,24 @@ props.content → completeMarkdownSyntax → md.parse → groupTokens → groupe
189
198
 
190
199
  ### 安全性
191
200
 
192
- `VNodeRenderer` 渲染的 HTML 统一经过 DOMPurify 过滤,并额外允许 KaTeX 所需标签:
201
+ HTML 渲染采用两层安全策略:
202
+
203
+ **第一层 — DOMPurify**:全局过滤,移除危险标签和属性,同时允许 KaTeX 和 `<font>` 标签所需的内容:
193
204
 
194
205
  ```typescript
195
206
  const domPurifyConfig = {
196
- ADD_TAGS: ['semantics', 'mrow', 'mi', 'mo', 'mn', 'msup', 'msub', 'mfrac', 'mtext', 'annotation'],
197
- ADD_ATTR: ['xmlns', 'mathvariant', 'encoding', 'style'],
207
+ ADD_TAGS: ['font', 'semantics', 'mrow', 'mi', 'mo', 'mn', 'msup', 'msub', 'mfrac', 'mtext', 'annotation'],
208
+ ADD_ATTR: ['xmlns', 'mathvariant', 'encoding', 'style', 'color', 'size', 'face'],
209
+ FORBID_TAGS: ['style'],
198
210
  };
199
211
  ```
200
212
 
213
+ > `FORBID_TAGS: ['style']` 防止 `<style>` 标签注入全局 CSS,样式属性通过 `style` 属性(attribute)而非 `<style>` 标签(tag)控制。
214
+
215
+ **第二层 — CSS 属性白名单**(`sanitizeCSS`):DOMPurify 处理后,对 `style` 属性值做二次过滤,仅保留白名单内的安全 CSS 属性,拦截 `url()`、`expression()`、`javascript:` 等危险模式。
216
+
217
+ **流式场景 — `sanitizeHtmlFragment`**:流式渲染中 HTML 标签可能被拆分到多个 `html_inline` token(如 `<font color="red">` 和 `</font>` 分属不同 token),DOMPurify 会自动闭合未匹配标签导致样式丢失。`sanitizeHtmlFragment` 只做危险模式匹配不自动闭合,专用于此场景。
218
+
201
219
  > `CodeContent`、`MermaidContent`、`LatexContent` 各自内部处理安全性(KaTeX `errorColor`、highlight.js 转义等),不经过 DOMPurify。
202
220
 
203
221
  ## 关联组件
@@ -411,6 +411,10 @@ interface BaseShortcutComponent {
411
411
  }
412
412
  ```
413
413
 
414
+ ## 样式说明
415
+
416
+ 组件内部使用 SCSS 变量 `$bk-prefix` 拼接 bkui-vue 组件类名(如 `Form`、`FormItem`、`Radio`、`Checkbox` 等),替代原先硬编码的 `.bk-*` 类名选择器,确保在不同构建环境下类名前缀的一致性。
417
+
414
418
  ## 关联组件
415
419
 
416
420
  - [ShortcutBtn](../atomic/shortcut-btn.md) — 与 Shortcut 数据模型一致的入口按钮
@@ -111,7 +111,7 @@ const isExecutionMessage = (m: Message): boolean => {
111
111
  | 消息类型 | 搜索范围 |
112
112
  | ---------- | ------------------------------------------------------------ |
113
113
  | toolCall | `function.name`、`mcpName`、`description`、`arguments`、`id` |
114
- | flow_agent | `task_name`、各节点 `name` |
114
+ | flow_agent | 各任务 `task_name`、各节点 `name` |
115
115
 
116
116
  ## 分享模式
117
117
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "2.0.0",
3
- "generatedAt": "2026-04-27T03:31:37.817Z",
3
+ "generatedAt": "2026-05-14T09:46:52.773Z",
4
4
  "domains": {
5
5
  "message": {
6
6
  "label": "消息展示",
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 过滤 CSS 属性值,仅保留白名单中的安全属性,
3
+ * 并拦截 url()、expression()、javascript: 等危险模式。
4
+ *
5
+ * 注意:此函数是第二道防线。第一道由 DOMPurify 处理(过滤 script/event handler/javascript: URI 等)。
6
+ */
7
+ export declare const sanitizeCSS: (cssValue: string) => string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 轻量级 HTML 片段净化,不自动闭合标签。
3
+ * 用于流式渲染中 html_inline / html_block token 的净化——
4
+ * DOMPurify 会把孤立的 `<font color="red">` 补成 `<font color="red"></font>`,
5
+ * 导致后续内容无法继承样式,因此流式场景需要使用此函数。
6
+ */
7
+ export declare const sanitizeHtmlFragment: (html: string) => string;
@@ -1,3 +1,5 @@
1
+ export * from './css-sanitizer';
1
2
  export * from './file';
3
+ export * from './html-sanitizer';
2
4
  export * from './markdown-completer';
3
5
  export * from './utils';
@@ -17,6 +17,12 @@ export interface TokenToVNodeOptions {
17
17
  * HTML 净化函数,用于处理 innerHTML 的内容
18
18
  */
19
19
  sanitize?: (html: string) => string;
20
+ /**
21
+ * HTML 片段净化函数,用于流式场景下 html_inline/html_block token 的净化。
22
+ * 与 sanitize 不同,此函数不会自动闭合未匹配的标签,
23
+ * 因为流式渲染中 HTML 标签可能被拆分到不同的 token 中。
24
+ */
25
+ sanitizeHtmlFragment?: (html: string) => string;
20
26
  }
21
27
  type TokenAttrs = [string, string][];
22
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueking/chat-x",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "description": "蓝鲸智云 AI Chat 组件库 —— 遵循 AG-UI,为 AI Agent 和人类开发者共同设计的对话 UI 组件库。",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -78,7 +78,7 @@
78
78
  "vitepress": "2.0.0-alpha.16",
79
79
  "vitest": "^4.0.18",
80
80
  "vue-tsc": "^3.1.4",
81
- "@blueking/chat-helper": "0.0.2"
81
+ "@blueking/chat-helper": "0.0.4"
82
82
  },
83
83
  "scripts": {
84
84
  "dev": "vite --config vite.config.ts",