import { useState } from "react"; import type { Message } from "@/types/Message"; interface Props { message: Message; replyTarget?: Message; onReply: (id: string) => void; } const TYPE_BORDER: Record = { text: "", code: "border-l-2 border-amber-500/70", result: "border-l-2 border-emerald-500/70", error: "border-l-2 border-red-500/70", plan: "border-l-2 border-blue-500/70", }; const TYPE_LABEL: Record = { code: { text: "CODE", color: "text-amber-500" }, result: { text: "RES", color: "text-emerald-500" }, error: { text: "ERR", color: "text-red-500" }, plan: { text: "PLAN", color: "text-blue-500" }, }; function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return "now"; if (mins < 60) return `${mins}m`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h`; return `${Math.floor(hrs / 24)}d`; } export function MessageItem({ message, replyTarget, onReply }: Props) { const [metaOpen, setMetaOpen] = useState(false); const isAgent = message.user.role === "agent"; const isDeleted = !!message.deleted_at; const border = TYPE_BORDER[message.type] || ""; const label = TYPE_LABEL[message.type]; const meta = message.metadata as Record | null; return (
{/* Agent glow — left edge */} {isAgent && (
)} {/* Reply context */} {replyTarget && (
^ {replyTarget.user.display_name} {replyTarget.content}
)}
{/* Avatar — small on mobile */}
{message.user.display_name[0]}
{/* Header */}
{message.user.display_name} {isAgent && ( AGT )} {label && ( {label.text} )} {/* Mobile: relative time. Desktop: full time */} {timeAgo(message.created_at)} {new Date(message.created_at).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit", })} #{Number(message.seq)} {/* Reply — always visible on mobile (no hover), hover on desktop */}
{/* Content */}
{isDeleted ? ( [deleted] ) : ( message.content )}
{/* Agent metadata — tap to expand on mobile, always compact on desktop */} {meta && isAgent && ( <> {/* Desktop: inline strip */}
{meta.model && {meta.model}} {meta.hostname && {meta.hostname}} {meta.cwd && {meta.cwd}} {meta.skill && {meta.skill}}
{/* Mobile: tap to expand */} {metaOpen && (
{meta.model &&
model: {meta.model}
} {meta.hostname &&
host: {meta.hostname}
} {meta.cwd &&
cwd: {meta.cwd}
} {meta.skill &&
skill: {meta.skill}
}
)} )}
); }