import React, { useState, useEffect, useRef } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeSanitize from 'rehype-sanitize'; import mermaid from 'mermaid'; import { X, Download, Loader, AlertCircle, FileText, File } from 'lucide-react'; mermaid.initialize({ startOnLoad: false, theme: 'dark', darkMode: true, themeVariables: { background: '#0f172a', primaryColor: '#1e3a5f', primaryTextColor: '#e2e8f0', primaryBorderColor: '#0ea5e9', lineColor: '#475569', secondaryColor: '#1a2e1a', tertiaryColor: '#2d1f14', edgeLabelBackground: '#1e293b', clusterBkg: '#1e293b', titleColor: '#e2e8f0', fontFamily: 'monospace' } }); let mermaidCounter = 0; function MermaidDiagram({ code }) { const ref = useRef(null); const [svgError, setSvgError] = useState(''); useEffect(() => { let cancelled = false; const id = `mermaid-kb-${++mermaidCounter}`; mermaid.render(id, code) .then(({ svg }) => { if (!cancelled && ref.current) { ref.current.innerHTML = svg; // Make SVG responsive const svgEl = ref.current.querySelector('svg'); if (svgEl) { svgEl.removeAttribute('width'); svgEl.removeAttribute('height'); svgEl.style.width = '100%'; svgEl.style.maxWidth = '100%'; } } }) .catch((err) => { if (!cancelled) setSvgError(err.message || 'Failed to render diagram'); }); return () => { cancelled = true; }; }, [code]); if (svgError) { return (
Mermaid render error: {svgError}
);
}
return (
);
}
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 (
{article.description}
)}Loading document...
Failed to Load Document
{error}
{children}
);
}
}}
>
{content}
{content}
)}
{/* PDF */}
{isPDF && (
Preview not available for this file type.