聊天消息

GitHub
显示带有图标、头像和操作的聊天消息。

用法

ChatMessage 组件为 <user>(用户)或 <assistant>(助手)聊天消息渲染一个 <article> 元素。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
使用 ChatMessages 组件来显示聊天消息列表。

组成部分 (Parts)

使用 parts 属性,以 AI SDK 格式显示消息内容。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
<template>
  <UChatMessage
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Hello! Tell me more about building AI chatbots with Nuxt UI.'
      }
    ]"
    role="user"
    id="1"
  />
</template>
parts 属性是 AI SDK 推荐的格式。每个部分都有一个 type(例如 'text')和相应的内容。ChatMessage 组件也支持已弃用的 content 属性以实现向后兼容。

方向

使用 side 属性来设置消息显示在左侧还是右侧。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
<template>
  <UChatMessage
    side="right"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Hello! Tell me more about building AI chatbots with Nuxt UI.'
      }
    ]"
    role="user"
    id="1"
  />
</template>
当使用 ChatMessages 组件时,side 属性会被自动设置为 left(用于 assistant 消息)和 right(用于 user 消息)。

变体

使用 variant 属性来更改消息的样式。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
<template>
  <UChatMessage
    variant="soft"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Hello! Tell me more about building AI chatbots with Nuxt UI.'
      }
    ]"
    role="user"
    id="1"
  />
</template>
当使用 ChatMessages 组件时,variant 属性会被自动设置为 naked(用于 assistant 消息)和 soft(用于 user 消息)。

颜色 4.8+

使用 color 属性来改变消息的颜色。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
<template>
  <UChatMessage
    variant="soft"
    color="primary"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Hello! Tell me more about building AI chatbots with Nuxt UI.'
      }
    ]"
    role="user"
    id="1"
  />
</template>

Icon

使用 icon 属性在消息旁显示一个 Icon 组件。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
<template>
  <UChatMessage
    icon="i-lucide-user"
    variant="soft"
    side="right"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Hello! Tell me more about building AI chatbots with Nuxt UI.'
      }
    ]"
    role="user"
    id="1"
  />
</template>

Avatar

使用 avatar 属性在消息旁显示一个 Avatar 组件。

你好!请详细介绍一下如何使用 Nuxt UI 构建 AI 聊天机器人。
<template>
  <UChatMessage
    :avatar="{
      src: 'https://github.com/benjamincanac.png',
      loading: 'lazy'
    }"
    variant="soft"
    side="right"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Hello! Tell me more about building AI chatbots with Nuxt UI.'
      }
    ]"
    role="user"
    id="1"
  />
</template>

你也可以使用 avatar.icon 属性将图标显示为头像。

Nuxt UI 为构建 AI 聊天机器人提供了多种功能,包括 ChatMessage、ChatMessages 和 ChatPrompt 组件。最佳实践包括使用 AI SDK 中的 Chat 类,通过变体 (variants) 实现适当的消息样式,以及利用内置操作进行消息交互。这些组件完全可定制,支持主题化和响应式设计。
<template>
  <UChatMessage
    :avatar="{
      icon: 'i-lucide-bot'
    }"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Nuxt UI offers several features for building AI chatbots including the ChatMessage, ChatMessages, and ChatPrompt components. Best practices include using the Chat class from AI SDK, implementing proper message styling with variants, and utilizing the built-in actions for message interactions. The components are fully customizable with theming support and responsive design.'
      }
    ]"
    role="assistant"
    id="1"
  />
</template>

操作

使用 actions 属性在消息下方显示操作按钮,当鼠标悬停在消息上时会显示这些操作。

Nuxt UI 为构建 AI 聊天机器人提供了多种功能,包括 ChatMessage、ChatMessages 和 ChatPrompt 组件。最佳实践包括使用 AI SDK 中的 Chat 类,通过变体 (variants) 实现适当的消息样式,以及利用内置操作进行消息交互。这些组件完全可定制,支持主题化和响应式设计。
<script setup lang="ts">
import type { ButtonProps } from '@nuxt/ui'

const actions = ref<ButtonProps[]>([
  {
    label: 'Copy to clipboard',
    icon: 'i-lucide-copy'
  }
])
</script>

<template>
  <UChatMessage
    :actions="actions"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Nuxt UI offers several features for building AI chatbots including the ChatMessage, ChatMessages, and ChatPrompt components. Best practices include using the Chat class from AI SDK, implementing proper message styling with variants, and utilizing the built-in actions for message interactions. The components are fully customizable with theming support and responsive design.'
      }
    ]"
    role="user"
    id="1"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ButtonProps } from '@nuxt/ui'

const actions = ref<ButtonProps[]>([
  {
    label: 'Copy to clipboard',
    icon: 'i-lucide-copy'
  }
])
</script>

