Message

Message 为聊天与会话界面提供消息行结构。它负责整体对齐、头像位置和具名 slot 的默认间距;应用负责消息数据、发送者、时间戳、送达状态和操作逻辑。

#适用场景

  • 需要把头像、发送者、时间、bubble、附件和 footer 组合成一条消息。
  • 需要同时支持接收消息和发送消息的起始侧/结束侧布局。
  • 需要把多个连续消息堆叠为一个发送者分组。
  • 需要以 Ghost Bubble 展示 Markdown、代码或没有卡片表面的富内容。

只有一条状态提示时使用 Marker;只有一个内容 surface 时使用 Bubble;不要为每种消息类型创建专用的 Message wrapper。

#导入

use gpui_kit::{ParentElement as _, StyleRefinement, Styled as _};
use gpui_kit::component::{
    ActiveTheme as _, Colorize as _, Sizable as _, StyledExt as _,
    attachment::{Attachment, AttachmentContent, AttachmentTitle},
    avatar::Avatar,
    bubble::{Bubble, BubbleVariant},
    button::{Button, ButtonVariants as _},
    message::{
        Message, MessageAlignment, MessageAvatar, MessageContent, MessageFooter,
        MessageGroup, MessageHeader,
    },
};

#结构

Message
├── MessageAvatar       # 可选,发送者身份
└── inner stack
    ├── MessageHeader   # 可选,发送者与时间
    ├── MessageContent  # 可选,Bubble、附件、Markdown 等
    └── MessageFooter   # 可选,送达状态、reaction、操作

所有具名 slot 都可以继续添加任意 GPUI element,并分别实现 StyledMessage 不持有消息模型,也不会替应用决定 header 或 footer 的文本。

#基础用法

一条完整消息可以同时提供 avatar、header、content 和 footer:

Message::new()
    .avatar_slot(
        MessageAvatar::new()
            .child(Avatar::new().name("Alice").size_8()),
    )
    .header(
        MessageHeader::new()
            .child("Alice")
            .child("10:24"),
    )
    .content(
        MessageContent::new().bubble(
            Bubble::new().child("可以帮我检查一下吗?"),
        ),
    )
    .footer(MessageFooter::new().child("已读"))

只需要头像和内容时,可以使用便利的 .avatar(...)

Message::new()
    .avatar(Avatar::new().name("Alice").size_8())
    .content(MessageContent::new().bubble(
        Bubble::new().child("收到的消息"),
    ))

#对齐

MessageAlignment 会作用于消息行和 MessageContent 内的 Bubble:

Message::new()
    .alignment(MessageAlignment::Start)
    .avatar(Avatar::new().name("Alice"))
    .content(MessageContent::new().bubble(
        Bubble::new().child("对方的消息"),
    ));

Message::new()
    .alignment(MessageAlignment::End)
    .avatar(Avatar::new().name("我"))
    .content(MessageContent::new().bubble(
        Bubble::new().with_variant(BubbleVariant::Secondary).child("我发送的消息"),
    ))
用途
Start接收消息或起始侧消息。
End发送消息或结束侧消息。组件会反转头像与内容的行方向。

MessageContent 内的 Bubble 可以不设置自己的 alignment,让 Message 统一传播布局。独立使用 Bubble 时再显式设置 alignment。

#Avatar

.avatar(...) 会把任意 element 包装进 MessageAvatar;需要调整 slot 自身时使用 .avatar_slot(...)

Message::new()
    .avatar_slot(
        MessageAvatar::new()
            .p_0()
            .child(Avatar::new().name("Support").size_8()),
    )
    .content(MessageContent::new().bubble(
        Bubble::new().child("我们已经处理了你的请求。"),
    ))

MessageAvatar 保留共享的 avatar 尺寸基线,并始终与消息内容的底边对齐;footer 渲染在头像行之下、按内容列缩进。身份 fallback、头像图片和名称文字由 Avatar 负责。

Header 适合放发送者、时间和其他低强调元信息:

