Added knowledge base enhancements for documentation viewing and preloaded Ivanti config for next feature
This commit is contained in:
384
frontend/src/components/KnowledgeBaseModal.js
Normal file
384
frontend/src/components/KnowledgeBaseModal.js
Normal file
@@ -0,0 +1,384 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Loader, AlertCircle, CheckCircle, Upload as UploadIcon, Download, FileText, Trash2 } from 'lucide-react';
|
||||
|
||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||
|
||||
export default function KnowledgeBaseModal({ onClose, onUpdate }) {
|
||||
const [phase, setPhase] = useState('idle'); // idle, uploading, success, error
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [category, setCategory] = useState('General');
|
||||
const [result, setResult] = useState(null);
|
||||
const [existingArticles, setExistingArticles] = useState([]);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
// Fetch existing articles on mount
|
||||
useEffect(() => {
|
||||
fetchExistingArticles();
|
||||
}, []);
|
||||
|
||||
const fetchExistingArticles = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/knowledge-base`, { credentials: 'include' });
|
||||
if (!response.ok) throw new Error('Failed to fetch articles');
|
||||
const data = await response.json();
|
||||
setExistingArticles(data);
|
||||
} catch (err) {
|
||||
console.error('Error fetching articles:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
// Validate file type
|
||||
const allowedExtensions = ['.pdf', '.md', '.txt', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.html', '.json', '.yaml', '.yml'];
|
||||
const ext = '.' + file.name.split('.').pop().toLowerCase();
|
||||
|
||||
if (!allowedExtensions.includes(ext)) {
|
||||
setError('File type not allowed. Please upload: PDF, Markdown, Text, Office docs, or HTML files.');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedFile(file);
|
||||
setError('');
|
||||
|
||||
// Auto-populate title from filename if empty
|
||||
if (!title) {
|
||||
const filename = file.name.replace(/\.[^/.]+$/, ''); // Remove extension
|
||||
setTitle(filename);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!selectedFile || !title.trim()) {
|
||||
setError('Please provide both a title and file');
|
||||
return;
|
||||
}
|
||||
|
||||
setPhase('uploading');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
formData.append('title', title.trim());
|
||||
formData.append('description', description.trim());
|
||||
formData.append('category', category);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/knowledge-base/upload`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Upload failed');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setResult(data);
|
||||
setPhase('success');
|
||||
|
||||
// Refresh the list of existing articles
|
||||
await fetchExistingArticles();
|
||||
|
||||
// Notify parent to refresh
|
||||
if (onUpdate) onUpdate();
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setPhase('error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async (id, filename) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/knowledge-base/${id}/download`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Download failed');
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error('Error downloading file:', err);
|
||||
setError('Failed to download file');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id, articleTitle) => {
|
||||
if (!window.confirm(`Are you sure you want to delete "${articleTitle}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/knowledge-base/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Delete failed');
|
||||
|
||||
// Refresh the list
|
||||
await fetchExistingArticles();
|
||||
|
||||
// Notify parent to refresh
|
||||
if (onUpdate) onUpdate();
|
||||
} catch (err) {
|
||||
console.error('Error deleting article:', err);
|
||||
setError('Failed to delete article');
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setPhase('idle');
|
||||
setSelectedFile(null);
|
||||
setTitle('');
|
||||
setDescription('');
|
||||
setCategory('General');
|
||||
setResult(null);
|
||||
setError('');
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
if (!bytes) return 'Unknown size';
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const getCategoryColor = (cat) => {
|
||||
const colors = {
|
||||
'General': '#94A3B8',
|
||||
'Policy': '#0EA5E9',
|
||||
'Procedure': '#10B981',
|
||||
'Guide': '#F59E0B',
|
||||
'Reference': '#8B5CF6'
|
||||
};
|
||||
return colors[cat] || '#94A3B8';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '700px' }}>
|
||||
{/* Header */}
|
||||
<div className="modal-header">
|
||||
<h2 className="modal-title">Knowledge Base</h2>
|
||||
<button onClick={onClose} className="modal-close">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="modal-body">
|
||||
{/* Idle Phase - Upload Form */}
|
||||
{phase === 'idle' && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2" style={{ color: '#94A3B8' }}>
|
||||
Title *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="e.g., Inventory Management Policy"
|
||||
className="intel-input w-full"
|
||||
maxLength={255}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2" style={{ color: '#94A3B8' }}>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Brief description of this document..."
|
||||
className="intel-input w-full"
|
||||
rows={3}
|
||||
maxLength={500}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2" style={{ color: '#94A3B8' }}>
|
||||
Category
|
||||
</label>
|
||||
<select
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
className="intel-input w-full"
|
||||
>
|
||||
<option value="General">General</option>
|
||||
<option value="Policy">Policy</option>
|
||||
<option value="Procedure">Procedure</option>
|
||||
<option value="Guide">Guide</option>
|
||||
<option value="Reference">Reference</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2" style={{ color: '#94A3B8' }}>
|
||||
Document File *
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf,.md,.txt,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.html,.json,.yaml,.yml"
|
||||
onChange={handleFileSelect}
|
||||
className="intel-input w-full"
|
||||
/>
|
||||
{selectedFile && (
|
||||
<p className="mt-2 text-sm" style={{ color: '#10B981' }}>
|
||||
Selected: {selectedFile.name} ({formatFileSize(selectedFile.size)})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
disabled={!selectedFile || !title.trim()}
|
||||
className={`intel-button w-full ${selectedFile && title.trim() ? 'intel-button-success' : 'opacity-50 cursor-not-allowed'}`}
|
||||
>
|
||||
<UploadIcon className="w-4 h-4 mr-2" />
|
||||
Upload Document
|
||||
</button>
|
||||
|
||||
{error && (
|
||||
<div className="flex items-start gap-2 p-3 rounded" style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #EF4444' }}>
|
||||
<AlertCircle className="w-5 h-5 flex-shrink-0" style={{ color: '#EF4444' }} />
|
||||
<p style={{ color: '#FCA5A5' }}>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Uploading Phase */}
|
||||
{phase === 'uploading' && (
|
||||
<div className="text-center py-8">
|
||||
<Loader className="w-12 h-12 animate-spin mx-auto mb-4" style={{ color: '#0EA5E9' }} />
|
||||
<p style={{ color: '#94A3B8' }}>Uploading document...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Success Phase */}
|
||||
{phase === 'success' && result && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 p-4 rounded" style={{ background: 'rgba(16, 185, 129, 0.1)', border: '1px solid #10B981' }}>
|
||||
<CheckCircle className="w-6 h-6" style={{ color: '#10B981' }} />
|
||||
<div>
|
||||
<p className="font-medium" style={{ color: '#34D399' }}>Upload Successful!</p>
|
||||
<p className="text-sm mt-1" style={{ color: '#94A3B8' }}>
|
||||
{result.title} has been added to the knowledge base.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button onClick={resetForm} className="intel-button w-full">
|
||||
Upload Another Document
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Phase */}
|
||||
{phase === 'error' && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start gap-2 p-4 rounded" style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #EF4444' }}>
|
||||
<AlertCircle className="w-6 h-6 flex-shrink-0" style={{ color: '#EF4444' }} />
|
||||
<div>
|
||||
<p className="font-medium" style={{ color: '#FCA5A5' }}>Upload Failed</p>
|
||||
<p className="text-sm mt-1" style={{ color: '#94A3B8' }}>{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={resetForm} className="intel-button w-full">
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Existing Articles Section */}
|
||||
{(phase === 'idle' || phase === 'success') && existingArticles.length > 0 && (
|
||||
<div className="mt-8">
|
||||
<h3 className="text-lg font-medium mb-4" style={{ color: '#94A3B8' }}>
|
||||
Existing Documents ({existingArticles.length})
|
||||
</h3>
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{existingArticles.map((article) => (
|
||||
<div
|
||||
key={article.id}
|
||||
className="intel-card p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<FileText className="w-4 h-4 flex-shrink-0" style={{ color: getCategoryColor(article.category) }} />
|
||||
<p className="font-medium truncate" style={{ color: '#E2E8F0' }}>
|
||||
{article.title}
|
||||
</p>
|
||||
</div>
|
||||
{article.description && (
|
||||
<p className="text-sm mb-2 line-clamp-2" style={{ color: '#94A3B8' }}>
|
||||
{article.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 text-xs" style={{ color: '#64748B' }}>
|
||||
<span
|
||||
className="px-2 py-0.5 rounded"
|
||||
style={{
|
||||
background: `${getCategoryColor(article.category)}33`,
|
||||
color: getCategoryColor(article.category)
|
||||
}}
|
||||
>
|
||||
{article.category}
|
||||
</span>
|
||||
<span>{formatDate(article.created_at)}</span>
|
||||
<span>{formatFileSize(article.file_size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-shrink-0">
|
||||
<button
|
||||
onClick={() => handleDownload(article.id, article.file_name)}
|
||||
className="intel-button intel-button-small intel-button-success"
|
||||
title="Download"
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(article.id, article.title)}
|
||||
className="intel-button intel-button-small"
|
||||
style={{ borderColor: '#EF4444', color: '#EF4444' }}
|
||||
title="Delete"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
236
frontend/src/components/KnowledgeBaseViewer.js
Normal file
236
frontend/src/components/KnowledgeBaseViewer.js
Normal file
@@ -0,0 +1,236 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { X, Download, Loader, AlertCircle, FileText, File } from 'lucide-react';
|
||||
|
||||
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
|
||||
|
||||
export default function KnowledgeBaseViewer({ article, onClose }) {
|
||||
const [content, setContent] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetchArticleContent();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [article.id]);
|
||||
|
||||
const fetchArticleContent = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/knowledge-base/${article.id}/content`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to fetch article content');
|
||||
|
||||
const text = await response.text();
|
||||
setContent(text);
|
||||
} catch (err) {
|
||||
console.error('Error fetching article content:', err);
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/knowledge-base/${article.id}/download`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Download failed');
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = article.file_name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error('Error downloading file:', err);
|
||||
setError('Failed to download file');
|
||||
}
|
||||
};
|
||||
|
||||
const isMarkdown = article.file_name?.endsWith('.md');
|
||||
const isText = article.file_name?.endsWith('.txt');
|
||||
const isPDF = article.file_name?.endsWith('.pdf');
|
||||
const isImage = /\.(jpg|jpeg|png|gif|bmp)$/i.test(article.file_name || '');
|
||||
|
||||
const getCategoryColor = (cat) => {
|
||||
const colors = {
|
||||
'General': '#94A3B8',
|
||||
'Policy': '#0EA5E9',
|
||||
'Procedure': '#10B981',
|
||||
'Guide': '#F59E0B',
|
||||
'Reference': '#8B5CF6'
|
||||
};
|
||||
return colors[cat] || '#94A3B8';
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(51, 65, 85, 0.9) 50%, rgba(30, 41, 59, 0.95) 100%)',
|
||||
border: '2px solid rgba(14, 165, 233, 0.4)',
|
||||
borderRadius: '0.5rem',
|
||||
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.6), 0 0 28px rgba(14, 165, 233, 0.15)',
|
||||
padding: '1.5rem',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-4 pb-4" style={{ borderBottom: '1px solid rgba(14, 165, 233, 0.2)' }}>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<FileText className="w-5 h-5" style={{ color: getCategoryColor(article.category) }} />
|
||||
<h2 className="text-xl font-semibold" style={{ color: '#E2E8F0', fontFamily: 'monospace' }}>
|
||||
{article.title}
|
||||
</h2>
|
||||
</div>
|
||||
{article.description && (
|
||||
<p className="text-sm mb-2" style={{ color: '#94A3B8' }}>
|
||||
{article.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 text-xs" style={{ color: '#64748B' }}>
|
||||
<span
|
||||
className="px-2 py-1 rounded"
|
||||
style={{
|
||||
background: `${getCategoryColor(article.category)}33`,
|
||||
color: getCategoryColor(article.category),
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
{article.category}
|
||||
</span>
|
||||
<span>Created: {formatDate(article.created_at)}</span>
|
||||
{article.created_by_username && (
|
||||
<span>By: {article.created_by_username}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="intel-button intel-button-small"
|
||||
title="Download"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="intel-button intel-button-small"
|
||||
title="Close"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="kb-content-area">
|
||||
{loading && (
|
||||
<div className="text-center py-12">
|
||||
<Loader className="w-8 h-8 animate-spin mx-auto mb-4" style={{ color: '#0EA5E9' }} />
|
||||
<p style={{ color: '#94A3B8' }}>Loading document...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex items-start gap-3 p-4 rounded" style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #EF4444' }}>
|
||||
<AlertCircle className="w-6 h-6 flex-shrink-0" style={{ color: '#EF4444' }} />
|
||||
<div>
|
||||
<p className="font-medium" style={{ color: '#FCA5A5' }}>Failed to Load Document</p>
|
||||
<p className="text-sm mt-1" style={{ color: '#94A3B8' }}>{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
<>
|
||||
{/* Markdown Rendering */}
|
||||
{isMarkdown && (
|
||||
<div className="markdown-content">
|
||||
<ReactMarkdown>{content}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Plain Text */}
|
||||
{isText && !isMarkdown && (
|
||||
<pre
|
||||
className="text-sm p-4 rounded overflow-auto"
|
||||
style={{
|
||||
background: 'rgba(15, 23, 42, 0.8)',
|
||||
border: '1px solid rgba(14, 165, 233, 0.2)',
|
||||
color: '#E2E8F0',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
maxHeight: '600px'
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</pre>
|
||||
)}
|
||||
|
||||
{/* PDF */}
|
||||
{isPDF && (
|
||||
<div className="text-center py-12">
|
||||
<File className="w-16 h-16 mx-auto mb-4" style={{ color: '#EF4444' }} />
|
||||
<p className="mb-4" style={{ color: '#94A3B8' }}>
|
||||
PDF Preview not available. Click the download button to view this file.
|
||||
</p>
|
||||
<button onClick={handleDownload} className="intel-button intel-button-success">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download PDF
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Images */}
|
||||
{isImage && (
|
||||
<div className="text-center">
|
||||
<img
|
||||
src={`${API_BASE}/knowledge-base/${article.id}/content`}
|
||||
alt={article.title}
|
||||
className="max-w-full h-auto rounded"
|
||||
style={{ border: '1px solid rgba(14, 165, 233, 0.3)' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Other file types */}
|
||||
{!isMarkdown && !isText && !isPDF && !isImage && (
|
||||
<div className="text-center py-12">
|
||||
<File className="w-16 h-16 mx-auto mb-4" style={{ color: '#94A3B8' }} />
|
||||
<p className="mb-4" style={{ color: '#94A3B8' }}>
|
||||
Preview not available for this file type.
|
||||
</p>
|
||||
<button onClick={handleDownload} className="intel-button intel-button-success">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
Download File
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user