import React, { useState, useRef, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; import { Reply, Copy, Check, Maximize2, Info, FileText, Hash, Target } from 'lucide-react'; import { advisors, getAdvisorColors } from '../data/advisors'; import { useTheme } from '../contexts/ThemeContext'; const MessageBubble = ({ message, onReply, onCopy, onExpand, showReplyButton = false }) => { const { isDark } = useTheme(); const [showTooltip, setShowTooltip] = useState(null); const [copiedStates, setCopiedStates] = useState({}); const [showInfoOverlay, setShowInfoOverlay] = useState(false); const overlayRef = useRef(null); const handleCopy = async (messageId, content) => { try { await navigator.clipboard.writeText(content); setCopiedStates(prev => ({ ...prev, [messageId]: true })); if (onCopy) onCopy(messageId, content); setTimeout(() => { setCopiedStates(prev => ({ ...prev, [messageId]: false })); }, 2000); } catch (err) { console.error('Failed to copy text: ', err); } }; const handleExpand = (messageId, persona_id) => { if (onExpand) onExpand(messageId, persona_id); }; const handleInfoToggle = () => { setShowInfoOverlay(!showInfoOverlay); }; const showTooltipWithDelay = (tooltipType) => { setTimeout(() => setShowTooltip(tooltipType), 500); }; const hideTooltip = () => { setShowTooltip(null); }; // Close overlay when clicking outside useEffect(() => { const handleClickOutside = (event) => { if (overlayRef.current && !overlayRef.current.contains(event.target)) { setShowInfoOverlay(false); } }; if (showInfoOverlay) { document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; } }, [showInfoOverlay]); // Preprocess markdown content to fix common formatting issues const preprocessMarkdown = (content) => { if (!content) return ''; // Ensure proper line breaks before numbered lists let processed = content.replace(/(\d+\.\s\*\*[^*]+\*\*)/g, '\n\n$1'); // Ensure proper line breaks after list items processed = processed.replace(/(\d+\.\s[^\n]+)(?=\s+\d+\.)/g, '$1\n'); // Fix spacing around bold headers processed = processed.replace(/(\*\*[^*]+\*\*)/g, '\n\n$1\n\n'); // Clean up multiple consecutive line breaks processed = processed.replace(/\n{3,}/g, '\n\n'); // Ensure proper paragraph breaks processed = processed.replace(/([.!?])\s+([A-Z])/g, '$1\n\n$2'); return processed.trim(); }; // ENHANCED MARKDOWN COMPONENTS WITH BETTER STYLING const markdownComponents = { // Bold text styling - for headers and key terms strong: ({ children }) => ( {children} ), // Italic text styling em: ({ children }) => ( {children} ), // Paragraph styling with proper spacing p: ({ children }) => (
{children}
), // Unordered list styling ul: ({ children }) => (
{children}
),
// Block quote styling
blockquote: ({ children }) => (
{children}) }; // RAG Metadata Component const RagInfoOverlay = ({ ragMetadata, colors }) => { const hasDocuments = ragMetadata?.usedDocuments || false; const chunksUsed = ragMetadata?.chunksUsed || 0; const documentChunks = ragMetadata?.documentChunks || []; return (
{message.content}
{message.content}