Message::new()
    .header(
        MessageHeader::new()
            .child("Alice")
            .child("·")
            .child("10:24"),
    )
    .content(MessageContent::new().bubble(
        Bubble::new().child("消息内容"),
    ))

Header 默认有水平内容 inset。需要和 Ghost Bubble 对齐时,Message 会根据 content 自动处理;也可以显式调用 .content_inset(false).content_inset(true)

#Content

Content 是消息主体,可以包含多个 Bubble、附件、图片、代码块或应用自己的富文本 renderer:

Message::new()
    .content(
        MessageContent::new()
            .bubble(Bubble::new().child("先看结论:"))
            .bubble(
                Bubble::new()
                    .with_variant(BubbleVariant::Ghost)
                    .child("这是第二段无表面富内容。"),
            ),
    )

MessageContent::bubble(...) 会保留 Bubble 的 Ghost 元信息,用于协调 Header 和 Footer 的 inset;用普通 .child(...) 添加的 Bubble 不参与这个类型级传播。

Footer 可放送达状态、reaction、Button 或其他次级信息:

Message::new()
    .content(MessageContent::new().bubble(
        Bubble::new().child("需要回复的内容"),
    ))
    .footer(
        MessageFooter::new()
            .child("未读")
            .child(Button::new("reply").ghost().small().label("回复")),
    )

Footer 内的 Button、Link 和其他控件由应用提供自己的事件、disabled、loading 和标签状态。

#富内容、附件与操作

Message 通过组合现有组件表达不同内容,不增加重复的消息专用控件:

Message::new()
    .avatar(Avatar::new().name("Alice"))
    .header(MessageHeader::new().child("Alice").child("刚刚"))
    .content(
        MessageContent::new()
            .bubble(Bubble::new().child("请查看这个文件。"))
            .child(
                Attachment::new().content(
                    AttachmentContent::new()
                        .title(AttachmentTitle::new("quarterly-report.pdf")),
                ),
            ),
    )
    .footer(
        MessageFooter::new()
            .child(Button::new("download").outline().small().label("下载")),
    )

需要 Markdown、代码或 HTML 时,在 MessageContent 中放入应用选择的 text renderer;Message 只提供布局和 alignment,不改变富文本的选择、复制和交互行为。

#消息分组

MessageGroup 用于堆叠同一发送者的连续消息:

MessageGroup::new()
    .child(
        Message::new()
            .avatar(Avatar::new().name("Alice"))
            .content(MessageContent::new().bubble(
                Bubble::new().child("第一条消息"),
            )),
    )
    .child(
        Message::new()
            .content(MessageContent::new().bubble(
                Bubble::new().child("同一发送者的第二条消息"),
            )),
    )

分组只负责垂直 stack 和共享间距;发送者变化、分组边界、时间戳和 avatar 是否重复显示由应用决定。需要不同发送者之间的间距时,在外层列表或自定义 group style 中表达。

#Ghost surface 与内容 inset

Ghost Bubble 没有背景、边框、padding,可用于 Markdown、代码或需要与消息行直接对齐的富内容:

Message::new()
    .header(
        MessageHeader::new()
            .child("系统")
            .child("刚刚"),
    )
    .content(
        MessageContent::new().bubble(
            Bubble::new()
                .with_variant(BubbleVariant::Ghost)
                .child("已完成索引更新。"),
        ),
    )
    .footer(MessageFooter::new().child("无需进一步操作"))

MessageContent::bubble(...) 中包含 Ghost Bubble 时,Header 和 Footer 默认移除水平 inset;这样元信息与无表面内容左边缘对齐。调用方可以覆盖该行为:

Message::new()
    .header(MessageHeader::new().content_inset(true).child("保留 inset"))
    .content(MessageContent::new().bubble(
        Bubble::new().with_variant(BubbleVariant::Ghost).child("内容"),
    ))
    .footer(MessageFooter::new().content_inset(false).child("移除 inset"))

