Skip to content

Conversation

@gene9831
Copy link
Collaborator

@gene9831 gene9831 commented Dec 1, 2025

Bubble 组件重构 PR 描述

📋 概述

本次 PR 对 packages/components/src/bubble 目录进行了大规模重构,从基于简单 props 的组件架构重构为基于消息分组和渲染器匹配的现代化架构。重构涉及核心组件重写、渲染器系统重构、类型系统优化以及新增多项功能特性。

🎯 主要变更

1. 核心组件重构

1.1 BubbleList.vue - 完全重写

变更前:

  • 基于 items prop 的简单列表渲染
  • 使用 @vueuse/coreuseScroll 实现自动滚动
  • 直接渲染 Bubble 组件
  • 支持 roles 配置和 loading 状态

变更后:

  • 基于 messages prop 的消息列表
  • 实现消息分组功能,支持两种分组策略:
    • consecutive: 连续相同角色的消息合并为一组
    • divider: 按分割角色分组(默认策略)
  • 使用 useAutoScroll composable 实现自动滚动
  • 通过 BubbleItem 组件渲染消息分组
  • 新增 groupStrategydividerRolefallbackRole 等配置项
  • 支持自定义分组函数
  • 新增 state-change 事件,支持消息状态变更通知

关键代码变更:

// 新增消息分组逻辑
const groupByRole = (messages: BubbleMessage[]): BubbleMessageGroup[]
const groupByDivider = (messages: BubbleMessage[], dividerRole: string): BubbleMessageGroup[]

// 支持数组 content 的密封分组机制
// 当消息的 content 为数组时,该消息单独作为一组,且后续消息不能添加到这个组

1.2 Bubble.vue - 架构重构

变更前:

  • 基于 content prop 的直接渲染
  • 使用 Flexbox 布局
  • 支持 customContentFieldabortedTextmaxWidth 等 props
  • 通过 ContentItem 组件渲染数组内容
  • 使用 contentRenderer prop 指定渲染器

变更后:

  • 基于 messageGroup 的渲染(通过 provide/inject)
  • 使用 CSS Grid 布局,提供更精确的布局控制
  • 移除 customContentFieldabortedText 等 props
  • 引入 BubbleBoxWrapperBubbleContentWrapper 组件
  • 支持 contentRenderModesingle | split)控制渲染模式
  • 新增多个插槽:prefixsuffixaftercontent-footer
  • 支持 reasoning_contenttool_calls 等 OpenAI 格式字段

布局变更:

/* 从 Flexbox 改为 Grid 布局 */
.tr-bubble__body {
  display: grid;
  grid-template-columns: auto 1fr; /* 左侧头像,右侧内容 */
  grid-template-rows: auto auto;
}

1.3 新增组件

  • BubbleItem.vue

    • 作为 BubbleListBubble 之间的中间层
    • 负责提供 messageGroup 给子组件
    • 透传插槽和事件
  • BubbleBoxWrapper.vue

    • 负责气泡容器的渲染
    • 通过 useBubbleBoxRenderer 选择匹配的容器渲染器
    • 支持动态属性和样式注入
  • BubbleContentWrapper.vue

    • 负责消息内容的渲染
    • 通过 useBubbleContentRenderer 选择匹配的内容渲染器
    • 处理 state-change 事件

2. 渲染器系统重构

2.1 渲染器架构变更

变更前:

  • 基于 Map<string, Renderer> 的静态映射
  • 通过 BubbleProvidercontentRenderers prop 配置
  • 使用 ContentItem 组件根据 type 字段匹配渲染器
  • 支持函数渲染器、类渲染器、组件渲染器