<template>
  <UChatMessage
    :actions="actions"
    :parts="[
      {
        type: 'text',
        id: '1',
        text: 'Nuxt UI offers several features for building AI chatbots including the ChatMessage, ChatMessages, and ChatPrompt components. Best practices include using the Chat class from AI SDK, implementing proper message styling with variants, and utilizing the built-in actions for message interactions. The components are fully customizable with theming support and responsive design.'
      }
    ]"
    role="user"
    id="1"
  />
</template>

示例

查看 Chat 概览页面以获取安装说明、服务器设置和使用示例。

API

属性

属性默认值类型
as'article'any

此组件应渲染为的元素或组件。

idstring

消息的唯一标识符。

role"system" | "user" | "assistant"

消息的角色。

partsUIMessagePart<TDataParts, TTools>[]

消息的组成部分。在 UI 中渲染消息时请使用此属性。

应避免使用系统消息(请改在服务器端设置系统提示词)。它们可以包含文本部分。

用户消息可以包含文本部分和文件部分。

助手消息可以包含文本、推理过程、工具调用和文件部分。

图标any
avatarAvatarProps & { [key: string]: any; }
variant'naked'"solid" | "outline" | "soft" | "subtle" | "naked"
color'neutral'"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
side'left'"left" | "right"
actions(Omit<ButtonProps, "onClick"> & { onClick?: ((e: MouseEvent, message: UIMessage<TMetadata, TDataParts, TTools>) => void) | undefined; })[]

在消息下方显示一系列操作。 label 将用于工具提示。 { size: 'xs', color: 'neutral', variant: 'ghost' }

紧凑falseboolean

以紧凑样式渲染消息。当在 UChatPalette 中使用时,会自动执行此操作。

内容string
metadataTMetadata

消息的元数据。

ui{ root?: ClassNameValue; header?: ClassNameValue; container?: ClassNameValue; body?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; files?: ClassNameValue; content?: ClassNameValue; actions?: ClassNameValue; }

插槽

插槽类型
页头UIMessage<TMetadata, TDataParts, TTools>
前置UIMessage<TMetadata, TDataParts, TTools> & { avatar: (AvatarProps & { [key: string]: any; }) | undefined; ui: object; }
filesOmit<UIMessage<TMetadata, TDataParts, TTools>, "parts"> & { parts: FileUIPart[]; }
主体UIMessage<TMetadata, TDataParts, TTools>
内容UIMessage<TMetadata, TDataParts, TTools> & { content?: string | undefined; }
actionsUIMessage<TMetadata, TDataParts, TTools> & { actions: (Omit<ButtonProps, "onClick"> & { onClick?: ((e: MouseEvent, message: UIMessage<TMetadata, TDataParts, TTools>) => void) | undefined; })[] | undefined; }

主题

