frontend_nyla/src/components/Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup.tsx

232 lines
No EOL
9.4 KiB
TypeScript

import React, { useEffect } from "react";
import ReactMarkdown from 'react-markdown';
import { MdClose } from "react-icons/md";
import { Document } from "./dashboardChatAreaTypes";
import { useFilePreview } from "../../../../hooks/useWorkflows";
import styles from './FilePreviewPopup.module.css';
interface FilePreviewPopupProps {
document: Document;
isOpen: boolean;
onClose: () => void;
}
const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, onClose }) => {
const { previewContent, fileMetadata, isLoading, error, fetchPreview, clearPreview } = useFilePreview();
useEffect(() => {
if (isOpen && document) {
// Use fileId if available, otherwise try to use id as fallback
const fileId = document.fileId || document.id;
if (fileId) {
console.log('FilePreviewPopup: calling fetchPreview with fileId:', fileId);
fetchPreview(String(fileId));
} else {
console.error('FilePreviewPopup: No fileId or id available on document:', document);
}
} else if (!isOpen) {
clearPreview();
}
}, [isOpen, document.fileId, document.id]);
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
const getPreviewComponent = () => {
if (isLoading) {
return <div className={styles.loading}>Loading preview...</div>;
}
if (error) {
return (
<div className={styles.error}>
<div>Error: {error}</div>
{fileMetadata && (
<div style={{ marginTop: '10px', fontSize: '12px', opacity: 0.7 }}>
Debug: {JSON.stringify(fileMetadata, null, 2)}
</div>
)}
</div>
);
}
if (!previewContent) {
return (
<div className={styles.no_preview}>
<div>No preview available</div>
{fileMetadata && (
<div style={{ marginTop: '10px', fontSize: '12px', opacity: 0.7 }}>
Available metadata: {Object.keys(fileMetadata).join(', ')}
<br />
Preview field: {fileMetadata.preview ? 'has data' : 'empty/null'}
<br />
Base64Encoded: {String(fileMetadata.base64Encoded)}
<br />
MimeType: {fileMetadata.mimeType}
</div>
)}
</div>
);
}
// Use metadata from backend response
const mimeType = fileMetadata?.mimeType;
const isBase64Encoded = fileMetadata?.base64Encoded;
const fileExtension = document.ext?.toLowerCase();
// Check if this is a markdown file by extension/MIME type first
const isMarkdownByType = fileExtension === 'md' ||
fileExtension === 'markdown' ||
mimeType === 'text/markdown' ||
mimeType === 'text/x-markdown';
// Content-based markdown detection for .txt files with markdown content
// BUT NOT for specific code file types
const isCodeFile = fileExtension === 'py' ||
fileExtension === 'js' ||
fileExtension === 'ts' ||
fileExtension === 'jsx' ||
fileExtension === 'tsx' ||
fileExtension === 'java' ||
fileExtension === 'cpp' ||
fileExtension === 'c' ||
fileExtension === 'php' ||
fileExtension === 'html' ||
fileExtension === 'css';
const hasMarkdownContent = !isCodeFile && previewContent && (
previewContent.includes('# ') || // Headers
previewContent.includes('## ') || // Headers
previewContent.includes('**') || // Bold
previewContent.includes('__') || // Bold
previewContent.includes('- ') || // Lists
previewContent.includes('* ') || // Lists
previewContent.includes('1. ') || // Numbered lists
previewContent.includes('```') || // Code blocks
previewContent.includes('[') && previewContent.includes('](') // Links
);
const isMarkdown = isMarkdownByType || (mimeType === 'text/plain' && hasMarkdownContent);
if (mimeType?.startsWith('image/')) {
// Image preview
const imageSrc = isBase64Encoded
? `data:${mimeType};base64,${previewContent}`
: previewContent;
return (
<img
src={imageSrc}
alt={document.name}
className={styles.image_preview}
/>
);
} else if (fileExtension === 'pdf' || mimeType === 'application/pdf') {
// PDF preview
const pdfSrc = isBase64Encoded
? `data:application/pdf;base64,${previewContent}`
: previewContent;
return (
<iframe
src={pdfSrc}
className={styles.pdf_preview}
title={document.name}
/>
);
} else if (isMarkdown) {
// Markdown preview
console.log('Rendering markdown with ReactMarkdown:', previewContent?.substring(0, 200));
return (
<div className={styles.markdown_preview}>
<ReactMarkdown>{previewContent}</ReactMarkdown>
</div>
);
} else if (fileExtension === 'py') {
// Python code preview
return (
<div className={styles.python_code_preview}>
<div className={styles.code_header}>
<span className={styles.code_language}>Python</span>
<span className={styles.code_filename}>
{document.ext ? `${document.name}.${document.ext}` : document.name}
</span>
</div>
<pre className={styles.python_code_content}>
<code>{previewContent}</code>
</pre>
</div>
);
} else if (mimeType?.startsWith('text/') || fileExtension === 'txt') {
// Enhanced text preview for all text files
return (
<div className={styles.enhanced_text_preview}>
{previewContent?.split('\n').map((line, index) => {
// Handle empty lines
if (line.trim() === '') {
return <div key={index} className={styles.text_line_break}></div>;
}
// Check if line looks like a header (all caps, starts with numbers, etc.)
const isHeader = line.match(/^[A-Z\s\d\.\-_]+:?\s*$/) && line.length < 80;
const isNumberedItem = line.match(/^\s*\d+\.\s/);
const isBulletItem = line.match(/^\s*[-*•]\s/);
const isIndented = line.match(/^\s{4,}/);
return (
<div
key={index}
className={`${styles.text_line} ${
isHeader ? styles.text_header :
isNumberedItem ? styles.text_numbered :
isBulletItem ? styles.text_bullet :
isIndented ? styles.text_indented : ''
}`}
>
{line}
</div>
);
})}
</div>
);
} else {
// Code/raw text preview for non-text files
return (
<pre className={styles.code_preview}>
{previewContent}
</pre>
);
}
};
if (!isOpen) return null;
return (
<div className={styles.overlay} onClick={handleBackdropClick}>
<div className={styles.popup}>
<div className={styles.header}>
<h3 className={styles.title}>
{document.ext ? `${document.name}.${document.ext}` : document.name}
</h3>
<button
className={styles.close_button}
onClick={onClose}
title="Close preview"
>
<MdClose />
</button>
</div>
<div className={styles.content}>
{getPreviewComponent()}
</div>
</div>
</div>
);
};
export default FilePreviewPopup;