Components · Chat
Stream Text
A streamed reply written out whole words at a time, at the rate the stream arrives, so bursts and pauses read as one steady cadence.
Installation
The command adds the @aiellie registry to components.json on its first run. With it there, shadcn installs it too: npx shadcn@latest add @aiellie/stream-text.
Usage
Pass the reply so far as text, growing as it arrives, and streaming while more is on its way. Words go out whole, at the rate the stream has been arriving, so a burst doesn't land all at once and a pause slows the writing instead of stopping it.
import { StreamText } from "@/components/aiellie/stream-text"
export function Reply({
text,
streaming,
}: {
text: string
streaming: boolean
}) {
return (
<p className="text-sm leading-6">
<StreamText text={text} streaming={streaming} />
</p>
)
}Composition
In a thread, it goes inside a MessagePart. It draws its own caret, so the message leaves its caret out. With the AI SDK, stream the latest reply and show the rest as they are.
"use client"
import { useChat } from "@ai-sdk/react"
import {
Message,
MessageContent,
MessagePart,
} from "@/components/aiellie/message"
import { StreamText } from "@/components/aiellie/stream-text"
export function Messages() {
const { messages, status } = useChat()
return messages.map((message, index) => {
const latest = index === messages.length - 1
const streaming = latest && status === "streaming"
return (
<Message
key={message.id}
align={message.role === "user" ? "end" : "start"}
variant={message.role === "user" ? "secondary" : "ghost"}
streaming={streaming}
>
<MessageContent>
{message.parts.map((part, key) =>
part.type === "text" ? (
<MessagePart key={key}>
{message.role === "assistant" ? (
<StreamText text={part.text} streaming={streaming} />
) : (
part.text
)}
</MessagePart>
) : null
)}
</MessageContent>
</Message>
)
})
}API reference
StreamText
Renders a span. Any other prop, like className, goes to it.
| Prop | Type | Default |
|---|---|---|
text (required)The reply so far. Each value should carry on from the one before. | string | — |
streamingWhether more is still on its way. While it is, the last word waits until it's whole and the newest words hold the live ink. | boolean | false |
paceWords a second, until the stream's own rate is known, and for revealInitial. | number | 16 |
revealInitialWrite out the text it mounts with too, instead of showing it at once. | boolean | false |
inlineCodeSet runs inside backticks as code. | boolean | true |
freshWordsHow many of the newest words hold the live ink while more are coming. | number | 2 |
onRevealedCalled once the reply is finished and every word of it is on screen. | () => void | — |
- The newest words and the caret take the
--livecolor from the aiellie theme, and fall back to the text color without it. The caret's blink comes fromtw-animate-css. - With reduced motion, or in a tab that's hidden, each word shows as it arrives instead of being paced.