Conversation
Streaming Message
Assistant message with token streaming, collapsible reasoning, citation chips and copy / retry actions.
3 variantsMIT
Preview
The 429s come from your org's output tokens per
Installation
Copies the component into components/ui and installs its dependencies and theme tokens.
pnpm dlx shadcn@latest add https://tuffui.com/r/streaming-message.jsonUsage
Pass your own data and wire the callbacks to your agent runtime.
import { StreamingMessage, type StreamingMessageData } from "@/components/ui/streaming-message";
export function AssistantTurn({ message }: { message: StreamingMessageData }) {
// Append text deltas to message.content as they arrive and flip status to
// "done" on message_stop (or "error" if the stream breaks).
return (
<StreamingMessage
data={message}
model="Claude Opus 5.5"
onCopy={(text) => analytics.track("copy_message", { length: text.length })}
onRetry={() => chat.regenerate(message.id)}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | StreamingMessageData | — | The message: content so far, status, optional reasoning, citations and error. Update it on every delta. |
| model | string | "Assistant" | Name shown in the message header. |
| showHeader | boolean | true | Show the avatar and model row. Turn it off inside a thread that already labels each turn. |
| onCopy | (content: string) => void | — | Called after the content is copied to the clipboard. |
| onRetry | () => void | — | Shows Retry on errors and Regenerate when done. |
| className | string | — | Extra classes for the root article. |
type StreamingMessageData = {
content: string; // paragraphs split on blank lines; `code` and **bold** supported
status: "streaming" | "done" | "error";
reasoning?: {
text: string;
streaming?: boolean; // shows "Thinking…" and keeps the panel open
durationMs?: number; // "Thought for 4s" once finished
};
citations?: { id: string; title: string; url: string; source?: string }[];
error?: string;
};