Skip to content

对话

How should I handle state?
Keep the source of truth in one place.

使用方法 ​

Chat 这一套组件,就像积木一样,包含搭建完整聊天窗口所需的各个部件:滚动的对话外框、消息列表、区分发送者的消息行、聊天气泡、AI 工具调用折叠栏、思考过程折叠、正在输入动画,以及未读消息提示线。消息正文通过插槽传入。组件本身不带 Markdown 解析器,你可以直接放纯文本,也可以接上你自己喜欢的 Markdown 渲染库。

什么时候用:

  • 搭建大模型 AI 对话界面、客服客服聊天窗口或即时聊天应用时。

什么时候不要用:

  • 如果只是为了让用户留个言、发一条评论,用普通的表单评论区组件更轻快。
  • 如果只是通知中心的一串流水消息,用时间线列表或通知流即可。

各个积木的组装分工如下:

  • ChatLayout 是最外层的纵向布局容器。消息记录区在上方自适应伸展,输入框贴在底部,底部还带有一层毛玻璃磨砂效果。默认由布局自己滚动;如果外层已经有滚动容器,可以传入 scrollRef,底部停靠会自动调整为跟随固定的模式。
  • 当用户浏览到最底部时,新生成的内容会自动把滚动条继续贴在底部。如果用户往上翻看历史记录,自动贴底会立刻暂停,并浮现一个回到最底部的圆形按钮。在没有贴底时来了新消息,按钮文字会变成「有新消息」,未读分割线 ChatUnreadDivider 也会标出新旧分界线。
  • ChatComposer 是底部的输入套件,放进 ChatLayout 的 composer 插槽里。它负责草稿状态、回车发送、Shift 加回车换行、停止生成按钮等。它还支持附件抽屉以及基于浏览器原生语音识别的麦克风听写功能。落地页和带文档面板的整页会话见应用模式。
vue
<script setup lang="ts">
import { ChatComposer, ChatLayout, ChatMessage, ChatMessageBubble, ChatMessageList } from '@astryx-vue/core/Chat'

function onSubmit(value: string) {
  console.log(value)
}
</script>

<template>
  <ChatLayout>
    <ChatMessageList>
      <ChatMessage sender="user">
        <ChatMessageBubble>
          帮我看一下发布说明。
        </ChatMessageBubble>
      </ChatMessage>
      <ChatMessage sender="assistant">
        <ChatMessageBubble name="助手">
          说明在文档里。
        </ChatMessageBubble>
      </ChatMessage>
    </ChatMessageList>
    <template #composer>
      <ChatComposer placeholder="输入消息……" @submit="onSubmit" />
    </template>
  </ChatLayout>
</template>
  • ChatMessageList 是承载消息记录的列表容器,带有 role="log"。当 AI 助手的文字还在持续生成时传入 isStreaming,读屏软件会等整段输出完毕后再统一朗读,避免不断打扰用户。当用户向上翻到顶部时,scrollToTopAction 会触发加载历史消息,加载完成前顶部会显示旋转动画。
  • ChatMessage 代表单条消息行。sender 可以是 user(用户)、assistant(AI 助手)或 system(系统通知)。系统通知会默认水平居中。
  • ChatToolCalls 用来展示 AI 调用的外部工具或函数。只有一次调用时直接显示一行;多次调用时会收拢进折叠栏,点击可以查看参数与结果明细。
  • ChatReasoning 是折叠卡片,用来展示 AI 的思考推理过程,默认折叠收起。
  • ChatTypingIndicator 用来展示「正在输入」的跳动三个小圆点,告诉用户对方正在打字或 AI 正在构思。

下面先看输入套件和消息列表放在一起的样子,再按布局、回到底部、消息、气泡、列表、元信息、系统消息、工具调用往下排。

建议 ​

建议做法
推荐记录放进消息列表,输入套件放进底部插槽,磨砂和回到底部的按钮才对得齐。
推荐助手正文还在增长时保持 isStreaming,写完再关掉。
推荐把 ChatUnreadDivider 放在读者还没看到的第一条前面。按钮上的「有新消息」负责播报,分隔线负责标位置。
推荐气泡里放纯文本、引文,或你自己的渲染结果。气泡只提供底色和圆角。
避免在布局里面再套一个滚动容器。记录的滚动只交给布局自己,或者交给 scrollRef 指向的那个元素。
避免每个流式 token 都自己播报一遍。正在输入靠自己的 role="status" 播报一次,新消息文案走布局里的 live region。

