ui-nyla/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatAreaMessageItem.tsx
2025-06-16 14:15:13 +02:00

192 lines
No EOL
7.9 KiB
TypeScript

import React, { useState } from "react";
import { FaDownload } from "react-icons/fa";
import { MdOutlineRemoveRedEye } from "react-icons/md";
import { Message, Document } from "./dashboardChatAreaTypes";
import FilePreviewPopup from "./FilePreviewPopup";
import styles from './DashboardChatArea.module.css';
interface MessageItemProps {
message: Message;
index: number;
}
// Helper function to format file size
const formatFileSize = (bytes?: number): string => {
if (!bytes) return '';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};
// Helper function to get file icon based on type or extension
const getFileIcon = (type?: string, ext?: string): string => {
// Use extension first if available, then fall back to MIME type
const extension = ext?.toLowerCase();
const mimeType = type?.toLowerCase();
// Check extension first
if (extension) {
// Images
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(extension)) return '🖼️';
// Videos
if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv'].includes(extension)) return '🎥';
// Audio
if (['mp3', 'wav', 'aac', 'flac', 'ogg', 'wma'].includes(extension)) return '🎵';
// Documents
if (extension === 'pdf') return '📕';
if (['doc', 'docx'].includes(extension)) return '📘';
if (['xls', 'xlsx'].includes(extension)) return '📊';
if (['ppt', 'pptx'].includes(extension)) return '📋';
if (['txt', 'md', 'rtf'].includes(extension)) return '📝';
// Archives
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) return '📦';
// Code files
if (['js', 'ts', 'jsx', 'tsx', 'html', 'css', 'py', 'java', 'cpp', 'c', 'php'].includes(extension)) return '💻';
}
// Fall back to MIME type if extension didn't match
if (mimeType) {
if (mimeType.includes('image')) return '🖼️';
if (mimeType.includes('video')) return '🎥';
if (mimeType.includes('audio')) return '🎵';
if (mimeType.includes('pdf')) return '📕';
if (mimeType.includes('text')) return '📝';
if (mimeType.includes('word') || mimeType.includes('document')) return '📘';
if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return '📊';
if (mimeType.includes('powerpoint') || mimeType.includes('presentation')) return '📋';
if (mimeType.includes('zip') || mimeType.includes('archive')) return '📦';
}
return '📄';
};
const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
const [previewDocument, setPreviewDocument] = useState<Document | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
// Debug logging to see if documents are present
console.log('MessageItem rendering:', {
messageId: message.id,
role: message.role,
hasDocuments: !!message.documents,
documentsLength: message.documents?.length || 0,
documents: message.documents
});
const handleDocumentClick = (document: Document) => {
// If there's a downloadUrl, use it; otherwise try the url
const downloadLink = document.downloadUrl || document.url;
if (downloadLink) {
// Open the document in a new tab
window.open(downloadLink, '_blank');
}
};
const handlePreview = (document: Document, e: React.MouseEvent) => {
e.stopPropagation();
console.log('handlePreview called with document:', document);
console.log('document.id:', document.id, 'document.fileId:', document.fileId);
// Use fileId if available, otherwise try to use id as fallback
const fileId = document.fileId || document.id;
if (!fileId) {
console.error('Neither fileId nor id is available on document:', document);
return;
}
console.log('Using fileId for preview:', fileId, 'type:', typeof fileId);
setPreviewDocument(document);
setIsPreviewOpen(true);
};
const handleClosePreview = () => {
setIsPreviewOpen(false);
setPreviewDocument(null);
};
const handleDownload = (document: Document, e: React.MouseEvent) => {
e.stopPropagation();
// TODO: Implement download functionality
console.log('Download document:', document.name);
};
return (
<div
key={message.id || index}
className={`${styles.message} ${styles[`message_${message.role}`]}`}
>
<div className={styles.message_role}>
{message.role === 'user' ? 'You' :
message.role === 'assistant' ? 'Assistant' : 'System'}
</div>
<div className={styles.message_content}>
{message.content}
</div>
{message.documents && message.documents.length > 0 && (
<div className={styles.message_documents}>
{message.documents.map((document, docIndex) => (
<div
key={document.id || docIndex}
className={styles.document_item}
onClick={() => handleDocumentClick(document)}
title={`Click to open ${document.name}`}
>
<span className={styles.document_icon}>
{getFileIcon(document.type, document.ext)}
</span>
<div className={styles.document_info}>
<div className={styles.document_name}>
{document.ext ? `${document.name}.${document.ext}` : document.name}
</div>
<div className={styles.document_meta}>
{document.size && (
<span className={styles.document_size}>
{formatFileSize(document.size)}
</span>
)}
</div>
</div>
<div className={styles.document_actions}>
<button
className={styles.document_action_button}
onClick={(e) => handlePreview(document, e)}
title="Preview document"
>
<MdOutlineRemoveRedEye />
</button>
<button
className={styles.document_action_button}
onClick={(e) => handleDownload(document, e)}
title="Download document"
>
<FaDownload />
</button>
</div>
</div>
))}
</div>
)}
{message.timestamp && (
<div className={styles.message_timestamp}>
{new Date(message.timestamp).toLocaleTimeString()}
</div>
)}
{/* File Preview Popup */}
{previewDocument && (
<FilePreviewPopup
document={previewDocument}
isOpen={isPreviewOpen}
onClose={handleClosePreview}
/>
)}
</div>
);
};
export default MessageItem;