“vinit5112”
Add all code
deb090d
raw
history blame
5.06 kB
import React from 'react';
import { motion } from 'framer-motion';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { tomorrow, prism } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { UserIcon, AcademicCapIcon } from '@heroicons/react/24/solid';
const MessageBubble = ({ message, darkMode, isLast }) => {
const isUser = message.role === 'user';
const messageVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.3,
ease: "easeOut"
}
}
};
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
});
};
return (
<motion.div
variants={messageVariants}
initial="hidden"
animate="visible"
className={`flex gap-4 mb-6 ${isUser ? 'justify-end' : 'justify-start'}`}
>
{!isUser && (
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
darkMode ? 'bg-primary-600' : 'bg-primary-500'
}`}>
<AcademicCapIcon className="w-5 h-5 text-white" />
</div>
)}
<div className={`max-w-[80%] ${isUser ? 'order-first' : ''}`}>
<div className={`rounded-2xl px-4 py-3 ${
isUser
? darkMode
? 'bg-primary-600 text-white'
: 'bg-primary-500 text-white'
: darkMode
? 'bg-gray-800 border border-gray-700'
: 'bg-white border border-gray-200 shadow-sm'
}`}>
{isUser ? (
<p className="whitespace-pre-wrap">{message.content}</p>
) : (
<div className="message-content">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
style={darkMode ? tomorrow : prism}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
ul: ({ children }) => <ul className="list-disc list-inside mb-2">{children}</ul>,
ol: ({ children }) => <ol className="list-decimal list-inside mb-2">{children}</ol>,
li: ({ children }) => <li className="mb-1">{children}</li>,
h1: ({ children }) => <h1 className="text-xl font-bold mb-2">{children}</h1>,
h2: ({ children }) => <h2 className="text-lg font-semibold mb-2">{children}</h2>,
h3: ({ children }) => <h3 className="text-md font-medium mb-2">{children}</h3>,
blockquote: ({ children }) => (
<blockquote className={`border-l-4 pl-4 italic my-2 ${
darkMode ? 'border-gray-600 text-gray-300' : 'border-gray-300 text-gray-600'
}`}>
{children}
</blockquote>
),
}}
>
{message.content}
</ReactMarkdown>
</div>
)}
</div>
{/* Timestamp and Sources */}
<div className={`text-xs mt-2 ${
darkMode ? 'text-gray-500' : 'text-gray-400'
} ${isUser ? 'text-right' : 'text-left'}`}>
<span>{formatTime(message.timestamp)}</span>
{!isUser && message.sources && message.sources.length > 0 && (
<div className="mt-2">
<span className="font-medium">Sources: </span>
{message.sources.map((source, index) => (
<span key={index} className={`inline-block mr-2 px-2 py-1 rounded text-xs ${
darkMode
? 'bg-gray-700 text-gray-300'
: 'bg-gray-100 text-gray-600'
}`}>
{source}
</span>
))}
</div>
)}
</div>
</div>
{isUser && (
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
darkMode ? 'bg-gray-700' : 'bg-gray-300'
}`}>
<UserIcon className={`w-5 h-5 ${
darkMode ? 'text-gray-300' : 'text-gray-600'
}`} />
</div>
)}
</motion.div>
);
};
export default MessageBubble;