解剖

部分说明
消息区可滚动的消息区域。默认插槽(一般是 ChatMessageList)排成纵向 flex,消息少时把内容推到底部。
毛玻璃停靠区底部粘住或固定的容器,带一层背景模糊。里面放滚到底按钮和输入区。
滚到底按钮用户往上滚,或有新消息时出现。默认是 ChatLayoutScrollButton。把 showScrollButton 设为 false 就隐藏;要换成别的控件,用 scrollButton 插槽。
输入区发消息的输入区域,一般是 ChatComposer。停在毛玻璃层里的底部。
空状态还没有消息时居中显示的占位。用 EmptyState 可以保持外观一致。
头像消息旁边的发送者头像,一般是 size 为 md 的 Avatar。系统消息不显示。
名字消息正文上方的发送者名字。用气泡时放在气泡上;自由内容则放在消息外层。
内容消息正文:一个或多个 ChatMessageBubble,或图片、工具调用这类自由插槽内容。非气泡内容可以包进幽灵气泡,好跟气泡的文字列对齐。
元信息时间、送达状态,以及消息下方的操作。放在最后一个气泡上,或放在消息外层。

示例 ​

ChatLayout — 面板

Can you review the Button component and fix the focus ring?

Found the issue — the border radius was hardcoded. Replaced with the theme token.

窄栏里的会话。输入文字后回车,这句话会出现在记录末尾。容器一窄就会用上紧凑密度。

ChatComposer — 外壳

notes.mdfocus.png
Balanced

带附件抽屉、顶栏按钮和语音按钮的输入区。没有字时发送按钮是浅色并且不能点,写上字才变成可点的实心按钮。只有附件、没有字,仍然不能发送。点抽屉把手可以收起附件。点麦克风会在支持语音的浏览器里开始听写。

ChatComposer — 状态

The connection is slow.

上面一条正在生成,编辑停用,停止按钮仍可点。中间是紧凑、无阴影,并带警告。下面把错误放在输入区上方。

ChatComposerInput — 提及

词首输入 @ 可以连续打多个词,比如 Ada Lov,列表会跟着缩到 Ada Lovelace。方向键把高亮移到某一行,回车插入的是这一行,不是列表里的第一条。没有匹配的句子会把列表藏起来,已经打的字还在。词首输入 / 是单词命令,打一个空格列表就关上。选中的人变成标记,命令按原文插入。

ChatTokenizedText — 气泡里的标记

Ask @Ada to check the focus ring.

同一组标记既能出现在输入框里,也能出现在已经发出的气泡里。

ChatLayoutScrollButton — 文案

Labels expand the button to give context

回到底部的按钮可以只显示图标,也可以带上新消息、未读回复这类说明。

ChatMessage — 头像与名字

A
Agent
I reviewed the pull request. The changes look solid — clean code and good test coverage.
Thanks! Merging it now.
A
Agent
I can run the deployment pipeline once it lands. Just let me know.

带头像和发送者名字。正文以气泡开头时,名字放在气泡上;没有气泡时,名字放在消息上。

ChatMessage — 透明气泡

Here is an analysis of your production metrics from last week. Traffic peaked at 12,400 requests per second on Wednesday, with a p99 latency of 45ms. Error rate stayed below 0.1% across all endpoints.
That looks great. Can you compare it to the week before?
Compared to the previous week, traffic is up 8% and latency improved by 3ms. The deployment on Tuesday seems to have helped.

透明气泡没有可见边界,仍保留内边距以便对齐。适合助手的长回复。

ChatMessage — 多气泡

I have a couple of questions about the new API.
First, how should we handle pagination?
And second, what's the rate limit?
A
Agent
Great questions! For pagination, use cursor-based with a limit parameter. The response includes a nextCursor field.
Rate limit is 100 requests per minute per API key. You'll get a 429 response with a Retry-After header if you exceed it.

用 group 收紧同一侧的圆角,把同一人连续发出的几条气泡连在一起。

ChatMessageBubble — 自定义内容

Can you pull up the Q3 report?
Navi
Sure, here is the artifact from last quarter.

Q3 Performance Report

12 pages · updated 2 days ago

气泡里可以放对齐到文字列的自定义内容。透明气泡拉满消息列,时间贴在气泡的元信息上。

ChatMessageBubble — 密度

Compact

The build completed in 4.2 seconds.
Ship it to staging.

