Skip to content

Conversation 系列 ​

对话容器组件,提供滚动管理和自动吸底功能。包含 4 个子组件:

  • Conversation — 根容器,provide 滚动上下文与消息布局上下文
  • ConversationContent — 可滚动消息区,max-width 768px 居中
  • ConversationEmpty — 欢迎屏(空对话占位)
  • ConversationScrollBtn — 回到底部浮动按钮

代码演示 ​

Conversation 作为根容器包裹消息区与输入区,ConversationContent 负责滚动与自动吸底,空对话时用 ConversationEmpty 展示欢迎屏。发送一条消息体验完整链路(消息追加、模拟回复、自动滚到底部):

有什么可以帮你的?

选择一个话题开始,或直接输入你的问题

消息布局 ​

layout 属性控制消息对齐模式,可实时切换体验两种布局的差异;im 模式下 messageAlign 决定用户消息停靠侧(AI 消息在对侧)。试试切换下方按钮:

  • stacked — 所有消息统一靠左,适合阅读型长回复
  • im — 用户与 AI 分列两侧,单条气泡限宽,适合即时问答
U
你
给我一句话解释什么是 Monorepo
AI Chat UI
U
你
它和 Multirepo 的核心区别是什么?
AI Chat UI
查看源码
vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  Conversation, ConversationContent, Message, MessageContent,
} from '@toimc/vue'

const layout = ref<'stacked' | 'im'>('stacked')
const messageAlign = ref<'left' | 'right'>('right')
</script>

<template>
  <!-- stacked:统一靠左;im:用户与 AI 分列两侧 -->
  <Conversation :layout="layout" :message-align="messageAlign">
    <ConversationContent>
      <Message from="user">
        <MessageContent>给我一句话解释什么是 Monorepo</MessageContent>
      </Message>
      <Message from="assistant">
        <MessageContent
          content="Monorepo 是把多个包放进同一个代码仓库管理的工程模式。"
        />
      </Message>
    </ConversationContent>
  </Conversation>
</template>

消息角色 ​

Message 的 from 属性区分三种角色样式:user 用户消息、assistant AI 消息、system 系统提示。AI 消息可通过 MessageContent 的 thinking 属性展示折叠式思考过程(点击「已思考 X 秒」展开),操作按钮 MessageActions 在 hover 时浮现:

U
你
用一句话说明 Composition API 解决了什么问题
AI Chat UI
AI Chat UI
已切换到简洁回答模式:优先给结论,必要时补充示例。
查看源码
vue
<script setup lang="ts">
import type { ThinkingInfo } from '@toimc/core'
import {
  Message, MessageContent, MessageActions, MessageAction,
} from '@toimc/vue'

const thinking: ThinkingInfo = {
  content: '用户要一句话解释,压缩到核心痛点…',
  duration: 4210, // 思考耗时(毫秒),useChat 流式时自动计算
}

const message = {
  role: 'assistant',
  content: 'Composition API 让逻辑按**功能**聚合而不是按选项类型分散。',
}
</script>

<template>
  <Message :from="message.role">
    <!-- thinking 可选:提供即显示折叠式思考过程 -->
    <MessageContent :content="message.content" :thinking="thinking" />

    <!-- hover 消息时浮现 -->
    <MessageActions>
      <MessageAction title="复制"><!-- 复制图标 --></MessageAction>
      <MessageAction title="重新生成"><!-- 重新生成图标 --></MessageAction>
    </MessageActions>
  </Message>
</template>

滚动管理 ​

ConversationContent 上监听 scroll 可检测是否位于底部(距底 50px 内视为吸底);离开底部时显示 ConversationScrollBtn,点击回到底部。向上滚动下面的长列表试试:

U
你
第 1 条:Vue 3 组合式函数的最佳实践 (向上滚动后,右下角会出现回到底部按钮)
AI Chat UI
U
你
第 3 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 5 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 7 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 9 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 11 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 13 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 15 条:Vue 3 组合式函数的最佳实践
AI Chat UI
U
你
第 17 条:Vue 3 组合式函数的最佳实践
AI Chat UI
查看源码
vue
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import {
  Conversation, ConversationContent, ConversationScrollBtn,
} from '@toimc/vue'

const scrollRef = ref<HTMLElement>()
const isAtBottom = ref(true)

function handleScroll() {
  const el = scrollRef.value
  if (!el) return
  isAtBottom.value = el.scrollHeight - el.scrollTop - el.clientHeight < 50
}

function scrollToBottom() {
  nextTick(() => {
    if (scrollRef.value) scrollRef.value.scrollTop = scrollRef.value.scrollHeight
  })
}
</script>

<template>
  <Conversation>
    <ConversationContent ref="scrollRef" @scroll="handleScroll">
      <!-- 消息列表 -->
    </ConversationContent>

    <ConversationScrollBtn v-if="!isAtBottom" @click="scrollToBottom" />
  </Conversation>
</template>

与 useChat 组合 ​

vue
<script setup lang="ts">
import { useChat } from '@toimc/core'
import {
  Conversation, ConversationContent, ConversationEmpty
} from '@toimc/vue'

const chat = useChat(adapter)
</script>

<template>
  <Conversation>
    <ConversationContent>
      <ConversationEmpty v-if="chat.messages.length === 0">
        <h3>有什么可以帮你的?</h3>
      </ConversationEmpty>

      <Message v-for="msg in chat.messages" :key="msg.id" :from="msg.role">
        <MessageContent>{{ msg.content }}</MessageContent>
      </Message>
    </ConversationContent>
  </Conversation>
</template>

API ​

Conversation ​

属性名类型默认值说明
layout'stacked' | 'im''stacked'消息对齐模式:stacked 统一靠左(现状);im 用户与 AI 分列两侧
messageAlign'left' | 'right''left'im 模式下用户消息停靠侧(AI 在对侧);stacked 下不生效
autoScrollbooleantrue新消息自动吸底
themePresetKey—内置预设名(default / purple / green / warm)
customThemeRecord<string, string>—自定义 CSS 变量键值对象
darkModeboolean | 'auto''auto'容器级明暗:true / false 强制,'auto' 跟随全局
插槽名说明
default消息内容区域(ConversationContent / ConversationScrollBtn 等)

通过 provide 把滚动上下文与消息布局上下文下发给子组件。

ConversationContent ​

消息区默认 max-width: var(--ai-chat-content-max-width) 居中,四周留白 24px(底部留白用于与输入区隔开);容器宽度 ≥ 1280px(xl 超大屏)时经容器查询自动放宽到 --ai-chat-content-max-width-wide(宽屏显示更宽的消息)。

插槽名说明
default消息内容区域

ConversationEmpty ​

插槽名说明
default空状态内容(欢迎屏)

ConversationScrollBtn ​

属性名类型默认值说明
badgenumber—未读角标数字;缺省读取滚动上下文的 unreadCount,显式传 0 强制隐藏角标
事件名说明
click原生 click 透传(非组件声明事件)。点击回底已由组件内置完成(调用滚动上下文的 scrollToBottom),宿主仅在需要附加逻辑时监听

未读角标:用户离底期间内容高度每增加一次计数 +1(贴底恒为 0),点击按钮或手动滚回贴底即清零;角标 aria-label 走 i18n conversation.scrollUnread。「新消息」以内容高度增加近似,无法区分消息新增与流式文本追加,精确计数请传 badge 覆盖。详见 ConversationScrollBtn。