|
|
| 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> |
| ); |
| }); |
|
|