Voice UI Kit

Message Content

Component for displaying the content of conversation messages

The MessageContent component renders the actual content of a conversation message, including text parts, thinking indicators, and timestamps. It handles multiple text parts and automatically shows a thinking indicator for empty messages.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [{ text: "Hello! This is a message." }],
    createdAt: new Date().toISOString()
  }}
/>

Component Variants

MessageContent

The MessageContent component displays message content with thinking indicator and timestamp.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [{ text: "Message content here" }],
    createdAt: new Date().toISOString()
  }}
/>
PropTypeDefault
message
ConversationMessage
-
botOutputRenderers?
Record<string, (content: string, metadata: { spoken: string; unspoken: string }) => React.ReactNode>
undefined
aggregationMetadata?
Record<string, { displayMode?: 'inline' | 'block'; isSpoken?: boolean }>
undefined
classNames?
{ messageContent?: string; thinking?: string; time?: string; }
{}

Usage

Basic Usage

Display message content with default styling.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [{ text: "Hello! How can I help you?" }],
    createdAt: new Date().toISOString()
  }}
/>

Multiple Parts

Messages can contain multiple text parts that are rendered with proper spacing.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [
      { text: "First part. " },
      { text: "Second part. " },
      { text: "Third part." }
    ],
    createdAt: new Date().toISOString()
  }}
/>

Empty Message (Thinking)

Empty messages automatically show a thinking indicator.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [],
    createdAt: new Date().toISOString()
  }}
/>

Custom Styling

Apply custom classes to different parts of the content.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [{ text: "Custom styled content" }],
    createdAt: new Date().toISOString()
  }}
  classNames={{
    messageContent: "text-lg leading-relaxed",
    thinking: "text-muted-foreground",
    time: "text-xs opacity-75"
  }}
/>

BotOutput with Custom Renderers

Use custom renderers for BotOutput content based on aggregation type. The component automatically detects BotOutputText objects and uses the appropriate renderer based on the aggregatedBy field.

import { MessageContent } from "@pipecat-ai/voice-ui-kit";

<MessageContent
  message={{
    role: "assistant",
    parts: [{
      text: { spoken: "Here's some code:", unspoken: "print('Hello')" },
      final: false,
      createdAt: new Date().toISOString(),
      aggregatedBy: "code"
    }],
    createdAt: new Date().toISOString()
  }}
  aggregationMetadata={{
    // Custom types are arbitrary: configure them in your app
    code: { displayMode: "block", isSpoken: false },
  }}
  botOutputRenderers={{
    code: (content, { spoken, unspoken }) => (
      <div>
        <span>{spoken}</span>
        <pre className="bg-gray-100 p-2 rounded mt-1">{unspoken}</pre>
      </div>
    ),
    link: (content, { spoken, unspoken }) => (
      <a href={unspoken} className="text-blue-500 underline">
        {spoken || unspoken}
      </a>
    )
  }}
/>

When a custom renderer is provided for the aggregatedBy type, it receives:

  • content: The full content string (spoken + unspoken)
  • metadata.spoken: The spoken portion of the text
  • metadata.unspoken: The unspoken portion of the text

If no custom renderer is provided, the component defaults to showing spoken text normally and unspoken text in a muted color.


Behavior

The component:

  1. Renders all text parts from message.parts array
  2. Groups parts by display mode: inline parts render together, block parts render on their own line
  3. Shows a Thinking component if the message has no parts or all parts are empty whitespace
  4. Displays a formatted timestamp using toLocaleTimeString()

Message Parts

Each message part can have a text property containing either:

  • A string or ReactNode for regular text content
  • A BotOutputText object with spoken and unspoken properties for assistant messages with BotOutput mode

The component:

  • Renders text parts sequentially
  • Adds spaces between consecutive text parts
  • Handles BotOutput text by showing spoken text normally and unspoken text in muted color
  • Supports custom renderers for BotOutput content based on aggregatedBy type
  • Handles empty or whitespace-only parts by showing thinking indicator

Integration

The MessageContent component is typically used within MessageContainer to render the content portion of a message. It uses the Thinking component to show loading/thinking states.