|
|
|
import React from 'react'; |
|
import type { ChatMessage } from '../types'; |
|
import { MarkdownRenderer } from './MarkdownRenderer'; |
|
import { UserIcon, DocumentTextIcon } from './icons'; |
|
|
|
|
|
export const ChatMessageItem: React.FC<{ message: ChatMessage }> = React.memo(({ message }) => { |
|
const isUser = message.sender === 'user'; |
|
|
|
const shouldDisplaySources = () => { |
|
if (message.sender === 'model' && message.groundingChunks && message.groundingChunks.length > 0) { |
|
const text = message.text.toLowerCase(); |
|
return text.includes("sources:") || text.includes("source:") || text.includes("fuentes:") || text.includes("fuente:"); |
|
} |
|
return false; |
|
}; |
|
|
|
const isImageFile = message.file && message.file.dataUrl && message.file.type.startsWith('image/'); |
|
const isDocumentFile = message.file && !isImageFile && (message.file.type.includes('pdf') || message.file.type.includes('word') || message.file.type.includes('document')); |
|
|
|
|
|
return ( |
|
<div className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-4`}> |
|
<div |
|
className={`max-w-xl lg:max-w-2xl px-4 py-3 rounded-xl shadow ${ |
|
isUser |
|
? 'bg-cyan-600 text-white rounded-br-none' |
|
: 'bg-slate-700 text-slate-100 rounded-bl-none' |
|
}`} |
|
> |
|
<div className="flex items-center space-x-2 mb-1"> {/* Adjusted items-start to items-center for better emoji alignment */} |
|
{isUser ? ( |
|
<UserIcon className="w-5 h-5 text-cyan-200 mt-0.5"/> |
|
) : ( |
|
<span role="img" aria-label="ReelBot avatar" className="text-xl self-center">🤖</span> |
|
)} |
|
<span className="font-semibold text-sm self-center">{isUser ? 'Tú' : 'ReelBot'}</span> |
|
</div> |
|
|
|
{/* File Preview */} |
|
{message.file && ( |
|
<div className="mb-2 p-2 border border-slate-500/50 rounded-md"> |
|
{isImageFile ? ( |
|
<img |
|
src={message.file.dataUrl} |
|
alt={message.file.name} |
|
className="max-w-xs max-h-48 rounded object-contain" |
|
/> |
|
) : isDocumentFile ? ( |
|
<div className="flex items-center space-x-2"> |
|
<DocumentTextIcon className="w-6 h-6 text-slate-400 flex-shrink-0" /> |
|
<span className="text-xs text-slate-300 truncate" title={message.file.name}> |
|
{message.file.name} |
|
</span> |
|
</div> |
|
) : null } |
|
</div> |
|
)} |
|
|
|
{/* Message Text */} |
|
{message.text && ( |
|
message.sender === 'model' ? ( |
|
<MarkdownRenderer markdownText={message.text} /> |
|
) : ( |
|
<p className="whitespace-pre-wrap break-words">{message.text}</p> |
|
) |
|
)} |
|
|
|
{message.error && <p className="text-red-300 text-xs mt-1">Error: {message.error}</p>} |
|
|
|
{shouldDisplaySources() && ( |
|
<div className="mt-3 pt-2 border-t border-slate-600"> |
|
<h4 className="text-xs font-semibold text-slate-400 mb-1">Sources:</h4> |
|
<ul className="list-disc list-inside space-y-1"> |
|
{message.groundingChunks!.map((chunk, index) => ( |
|
<li key={index} className="text-xs"> |
|
<a |
|
href={chunk.web.uri} |
|
target="_blank" |
|
rel="noopener noreferrer" |
|
className="text-cyan-400 hover:text-cyan-300 hover:underline truncate block" |
|
title={chunk.web.uri} |
|
> |
|
{chunk.web.title || chunk.web.uri} |
|
</a> |
|
</li> |
|
))} |
|
</ul> |
|
</div> |
|
)} |
|
</div> |
|
</div> |
|
); |
|
}); |
|
|