import React, { useState, useEffect } from 'react'; import { Box, Button, Grid, useMediaQuery, Typography, Card, CardContent, List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Switch, FormControlLabel, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Chip, Divider, Paper, } from '@mui/material'; import { styled } from '@mui/material/styles'; import { CloudUpload, Edit, Delete, Visibility, Close, } from '@mui/icons-material'; import { useTheme } from '@mui/material/styles'; import { useAuth } from "hooks/AuthContext"; import * as Types from 'types/types'; import { BackstoryElementProps } from './BackstoryTab'; import { useAppState } from 'hooks/GlobalContext'; const VisuallyHiddenInput = styled('input')({ clip: 'rect(0 0 0 0)', clipPath: 'inset(50%)', height: 1, overflow: 'hidden', position: 'absolute', bottom: 0, left: 0, whiteSpace: 'nowrap', width: 1, }); const DocumentManager = (props: BackstoryElementProps) => { const theme = useTheme(); const { setSnack } = useAppState(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const { user, apiClient } = useAuth(); const [documents, setDocuments] = useState([]); const [selectedDocument, setSelectedDocument] = useState(null); const [documentContent, setDocumentContent] = useState(''); const [isViewingContent, setIsViewingContent] = useState(false); const [editingDocument, setEditingDocument] = useState(null); const [editingName, setEditingName] = useState(''); const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); // Check if user is a candidate const candidate = user?.userType === 'candidate' ? user as Types.Candidate : null; // Load documents on component mount useEffect(() => { if (candidate) { loadDocuments(); } }, [candidate]); const loadDocuments = async () => { try { const results = await apiClient.getCandidateDocuments(); setDocuments(results.documents); } catch (error) { console.error(error); setSnack('Failed to load documents', 'error'); } }; // Handle document upload const handleDocumentUpload = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase(); let docType : Types.DocumentType | null = null; switch (fileExtension.substring(1)) { case "pdf": docType = "pdf"; break; case "docx": docType = "docx"; break; case "md": docType = "markdown"; break; case "txt": docType = "txt"; break; } if (!docType) { setSnack('Invalid file type. Please upload .txt, .md, .docx, or .pdf files only.', 'error'); return; } try { // Upload file (replace with actual API call) const controller = apiClient.uploadCandidateDocument(file, { includeInRAG: true, isJobDocument: false }); const result = await controller.promise; setDocuments(prev => [...prev, result.document]); setSnack(`Document uploaded: ${file.name}`, 'success'); // Reset file input e.target.value = ''; } catch (error) { setSnack('Failed to upload document', 'error'); } } }; // Handle document deletion const handleDeleteDocument = async (document: Types.Document) => { try { // Call API to delete document await apiClient.deleteCandidateDocument(document); setDocuments(prev => prev.filter(doc => doc.id !== document.id)); setSnack('Document deleted successfully', 'success'); // Close content view if this document was being viewed if (selectedDocument?.id === document.id) { setIsViewingContent(false); setSelectedDocument(null); setDocumentContent(''); } } catch (error) { setSnack('Failed to delete document', 'error'); } }; // Handle RAG flag toggle const handleRAGToggle = async (document: Types.Document, includeInRAG: boolean) => { try { document.options = { includeInRAG }; // Call API to update RAG flag await apiClient.updateCandidateDocument(document); setDocuments(prev => prev.map(doc => doc.id === document.id ? { ...doc, includeInRAG } : doc ) ); setSnack(`Document ${includeInRAG ? 'included in' : 'excluded from'} RAG`, 'success'); } catch (error) { setSnack('Failed to update RAG setting', 'error'); } }; // Handle document rename const handleRenameDocument = async (document: Types.Document, newName: string) => { if (!newName.trim()) { setSnack('Document name cannot be empty', 'error'); return; } try { // Call API to rename document document.filename = newName await apiClient.updateCandidateDocument(document); setDocuments(prev => prev.map(doc => doc.id === document.id ? { ...doc, filename: newName.trim() } : doc ) ); setSnack('Document renamed successfully', 'success'); setIsRenameDialogOpen(false); setEditingDocument(null); setEditingName(''); } catch (error) { setSnack('Failed to rename document', 'error'); } }; // Handle document content viewing const handleViewDocument = async (document: Types.Document) => { try { setSelectedDocument(document); setIsViewingContent(true); // Call API to get document content const result = await apiClient.getCandidateDocumentText(document); setDocumentContent(result.content); } catch (error) { setSnack('Failed to load document content', 'error'); setIsViewingContent(false); } }; // Start rename process const startRename = (document: Types.Document, currentName: string) => { setEditingDocument(document); setEditingName(currentName); setIsRenameDialogOpen(true); }; // Format file size const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; // Get file type color const getFileTypeColor = (type: string): 'primary' | 'secondary' | 'success' | 'warning' => { switch (type) { case 'pdf': return 'primary'; case 'docx': return 'secondary'; case 'txt': return 'success'; case 'md': return 'warning'; default: return 'primary'; } }; if (!candidate) { return (You must be logged in as a candidate to view this content.); } return ( <> Documents {documents.length === 0 ? ( No additional documents uploaded ) : ( {documents.map((doc, index) => ( {index > 0 && } {doc.filename} {doc.options?.includeInRAG && ( )} } secondary={ {formatFileSize(doc.size)} • {doc?.uploadDate?.toLocaleDateString()} handleRAGToggle(doc, e.target.checked)} size="small" /> } label={ Include in RAG } /> } /> handleViewDocument(doc)} title="View content" > startRename(doc, doc.filename)} title="Rename" > handleDeleteDocument(doc)} title="Delete" color="error" > ))} )} {/* Document Content Viewer */} {isViewingContent && ( Document Content { setIsViewingContent(false); setSelectedDocument(null); setDocumentContent(''); }} >
                    {documentContent || 'Loading content...'}
                  
)} {/* Rename Dialog */} setIsRenameDialogOpen(false)} maxWidth="sm" fullWidth > Rename Document setEditingName(e.target.value)} onKeyPress={(e) => { if (e.key === 'Enter' && editingDocument) { handleRenameDocument(editingDocument, editingName); } }} />
); }; export { DocumentManager };