Skip to content

Message 系列 ​

消息渲染组件,包含 5 个子组件:

  • Message — 消息项,根据 from 区分用户/助手样式
  • MessageContent — 消息正文容器(支持 thinking / streaming)
  • MessageActions — 操作按钮容器(hover 显示)
  • MessageAction — 单个操作按钮
  • MessageAttachments — 附件容器

消息域扩展组件(独立页面):

  • MessageFeedback — 消息 👍/👎 反馈与点踩评论
  • 预设操作五件(见 message-actions):MessageActionCopy 复制回显 / MessageActionRetry 重试 / MessageActionSpeak 朗读 / MessageActionEdit 行内编辑 / MessageActionFeedback 紧凑反馈
  • BranchPicker — 消息分支版本翻页(分支数据约定 Message.metadata.branches)

基础用法 ​

用户消息直接走默认插槽;AI 消息传 content prop 走 Markdown 渲染(需在应用入口 app.provide(markdownRendererKey, MarkdownRenderer))。MessageActions 默认透明,hover 到消息时浮现:

U
你
你好,请帮我写一段代码
AI Chat UI
查看源码
vue
<template>
  <Message from="user">
    <MessageContent>你好,请帮我写一段代码</MessageContent>
  </Message>

  <Message from="assistant">
    <MessageContent :content="message.content" />
    <MessageActions>
      <MessageAction title="复制" @click="copy">
        <!-- 复制图标 -->
      </MessageAction>
      <MessageAction title="重新生成" @click="regenerate">
        <!-- 重新生成图标 -->
      </MessageAction>
    </MessageActions>
  </Message>
</template>

消息角色 ​

from 属性三种取值对应不同视觉:user 用户提问、assistant AI 回复(带头像)、system 系统提示(弱化样式):

U
你
用一句话说明 Composition API 解决了什么问题
AI Chat UI
AI Chat UI
已切换到简洁回答模式:优先给结论,必要时补充示例。

消息的水平对齐由父级 Conversation 的 layout / messageAlign 控制(详见 Conversation 文档)。

思考过程 ​

MessageContent 传入 thinking 后,消息顶部展示折叠式思考块。点击下方按钮体验完整的流式生命周期 —— 正在思考…(内容实时展开,可随时手动折叠)→ 正文开始流式时思考块自动收起为「已思考 X 秒」(思考光标消失,光标只跟正文走):

U
你
Vue 3 的响应式系统为什么用 Proxy 重写?
AI Chat UI

思考内容由 useChat 消费 thinking chunk 自动累积,思考结束于首个正文/工具帧到达(此刻收尾 active 并就地计算耗时,done 帧与中断路径兜底),无需手动维护:

查看源码
vue
<script setup lang="ts">
import { ref } from 'vue'
import { Message, MessageContent } from '@toimc/vue'

const streaming = ref(true)
const message = ref({
  content: '',
  thinking: {
    content: '正在分析用户的请求…\n拆解为关键概念…',
    duration: 4210, // done 时由 useChat 自动计算
  },
})
</script>

<template>
  <Message from="assistant">
    <MessageContent
      :content="message.content"
      :thinking="message.thinking"
      :streaming="streaming"
    />
  </Message>
</template>

适配器侧只需按顺序 yield thinking 与 text chunk:

typescript
async *sendMessage({ messages, signal }) {
  yield { type: 'thinking', content: '正在分析用户请求...\n' }
  yield { type: 'thinking', content: '拆解问题为关键概念...\n' }
  yield { type: 'text', content: '根据分析,这里是关键概念:' }
  yield { type: 'done', content: '' } // 触发耗时计算并折叠
}

A/B 回复对比 ​

服务端响应可在 Message 上携带 comparison 载荷,渲染层据此显示 ComparisonMessage 对比卡;点「喜欢这个」后选中内容原地固化为普通 AI 消息(content 被写入、载荷被清除),可点「重置演示」反复体验:

assistant 消息携带 comparison 载荷 → 渲染对比卡,点「喜欢这个」试试
U
你
Vue 3 里响应式状态选 ref 还是 reactive?给我两个不同角度的回答
AI Chat UI
回复 A · 基础视角
回复 B · 工程视角
查看源码
vue
<script setup lang="ts">
import type { Message } from '@toimc/core'
import { Message, MessageContent, ComparisonMessage } from '@toimc/vue'

function onPrefer(
  msg: Message,
  p: { chosen: 'A' | 'B'; left: string; right: string },
) {
  // 原地固化:状态存于消息本身,切换会话不丢失
  msg.content = p.chosen === 'A' ? p.left : p.right
  msg.comparison = undefined
}
</script>

