import { useState } from 'react' function Avatar({ character }) { const name = character?.name || 'AI' const image = character?.image || null if (image) { return {name} } return (
{name[0]}
) } function ImageOverlay({ src, onClose }) { return (
Full size e.stopPropagation()} />
) } const IMAGE_URL_RE = /(https?:\/\/[^\s]+\.(?:png|jpg|jpeg|gif|webp))/gi function RichContent({ text }) { const [overlayImage, setOverlayImage] = useState(null) const parts = [] let lastIndex = 0 let match IMAGE_URL_RE.lastIndex = 0 while ((match = IMAGE_URL_RE.exec(text)) !== null) { if (match.index > lastIndex) { parts.push({ type: 'text', value: text.slice(lastIndex, match.index) }) } parts.push({ type: 'image', value: match[1] }) lastIndex = IMAGE_URL_RE.lastIndex } if (lastIndex < text.length) { parts.push({ type: 'text', value: text.slice(lastIndex) }) } if (parts.length === 1 && parts[0].type === 'text') { return <>{text} } return ( <> {parts.map((part, i) => part.type === 'image' ? ( ) : ( {part.value} ) )} {overlayImage && setOverlayImage(null)} />} ) } export default function MessageBubble({ message, onReplay, onRetry, character }) { const isUser = message.role === 'user' return (
{!isUser && }
{isUser ? message.content : }
{!isUser && (
{message.model && ( {message.model} )} {message.isError && onRetry && ( )} {!message.isError && onReplay && ( )}
)}
) }