变更后:

  • 基于匹配函数(find)的动态匹配系统
  • 支持优先级(priority)配置
  • 支持属性注入(attributes
  • 分离容器渲染器(Box Renderer)和内容渲染器(Content Renderer)
  • 支持多层次的 fallback 机制(prop 级别 > provider 级别 > 默认)

新的渲染器匹配接口:

export type BubbleBoxRendererMatch = {
  find: (messages: BubbleMessage[], contentIndex?: number) => boolean
  renderer: Component<BubbleBoxRendererProps>
  priority?: number
  attributes?: Record<string, string>
}

export type BubbleContentRendererMatch = {
  find: (message: BubbleMessage, contentIndex?: number) => boolean
  renderer: Component<BubbleContentRendererProps>
  priority?: number
  attributes?: Record<string, string>
}

2.2 渲染器文件变更

删除的文件:

  • renderers/class-renderer.ts - 类渲染器基类
  • renderers/collapsible-text.tsrenderers/collapsible-text.vue
  • renderers/tool.tsrenderers/tool.vue
  • renderers/markdown.ts - Markdown 类渲染器
  • renderers/text.tsrenderers/text.vue - 旧版文本渲染器
  • renderers/defaultRendererMap.ts - 旧的渲染器映射
  • renderers/index.tsrenderers/index.type.ts - 旧的导出文件

新增的文件:

  • renderers/Box.vue - 基础容器渲染器
  • renderers/Text.vue - 文本内容渲染器
  • renderers/defaultRenderers.ts - 默认渲染器配置
  • renderers/allRenderers.ts - 渲染器导出

2.3 BubbleProvider.vue 重构

变更前:

  • 通过 contentRenderers prop 配置渲染器映射
  • 使用 provide 注入渲染器 Map 和 fallback 渲染器

变更后:

  • 通过 boxRendererMatchescontentRendererMatches 配置渲染器匹配规则
  • 通过 fallbackBoxRendererfallbackContentRenderer 配置 fallback 渲染器
  • 支持 store prop 提供全局状态
  • 自动合并默认渲染器并排序

3. Composables 系统

新增 composables/ 目录,将逻辑从组件中抽离:

3.1 useBubbleBoxRenderer.ts

  • 管理容器渲染器的匹配和选择
  • 支持多层次的 fallback 机制
  • 提供 setupBubbleBoxRenderersetupBubblePropBoxRenderer 函数

3.2 useBubbleContentRenderer.ts

  • 管理内容渲染器的匹配和选择
  • 支持多层次的 fallback 机制
  • 提供 setupBubbleContentRenderersetupBubblePropContentRenderer 函数

3.3 useBubbleStore.ts

  • 提供全局状态管理
  • 支持在 BubbleListBubble 组件之间共享数据
  • 自动检测已存在的 store,避免重复创建

3.4 useBubbleStateChange.ts

  • 处理状态变更事件
  • 通过 provide/inject 机制传递状态变更函数

3.5 useMessageContent.ts

  • 解析和提取消息内容
  • 支持从 state.contentcontent 字段获取内容
  • 支持数组内容的索引访问

3.6 useMessageGroup.ts

  • 管理消息分组的 provide/inject
  • BubbleItemBubble 之间传递消息分组信息

3.7 useOmitMessageFields.ts

  • 处理消息字段过滤
  • 用于在渲染器中排除特定字段

4. 类型系统重构

4.1 消息类型变更

变更前:

export interface BubbleProps {
  content?: string | BubbleContentItem[]
  role?: string
  // ...
}

变更后:

// 支持 OpenAI 格式的聊天消息
interface ChatMessage<T extends ChatMessageContent = ChatMessageContent> {
  role: string
  content?: T
  reasoning_content?: string
  tool_calls?: ToolCall[]
  tool_call_id?: string
  name?: string
}

export type BubbleMessage<T, S> = ChatMessageWithOptionalRole<T> & {
  id?: string
  loading?: boolean
  state?: S
}

4.2 分组类型优化

新增 BubbleMessageGroup 字段:

  • messageIndexes: number[] - 记录消息在原始数组中的索引
  • startIndex: number - 记录分组在原始数组中的起始索引
  • 移除 isPolymorphic 标记(不再需要)

4.3 Props 接口重构

BubbleListProps 变更:

  • itemsmessages(类型从 BubbleProps[] 改为 BubbleMessage[]
  • 移除 rolesloadingloadingRole
  • 新增 groupStrategydividerRolefallbackRoleroleConfigs
  • 新增 contentRenderModeautoScroll

BubbleProps 变更:

  • 继承 BubbleMessage 类型
  • 移除 contentcustomContentFieldabortedTextmaxWidth
  • 新增 contentRenderModefallbackBoxRendererfallbackContentRenderer
  • shape 支持 'none' 选项

4.4 新增类型

  • BubbleBoxRendererMatch - 容器渲染器匹配规则
  • BubbleContentRendererMatch - 内容渲染器匹配规则
  • BubbleBoxRendererProps - 容器渲染器 props
  • BubbleContentRendererProps - 内容渲染器 props
  • BubbleRoleConfig - 角色配置类型
  • BubbleListSlots - 列表插槽类型

5. 工具函数

5.1 utils.ts 新增

新增 utils.ts 文件,提供工具函数:

  • getJsonrepair() - 动态导入 jsonrepair 库(用于工具调用参数修复)
  • getMarkdownItAndDompurify() - 动态导入 markdown-it 和 DOMPurify(用于 Markdown 渲染)

这些函数使用动态导入,避免在不需要时加载大型依赖。

6. 常量定义

6.1 constants.ts 新增

新增 constants.ts 文件,定义所有注入键和常量:

  • BUBBLE_MESSAGE_GROUP_KEY - 消息分组注入键
  • BUBBLE_BOX_RENDERER_MATCHES_KEY - 容器渲染器匹配规则注入键
  • BUBBLE_CONTENT_RENDERER_MATCHES_KEY - 内容渲染器匹配规则注入键
  • BUBBLE_STORE_KEY - 全局状态注入键
  • BUBBLE_STATE_CHANGE_FN_KEY - 状态变更函数注入键
  • BubbleRendererMatchPriority - 渲染器匹配优先级常量

7. 删除的文件和目录

7.1 组件文件

  • components/ContentItem.vue - 旧的内容项组件
  • components/index.ts - 组件导出文件

7.2 渲染器文件

  • renderers/class-renderer.ts
  • renderers/collapsible-text.tsrenderers/collapsible-text.vue
  • renderers/tool.tsrenderers/tool.vue
  • renderers/markdown.ts
  • renderers/text.tsrenderers/text.vue
  • renderers/defaultRendererMap.ts
  • renderers/index.tsrenderers/index.type.ts

📊 变更统计

文件变更

  • 新增文件:15+
  • 删除文件:20+
  • 修改文件:5+

主要新增文件

  • BubbleItem.vue
  • BubbleBoxWrapper.vue
  • BubbleContentWrapper.vue
  • constants.ts
  • utils.ts
  • composables/ 目录(7 个文件)
  • renderers/Box.vue
  • renderers/Text.vue
  • renderers/defaultRenderers.ts
  • renderers/allRenderers.ts

主要删除文件

  • components/ContentItem.vue
  • components/index.ts
  • renderers/ 目录下的多个旧文件

🔄 迁移指南

Props 变更

BubbleList:

// 旧版本
<BubbleList
  :items="items"
  :roles="roles"
  :loading="loading"
  :loading-role="loadingRole"
/>

// 新版本
<BubbleList
  :messages="messages"
  :role-configs="roleConfigs"
  :group-strategy="'divider'"
  :divider-role="'user'"
  :fallback-role="'assistant'"
/>

Bubble:

// 旧版本
<Bubble
  :content="content"
  :custom-content-field="customContentField"
  :aborted-text="abortedText"
  :max-width="maxWidth"
/>

// 新版本
<Bubble
  :role="role"
  :content="content"
  :content-render-mode="'single'"
/>

渲染器配置变更

// 旧版本:使用 Map
const rendererMap = new Map([
  ['text', TextRenderer],
  ['tool', ToolRenderer],
])

<BubbleProvider :content-renderers="rendererMap" />

// 新版本:使用匹配函数
const contentRendererMatches = [
  {
    find: (message) => message.content?.type === 'text',
    renderer: TextRenderer,
    priority: 0,
  },
  {
    find: (message) => message.role === 'tool',
    renderer: ToolRenderer,
    priority: 20,
  },
]

<BubbleProvider :content-renderer-matches="contentRendererMatches" />

消息格式变更

// 旧版本
interface BubbleItem {
  content: string | BubbleContentItem[]
  role?: string
  // ...
}

// 新版本(支持 OpenAI 格式)
interface BubbleMessage {
  role?: string
  content?: string | ChatMessageContentItem[]
  reasoning_content?: string
  tool_calls?: ToolCall[]
  tool_call_id?: string
  name?: string
  id?: string
  loading?: boolean
  state?: Record<string, unknown>
}

🎨 设计改进

1. 关注点分离

  • 将渲染逻辑、状态管理、内容解析等逻辑抽离到 composables
  • 组件专注于 UI 结构和数据绑定
  • 渲染器系统独立,易于扩展和维护

2. 可扩展性提升

  • 基于匹配函数的渲染器系统,易于添加新的渲染器
  • 支持优先级控制,灵活处理渲染器冲突
  • 支持多层次的 fallback 机制(prop > provider > default)
  • 支持自定义分组策略

3. 类型安全

  • 使用泛型提升类型推断能力
  • 统一的类型定义,减少类型转换
  • 完整的 TypeScript 类型覆盖
  • 支持 OpenAI 格式的消息类型

4. 性能优化

  • 使用计算属性缓存渲染器匹配结果
  • 动态导入大型依赖(markdown-it, DOMPurify, jsonrepair)
  • 移除未使用的渲染器组件
  • 使用 CSS Grid 替代 Flexbox,提升布局性能

5. 功能增强

  • 支持消息分组,提升用户体验
  • 支持状态管理,实现组件间数据共享
  • 支持自动滚动,提升交互体验
  • 支持多种插槽,提升扩展性

🧪 测试建议

1. 功能测试

  • 基础文本消息渲染
  • 多模态内容渲染(数组 content)
  • 消息分组功能(consecutive 和 divider 策略)
  • 数组 content 的密封分组机制
  • Slot 功能测试(prefix, suffix, after, content-footer)
  • 状态变更事件测试
  • 自动滚动功能测试
  • reasoning_content 和 tool_calls 支持

2. 渲染器测试

  • 默认渲染器匹配
  • 自定义渲染器匹配
  • Fallback 渲染器测试(prop > provider > default)
  • 优先级测试
  • 属性注入测试

3. 兼容性测试

  • 现有使用场景的兼容性
  • 类型系统兼容性
  • Props 变更的迁移测试
  • 渲染器配置迁移测试

⚠️ 破坏性变更

  1. BubbleList Props 变更

    • itemsmessages(类型和结构都改变)
    • rolesroleConfigs(结构改变)
    • 移除 loadingloadingRole
    • 新增 groupStrategydividerRolefallbackRole
  2. Bubble Props 变更

    • 移除 content prop(改为从 BubbleMessage 继承)
    • 移除 customContentFieldabortedTextmaxWidth
    • shape 新增 'none' 选项
  3. 渲染器系统

    • 旧的基于 Map 的渲染器配置不再支持
    • 必须使用新的匹配函数系统
  4. 类型系统

    • BubbleProps 结构完全改变
    • 消息类型从 BubbleProps 改为 BubbleMessage
  5. 组件移除

    • ContentItem 组件已移除
    • 旧的渲染器组件已移除

🚀 后续计划

  1. 添加更多内置渲染器(Markdown、Image、Tool 等)
  2. 完善文档和示例
  3. 性能优化和代码分割
  4. 单元测试和集成测试覆盖
  5. 支持更多消息格式(如 Anthropic Claude 格式)

Summary by CodeRabbit

  • Breaking Changes

    • TrBubbleList props renamed: items → messages, roles → role-configs. Remove maxWidth from role configs.
  • New Features

    • Redesigned bubble rendering with message grouping and richer slot/hooks.
    • Added CSS animations: ping, pulse, bounce.
  • Documentation

    • Bubble docs updated with detailed rendering/formatting behavior and examples.
  • Chores

    • Docs packaging updated to accommodate markdown and sanitization tooling; color and minor style tweaks in docs theme.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Refactors the Bubble system to a message-group and composable-based architecture: introduces BubbleMessage model, grouping strategies, new wrapper components and composables for renderer resolution and state, replaces legacy renderers with Box/Text fallbacks, updates public API and docs, and adjusts packaging/dependencies.

Changes

Cohort / File(s) Change Summary
Core Bubble Components
packages/components/src/bubble/Bubble.vue, packages/components/src/bubble/BubbleList.vue, packages/components/src/bubble/BubbleProvider.vue
Replaced content-driven rendering with message-group/composable approach; added contentRenderMode, state-change emits, message grouping strategies, auto-scroll expose, and provider composable hooks for renderer matches/fallbacks.
Bubble Wrapper & Item Components
packages/components/src/bubble/BubbleBoxWrapper.vue, packages/components/src/bubble/BubbleContentWrapper.vue, packages/components/src/bubble/BubbleItem.vue
New wrapper components to resolve box/content renderers and forward state-change; BubbleItem groups messages and integrates slots and event propagation.
Composables & Injection Keys
packages/components/src/bubble/composables/*, packages/components/src/bubble/constants.ts, packages/components/src/bubble/composables/index.ts
Added multiple composables (useBubbleBoxRenderer, useBubbleContentRenderer, useBubbleStateChangeFn, useBubbleStore, useMessageContent, useMessageGroup, useOmitMessageFields) and new injection keys and priority constants.
Renderer Components & Defaults
packages/components/src/bubble/renderers/Box.vue, packages/components/src/bubble/renderers/Text.vue, packages/components/src/bubble/renderers/allRenderers.ts, packages/components/src/bubble/renderers/defaultRenderers.ts
Added Box and Text renderer SFCs, aggregator allRenderers, and defaultRenderers with empty match arrays and Box/Text fallbacks.
Removed Legacy Renderers & Components
packages/components/src/bubble/components/ContentItem.vue, packages/components/src/bubble/renderers/markdown.ts, packages/components/src/bubble/renderers/tool.vue, packages/components/src/bubble/renderers/collapsible-text.vue, packages/components/src/bubble/renderers/class-renderer.ts, packages/components/src/bubble/renderers/text.vue, packages/components/src/bubble/renderers/tool.vue
Deleted legacy ContentItem and multiple renderer implementations (markdown, tool, collapsible-text, class renderer, text) and removed related re-exports.
Renderer Index & Types Cleanup
packages/components/src/bubble/renderers/index.ts, packages/components/src/bubble/renderers/index.type.ts, packages/components/src/bubble/renderers/defaultRendererMap.ts, packages/components/src/bubble/renderers/text.ts, packages/components/src/bubble/renderers/tool.ts, packages/components/src/bubble/renderers/collapsible-text.ts
Cleared old re-exports and removed legacy renderer type aliases and defaultRendererMap/provider keys.
Type System Overhaul
packages/components/src/bubble/index.type.ts
Replaced old props/types with a message-centric model: added ChatMessage/ChatMessageContent/BubbleMessage, renderer match types, BubbleMessageGroup, new BubbleList/BubbleProvider props and grouping strategy types.
Public API & Exports
packages/components/src/bubble/index.ts, packages/components/src/index.ts
Removed BubbleMarkdownContentRenderer and BubbleContentClassRenderer exports; added BubbleRendererMatchPriority, BubbleRenderers, and new composable exports.
Composables Barrel & Utils
packages/components/src/bubble/composables/index.ts, packages/components/src/bubble/utils.ts
Added barrel export and utils for lazy dynamic imports (jsonrepair, markdown-it, dompurify).
Docs & Demos
docs/src/components/bubble.md, docs/demos/examples/Assistant.vue, docs/demos/tools/*
Updated docs: removed many inline demos, added behavior section for content rendering; demo usages updated from items/roles to messages/role-configs and removed maxWidth role props.
Styling & Build Config
docs/.vitepress/theme/style.css, packages/components/src/styles/animations.css, packages/components/src/styles/components/bubble.less, packages/components/vite.config.ts
Minor CSS tweaks; added animations; refactored bubble Less variables; added markdown-it and dompurify to Vite external list.
Dependency & Packaging Changes
docs/package.json, packages/components/package.json
Added dompurify and markdown-it to docs deps; moved dompurify/markdown-it to peer/devDependencies in components package and added jsonrepair to dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BubbleList
    participant Grouping
    participant BubbleItem
    participant Bubble
    participant BoxResolver
    participant ContentResolver
    participant Renderer

    Client->>BubbleList: provide messages[]
    BubbleList->>Grouping: apply groupStrategy
    Grouping-->>BubbleList: messageGroups[]
    BubbleList->>BubbleItem: render group
    BubbleItem->>Bubble: render messageGroup
    Bubble->>BoxResolver: useBubbleBoxRenderer(messages, contentIndex?)
    BoxResolver-->>Bubble: box component + attrs
    Bubble->>ContentResolver: useBubbleContentRenderer(message, contentIndex)
    ContentResolver-->>Bubble: content component
    Bubble->>Renderer: render component(s)
    Renderer-->>Client: rendered DOM
    Note right of Bubble: state-change events emitted from BubbleContentWrapper and bubble bubble-level emit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • hexqi

Poem

🐰 Bubbles hop from old to new,
Hooks and groups stitch every view,
Wrappers hum, renderers debut,
A rabbit claps — the refactor grew!

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: a comprehensive refactoring of bubble components specifically for the 0.4.0 version release.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 30, 2025

✅ Preview build completed successfully!

Click the image above to preview.
Preview will be automatically removed when this PR is closed.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 30, 2025

@gene9831 gene9831 changed the title bubble/v0.4.0 refactor(bubble): refactor bubble components for 0.4.0 version Dec 30, 2025
@gene9831 gene9831 marked this pull request as ready for review December 30, 2025 06:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (8)
docs/.vitepress/theme/style.css (1)

45-53: Consider using a more stable selector for the Bubble demo styles.

The selector div:has(h1[id='bubble-气泡组件']) relies on a specific heading ID that may change if documentation is updated or localized. Additionally, nested CSS syntax (.vitepress-demo-plugin-preview inside the :has() block) requires CSS nesting support.

Consider adding a dedicated class to the Bubble demo container for more maintainable targeting.

packages/components/src/bubble/utils.ts (1)

13-29: Consider caching failed import attempts.

When the dynamic imports fail, markdownItAndDompurify is set to null. Since null is falsy, the check on line 16 won't short-circuit subsequent calls, causing the import to be retried and the warning to be logged repeatedly.

Consider using a sentinel value or a separate flag to track failed attempts:

🔎 Proposed fix to prevent retry spam
-let markdownItAndDompurify: { markdown: typeof MarkdownIt; dompurify: typeof DOMPurify } | null = null
+let markdownItAndDompurify: { markdown: typeof MarkdownIt; dompurify: typeof DOMPurify } | null | false = null

 export const getMarkdownItAndDompurify = async () => {
+  if (markdownItAndDompurify === false) {
+    return null
+  }
   if (markdownItAndDompurify) {
     return markdownItAndDompurify
   }

   try {
     const [md, dompurify] = await Promise.all([import('markdown-it'), import('dompurify')])
     markdownItAndDompurify = { markdown: md.default, dompurify: dompurify.default }
     return markdownItAndDompurify
   } catch {
     console.warn('[BubbleMarkdownRenderer] install markdown-it and dompurify to use markdown renderer')
-    markdownItAndDompurify = null
+    markdownItAndDompurify = false
     return null
   }
 }
packages/components/src/bubble/composables/useMessageContent.ts (1)

9-22: Consider documenting the generic type parameter constraints.

The generic type T is asserted on line 21 without runtime validation. While this provides flexibility, incorrect usage (e.g., useMessageContent<number>(...) when content is actually a string) will fail silently at runtime. Consider adding JSDoc to clarify the caller's responsibility for type safety, or constraining T to valid content types.

📝 Suggested documentation enhancement
+/**
+ * Resolves message content reactively with optional array indexing.
+ * @template T - Expected content type (caller must ensure this matches actual content type)
+ * @param message - The bubble message (ref, getter, or value)
+ * @param contentIndex - Optional index for array content (defaults to 0)
+ * @returns Computed ref with resolved content
+ */
 export const useMessageContent = <T = string | ChatMessageContentItem | undefined>(
   message: MaybeRefOrGetter<BubbleMessage>,
   contentIndex?: number,
 ) => {
packages/components/src/bubble/composables/useBubbleStore.ts (1)

31-33: Consider warning when store is not provided.

The function returns an empty object when BUBBLE_STORE_KEY is not provided (line 32), which could mask integration errors where consumers expect a store but forgot to call setupBubbleStore. Consider either:

  • Adding a development-mode warning when the default is used
  • Documenting that consumers must call setupBubbleStore before using useBubbleStore
💡 Option 1: Add development warning
 export function useBubbleStore<T extends Record<string, unknown> = Record<string, unknown>>(): T {
-  return inject(BUBBLE_STORE_KEY, {}) as T
+  const store = inject(BUBBLE_STORE_KEY, undefined)
+  if (!store && process.env.NODE_ENV !== 'production') {
+    console.warn('[useBubbleStore] Store not provided. Did you forget to call setupBubbleStore?')
+  }
+  return (store || {}) as T
 }
packages/components/src/bubble/composables/useOmitMessageFields.ts (2)

17-20: Potential type safety issue with Object.entries/fromEntries.

The Object.entries() and Object.fromEntries() pattern (lines 18-19) will lose non-enumerable properties, getters, and prototype chain information from the original message object. The type assertion as Omit<BubbleMessage, K> assumes the filtered result matches the expected type, but this isn't verified at runtime. If BubbleMessage contains non-plain properties, this could cause subtle bugs.

Consider using a more type-safe approach with object destructuring if the omitted fields are known statically, or document that this utility only works with plain message objects.


22-27: Consider performance implications of computed object spread.

The restProps computed ref creates a new object via spread operator on every access (lines 23-25). If this composable is used in frequently re-rendering components, consider whether the computed ref is necessary or if the operation could be deferred/memoized differently.

This is likely acceptable for most use cases, but worth noting if performance profiling reveals issues.

packages/components/src/bubble/BubbleList.vue (1)

23-24: Consider using a more idiomatic pattern for conditional function assignment.

The scrollToBottomFn is initialized as an empty async function and conditionally reassigned. This works but the let with conditional reassignment could be slightly confusing.

🔎 Alternative approach using a ref
-let scrollToBottomFn: (behavior?: ScrollBehavior) => Promise<void> = async () => {}
+const scrollToBottomFn = ref<(behavior?: ScrollBehavior) => Promise<void>>(async () => {})

 if (props.autoScroll) {
   // ...
-  scrollToBottomFn = scrollToBottom
+  scrollToBottomFn.value = scrollToBottom
 }

 defineExpose({
-  scrollToBottom: scrollToBottomFn,
+  scrollToBottom: (...args: Parameters<typeof scrollToBottomFn.value>) => scrollToBottomFn.value(...args),
 })

However, based on learnings, the autoScroll prop setup is intentionally non-reactive and only initialized once, so the current approach is acceptable.

packages/components/src/bubble/Bubble.vue (1)

74-91: Split mode rendering iterates over resolved content correctly.

The loop iterates using resolveMessageContent(messages[0]) which is called twice (once in v-for, once potentially in shouldSplit computed). Consider caching this value.

🔎 Cache resolved content for split mode

Add a computed property to avoid redundant calls:

const splitContent = computed(() => {
  if (shouldSplit.value) {
    return resolveMessageContent(messages.value[0]) as unknown[]
  }
  return []
})

Then use in template:

-          <BubbleBoxWrapper
-            v-for="(_, index) in resolveMessageContent(messages[0])"
+          <BubbleBoxWrapper
+            v-for="(_, index) in splitContent"
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbb912c and 436719b.

⛔ Files ignored due to path filters (1)
  • packages/svgs/src/assets/atom.svg is excluded by !**/*.svg
📒 Files selected for processing (46)
  • docs/.vitepress/theme/style.css
  • docs/demos/examples/Assistant.vue
  • docs/demos/tools/conversation/Basic.vue
  • docs/demos/tools/message/Basic.vue
  • docs/package.json
  • docs/src/components/bubble.md
  • packages/components/package.json
  • packages/components/src/bubble/Bubble.vue
  • packages/components/src/bubble/BubbleBoxWrapper.vue
  • packages/components/src/bubble/BubbleContentWrapper.vue
  • packages/components/src/bubble/BubbleItem.vue
  • packages/components/src/bubble/BubbleList.vue
  • packages/components/src/bubble/BubbleProvider.vue
  • packages/components/src/bubble/components/ContentItem.vue
  • packages/components/src/bubble/components/index.ts
  • packages/components/src/bubble/composables/index.ts
  • packages/components/src/bubble/composables/useBubbleBoxRenderer.ts
  • packages/components/src/bubble/composables/useBubbleContentRenderer.ts
  • packages/components/src/bubble/composables/useBubbleStateChange.ts
  • packages/components/src/bubble/composables/useBubbleStore.ts
  • packages/components/src/bubble/composables/useMessageContent.ts
  • packages/components/src/bubble/composables/useMessageGroup.ts
  • packages/components/src/bubble/composables/useOmitMessageFields.ts
  • packages/components/src/bubble/constants.ts
  • packages/components/src/bubble/index.ts
  • packages/components/src/bubble/index.type.ts
  • packages/components/src/bubble/renderers/Box.vue
  • packages/components/src/bubble/renderers/Text.vue
  • packages/components/src/bubble/renderers/allRenderers.ts
  • packages/components/src/bubble/renderers/class-renderer.ts
  • packages/components/src/bubble/renderers/collapsible-text.ts
  • packages/components/src/bubble/renderers/collapsible-text.vue
  • packages/components/src/bubble/renderers/defaultRendererMap.ts
  • packages/components/src/bubble/renderers/defaultRenderers.ts
  • packages/components/src/bubble/renderers/index.ts
  • packages/components/src/bubble/renderers/index.type.ts
  • packages/components/src/bubble/renderers/markdown.ts
  • packages/components/src/bubble/renderers/text.ts
  • packages/components/src/bubble/renderers/text.vue
  • packages/components/src/bubble/renderers/tool.ts
  • packages/components/src/bubble/renderers/tool.vue
  • packages/components/src/bubble/utils.ts
  • packages/components/src/index.ts
  • packages/components/src/styles/animations.css
  • packages/components/src/styles/components/bubble.less
  • packages/components/vite.config.ts
💤 Files with no reviewable changes (14)
  • packages/components/src/bubble/renderers/collapsible-text.ts
  • packages/components/src/bubble/renderers/class-renderer.ts
  • packages/components/src/bubble/renderers/index.ts
  • packages/components/src/bubble/renderers/markdown.ts
  • packages/components/src/bubble/renderers/index.type.ts
  • packages/components/src/bubble/renderers/tool.ts
  • packages/components/src/bubble/components/index.ts
  • packages/components/src/bubble/renderers/defaultRendererMap.ts
  • packages/components/src/bubble/renderers/text.ts
  • packages/components/src/bubble/renderers/collapsible-text.vue
  • packages/components/src/bubble/components/ContentItem.vue
  • docs/src/components/bubble.md
  • packages/components/src/bubble/renderers/tool.vue
  • packages/components/src/bubble/renderers/text.vue
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: gene9831
Repo: opentiny/tiny-robot PR: 123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
📚 Learning: 2025-06-25T07:04:18.791Z
Learnt from: gene9831
Repo: opentiny/tiny-robot PR: 123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.

Applied to files:

  • packages/components/src/bubble/renderers/Box.vue
  • packages/components/src/bubble/composables/useMessageGroup.ts
  • packages/components/src/bubble/index.ts
  • packages/components/src/bubble/renderers/allRenderers.ts
  • packages/components/src/bubble/composables/useBubbleContentRenderer.ts
  • docs/demos/tools/conversation/Basic.vue
  • docs/demos/tools/message/Basic.vue
  • packages/components/src/bubble/BubbleItem.vue
  • packages/components/src/bubble/constants.ts
  • packages/components/src/bubble/composables/useBubbleBoxRenderer.ts
  • packages/components/src/bubble/composables/useMessageContent.ts
  • packages/components/src/bubble/BubbleBoxWrapper.vue
  • packages/components/src/bubble/BubbleList.vue
  • packages/components/src/bubble/Bubble.vue
  • packages/components/src/bubble/index.type.ts
  • packages/components/src/bubble/renderers/defaultRenderers.ts
  • packages/components/src/bubble/renderers/Text.vue
  • packages/components/src/bubble/BubbleProvider.vue
  • packages/components/src/bubble/BubbleContentWrapper.vue
📚 Learning: 2025-06-18T09:29:47.974Z
Learnt from: SonyLeo
Repo: opentiny/tiny-robot PR: 119
File: packages/components/src/attachments/index.less:213-213
Timestamp: 2025-06-18T09:29:47.974Z
Learning: 在 packages/components/src/attachments/index.less 中,.tr-file-card__close 的背景色使用了硬编码的 rgb(194, 194, 194),但这个UI元素(关闭按钮)将会被直接替换为图标,所以不需要抽取为CSS变量。

Applied to files:

  • packages/components/src/styles/components/bubble.less
  • packages/components/src/bubble/Bubble.vue
  • docs/.vitepress/theme/style.css
📚 Learning: 2025-12-29T09:18:14.040Z
Learnt from: gene9831
Repo: opentiny/tiny-robot PR: 270
File: packages/components/src/bubble/BubbleList.vue:42-60
Timestamp: 2025-12-29T09:18:14.040Z
Learning: In packages/components/src/bubble/BubbleList.vue, the autoScroll prop setup is intentionally non-reactive - it only needs to be initialized once at component creation and does not need to respond to prop changes during the component lifecycle.

Applied to files:

  • packages/components/src/bubble/BubbleList.vue
🧬 Code graph analysis (7)
packages/components/src/bubble/composables/useMessageGroup.ts (2)
packages/components/src/bubble/index.type.ts (1)
  • BubbleMessageGroup (57-62)
packages/components/src/bubble/constants.ts (1)
  • BUBBLE_MESSAGE_GROUP_KEY (8-9)
packages/components/src/bubble/composables/useBubbleStore.ts (1)
packages/components/src/bubble/constants.ts (1)
  • BUBBLE_STORE_KEY (36-36)
packages/components/src/bubble/renderers/allRenderers.ts (2)
packages/components/src/index.ts (1)
  • BubbleRenderers (42-42)
packages/components/src/bubble/index.ts (1)
  • BubbleRenderers (46-46)
packages/components/src/bubble/composables/useOmitMessageFields.ts (1)
packages/components/src/bubble/index.type.ts (2)
  • BubbleContentRendererProps (80-86)
  • BubbleMessage (38-45)
packages/components/src/bubble/composables/useBubbleStateChange.ts (3)
packages/components/src/bubble/constants.ts (1)
  • BUBBLE_STATE_CHANGE_FN_KEY (38-39)
packages/components/src/index.ts (1)
  • useBubbleStateChangeFn (45-45)
packages/components/src/bubble/index.ts (1)
  • useBubbleStateChangeFn (41-41)
packages/components/src/bubble/index.type.ts (1)
packages/kit/src/types.ts (1)
  • ChatMessage (11-16)
packages/components/src/bubble/renderers/defaultRenderers.ts (1)
packages/components/src/bubble/index.type.ts (2)
  • BubbleBoxRendererMatch (64-69)
  • BubbleContentRendererMatch (71-76)
🔇 Additional comments (44)
packages/components/src/styles/animations.css (1)

6-31: LGTM! Well-implemented utility animations.

The three new animations (ping, pulse, bounce) are cleanly implemented using performant properties (transform and opacity). The bounce animation's cubic-bezier timing functions create smooth, natural motion. These utility animations follow common conventions and will work well for loading states, notifications, and interactive feedback in the bubble components.

docs/demos/examples/Assistant.vue (1)

54-55: LGTM! API surface updated correctly.

The prop names have been correctly updated to align with the new TrBubbleList API: itemsmessages and rolesrole-configs. The bindings remain semantically correct.

docs/demos/tools/conversation/Basic.vue (1)

3-3: LGTM! Consistent API update.

The TrBubbleList props have been updated consistently with the new API surface, matching the changes across other demo files.

docs/package.json (1)

26-27: LGTM!

The addition of dompurify and markdown-it as direct dependencies in the docs project correctly satisfies the peer dependency requirements from @opentiny/tiny-robot components package. Version ranges are consistent across packages.

packages/components/vite.config.ts (1)

46-46: LGTM!

Adding markdown-it and dompurify to the external list correctly aligns with their move to peerDependencies, ensuring these packages are not bundled and must be provided by consumers.

docs/.vitepress/theme/style.css (1)

91-122: LGTM!

The color value normalization and formatting adjustments improve style consistency without changing behavior.

packages/components/src/styles/components/bubble.less (1)

1-48: LGTM!

The CSS variable reorganization is well-structured with clear categorization. The self-referential pattern on line 24 (box-image-border: 4px solid var(--tr-bubble-box-image-border-color)) works correctly since all variables are generated in the same :root scope.

packages/components/src/bubble/composables/useBubbleStateChange.ts (1)

1-12: LGTM!

Clean implementation following Vue 3 provide/inject patterns. The fallback warning function in useBubbleStateChangeFn provides graceful degradation when the provider is not set up, which is appropriate for optional state management scenarios.

packages/components/src/bubble/constants.ts (1)

1-56: LGTM!

Well-structured constants module with:

  • Properly typed InjectionKey constants using Symbol() for collision-free injection
  • MaybeRefOrGetter typing enabling reactive flexibility
  • BubbleRendererMatchPriority with strategic gaps (0, 10, 20) allowing future intermediate priorities
  • Clear bilingual documentation
packages/components/src/bubble/index.type.ts (3)

64-76: LGTM!

The asymmetric signatures are intentional and correct:

  • BubbleBoxRendererMatch.find(messages[]) - box renderer wraps message groups
  • BubbleContentRendererMatch.find(message) - content renderer handles individual messages

This aligns well with the message-group architecture.


38-45: LGTM!

The generic BubbleMessage<T, S> type provides good flexibility for typed content (T extends ChatMessageContent) and custom state (S extends Record<string, unknown>), enabling type-safe usage while maintaining OpenAI format compatibility.


22-32: No action needed—this divergence is intentional and already documented.

The ChatMessage in bubble's index.type.ts is an internal interface (not exported) designed specifically for OpenAI-compatible message handling, as indicated by the existing comment "聊天消息接口(支持 OpenAI 格式)". It serves only to define the internal structure of BubbleMessage, which is the actual exported type. This is a separate, purpose-built type hierarchy from kit's more general ChatMessage, and the code already clarifies this distinction through its documentation.

packages/components/package.json (2)

19-23: LGTM!

Moving dompurify and markdown-it to peerDependencies is the correct approach for this refactor - it allows consumers to control these dependency versions and enables tree-shaking when not used. The devDependencies entries ensure local development works correctly.


28-28: jsonrepair is actively used in the codebase.

The dependency is lazily imported via dynamic import in packages/components/src/bubble/utils.ts for bubble component functionality.

packages/components/src/bubble/renderers/allRenderers.ts (1)

1-7: LGTM! Clean barrel export.

The structure is straightforward and aligns well with the composable-based rendering architecture. The consolidated BubbleRenderers export provides a clear public API surface for the default renderer components.

packages/components/src/bubble/utils.ts (1)

4-11: LGTM! Proper lazy-loading with caching.

The caching pattern correctly ensures that jsonrepair is loaded only once, and subsequent calls return the cached promise.

packages/components/src/bubble/renderers/Box.vue (3)

1-5: LGTM! Props definition follows the intended pattern.

The component correctly defines props via BubbleBoxRendererProps type. Per the design (based on retrieved learnings), renderer components work with fixed types and don't need reactive prop handling.


7-11: LGTM! Clean template structure.

The template uses data attributes for styling variants and provides a slot for flexible content composition.


13-43: LGTM! Comprehensive theming with CSS variables.

The styling approach uses CSS variables for all themeable properties and provides multiple variants (shape, placement, box-type) through data attributes. The logic for corner shape inheriting from rounded and then selectively overriding corners is appropriate.

packages/components/src/bubble/renderers/Text.vue (3)

1-8: LGTM! Proper composable usage.

The component correctly uses useMessageContent with a getter function for reactive message access and passes contentIndex directly.


10-14: LGTM! Template handles both string and object content.

The conditional rendering and type checking logic correctly handles content that may be either a string or an object with a text property. The optional chaining ensures safety even if the object structure varies.


16-32: LGTM! Well-structured text styling.

The styling correctly handles text presentation with proper whitespace preservation (pre-wrap), word breaking, and spacing between consecutive text blocks.

packages/components/src/bubble/BubbleContentWrapper.vue (2)

1-22: LGTM! Well-structured event handling and renderer resolution.

The component correctly:

  • Resolves the content renderer via useBubbleContentRenderer
  • Defines typed emits for state changes
  • Wraps state changes with contentIndex context
  • Establishes the state change provider via setupBubbleStateChangeFn

24-26: LGTM! Clean dynamic component rendering.

The template correctly renders the resolved renderer component and forwards all props via v-bind.

packages/components/src/bubble/index.ts (1)

38-46: LGTM! Public API reorganized for composable-based architecture.

The new exports shift from direct renderer classes to composables and a consolidated BubbleRenderers object. This aligns with the PR objectives for the v0.4.0 refactor and provides a cleaner, more flexible API surface.

Note: This is a breaking change from the previous API, but this is clearly intentional for the major version update.

packages/components/src/bubble/composables/useMessageGroup.ts (2)

5-7: LGTM! Clean provider setup.

The function correctly establishes the message group provider using Vue's provide API with the appropriate injection key.


9-11: LGTM! Proper injection with sensible default.

The function correctly injects the message group with undefined as the default, which is appropriate for optional context that may not always be provided.

packages/components/src/bubble/composables/index.ts (1)

1-7: LGTM! Clean composables barrel export.

The barrel file properly aggregates all bubble composables, providing a clean single import point for consumers of the bubble API.

packages/components/src/bubble/BubbleItem.vue (1)

1-42: LGTM!

The component properly establishes reactive message group context via setupBubbleMessageGroup and cleanly forwards slots and events to the wrapped Bubble component. The state-change event delegation pattern is well-implemented.

packages/components/src/bubble/renderers/defaultRenderers.ts (1)

1-11: LGTM!

The use of markRaw on lines 10-11 is appropriate for preventing unnecessary reactivity on component definitions, which can improve performance. The empty default match arrays provide a clean foundation for the composable-based renderer system.

packages/components/src/bubble/BubbleBoxWrapper.vue (1)

12-12: The contentIndex parameter is intentionally non-reactive. Component instances with specific contentIndex values are created and destroyed via the v-for loop in the parent component; contentIndex does not change within a single instance's lifecycle. This pattern is consistent across useBubbleBoxRenderer, useBubbleContentRenderer, and useMessageContent, indicating it is the intended design.

Likely an incorrect or invalid review comment.

packages/components/src/bubble/composables/useBubbleBoxRenderer.ts (3)

11-22: LGTM! Clean provider setup with conditional injection.

The conditional provide calls ensure that only defined values are injected, avoiding unnecessary overrides of parent providers. This aligns well with the composable-driven architecture.


28-35: Well-documented prop-level fallback mechanism.

The JSDoc clearly explains the purpose - providing prop-level configuration that won't override parent provider. This separation of concerns between provider-level and prop-level configuration is a good pattern.


37-64: Solid renderer resolution with clear fallback priority.

The resolution logic is clean:

  1. Match against provided/default renderer matches
  2. Fall back through prop-level → provider-level → default

One minor observation: toValue(fallbackBoxRenderer) on line 62 could return undefined when injected with undefined as the default (line 45), but this is correctly handled by the || chain.

Based on learnings, the non-reactive resolution approach aligns with the intentional design where renderer resolution doesn't need to respond to type changes after initialization.

packages/components/src/bubble/BubbleList.vue (3)

95-131: Divider grouping logic looks correct but has subtle role assignment behavior.

On line 121, when a message is not a divider, the group role is set to message.role || ''. However, this means non-divider messages with different roles could be grouped together if they appear consecutively after a divider.

For example: [{role: 'user'}, {role: 'assistant'}, {role: 'system'}] with dividerRole: 'user' would group assistant and system messages together with role = 'assistant'.

Is this the intended behavior? The comment suggests grouping by "divider/non-divider type" which would make this correct, but it may be worth documenting this behavior explicitly.


52-87: Good grouping logic with proper sealing mechanism for array content.

The isLastGroupSealed flag correctly prevents subsequent messages from being added to a group when the previous message had array content. The Chinese comments are helpful for understanding the business logic.

The edge case of undefined or empty role strings is properly handled: when message.role is undefined or empty (defaulting to ''), the downstream BubbleItem component receives the fallback role (group.role || props.fallbackRole) with a default value of 'assistant', ensuring consumers never encounter empty role strings.


168-168: The messageIndex calculation is correct. The Bubble component emits messageIndex as a zero-based offset within the group's message array, and BubbleList correctly converts it to an absolute index by adding group.startIndex. No changes needed.

packages/components/src/bubble/Bubble.vue (4)

29-40: Good fallback pattern for standalone vs. nested usage.

The component correctly supports both:

  1. Nested usage (messages from parent BubbleItem via injection)
  2. Standalone usage (message constructed from individual props)

The computed messages array ensures a consistent interface for rendering.


43-44: Prop-level renderer setup uses reactive getters correctly.

The arrow functions () => props.fallbackBoxRenderer ensure reactivity is maintained when props change. This is the correct pattern for passing props to setup functions that expect MaybeRefOrGetter.


96-104: Nested template loops handle array content within single mode.

The logic correctly handles messages where content is an array by rendering multiple BubbleContentWrapper instances. The key structure content-${contentIndex} is appropriate here since it's scoped within each message.


177-187: CSS module for avatar positioning is well-structured.

Using a CSS module for the avatar grid positioning separates concerns between scoped layout styles and module-based component-specific styles. The placement-based grid positioning (grid-area) cleanly handles LTR/RTL-like layouts.

packages/components/src/bubble/BubbleProvider.vue (3)

16-26: Priority sorting places lower values first - verify this is intentional.

The sort (a.priority ?? 0) - (b.priority ?? 0) results in ascending order, meaning renderers with lower priority values are checked first. This could be counter-intuitive if "higher priority" is expected to mean "checked first."

Typically, priority systems use either:

  • Higher number = higher priority (descending sort needed)
  • Lower number = higher priority (current ascending sort)

Please verify the intended priority semantics match the implementation. If higher numbers should take precedence:

🔎 Potential fix for descending priority
-    .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
+    .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))

28-37: Clean fallback resolution and setup hook invocation.

The computed properties correctly provide default values when props are undefined, and the setup hooks are called with reactive computed refs ensuring the injected values stay in sync with prop changes.


12-14: Consider adding default values for optional props.

Unlike BubbleList.vue which uses withDefaults, this component uses plain defineProps<BubbleProviderProps>(). If BubbleProviderProps has optional fields without defaults, TypeScript will infer them as potentially undefined.

The current implementation handles this correctly via props.boxRendererMatches || [] patterns, so this is just a stylistic observation.

… 'items' and 'roles' to 'messages' and 'role-configs' for improved clarity
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
docs/demos/tools/conversation/IndexedDB.vue (1)

34-43: Consider renaming the variable for clarity.

The variable is named roles while the prop is now role-configs. For better alignment with the new API, consider renaming:

-const roles: Record<string, BubbleRoleConfig> = {
+const roleConfigs: Record<string, BubbleRoleConfig> = {
   assistant: {
     placement: 'start',
     avatar: aiAvatar,
   },
   user: {
     placement: 'end',
     avatar: userAvatar,
   },
 }

And update the template binding:

-<tr-bubble-list :messages="messages" :role-configs="roles"></tr-bubble-list>
+<tr-bubble-list :messages="messages" :role-configs="roleConfigs"></tr-bubble-list>

Note: The removal of maxWidth: '80%' from both role configurations appears intentional as part of the v0.4.0 refactor. Verify that bubble width constraints are now handled by the component's internal defaults or other configuration.

docs/demos/tools/conversation/LocalStorage.vue (1)

34-43: Same observations as IndexedDB.vue.

The role configuration follows the same pattern with maxWidth removed and variable named roles while the prop is role-configs. Consider the same optional refactor suggested in IndexedDB.vue for consistency and clarity.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 436719b and bc3666c.

📒 Files selected for processing (2)
  • docs/demos/tools/conversation/IndexedDB.vue
  • docs/demos/tools/conversation/LocalStorage.vue
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: gene9831
Repo: opentiny/tiny-robot PR: 123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.
📚 Learning: 2025-06-25T07:04:18.791Z
Learnt from: gene9831
Repo: opentiny/tiny-robot PR: 123
File: packages/components/src/bubble/message/Message.vue:22-40
Timestamp: 2025-06-25T07:04:18.791Z
Learning: In the Message component (packages/components/src/bubble/message/Message.vue), the renderer resolution is intentionally not reactive to type changes - the component is designed to work with a fixed type that doesn't change after initialization.

Applied to files:

  • docs/demos/tools/conversation/IndexedDB.vue
  • docs/demos/tools/conversation/LocalStorage.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / build
🔇 Additional comments (2)
docs/demos/tools/conversation/IndexedDB.vue (1)

3-3: LGTM! API migration correctly implemented.

The prop bindings have been successfully updated to use the new API surface (:messages and :role-configs), aligning with the v0.4.0 bubble component refactor.

docs/demos/tools/conversation/LocalStorage.vue (1)

3-3: LGTM! Consistent API migration.

The prop bindings correctly use the new API surface, matching the implementation in IndexedDB.vue.

@hexqi hexqi merged commit 03aaa92 into opentiny:develop Dec 30, 2025
4 checks passed
@github-actions
Copy link
Contributor

🧹 Preview Cleaned Up

The preview deployment has been removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants