DesignX
AI Chat@paxeer/ai-chat/message-threadimplemented

Message thread

Role-mapped conversation on Card + ScrollArea.

Install

$pnpm exec designx add @paxeer/ai-chat/message-thread

Preview

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