# 对话消息气泡

## 使用方法

`ChatMessageBubble` 是消息里那块带底色和圆角的内容框。它不给发送者属性，而是从外层 `ChatMessage` 自己读出来，于是底色、圆角、对齐都会跟着走。不在 `ChatMessage` 里时，按助手那一侧显示。

`variant` 选外观：`filled` 是常规的实心气泡；`ghost` 只保留内边距、去掉底色，正文没有可见边界但仍能对齐。名字和元信息各有插槽，左右内边距跟气泡正文一致。同一个人的连续几条用 `group` 写着 first / middle / last，靠发送者那一侧的圆角会收小，看起来是一整段。

气泡里只有一个代码块，或者只有带 `astryx-chat-card` 类的元素时，气泡会自己去掉内边距和底色，让卡片跟气泡边缘齐平。

```vue
<script setup lang="ts">
import { ChatMessage, ChatMessageBubble } from '@astryx-vue/core/Chat'
</script>

<template>
  <ChatMessage sender="user">
    <ChatMessageBubble>
      Can you review the token validation changes?
    </ChatMessageBubble>
  </ChatMessage>
</template>
```

## 示例

常见的用法和不同的状态。

### 自定义内容

消息里放自定义内容，并且跟气泡的文字列对齐。示例把一个卡片包在 width="100%" 的透明气泡里，左边缘就贴着气泡文字，宽度铺满整列而不是被默认的气泡宽上限卡住；时间放在气泡的元信息插槽里。

```vue
<script setup lang="ts">
import { ChatMessage, ChatMessageBubble, ChatMessageList, ChatMessageMetadata } from '@astryx-vue/core/Chat'
</script>

<template>
  <ChatMessageList>
    <ChatMessage sender="user">
      <ChatMessageBubble>
        {{ 'Can you pull up the Q3 report?' }}
      </ChatMessageBubble>
    </ChatMessage>
    <ChatMessage sender="assistant">
      <ChatMessageBubble variant="ghost" width="100%" name="Navi">
        {{ 'Sure, here is the artifact from last quarter.' }}
        <div class="chat-preview__card">
          <p class="chat-preview__card-title">
            {{ 'Q3 Performance Report' }}
          </p>
          <p class="chat-preview__card-meta">
            {{ '12 pages · updated 2 days ago' }}
          </p>
        </div>
        <template #metadata>
          <ChatMessageMetadata footer="Claude Opus 4.6" timestamp="9:45 AM" />
        </template>
      </ChatMessageBubble>
    </ChatMessage>
  </ChatMessageList>
</template>
```

### 疏密

紧凑、平衡、宽松三种疏密并排看。疏密会影响气泡内边距、圆角，以及同一组气泡之间的间距。

```vue
<script setup lang="ts">
import type { ChatDensity } from '@astryx-vue/core/Chat'
import { ChatMessage, ChatMessageBubble, ChatMessageList } from '@astryx-vue/core/Chat'
import { Divider } from '@astryx-vue/core/Divider'

const densities: { id: ChatDensity, label: string }[] = [
  { id: 'compact', label: 'Compact' },
  { id: 'balanced', label: 'Balanced' },
  { id: 'spacious', label: 'Spacious' },
]
</script>

<template>
  <div class="chat-preview-densities">
    <template v-for="(density, index) in densities" :key="density.id">
      <Divider v-if="index > 0" />
      <div>
        <p class="chat-preview__caption">
          {{ density.label }}
        </p>
        <ChatMessageList :density="density.id">
          <ChatMessage sender="assistant">
            <ChatMessageBubble>
              {{ 'The build completed in 4.2 seconds.' }}
            </ChatMessageBubble>
          </ChatMessage>
          <ChatMessage sender="user">
            <ChatMessageBubble>
              {{ 'Ship it to staging.' }}
            </ChatMessageBubble>
          </ChatMessage>
        </ChatMessageList>
      </div>
    </template>
  </div>
</template>
```

### 分组

多条气泡用 first、middle、last 标位置。靠发送者那一侧的圆角会收小，看着是一口气说完的一段。

