Skip to content

Commit 59486a0

Browse files
committed
fix(bubble): clarify message grouping logic in documentation
- Updated the documentation in `list-array-content.vue` and `bubble.md` to specify that messages with an array content are treated as independent groups only when the message role is 'user'. - Adjusted comments in the demo files to reflect the new grouping logic, enhancing clarity for developers on how messages are processed. - Refined the content extraction logic in `custom-renderer.vue` to ensure consistent handling of content items.
1 parent b4f557c commit 59486a0

File tree

3 files changed

+51
-32
lines changed

3 files changed

+51
-32
lines changed

docs/demos/bubble/custom-renderer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const CodeBlockRenderer = defineComponent({
4343
},
4444
setup(props: BubbleContentRendererProps) {
4545
// 使用 useMessageContent 来正确处理数组内容和 contentIndex
46-
const { contentItem } = useMessageContent(props)
46+
const { content: contentItem } = useMessageContent(props)
4747
4848
return () => {
4949
const content = contentItem.value as unknown as CodeMessage

docs/demos/bubble/list-array-content.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div style="display: flex; flex-direction: column; gap: 16px">
33
<p style="font-size: 12px; color: #666; margin: 0">
4-
当消息的 content 为数组时,该消息会被单独分组(密封),后续消息不会合并到该组。
4+
当 message.role === 'user' 且 content 为数组时,该消息会被单独分组(密封),后续消息不会合并到该组。
55
</p>
66
<tr-bubble-list :messages="messages" :role-configs="roles"></tr-bubble-list>
77
</div>
@@ -17,21 +17,21 @@ const userAvatar = h(IconUser, { style: { fontSize: '32px' } })
1717
1818
const messages: BubbleListProps['messages'] = [
1919
{
20-
role: 'ai',
21-
// content 为数组,会被单独分组(密封)
20+
role: 'user',
21+
// role 为 user 且 content 为数组时,会被单独分组(密封)
2222
content: [
2323
{ type: 'text', text: '第一部分' },
2424
{ type: 'text', text: '第二部分' },
2525
],
2626
},
2727
{
2828
role: 'ai',
29-
// 虽然角色相同,但因为上一条是数组(密封),所以这条会单独成组
29+
// 上一条为 user+数组(密封),所以这条单独成组
3030
content: '第二条消息(单独成组)',
3131
},
3232
{
3333
role: 'ai',
34-
// 这条会与上一条合并(因为上一条不是密封的)
34+
// 与上一条角色相同且上一条非密封,合并到同一组
3535
content: '第三条消息(与第二条合并)',
3636
},
3737
]

docs/src/components/bubble.md

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ outline: [1, 3]
44

55
# Bubble 气泡组件
66

7-
Bubble 气泡组件用于展示消息气泡,支持流式文本、头像、位置、加载中、终止状态、操作按钮等功能。组件采用渲染器架构,支持灵活的内容渲染和自定义扩展。
7+
:::danger 重大版本升级 v0.4
8+
Bubble 在 v0.4 进行了重大升级。
9+
10+
**从 v0.3.x 升级?** 请查看 [Bubble 迁移指南](../migration/bubble-migration)
811

9-
## 组件简介
12+
**新项目:** 直接使用下方 v0.4 的 API 和示例即可。
13+
:::
1014

11-
Bubble 组件是一个用于聊天场景的消息气泡组件,主要解决以下问题:
15+
Bubble 气泡组件用于展示消息气泡,支持流式文本、头像、位置、加载中、终止状态、操作按钮等功能。组件采用渲染器架构,支持灵活的内容渲染和自定义扩展。
16+
17+
主要解决以下问题:
1218

1319
- **消息展示**:支持文本、图片、Markdown 等多种内容类型的渲染
1420
- **流式输出**:支持流式文本展示,适用于 AI 对话场景
@@ -77,6 +83,7 @@ pnpm add markdown-it dompurify
7783
Bubble 组件支持渲染图片内容。当 `content` 为数组且包含 `type: 'image_url'` 的内容项时,会自动使用 Image 渲染器。
7884

7985
图文混合时,可以通过 `contentRenderMode` 控制渲染方式:
86+
8087
- `'single'` 模式:文本和图片在同一个 box 中渲染
8188
- `'split'` 模式:每个内容项(文本或图片)单独一个 box
8289

@@ -133,13 +140,13 @@ BubbleList 支持多种分组策略:
133140

134141
**数组内容的分组**
135142

136-
当消息的 `content` 为数组时,该消息会被单独作为一个独立分组(密封),后续的消息(即使角色相同)也不会被添加到这个分组中。
143+
`message.role === 'user'` `content` 为数组时,该消息会被单独作为一个独立分组(密封),后续的消息(即使角色相同)也不会被添加到这个分组中。
137144

138145
<demo vue="../../demos/bubble/list-array-content.vue" />
139146

140147
> **注意**:分组策略的特殊处理规则:
141148
>
142-
> - 当消息的 `content` 为数组时,该消息会被单独作为一个独立分组(密封),后续的消息(即使角色相同)也不会被添加到这个分组中
149+
> - `message.role === 'user'` `content` 为数组时,该消息会被单独作为一个独立分组(密封),后续的消息(即使角色相同)也不会被添加到这个分组中
143150
> - `hidden` 消息的分组规则:连续的 `hidden` 消息可以同一组
144151
145152
### 隐藏角色
@@ -159,7 +166,6 @@ BubbleList 支持多种分组策略:
159166
> 1. **常规自动滚动**:当消息内容变化时(如消息数量、内容、推理内容),如果满足以下条件会自动滚动:
160167
> - BubbleList 必须是可滚动容器(`scrollHeight > clientHeight`
161168
> - 滚动容器需要接近底部
162-
>
163169
> 2. **用户消息特殊处理**:当最后一条消息的 `role``'user'` 时,会立即使用平滑滚动(`smooth`)滚动到底部,无需满足上述条件。这确保了用户发送消息后能立即看到自己发送的内容。
164170
165171
### 自定义渲染器
@@ -317,8 +323,9 @@ defineProps<BubbleBoxRendererProps>()
317323

318324
- 使用 `markRaw` 包装渲染器组件,避免 Vue 的响应式处理
319325
- 为了不修改源数据内部内容和结构,UI 相关的数据应放在消息的 `state` 属性中
320-
- Box 渲染器的 `find` 函数接收 `messages` 数组和 `resolvedContents` 数组
321-
- Content 渲染器的 `find` 函数接收单个 `message``resolvedContent`
326+
- Box 渲染器的 `find` 函数签名:`(messages, content, contentIndex) => boolean`,其中 `content` 仅在 split 模式有值
327+
- Content 渲染器的 `find` 函数签名:`(message, content, contentIndex) => boolean``content` 为统一化后的 `ChatMessageContentItem`
328+
- 在 Content 渲染器中可使用 `useMessageContent(props)` 获取当前 `content``contentText`,以正确处理 `contentIndex` 与数组内容
322329

323330
```vue
324331
<template>
@@ -355,7 +362,7 @@ Bubble 组件支持通过 `state` 属性存储 UI 相关的数据,并通过 `s
355362
| `tool_calls` | `ToolCall[]` | - | 工具调用列表(用于 Tool 渲染器) |
356363
| `tool_call_id` | `string` | - | 工具调用 ID |
357364
| `name` | `string` | - | 消息名称 |
358-
| `id` | `string \| number \| symbol` | - | 气泡唯一标识 |
365+
| `id` | `string` | - | 气泡唯一标识 |
359366
| `loading` | `boolean` | `false` | 是否显示加载状态 |
360367
| `state` | `Record<string, unknown>` | - | 消息状态数据(用于存储 UI 相关的数据,不会影响消息内容) |
361368
| `hidden` | `boolean` | `false` | 是否隐藏气泡 |
@@ -369,16 +376,22 @@ Bubble 组件支持通过 `state` 属性存储 UI 相关的数据,并通过 `s
369376

370377
**BubbleListProps** - 气泡列表组件的属性配置
371378

372-
| 属性 | 类型 | 默认值 | 说明 |
373-
| ------------------- | ------------------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
374-
| `messages` | `BubbleMessage[]` | - | **必填**,消息数组 |
375-
| `groupStrategy` | `'consecutive' \| 'divider' \| BubbleGroupFunction` | `'divider'` | 分组策略:<br/>- `'consecutive'`: 连续相同角色的消息合并为一组<br/>- `'divider'`: 按分割角色分组<br/>- 自定义函数: 自定义分组逻辑 |
376-
| `dividerRole` | `string` | `'user'` | `'divider'` 策略的分割角色,具有此角色的消息将作为分割线 |
377-
| `fallbackRole` | `string` | `'assistant'` | 当消息没有角色或角色为空时,使用此角色 |
378-
| `roleConfigs` | `Record<string, BubbleRoleConfig>` | - | 每个角色的默认配置项(头像、位置、形状等) |
379-
| `contentRenderMode` | `'single' \| 'split'` | - | 内容渲染模式 |
380-
| `contentResolver` | `(message: BubbleMessage) => ChatMessageContent \| undefined` | `(message) => message.content` | 内容解析函数,用于解析消息内容 |
381-
| `autoScroll` | `boolean` | `false` | 是否自动滚动到底部。需要满足以下条件:<br/>- BubbleList 是可滚动容器(需要 scrollHeight > clientHeight)<br/>- 滚动容器接近底部 |
379+
| 属性 | 类型 | 默认值 | 说明 |
380+
| ------------------- | ------------------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
381+
| `messages` | `BubbleMessage[]` | - | **必填**,消息数组 |
382+
| `groupStrategy` | `'consecutive' \| 'divider' \| BubbleGroupFunction` | `'divider'` | 分组策略:<br/>- `'consecutive'`: 连续相同角色的消息合并为一组<br/>- `'divider'`: 按分割角色分组(连续的分割角色在一组,其他消息在另一组)<br/>- 自定义函数: `(messages, dividerRole?) => BubbleMessageGroup[]` |
383+
| `dividerRole` | `string` | `'user'` | `'divider'` 策略的分割角色,具有此角色的消息将作为分割线 |
384+
| `fallbackRole` | `string` | `'assistant'` | 当消息没有角色或角色为空时,使用此角色 |
385+
| `roleConfigs` | `Record<string, BubbleRoleConfig>` | - | 每个角色的默认配置项(头像、位置、形状等) |
386+
| `contentRenderMode` | `'single' \| 'split'` | - | 内容渲染模式 |
387+
| `contentResolver` | `(message: BubbleMessage) => ChatMessageContent \| undefined` | `(message) => message.content` | 内容解析函数,用于解析消息内容 |
388+
| `autoScroll` | `boolean` | `false` | 是否自动滚动到底部。需要满足以下条件:<br/>- BubbleList 是可滚动容器(需要 scrollHeight > clientHeight)<br/>- 滚动容器接近底部 |
389+
390+
**BubbleList Expose**
391+
392+
| 方法 | 签名 | 说明 |
393+
| ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------- |
394+
| `scrollToBottom` | `(behavior?: ScrollBehavior) => Promise<void>` | 滚动到底部。传入 `'smooth'` 可平滑滚动。若未启用 `autoScroll`,调用后无实际滚动效果。 |
382395

383396
**BubbleProviderProps** - 气泡提供者组件的属性配置
384397

@@ -394,9 +407,9 @@ Bubble 组件支持通过 `state` 属性存储 UI 相关的数据,并通过 `s
394407

395408
**Bubble 和 BubbleList 组件的事件**
396409

397-
| 事件名 | 参数类型 | 说明 |
398-
| -------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
399-
| `state-change` | `{ key: string; value: unknown; messageIndex: number; contentIndex?: number }` | 当消息状态改变时触发。`key` 为状态键名,`value` 为状态值,`messageIndex` 为消息索引,`contentIndex` 为内容索引(可选) |
410+
| 事件名 | 参数类型 | 说明 |
411+
| -------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
412+
| `state-change` | `{ key: string; value: unknown; messageIndex: number; contentIndex: number }` | 当消息状态改变时触发。`key` 为状态键名,`value` 为状态值,`messageIndex` 为消息索引,`contentIndex` 为内容索引 |
400413

401414
## Slots
402415

@@ -433,7 +446,7 @@ interface BubbleMessage<
433446
tool_calls?: ToolCall[]
434447
tool_call_id?: string
435448
name?: string
436-
id?: string | number | symbol
449+
id?: string
437450
loading?: boolean
438451
state?: S
439452
}
@@ -488,7 +501,7 @@ type BubbleRoleConfig = Pick<
488501
type BubbleBoxRendererMatch = {
489502
find: (
490503
messages: BubbleMessage[],
491-
resolvedContents: BubbleMessage['content'][],
504+
content: ChatMessageContentItem | undefined,
492505
contentIndex: number | undefined,
493506
) => boolean
494507
renderer: Component<BubbleBoxRendererProps>
@@ -497,17 +510,23 @@ type BubbleBoxRendererMatch = {
497510
}
498511
```
499512
513+
- `content`: 仅在 `split` 模式(`contentIndex` 为数字)时传入,为当前消息经 `contentResolver` 解析后对应索引的内容项;`contentIndex``undefined``content` 也为 `undefined`
514+
- `contentIndex`: 仅在 split 模式下传入,此时 `messages` 长度为 1
515+
500516
**BubbleContentRendererMatch** - 内容渲染器匹配规则
501517
502518
```typescript
503519
type BubbleContentRendererMatch = {
504-
find: (message: BubbleMessage, resolvedContent: BubbleMessage['content'], contentIndex: number | undefined) => boolean
520+
find: (message: BubbleMessage, content: ChatMessageContentItem, contentIndex: number) => boolean
505521
renderer: Component<BubbleContentRendererProps>
506522
priority?: number
507523
attributes?: Record<string, string>
508524
}
509525
```
510526
527+
- `content`: 当前消息经 `contentResolver` 解析并统一化后的内容项;若为数组则取 `contentIndex` 对应项,若为字符串则转为 `{ type: 'text', text: string }`
528+
- `contentIndex`: 内容索引,字符串解析时为 0
529+
511530
**BubbleBoxRendererProps** - Box 渲染器属性
512531
513532
```typescript
@@ -522,7 +541,7 @@ type BubbleContentRendererProps<
522541
S extends Record<string, unknown> = Record<string, unknown>,
523542
> = {
524543
message: BubbleMessage<T, S>
525-
contentIndex?: number
544+
contentIndex: number
526545
}
527546
```
528547

0 commit comments

Comments
 (0)