import { Message, EmailActionOptions } from "@/types/chat"; import SourceCard from "./SourceCard"; import RichResponseRenderer from "./rich/RichResponseRenderer"; import HexAvatar from "./HexAvatar"; interface MessageBubbleProps { message: Message; onEmailAction?: (content: string, options: EmailActionOptions) => void; } // Escape HTML before applying markdown — the result feeds // dangerouslySetInnerHTML, and LLM output must never inject markup. function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function formatContent(text: string): string { return escapeHtml(text) .replace(/\*\*(.*?)\*\*/g, "$1") .replace(/\*(.*?)\*/g, "$1") .replace(/`([^`]+)`/g, "$1") .replace(/\n/g, "
"); } export default function MessageBubble({ message, onEmailAction }: MessageBubbleProps) { const isUser = message.role === "user"; const time = message.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); if (isUser) { return (
{message.content}

{time}

); } return (
{/* NegOptim AI avatar — hex node mark */}
{/* Sender label */}
NegOptim AI {time}
{/* Answer card */}
{/* Gradient left accent */}
{message.sources && message.sources.length > 0 && (
)}
{/* Rich layout below the card */} {message.responseType && message.responseType !== "plain" && message.structuredData && ( )}
); }