聊天消息

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

用法

ChatMessage组件为user或assistant聊天消息呈现<article>元素。

Hello! Tell me more about building AI chatbots with Nuxt UI.
使用ChatMessages组件显示聊天消息列表。

零件

使用parts prop以AI SDK格式显示消息内容。

Hello! Tell me more about building AI chatbots with Nuxt UI.
<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 prop是AI SDK的推荐格式。每个部分都有一个type(例如“text”)和相应的内容。ChatMessage组件也支持弃用的content prop,以实现向后兼容。

Side

使用side属性在左侧或右侧显示消息。

Hello! Tell me more about building AI chatbots with Nuxt UI.
<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属性对于assistant消息设置为left,对于user消息设置为right。

Variant

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

Hello! Tell me more about building AI chatbots with Nuxt UI.
<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属性对于assistant消息设置为naked,对于user消息设置为soft。

颜色:badge{label="4.8+" class="align-text-top"}

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

Hello! Tell me more about building AI chatbots with Nuxt UI.
<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组件。

Hello! Tell me more about building AI chatbots with Nuxt UI.
<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组件。

Hello! Tell me more about building AI chatbots with Nuxt UI.
<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 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.
<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

使用actions prop在消息下方显示操作,当鼠标悬停在消息上方时将显示这些操作。

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.
<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

Props

Prop Default Type
as
'article'
any

The element or component this component should render as.

id
string

A unique identifier for the message.

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

The role of the message.

parts
UIMessagePart<TDataParts, TTools>[]

The parts of the message. Use this for rendering the message in the UI.

System messages should be avoided (set the system prompt on the server instead). They can have text parts.

User messages can have text parts and file parts.

Assistant messages can have text, reasoning, tool invocation, and file parts.

icon
any
avatar
AvatarProps & { [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; })[]

Display a list of actions under the message. The label will be used in a tooltip. { size: 'xs', color: 'neutral', variant: 'ghost' }

compact
false
boolean

Render the message in a compact style. This is done automatically when used inside a UChatPalette.

content
string
metadata
TMetadata

The metadata of the message.

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

Slots

Slot Type
header
UIMessage<TMetadata, TDataParts, TTools>
leading
UIMessage<TMetadata, TDataParts, TTools> & { avatar: (AvatarProps & { [key: string]: any; }) | undefined; ui: object; }
files
Omit<UIMessage<TMetadata, TDataParts, TTools>, "parts"> & { parts: FileUIPart[]; }
body
UIMessage<TMetadata, TDataParts, TTools>
content
UIMessage<TMetadata, TDataParts, TTools> & { content?: string | undefined; }
actions
UIMessage<TMetadata, TDataParts, TTools> & { actions: (Omit<ButtonProps, "onClick"> & { onClick?: ((e: MouseEvent, message: UIMessage<TMetadata, TDataParts, TTools>) => void) | undefined; })[] | undefined; }

Theme

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 ease-out'
        ]
      },
      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: {
            body: 'w-full',
            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 ease-out'
            ]
          },
          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: {
                body: 'w-full',
                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'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitHub.

Changelog

No recent changes