<template>
  <Message v-for="msg in messages" :key="msg.id" :from="msg.role">
    <ComparisonMessage
      v-if="msg.comparison"
      :left="msg.comparison.left"
      :right="msg.comparison.right"
      :left-label="msg.comparison.leftLabel"
      :right-label="msg.comparison.rightLabel"
      @prefer="(p) => onPrefer(msg, p)"
    />
    <MessageContent v-else :content="msg.content" />
  </Message>
</template>

附件消息 ​

MessageAttachments 作为附件容器放在正文上方或下方,内部用 Attachment 系列 渲染每个附件:图片走缩略图,文档走类型图标 + 名称/大小:

U
你
component-architecture.png
component-architecture.pngPNG · 248.0 KB
theme-tokens.png
theme-tokens.pngPNG · 190.0 KB
这是我们现在的组件架构图和主题令牌设计,帮我review一下
AI Chat UI
📄
ai-chat-ui-调研报告.pdfPDF · 3.2 MB
查看源码
vue
<script setup lang="ts">
import type { Attachment } from '@toimc/core'
import {
  Message, MessageContent, MessageAttachments,
  Attachment, AttachmentPreview, AttachmentInfo,
} from '@toimc/vue'

const files: Attachment[] = [
  { id: 'att-1', name: '架构图.png', mediaType: 'image/png',
    size: 253952, url: 'https://example.com/arch.png' },
  { id: 'att-2', name: '调研报告.pdf', mediaType: 'application/pdf',
    size: 3355443 },
]
</script>

<template>
  <Message from="user">
    <!-- 附件在正文上方 -->
    <MessageAttachments>
      <Attachment v-for="f in files" :key="f.id" :data="f">
        <AttachmentPreview />
        <AttachmentInfo />
      </Attachment>
    </MessageAttachments>
    <MessageContent>帮我 review 这份材料</MessageContent>
  </Message>
</template>

API ​

Message ​

属性名类型默认值说明
from'user' | 'assistant' | 'system'—消息角色(必填)
插槽名说明
default消息体(MessageContent / MessageActions 等)
avatar整个头像区,覆盖默认头像(用户 U 字圆标 / AI 闪电徽标)
avatar-text用户头像文字(默认 U)
avatar-iconAI 头像图标(默认闪电 svg)
role角色名(默认 i18n message.you / message.assistant)

消息的水平对齐由父级 Conversation 的 layout / messageAlign 控制(经 provide/inject 下发):

  • layout='stacked'(默认):保持现状,所有消息统一靠左。
  • layout='im':用户消息与 AI 消息分列两侧,单条气泡限宽 --ai-chat-message-max-width(默认 480px);messageAlign 决定用户消息停靠侧,AI 在对侧。

未检测到布局上下文时回退现状布局,向后兼容。

MessageContent ​

属性名类型默认值说明
contentstring—消息正文内容(传值后走 Markdown 渲染,不传走默认插槽)
thinkingThinkingInfo—思考过程信息,提供后显示折叠式思考块
streamingboolean—是否正在流式更新(思考块仅在思考自身进行中(thinking.active 非 false)显示「正在思考…」,正文流式期间思考块保持收起;正文为空时显示三点跳动指示(发送→首 token 的空窗反馈),有正文时行尾由 MarkdownRenderer 挂呼吸光标)
toolCallsToolCallInfo[]—opt-in 工具调用渲染区:传入后消息正文前自动渲染工具调用——completed 且 result.ui 为合法 UISchema 时走 GenUIRenderer(type 未注册落回 ToolCall 面板),其余状态渲染 ToolCall 面板;不传时行为与旧版一致(宿主自行组装)
typescript
interface ThinkingInfo {
  content: string   // 思考内容
  duration?: number // 思考耗时(毫秒),思考结束时由 useChat 自动计算
  startTime?: Date  // 思考开始时间
  active?: boolean  // 思考是否仍在进行:useChat 流式期间维护,首个正文/工具帧到达即置 false
}

MessageAttachments ​

无 Props。附件项数据经 Attachment 的 data 传入。

插槽名说明
default附件项(Attachment 系列)

MessageActions ​

无 Props。默认 opacity: 0,父级 .ai-chat-message hover 时显示。

MessageAction ​

属性名类型默认值说明
labelstring—无障碍文本(不渲染视觉内容,作为 title 的兜底)
tooltipstring—悬停提示,优先于 label 作为按钮 title
disabledbooleanfalse禁用按钮
插槽名说明
default按钮图标/内容
事件名说明
click按钮点击