import { memo, useEffect, useMemo, useRef } from 'react' import { motion } from 'framer-motion' import { Copy, FileText, Pencil, RefreshCw, Sparkles, Volume2, VolumeX } from 'lucide-react' import { renderMarkdown } from '../../utils/markdown' import TypingIndicator from './TypingIndicator' function addCodeCopyButtons(container) { container.querySelectorAll('pre').forEach((pre) => { if (pre.dataset.enhanced === 'true') return pre.dataset.enhanced = 'true' const wrapper = document.createElement('div') wrapper.className = 'code-shell' const toolbar = document.createElement('div') toolbar.className = 'code-toolbar' const badge = document.createElement('span') badge.className = 'code-badge' badge.textContent = pre.querySelector('code')?.className?.replace('hljs language-', '') || 'code' const button = document.createElement('button') button.className = 'code-copy-button' button.type = 'button' button.textContent = 'Copy' button.addEventListener('click', async () => { const code = pre.querySelector('code')?.textContent || pre.textContent || '' await navigator.clipboard.writeText(code) button.textContent = 'Copied' window.setTimeout(() => { button.textContent = 'Copy' }, 1400) }) toolbar.appendChild(badge) toolbar.appendChild(button) pre.parentNode.insertBefore(wrapper, pre) wrapper.appendChild(toolbar) wrapper.appendChild(pre) }) } function MessageBubble({ message, user, onCopyMessage, onRegenerate, onEdit, onSpeak, onPreviewFile, speaking, }) { const isUser = message.role === 'user' const contentRef = useRef(null) const html = useMemo( () => (isUser ? '' : renderMarkdown(message.content || '')), [isUser, message.content], ) useEffect(() => { if (!isUser && contentRef.current) { addCodeCopyButtons(contentRef.current) } }, [html, isUser]) return ( {!isUser ? (
) : null}
{message.attachments?.length ? (
{message.attachments.map((attachment, index) => ( ))}
) : null} {isUser ? (

{message.content}

) : message.streaming && !message.content ? ( ) : (
)} {!isUser && message.streaming && message.content ? : null}
{!isUser ? ( <> onCopyMessage(message)} /> onSpeak(message)} /> onRegenerate(message.id)} /> ) : ( onEdit(message)} /> )} {message.model_used ? ( <> {(message.provider || 'assistant').replaceAll('-', ' ')} ยท {message.model_used} {`${(message.provider || 'assistant').replaceAll('-', ' ')} / ${message.model_used}`} ) : null}
{isUser ? (
{user?.avatar_url ? ( {user.username} ) : ( (user?.username || 'You').slice(0, 1).toUpperCase() )}
) : null} ) } function MessageAction({ icon: Icon, label, onClick }) { return ( ) } export default memo(MessageBubble)