import { useState } from "react";
import { ChevronRight, Wrench } from "lucide-react";
import { useTranslation } from "react-i18next";
import { MarkdownText } from "@/components/MarkdownText";
import { cn } from "@/lib/utils";
import type { UIMessage } from "@/lib/types";
interface MessageBubbleProps {
message: UIMessage;
}
/**
* Render a single message. Following agent-chat-ui: user turns are a rounded
* "pill" right-aligned with a muted fill; assistant turns render as bare
* markdown so prose/code read like a document rather than a chat bubble.
* Each turn fades+slides in for a touch of motion polish.
*
* Trace rows (tool-call hints, progress breadcrumbs) render as a subdued
* collapsible group so intermediate steps never masquerade as replies.
*/
export function MessageBubble({ message }: MessageBubbleProps) {
const baseAnim = "animate-in fade-in-0 slide-in-from-bottom-1 duration-300";
if (message.kind === "trace") {
return ;
}
if (message.role === "user") {
return (
);
}
/** Blinking cursor appended at the end of streaming text. */
function StreamCursor() {
const { t } = useTranslation();
return (
);
}
/** Pre-token-arrival placeholder: three bouncing dots. */
function TypingDots() {
const { t } = useTranslation();
return (
);
}
function Dot({ delay }: { delay: string }) {
return (
);
}
interface TraceGroupProps {
message: UIMessage;
animClass: string;
}
/**
* Collapsible group of tool-call / progress breadcrumbs. Defaults to
* expanded for discoverability; a single click on the header folds the
* group down to a one-line summary so it never dominates the thread.
*/
function TraceGroup({ message, animClass }: TraceGroupProps) {
const { t } = useTranslation();
const lines = message.traces ?? [message.content];
const count = lines.length;
const [open, setOpen] = useState(true);
return (