Balanced

The build completed in 4.2 seconds.
Ship it to staging.

Spacious

The build completed in 4.2 seconds.
Ship it to staging.

紧凑、均衡、宽松并排。密度决定气泡内边距、圆角,以及成组气泡之间的间距。

ChatMessageBubble — 分组

A
Agent
I reviewed the three files you shared.
The data model looks solid, but the API handler has a race condition on concurrent writes.
I can draft a fix if you want.
Yes please!
Also add a test for the concurrent case.

一条消息里用 first、middle、last 收紧发送者那一侧的圆角。

ChatMessageBubble — 元信息

A
Agent
Your deployment finished successfully. All 14 checks passed.
Great, can you send me the production URL?

名字和元信息都对齐气泡的内边距。名字放在第一条气泡上,元信息放在最后一条上。

ChatMessageBubble — 变体

Filled — sender-colored background (default)

Can you summarize the latest deployment logs?

Ghost — transparent background, keeps alignment padding

The last deploy completed at 2:41 PM with zero errors across all three regions.

实心和透明两种气泡,用户和助手都会用到。实心是普通消息,透明用来对齐、但不画边界。

ChatMessageList — 密度

Compact

How does density work?
A
Density provides default spacing at every level — message gap, bubble padding, and gap between child elements. Use gap to tune row spacing independently.

Balanced

How does density work?
A
Density provides default spacing at every level — message gap, bubble padding, and gap between child elements. Use gap to tune row spacing independently.

Spacious

How does density work?
A
Density provides default spacing at every level — message gap, bubble padding, and gap between child elements. Use gap to tune row spacing independently.

三种密度并排。侧栏用紧凑,整页对话多用均衡,长文用宽松。行距要和密度分开时,用 gap。

ChatMessageList — 完整会话

useReducer.tsformState.ts
Can you review these files?
A
Sure! Here's the key pattern from useReducer.ts:
The reducer is pure and easy to test — pass in state and action, assert on the output.
    
tsx
const [state, dispatch] = useReducer(
(state, action) => ({
...state,
[action.field]: action.value,
}),
{ name: '', email: '' }
);
Agent shared a code snippet
That's clean, thanks!

一次会话里放上日期分隔、文件名、代码、透明气泡和元信息。

ChatMessageMetadata — 底部操作

Summarize the Q1 revenue report.
Q1 revenue reached $2.4B, up 18% year-over-year. Enterprise subscriptions drove 62% of the growth, while ad revenue held steady at $890M.

助手回复下面的操作:复制、重试、有用、没用,以及模型名。

ChatMessageMetadata — 状态

Deploying the update now…
Config pushed to staging.
Verified on the staging cluster.
Looks good — promoting to prod.
Rollback triggered, checking logs.

五种送达状态:发送中、已发送、已送达、已读、失败。每条都带时间。

ChatMessageMetadata — 时间

Thanks — any blockers I should know about?
Relative timestamps work too — helpful for older messages where the exact time matters less than recency.

只有时间的元信息。可以是钟点,也可以是相对时间。

ChatSystemMessage — 图标

Icons reinforce the message type

Jordan was added to the conversation
Messages are end-to-end encrypted
Agent is generating a response…
Conversation verified by admin

系统消息前面放一个图标,用来区分加入、加密、生成和核验。

ChatSystemMessage — 状态更新

Sarah Chen joined the conversation
Topic changed to "Q2 Launch Planning"
Alex Rivera left the conversation
Conversation marked as resolved

会话里的状态流水:谁加入、话题改了、谁离开、日期,以及已解决。

ChatSystemMessage — 变体

Default

Alex joined the conversation
Conversation marked as resolved

Divider

默认和分隔线并排。默认用来写一行状态,分隔线用来标日期或分段。

ChatToolCalls — 可展开

工具调用可以展开结果,比如差异和命令输出。点一行就能打开。

ChatToolCalls — 简单

已发送bashgit status1.2s
已发送readsrc/components/DataGrid.tsx30ms
已发送editsrc/components/DataGrid.tsx+24-885ms
已发送editsrc/components/DataGrid.test.tsx+4560ms

上面是单条调用,下面是可折叠的一组,并带上增删行数。

ChatToolCalls — 各种状态

bashyarn build
bashyarn test
已发送editsrc/App.tsx+8-2120ms
错误:3 lint errors foundbashyarn lint

等待、运行、完成、失败四种状态放在同一组里。