app.config.ts
export default defineAppConfig({
  ui: {
    chatMessage: {
      slots: {
        root: 'group/message relative w-full',
        header: 'flex mb-1.5',
        container: 'relative flex items-start',
        body: 'min-w-0',
        leading: 'inline-flex items-center justify-center min-h-6',
        leadingIcon: 'shrink-0',
        leadingAvatar: 'shrink-0',
        leadingAvatarSize: '',
        files: 'flex items-center gap-1.5',
        content: 'relative text-pretty wrap-break-word *:first:mt-0 *:last:mb-0',
        actions: [
          '[@media(hover:hover)]:opacity-0 group-hover/message:opacity-100 absolute bottom-0 flex items-center',
          'transition-opacity'
        ]
      },
      variants: {
        variant: {
          solid: '',
          outline: '',
          soft: '',
          subtle: '',
          naked: ''
        },
        color: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        side: {
          left: {},
          right: {
            container: 'justify-end ms-auto max-w-[75%]',
            header: 'justify-end',
            actions: 'right-0'
          }
        },
        leading: {
          true: ''
        },
        actions: {
          true: ''
        },
        compact: {
          true: {
            root: 'scroll-mt-3',
            container: 'gap-1.5 pb-3',
            content: 'space-y-2',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs'
          },
          false: {
            root: 'scroll-mt-4 sm:scroll-mt-6',
            container: 'gap-3 pb-8',
            content: 'space-y-4',
            leadingIcon: 'size-8',
            leadingAvatarSize: 'md'
          }
        }
      },
      compoundVariants: [
        {
          compact: true,
          actions: true,
          class: {
            container: 'pb-8'
          }
        },
        {
          variant: [
            'solid',
            'outline',
            'soft',
            'subtle'
          ],
          compact: false,
          class: {
            content: 'px-4 py-3 rounded-lg min-h-12',
            leading: 'mt-2'
          }
        },
        {
          variant: [
            'solid',
            'outline',
            'soft',
            'subtle'
          ],
          compact: true,
          class: {
            content: 'px-2 py-1 rounded-lg min-h-8',
            leading: 'mt-1'
          }
        },
        {
          variant: 'naked',
          side: 'left',
          class: {
            content: 'w-full'
          }
        },
        {
          color: 'primary',
          variant: 'solid',
          class: {
            content: 'bg-primary text-inverted'
          }
        },
        {
          color: 'primary',
          variant: 'outline',
          class: {
            content: 'text-primary ring ring-primary/25'
          }
        },
        {
          color: 'primary',
          variant: 'soft',
          class: {
            content: 'bg-primary/10 text-primary'
          }
        },
        {
          color: 'primary',
          variant: 'subtle',
          class: {
            content: 'bg-primary/10 text-primary ring ring-primary/25'
          }
        },
        {
          color: 'primary',
          variant: 'naked',
          class: {
            content: 'text-primary'
          }
        },
        {
          color: 'neutral',
          variant: 'solid',
          class: {
            content: 'bg-inverted text-inverted'
          }
        },
        {
          color: 'neutral',
          variant: 'outline',
          class: {
            content: 'bg-default ring ring-default'
          }
        },
        {
          color: 'neutral',
          variant: 'soft',
          class: {
            content: 'bg-elevated/50'
          }
        },
        {
          color: 'neutral',
          variant: 'subtle',
          class: {
            content: 'bg-elevated/50 ring ring-default'
          }
        }
      ],
      defaultVariants: {
        side: 'left',
        variant: 'naked',
        color: 'neutral'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        chatMessage: {
          slots: {
            root: 'group/message relative w-full',
            header: 'flex mb-1.5',
            container: 'relative flex items-start',
            body: 'min-w-0',
            leading: 'inline-flex items-center justify-center min-h-6',
            leadingIcon: 'shrink-0',
            leadingAvatar: 'shrink-0',
            leadingAvatarSize: '',
            files: 'flex items-center gap-1.5',
            content: 'relative text-pretty wrap-break-word *:first:mt-0 *:last:mb-0',
            actions: [
              '[@media(hover:hover)]:opacity-0 group-hover/message:opacity-100 absolute bottom-0 flex items-center',
              'transition-opacity'
            ]
          },
          variants: {
            variant: {
              solid: '',
              outline: '',
              soft: '',
              subtle: '',
              naked: ''
            },
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            side: {
              left: {},
              right: {
                container: 'justify-end ms-auto max-w-[75%]',
                header: 'justify-end',
                actions: 'right-0'
              }
            },
            leading: {
              true: ''
            },
            actions: {
              true: ''
            },
            compact: {
              true: {
                root: 'scroll-mt-3',
                container: 'gap-1.5 pb-3',
                content: 'space-y-2',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs'
              },
              false: {
                root: 'scroll-mt-4 sm:scroll-mt-6',
                container: 'gap-3 pb-8',
                content: 'space-y-4',
                leadingIcon: 'size-8',
                leadingAvatarSize: 'md'
              }
            }
          },
          compoundVariants: [
            {
              compact: true,
              actions: true,
              class: {
                container: 'pb-8'
              }
            },
            {
              variant: [
                'solid',
                'outline',
                'soft',
                'subtle'
              ],
              compact: false,
              class: {
                content: 'px-4 py-3 rounded-lg min-h-12',
                leading: 'mt-2'
              }
            },
            {
              variant: [
                'solid',
                'outline',
                'soft',
                'subtle'
              ],
              compact: true,
              class: {
                content: 'px-2 py-1 rounded-lg min-h-8',
                leading: 'mt-1'
              }
            },
            {
              variant: 'naked',
              side: 'left',
              class: {
                content: 'w-full'
              }
            },
            {
              color: 'primary',
              variant: 'solid',
              class: {
                content: 'bg-primary text-inverted'
              }
            },
            {
              color: 'primary',
              variant: 'outline',
              class: {
                content: 'text-primary ring ring-primary/25'
              }
            },
            {
              color: 'primary',
              variant: 'soft',
              class: {
                content: 'bg-primary/10 text-primary'
              }
            },
            {
              color: 'primary',
              variant: 'subtle',
              class: {
                content: 'bg-primary/10 text-primary ring ring-primary/25'
              }
            },
            {
              color: 'primary',
              variant: 'naked',
              class: {
                content: 'text-primary'
              }
            },
            {
              color: 'neutral',
              variant: 'solid',
              class: {
                content: 'bg-inverted text-inverted'
              }
            },
            {
              color: 'neutral',
              variant: 'outline',
              class: {
                content: 'bg-default ring ring-default'
              }
            },
            {
              color: 'neutral',
              variant: 'soft',
              class: {
                content: 'bg-elevated/50'
              }
            },
            {
              color: 'neutral',
              variant: 'subtle',
              class: {
                content: 'bg-elevated/50 ring ring-default'
              }
            }
          ],
          defaultVariants: {
            side: 'left',
            variant: 'naked',
            color: 'neutral'
          }
        }
      }
    })
  ]
})
为便于阅读,compoundVariants 中的某些颜色已省略。请在 GitHub 上查看源代码。

更新日志

暂无近期更新