21 / 122 · Messages
Speaker identity
Who is talking, once a thread holds more than a user and one model.
YouFind out why the converter drops turns.
MaintaineropusSplitting this: one worker reads the converter, one reads the tests.
readerhaikuconvertMessages returns early when parts is empty.
read_file42mspackages/core/src/convertMessages.ts
Installation
npx shadcn@latest add "@assistant-ui/elements-speaker-identity"
Usage
import { SpeakerIdentity } from "@/components/elements/speaker-identity";
<SpeakerIdentity turns={turns} />Props
SpeakerIdentity
turns*SpeakerTurn[]Turns in order, each naming who produced it.
classNamestringExtra classes merged onto the root.
SpeakerTurn
id*stringStable identity for the row key.
text*stringWhat this speaker said.
kind*"user" | "agent" | "subagent" | "tool"Chooses the badge shape and tone. Subagents get a round badge so they read as delegated.
name*stringDisplay name for the speaker.
detailstringSecondary fact next to the name: the model, a duration.
Source
speaker-identity.tsx"use client";
import { BotIcon, UserIcon, WrenchIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { mono } from "./surfaces";
export type SpeakerKind = "user" | "agent" | "subagent" | "tool";
export interface SpeakerTurn {
id: string;
kind: SpeakerKind;
name: string;
detail?: string;
text: string;
}
const TONE: Record<SpeakerKind, string> = {
user: "bg-foreground/[0.06] text-foreground/55",
agent: "bg-blue-500/12 text-blue-600 dark:bg-blue-400/15 dark:text-blue-400",
subagent: "bg-foreground/[0.06] text-foreground/45",
tool: "bg-foreground/[0.04] text-foreground/40",
};
export function SpeakerIdentity({
turns,
className,
}: {
turns: readonly SpeakerTurn[];
className?: string;
}) {
return (
<div className={cn("flex w-full max-w-sm flex-col gap-3.5", className)}>
{turns.map((turn) => (
<div key={turn.id} className="flex gap-2.5">
<span
className={cn(
"flex size-6 shrink-0 items-center justify-center rounded-lg",
TONE[turn.kind],
turn.kind === "subagent" && "rounded-full",
)}
>
{turn.kind === "user" ? (
<UserIcon className="size-3" />
) : turn.kind === "tool" ? (
<WrenchIcon className="size-3" />
) : (
<BotIcon className="size-3" />
)}
</span>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="flex items-baseline gap-1.5">
<span className="text-[13px] font-medium">{turn.name}</span>
{turn.detail && (
<span className={cn(mono, "text-foreground/30")}>
{turn.detail}
</span>
)}
</span>
<span className="text-foreground/65 text-[13.5px] leading-relaxed">
{turn.text}
</span>
</div>
</div>
))}
</div>
);
}