Show open risk on the ETH book and draft a conservative bid.
AI Chat@paxeer/ai-chat/message-threadimplemented
Message thread
Role-mapped conversation on Card + ScrollArea.
Install
$
pnpm exec designx add @paxeer/ai-chat/message-threadPreview
Loading preview...
Source code
components/designx/ai-chat/message-thread.tsxtsx
import { Card, ScrollRegion, cn } from "@paxeer/core";
export type ChatRole = "user" | "assistant" | "system";
export interface ChatMessage {
id: string;
role: ChatRole;
body: string;
time?: string;
}
export interface MessageThreadProps {
messages: ChatMessage[];
className?: string;
}
const roleSurface: Record<ChatRole, string> = {
user: "bg-primary-container text-on-primary-container ml-8",
assistant: "bg-surface-container-highest text-on-surface mr-8",
system: "bg-secondary-container text-on-secondary-container mx-6 text-center",
};
export function MessageThread({ messages, className }: MessageThreadProps) {
return (
<Card className={cn("min-h-0 gap-0 py-0 overflow-hidden", className)} data-slot="message-thread">
<ScrollRegion label="Conversation" className="h-full">
<div className="flex flex-col gap-3 p-3">
{messages.length === 0 ? (
<p className="py-10 text-center text-[length:var(--md-sys-typescale-body-small-size)] text-on-surface-variant">
No messages yet. Start from the composer.
</p>
) : (
messages.map((message) => (
<article
key={message.id}
className={cn(
"rounded-[var(--md-sys-shape-corner-medium)] px-3 py-2",
roleSurface[message.role],
)}
>
<header className="mb-1 flex items-center justify-between gap-2 text-[length:var(--md-sys-typescale-label-small-size)] opacity-80">
<span className="capitalize">{message.role}</span>
{message.time ? <time>{message.time}</time> : null}
</header>
<p className="whitespace-pre-wrap text-[length:var(--md-sys-typescale-body-medium-size)]">
{message.body}
</p>
</article>
))
)}
</div>
</ScrollRegion>
</Card>
);
}
Dependencies
Registry dependencies