232 lines
No EOL
9.4 KiB
TypeScript
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;
|