.content_inset(...) 是 slot 的显式设置,优先于 Message 根据 Ghost Bubble 推导的默认值。.px_0() 等普通 Styled refinement 仍可用于更细的布局调整。

#自定义样式与主题 token

MessageMessageGroupMessageAvatarMessageHeaderMessageContentMessageFooter 都实现 Styled。具名 slot 之间的 stack 使用 with_stack_style(...)

Message::new()
    .with_stack_style(StyleRefinement::default().gap_3())
    .p_3()
    .rounded(cx.theme().radius_lg)
    .bg(cx.theme().muted.opacity(0.35))
    .avatar_slot(
        MessageAvatar::new()
            .bg(cx.theme().secondary)
            .child(Avatar::new().name("A")),
    )
    .header(MessageHeader::new().px_0().child("Alice · 10:24"))
    .content(MessageContent::new().bubble(
        Bubble::new().child("遵循当前主题的消息 surface"),
    ))

推荐使用 cx.theme() 的语义颜色、圆角和共享 design scale。Message 的外层、inner stack、avatar、header、content 和 footer 都有独立的样式入口,调用方可以调整表面、间距、文字层级和对齐,而不需要复制 Message 的布局实现。

#组件边界

  • Message 不持有发送者、时间戳、送达状态、reaction 或操作状态;这些数据由应用生成对应的 child。
  • MessageContent::bubble(...) 是专门用于 Bubble 的类型化便利入口,用于传播 Ghost surface 元信息;其他 element 使用普通 .child(...)
  • 应用操作使用 Button,URL 使用 Link,附件使用 Attachment;不创建消息专用的 Action、Link 或 Attachment wrapper。
  • 需要消息列表、尾部跟随、未读定位或历史加载时,使用 MessageScroller 管理虚拟列表;Message 只负责单行布局。

#可访问性

  • Avatar 是身份辅助信息,不应是唯一的发送者标识;Header 应提供可读发送者或系统来源。
  • 时间、送达状态、失败状态和未读信息应作为可读文本提供,不能只用颜色、位置或 icon 表达。
  • Footer 中的 icon-only Button 应提供可见的 .label(...) 或其他可读名称,tooltip 只作为补充提示;发送者操作应使用明确的 Button/Link 语义。
  • Bubble、Attachment 和富文本 child 的键盘行为由各自组件负责;Message 不会自动为普通 div 增加焦点。
  • 应用自定义消息动画时,应在 reduced motion 下保持静态结果;Message 自身没有额外动画。
  • 长消息和代码内容应保持可读的换行、选择和滚动策略,不要依赖 hover 才能访问完整内容。

#API 参考

#Message

方法说明
new()创建默认起始侧对齐的消息。
alignment(MessageAlignment)设置起始侧或结束侧对齐。
with_stack_style(StyleRefinement)调整 Header、Content、Footer 内部 stack。
avatar(element)将任意 element 包装进 MessageAvatar
avatar_slot(MessageAvatar)设置完整的 avatar slot。
header(MessageHeader)设置 header slot。
content(MessageContent)设置 content slot。
footer(MessageFooter)设置 footer slot。
Styled调整消息行自身的布局与 surface。

#MessageGroup

方法说明
new()创建连续消息的垂直 stack。
child(element)按顺序添加消息。
Styled调整分组间距、宽度和布局。

#MessageAvatar

方法说明
new()创建身份 slot。
child(element)添加 Avatar 或其他身份内容。
Styled调整 slot 的尺寸、背景和位置。

#MessageHeader / MessageFooter

方法说明
new()创建对应的元信息或次级内容 slot。
content_inset(bool)显式保留或移除默认水平 inset。
child(element)添加文本、状态或操作。
Styled调整文字、间距和布局。

#MessageContent

方法说明
new()创建消息主体 slot。
bubble(Bubble)添加 Bubble,并参与 Ghost surface 的 inset 协调。
child(element)添加任意富内容,不参与 Bubble 类型元信息传播。
Styled调整主体的 stack、宽度和对齐。

#类型链接