Voice UI Kit
View source

usePipecatConversation

Hook that derives a clean, ordered conversation stream from RTVI events.

Overview

usePipecatConversation provides access to conversation state managed by the @pipecat-ai/client-react PipecatClientProvider. The kit re-exports the upstream hook for convenience. The provider handles all Pipecat RTVI events to produce a clean message list suitable for rendering a chat transcript. It handles streaming text, placeholder messages, finalization, and merging of consecutive messages from the same role.

Setup

Conversation state is automatically managed by the PipecatClientProvider when you use PipecatAppBase, so no additional setup is required:

import { PipecatAppBase, usePipecatConversation } from "@pipecat-ai/voice-ui-kit";

export default function App() {
  return (
    <PipecatAppBase
      transportType="smallwebrtc"
      connectParams={{ webrtcUrl: "/api/offer" }}
    >
      <YourApp />
    </PipecatAppBase>
  );
}

Usage

Basic

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

export function ConversationList() {
  const { messages } = usePipecatConversation();
  return (
    <ul>
      {messages.map((m, i) => (
        <li key={i}>
          <strong>{m.role}:</strong>{" "}
          {m.parts.map((part, idx) => {
            if (part.text && typeof part.text === "object" && "spoken" in part.text) {
              return <span key={idx}>{part.text.spoken + part.text.unspoken}</span>;
            }
            return <span key={idx}>{String(part.text)}</span>;
          })}
        </li>
      ))}
    </ul>
  );
}

With lifecycle callbacks

const { messages } = usePipecatConversation({
  onMessageCreated: (message) => console.log("New message:", message),
  onMessageUpdated: (message) => console.log("Message updated:", message),
});

Custom aggregation metadata (inline vs block, spoken vs not spoken)

Only "word" and "sentence" are considered default aggregation types. Any other aggregatedBy value is custom and arbitrary, and should be configured through aggregationMetadata (and optionally botOutputRenderers in your UI components).

This metadata influences:

  • Rendering: whether an aggregation should be treated as inline text or a block (e.g. code blocks)
  • Karaoke-style highlighting: whether the aggregation is expected to be spoken (isSpoken)
const { messages } = usePipecatConversation({
  aggregationMetadata: {
    code: { displayMode: "block", isSpoken: false },
    credit_card: { displayMode: "inline", isSpoken: false },
  },
});

Notes:

  • displayMode: "block" parts are rendered on their own line by MessageContent.
  • For cleaner rendering, non-spoken block parts are trimmed of leading/trailing whitespace before display (the stored text is not modified).

Full example

import React, { useEffect, useRef } from "react";
import { usePipecatConversation } from "@pipecat-ai/voice-ui-kit";

export function ConversationExample() {
  const { messages } = usePipecatConversation();
  const endRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    endRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  return (
    <div style={{ height: 300, overflow: "auto", fontFamily: "monospace", fontSize: 12 }}>
      {messages.map((m, i) => (
        <div key={i}>
          <strong>[{m.role}]</strong>{" "}
          {m.parts.map((part, idx) => {
            if (part.text && typeof part.text === "object" && "spoken" in part.text) {
              return <span key={idx}>{part.text.spoken + part.text.unspoken}</span>;
            }
            return <span key={idx}>{String(part.text)}</span>;
          })}
          <span style={{ opacity: 0.6, marginLeft: 8 }}>
            {new Date(m.createdAt).toLocaleTimeString()}
          </span>
        </div>
      ))}
      <div ref={endRef} />
    </div>
  );
}

Injecting messages

You can inject messages directly via the hook, or by using ConsoleTemplate callbacks.

import React from "react";
import { usePipecatConversation } from "@pipecat-ai/voice-ui-kit";

export function InjectViaHook() {
  const { injectMessage } = usePipecatConversation();
  return (
    <button
      onClick={() =>
        injectMessage({
          role: "system",
          parts: [{ text: "Hello from the system", final: true, createdAt: new Date().toISOString() }],
          createdAt: new Date().toISOString()
        })
      }
    >
      Inject message
    </button>
  );
}

Changelog

v0.10.0
  • Implementation moved to @pipecat-ai/client-react. The kit now re-exports the upstream hook. ConversationProvider is no longer required.
v0.3.0
  • Added. Derives a clean, ordered conversation stream from RTVI events for chat-style transcripts.