```vue
<script setup lang="ts">
import { Avatar } from '@astryx-vue/core/Avatar'
import { ChatMessage, ChatMessageBubble, ChatMessageList, ChatMessageMetadata } from '@astryx-vue/core/Chat'

const agent = 'Agent'
</script>

<template>
  <ChatMessageList>
    <ChatMessage sender="assistant">
      <template #avatar>
        <Avatar :name="agent" size="md" />
      </template>
      <ChatMessageBubble group="first" :name="agent">
        {{ 'I reviewed the three files you shared.' }}
      </ChatMessageBubble>
      <ChatMessageBubble group="middle">
        {{ 'The data model looks solid, but the API handler has a race condition on concurrent writes.' }}
      </ChatMessageBubble>
      <ChatMessageBubble group="last">
        {{ 'I can draft a fix if you want.' }}
        <template #metadata>
          <ChatMessageMetadata timestamp="10:45 AM" />
        </template>
      </ChatMessageBubble>
    </ChatMessage>
    <ChatMessage sender="user">
      <ChatMessageBubble group="first">
        {{ 'Yes please!' }}
      </ChatMessageBubble>
      <ChatMessageBubble group="last">
        {{ 'Also add a test for the concurrent case.' }}
        <template #metadata>
          <ChatMessageMetadata status="delivered" timestamp="10:46 AM" />
        </template>
      </ChatMessageBubble>
    </ChatMessage>
  </ChatMessageList>
</template>
```

### 元信息

名字和元信息插槽，位置跟气泡内边距对齐。一条消息里，名字放第一条气泡，元信息放最后一条气泡。

```vue
<script setup lang="ts">
import { Avatar } from '@astryx-vue/core/Avatar'
import { ChatMessage, ChatMessageBubble, ChatMessageList, ChatMessageMetadata } from '@astryx-vue/core/Chat'

const agent = 'Agent'
</script>

<template>
  <ChatMessageList>
    <ChatMessage sender="assistant">
      <template #avatar>
        <Avatar :name="agent" size="md" />
      </template>
      <ChatMessageBubble :name="agent">
        {{ 'Your deployment finished successfully. All 14 checks passed.' }}
        <template #metadata>
          <ChatMessageMetadata footer="Claude Opus 4.6" timestamp="9:15 AM" />
        </template>
      </ChatMessageBubble>
    </ChatMessage>
    <ChatMessage sender="user">
      <ChatMessageBubble>
        {{ 'Great, can you send me the production URL?' }}
        <template #metadata>
          <ChatMessageMetadata status="read" timestamp="9:16 AM" />
        </template>
      </ChatMessageBubble>
    </ChatMessage>
  </ChatMessageList>
</template>
```

### 变体

实心和透明两种气泡，用户和助手两侧都能用。常规消息用实心；不想看到边界、又要对齐时用透明。

```vue
<script setup lang="ts">
import { ChatMessage, ChatMessageBubble, ChatMessageList } from '@astryx-vue/core/Chat'
</script>

<template>
  <ChatMessageList>
    <p class="chat-preview__caption">
      {{ 'Filled — sender-colored background (default)' }}
    </p>
    <ChatMessage sender="user">
      <ChatMessageBubble>
        {{ 'Can you summarize the latest deployment logs?' }}
      </ChatMessageBubble>
    </ChatMessage>
    <p class="chat-preview__caption">
      {{ 'Ghost — transparent background, keeps alignment padding' }}
    </p>
    <ChatMessage sender="assistant">
      <ChatMessageBubble variant="ghost">
        {{ 'The last deploy completed at 2:41 PM with zero errors across all three regions.' }}
      </ChatMessageBubble>
    </ChatMessage>
  </ChatMessageList>
</template>
```

## 属性

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `variant` | `'filled' \| 'ghost'` | `'filled'` | `'filled'` 铺发送者那一侧的底色。`'ghost'` 保留内边距、去掉底色，混排内容照样能对齐。气泡里只有一个代码块，或者只有带 `astryx-chat-card` 类的元素时，气泡会去掉内边距和底色，让卡片跟气泡边缘齐平。 |
| `name` | `string` | — | 气泡上方的发送者名字，左右位置跟气泡内边距对齐。写在一条消息的第一条气泡上；如果第一条内容不是气泡，改用 `ChatMessage` 的 `name`。 |
| `group` | `'first' \| 'middle' \| 'last'` | — | 在一组气泡里的位置。会把靠发送者那一侧的圆角收小。单独一条气泡不用写。 |
| `width` | `string` | — | 换掉默认的 `max(80%, 280px)` 宽度上限。想让气泡里的内容铺满整列，就写 `100%`。配合 `variant="ghost"` 用来放卡片和附件。 |

## 插槽

- `default`：气泡正文。纯文本、Markdown 或者任意元素都行。气泡只负责表面。
- `name`：替代 `name` 属性。
- `metadata`：气泡下方的元信息，位置跟气泡内边距对齐。写在一条消息的最后一条气泡上；如果最后一条内容不是气泡，改用 `ChatMessage` 的 `metadata` 插槽。

## 暴露

- `element`：气泡表面那一层元素。
