import React, { useState, useCallback, useEffect } from 'react'; import { Box, Card, CardContent, Typography, TextField, Button, Paper, Stepper, Step, StepLabel, StepContent, Alert, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Accordion, AccordionSummary, AccordionDetails, CircularProgress, Container, Tabs, Tab, LinearProgress, Divider, } from '@mui/material'; import Grid from '@mui/material/GridLegacy'; import { ExpandMore as ExpandMoreIcon, BugReport as BugReportIcon, CheckCircle as CheckCircleIcon, Error as ErrorIcon, Warning as WarningIcon, Pending as PendingIcon, PlayArrow as PlayArrowIcon, CloudUpload as UploadIcon, Search as SearchIcon, Refresh as RefreshIcon, Visibility as PreviewIcon, } from '@mui/icons-material'; import { useTranslation } from 'react-i18next'; import { api } from '../services/api'; interface DebugStep { step: number; name: string; status: string; details: any; success: boolean; error?: string; } interface DebugInfo { document_id: string; filename: string; overall_status: string; pipeline_steps: DebugStep[]; failed_document_info?: any; user_settings: any; debug_timestamp: string; detailed_processing_logs?: any[]; file_analysis?: { file_size: number; mime_type: string; is_text_file: boolean; is_image_file: boolean; character_count: number; word_count: number; estimated_processing_time: number; complexity_score: number; [key: string]: any; }; } const DebugPage: React.FC = () => { const { t } = useTranslation(); const [activeTab, setActiveTab] = useState(0); const [documentId, setDocumentId] = useState(''); const [debugInfo, setDebugInfo] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); // Upload functionality const [selectedFile, setSelectedFile] = useState(null); const [uploading, setUploading] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [uploadedDocumentId, setUploadedDocumentId] = useState(''); const [monitoringInterval, setMonitoringInterval] = useState(null); const [processingStatus, setProcessingStatus] = useState(''); // Auto-switch to debug results tab when debugInfo is available useEffect(() => { if (debugInfo && activeTab !== 2) { setActiveTab(2); } }, [debugInfo]); // Reset activeTab when debugInfo is cleared useEffect(() => { if (!debugInfo && activeTab === 2) { setActiveTab(0); } }, [debugInfo, activeTab]); const getStepIcon = (status: string, success: boolean) => { if (status === 'processing') return ; if (success || status === 'completed' || status === 'passed') return ; if (status === 'failed' || status === 'error') return ; if (status === 'pending' || status === 'not_reached') return ; if (status === 'not_queued' || status === 'ocr_disabled') return ; return ; }; const getStatusColor = (status: string, success: boolean): "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning" => { if (status === 'processing') return 'info'; if (success || status === 'completed' || status === 'passed') return 'success'; if (status === 'failed' || status === 'error') return 'error'; if (status === 'pending' || status === 'not_reached') return 'default'; if (status === 'not_queued' || status === 'ocr_disabled') return 'warning'; return 'primary'; }; const fetchDebugInfo = useCallback(async (docId?: string, retryCount = 0) => { const targetDocId = docId || documentId; if (!targetDocId.trim()) { setError(t('debug.errors.enterDocumentId')); return; } setLoading(true); if (retryCount === 0) { setError(''); // Only clear error on first attempt } try { const response = await api.get(`/documents/${targetDocId}/debug`); setDebugInfo(response.data); setError(''); // Clear any previous errors } catch (err: any) { console.error('Debug fetch error:', err); // If it's a 404 and we haven't retried much, try again after a short delay if (err.response?.status === 404 && retryCount < 3) { console.log(`Document not found, retrying in ${(retryCount + 1) * 1000}ms... (attempt ${retryCount + 1})`); setTimeout(() => { fetchDebugInfo(docId, retryCount + 1); }, (retryCount + 1) * 1000); return; } const errorMessage = err.response?.status === 404 ? t('debug.errors.documentNotFound', { documentId: targetDocId }) : err.response?.data?.message || t('debug.errors.fetchFailed', { message: err.message }); setError(errorMessage); setDebugInfo(null); } finally { if (retryCount === 0) { setLoading(false); } } }, [documentId, t]); const handleFileSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { setSelectedFile(file); setError(''); } }; const uploadDocument = useCallback(async () => { if (!selectedFile) { setError(t('debug.upload.selectFile')); return; } setUploading(true); setUploadProgress(0); setError(''); setProcessingStatus(t('debug.upload.uploading')); try { const formData = new FormData(); formData.append('file', selectedFile); const response = await api.post('/documents', formData, { headers: { 'Content-Type': 'multipart/form-data', }, onUploadProgress: (progressEvent) => { const progress = progressEvent.total ? Math.round((progressEvent.loaded * 100) / progressEvent.total) : 0; setUploadProgress(progress); }, }); const uploadedDoc = response.data; setUploadedDocumentId(uploadedDoc.id); setDocumentId(uploadedDoc.id); setProcessingStatus(t('debug.upload.uploadedStartingOcr')); // Start monitoring the processing startProcessingMonitor(uploadedDoc.id); } catch (err: any) { setError(err.response?.data?.message || t('debug.upload.uploadFailed')); setProcessingStatus(t('debug.upload.uploadFailedStatus')); } finally { setUploading(false); setUploadProgress(0); } }, [selectedFile, t]); const startProcessingMonitor = useCallback((docId: string) => { // Clear any existing interval if (monitoringInterval) { clearInterval(monitoringInterval); } const interval = setInterval(async () => { try { const response = await api.get(`/documents/${docId}`); const doc = response.data; if (doc.ocr_status === 'completed' || doc.ocr_status === 'failed') { setProcessingStatus(t('debug.monitoring.processingComplete', { status: doc.ocr_status })); clearInterval(interval); setMonitoringInterval(null); // Auto-fetch debug info when processing is complete OR failed (but don't switch tabs) setTimeout(() => { fetchDebugInfo(docId); // Don't auto-switch tabs - let user decide when to view debug info }, 2000); // Give it a bit more time to ensure document is saved } else if (doc.ocr_status === 'processing') { setProcessingStatus(t('debug.monitoring.ocrInProgress')); } else if (doc.ocr_status === 'pending') { setProcessingStatus(t('debug.monitoring.queuedForOcr')); } else { setProcessingStatus(t('debug.monitoring.checkingStatus')); } } catch (err) { console.error('Error monitoring processing:', err); } }, 2000); // Check every 2 seconds setMonitoringInterval(interval); // Auto-clear monitoring after 5 minutes setTimeout(() => { clearInterval(interval); setMonitoringInterval(null); setProcessingStatus(t('debug.monitoring.monitoringTimeout')); }, 300000); }, [monitoringInterval, fetchDebugInfo, t]); // Cleanup interval on unmount useEffect(() => { return () => { if (monitoringInterval) { clearInterval(monitoringInterval); } }; }, [monitoringInterval]); const renderStepDetails = (step: DebugStep) => { const details = step.details; return ( {step.error && ( {step.error} )} {step.step === 1 && ( // File Upload & Ingestion {t('debug.steps.fileInformation.title')} {t('debug.steps.fileInformation.filename')} {details.filename} {t('debug.steps.fileInformation.original')} {details.original_filename} {t('debug.steps.fileInformation.size')} {(details.file_size / 1024 / 1024).toFixed(2)} MB {t('debug.steps.fileInformation.mimeType')} {details.mime_type} {t('debug.steps.fileInformation.fileExists')} {t('debug.steps.fileMetadata.title')} {details.file_metadata ? ( <> {t('debug.steps.fileMetadata.actualSize')} {(details.file_metadata.size / 1024 / 1024).toFixed(2)} MB {t('debug.steps.fileMetadata.isFile')} {details.file_metadata.is_file ? t('debug.steps.fileInformation.yes') : t('debug.steps.fileInformation.no')} {t('debug.steps.fileMetadata.modified')} {details.file_metadata.modified ? new Date(details.file_metadata.modified.secs_since_epoch * 1000).toLocaleString() : t('debug.steps.fileMetadata.unknown')} ) : ( {t('debug.steps.fileMetadata.notAvailable')} )} {t('debug.steps.fileMetadata.created')} {new Date(details.created_at).toLocaleString()} {details.file_analysis && ( Detailed File Analysis Basic Analysis File Type: {details.file_analysis.file_type} Size: {(details.file_analysis.file_size_bytes / 1024 / 1024).toFixed(2)} MB Readable: {details.file_analysis.error_details && ( File Error: {details.file_analysis.error_details} )} {details.file_analysis.pdf_info ? ( PDF Analysis Valid PDF: PDF Version: {details.file_analysis.pdf_info.pdf_version || 'Unknown'} Pages: {details.file_analysis.pdf_info.page_count || 'Unknown'} Has Text: Has Images: Encrypted: Font Count: {details.file_analysis.pdf_info.font_count} Text Length: {details.file_analysis.pdf_info.estimated_text_length} chars {details.file_analysis.pdf_info.text_extraction_error && ( PDF Text Extraction Error: {details.file_analysis.pdf_info.text_extraction_error} )} ) : details.file_analysis.text_preview ? ( Text Preview {details.file_analysis.text_preview} ) : ( File Content No preview available for this file type )} )} )} {step.step === 2 && ( // OCR Queue Enrollment Queue Status User OCR Enabled: Queue Entries: {details.queue_entries_count} {details.queue_history && details.queue_history.length > 0 && ( Queue History Status Priority Created Started Completed Attempts Worker {(details.queue_history || []).map((entry: any, index: number) => ( {entry.priority} {new Date(entry.created_at).toLocaleString()} {entry.started_at ? new Date(entry.started_at).toLocaleString() : '-'} {entry.completed_at ? new Date(entry.completed_at).toLocaleString() : '-'} {entry.attempts} {entry.worker_id || '-'} ))}
)}
)} {step.step === 3 && ( // OCR Processing OCR Results Text Length: {details.ocr_text_length} characters Confidence: {details.ocr_confidence ? `${details.ocr_confidence.toFixed(1)}%` : 'N/A'} Word Count: {details.ocr_word_count || 0} Processing Time: {details.ocr_processing_time_ms ? `${details.ocr_processing_time_ms}ms` : 'N/A'} Completed: {details.ocr_completed_at ? new Date(details.ocr_completed_at).toLocaleString() : 'Not completed'} Processing Details Has Processed Image: {details.processed_image_info && ( <> Image Size: {details.processed_image_info.image_width}x{details.processed_image_info.image_height} File Size: {(details.processed_image_info.file_size / 1024).toFixed(1)} KB Processing Steps: {details.processed_image_info.processing_steps?.join(', ') || 'None'} {details.processed_image_info.processing_parameters && ( Processing Parameters: {JSON.stringify(details.processed_image_info.processing_parameters)} )} )} )} {step.step === 4 && ( // Quality Validation Quality Thresholds Min Confidence: {details.quality_thresholds.min_confidence}% Brightness: {details.quality_thresholds.brightness_threshold} Contrast: {details.quality_thresholds.contrast_threshold} Noise: {details.quality_thresholds.noise_threshold} Sharpness: {details.quality_thresholds.sharpness_threshold} Actual Values Confidence: {details.actual_values.confidence ? `${details.actual_values.confidence.toFixed(1)}%` : 'N/A'} Word Count: {details.actual_values.word_count || 0} Processed Image Available: {details.actual_values.processing_parameters && ( Processing Parameters: {JSON.stringify(details.actual_values.processing_parameters)} )} Quality Checks {Object.entries(details.quality_checks || {}).map(([check, passed]: [string, any]) => ( : passed === false ? : } /> ))} )}
); }; const renderUploadTab = () => ( {t('debug.upload.title')} {t('debug.upload.description')} {selectedFile && ( {t('debug.upload.selected')} {selectedFile.name} ({(selectedFile.size / 1024 / 1024).toFixed(2)} MB) )} {selectedFile && ( )} {uploading && uploadProgress > 0 && ( {t('debug.upload.uploadProgress', { percent: uploadProgress })} )} {processingStatus && ( {processingStatus} {monitoringInterval && ( )} )} {uploadedDocumentId && ( {t('debug.upload.documentId')} {uploadedDocumentId} )} {selectedFile && selectedFile.type.startsWith('image/') && ( {t('debug.preview')} )} ); const renderSearchTab = () => ( {t('debug.search.title')} {t('debug.search.description')} setDocumentId(e.target.value)} placeholder={t('debug.search.documentIdPlaceholder')} fullWidth size="small" /> {error && ( {error} )} ); return ( {t('debug.title')} {t('debug.subtitle')} setActiveTab(newValue)}> } iconPosition="start" /> } iconPosition="start" /> {debugInfo && ( } iconPosition="start" /> )} {activeTab === 0 && renderUploadTab()} {activeTab === 1 && renderSearchTab()} {error && ( {t('debug.errors.debugError')} {error} )} {debugInfo && activeTab === 2 && ( {t('debug.document.title', { filename: debugInfo.filename })} {t('debug.document.debugRunAt', { timestamp: new Date(debugInfo.debug_timestamp).toLocaleString() })} {t('debug.pipeline.title')} {(debugInfo.pipeline_steps || []).map((step) => ( {step.name} {renderStepDetails(step)} ))} {debugInfo.failed_document_info && ( Failed Document Information Failure Details Failure Reason: {debugInfo.failed_document_info.failure_reason} Failure Stage: {debugInfo.failed_document_info.failure_stage} Retry Count: {debugInfo.failed_document_info.retry_count || 0} Created: {new Date(debugInfo.failed_document_info.created_at).toLocaleString()} {debugInfo.failed_document_info.last_retry_at && ( Last Retry: {new Date(debugInfo.failed_document_info.last_retry_at).toLocaleString()} )} Failed OCR Results {debugInfo.failed_document_info.failed_ocr_text ? ( <> OCR Text Length: {debugInfo.failed_document_info.failed_ocr_text.length} chars OCR Confidence: {debugInfo.failed_document_info.failed_ocr_confidence?.toFixed(1)}% Word Count: {debugInfo.failed_document_info.failed_ocr_word_count || 0} Processing Time: {debugInfo.failed_document_info.failed_ocr_processing_time_ms || 0}ms ) : ( No OCR results available )} {debugInfo.failed_document_info.error_message && ( Error Message: {debugInfo.failed_document_info.error_message} )} {debugInfo.failed_document_info.content_preview && ( Content Preview {debugInfo.failed_document_info.content_preview} )} )} {debugInfo.detailed_processing_logs && debugInfo.detailed_processing_logs.length > 0 && ( Detailed Processing Logs Complete history of all OCR processing attempts for this document. Attempt Status Priority Created Started Completed Duration Wait Time Attempts Worker Error {(debugInfo.detailed_processing_logs || []).map((log: any, index: number) => ( {index + 1} {log.priority} {new Date(log.created_at).toLocaleString()} {log.started_at ? new Date(log.started_at).toLocaleString() : '-'} {log.completed_at ? new Date(log.completed_at).toLocaleString() : '-'} {log.processing_duration_ms ? `${log.processing_duration_ms}ms` : '-'} {log.queue_wait_time_ms ? `${log.queue_wait_time_ms}ms` : '-'} {log.attempts || 0} {log.worker_id || '-'} {log.error_message ? ( {log.error_message} ) : '-'} ))}
)} {debugInfo.file_analysis && ( File Analysis Summary File Properties File Type: {debugInfo.file_analysis.file_type} Size: {(debugInfo.file_analysis.file_size_bytes / 1024 / 1024).toFixed(2)} MB Readable: {debugInfo.file_analysis.pdf_info && ( PDF Properties Valid PDF: Has Text Content: Text Length: {debugInfo.file_analysis.pdf_info.estimated_text_length} chars Page Count: {debugInfo.file_analysis.pdf_info.page_count || 'Unknown'} Encrypted: )} {debugInfo.file_analysis.pdf_info?.text_extraction_error && ( PDF Text Extraction Issue: {debugInfo.file_analysis.pdf_info.text_extraction_error} )} )} {(debugInfo.pipeline_steps || []).some(step => step.step === 3 && step.details?.has_processed_image) && ( Processed Images Original Document Processed Image (OCR Input) { (e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).parentNode?.appendChild( document.createTextNode('Processed image not available') ); }} sx={{ maxWidth: '100%', maxHeight: '300px', objectFit: 'contain', border: '1px solid', borderColor: 'divider', borderRadius: 1 }} /> )} }> User Settings OCR Settings Background OCR: {debugInfo.user_settings?.enable_background_ocr ? 'Enabled' : 'Disabled'} Min Confidence: {debugInfo.user_settings?.ocr_min_confidence || 'N/A'}% Max File Size: {debugInfo.user_settings?.max_file_size_mb || 'N/A'} MB Quality Thresholds Brightness: {debugInfo.user_settings?.ocr_quality_threshold_brightness || 'N/A'} Contrast: {debugInfo.user_settings?.ocr_quality_threshold_contrast || 'N/A'} Noise: {debugInfo.user_settings?.ocr_quality_threshold_noise || 'N/A'} Sharpness: {debugInfo.user_settings?.ocr_quality_threshold_sharpness || 'N/A'}
)}
); }; export default DebugPage;