import React, { useState, useEffect } from 'react'; import { Box, Container, Typography, Paper, Card, CardContent, Grid, Chip, LinearProgress, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Alert, Button, IconButton, CircularProgress, } from '@mui/material'; import { Refresh as RefreshIcon, Folder as FolderIcon, CheckCircleOutline as CheckCircleIcon, Error as ErrorIcon, Schedule as ScheduleIcon, Visibility as VisibilityIcon, CloudUpload as CloudUploadIcon, Description as DescriptionIcon, } from '@mui/icons-material'; import { useTheme } from '@mui/material/styles'; import { queueService, QueueStats } from '../services/api'; interface WatchConfig { watchFolder: string; watchInterval: number; maxFileAge: number; allowedTypes: string[]; isActive: boolean; strategy: string; } const WatchFolderPage: React.FC = () => { const theme = useTheme(); const [queueStats, setQueueStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [lastRefresh, setLastRefresh] = useState(null); const [requeuingFailed, setRequeuingFailed] = useState(false); // Mock configuration data (would typically come from API) const watchConfig: WatchConfig = { watchFolder: import.meta.env.VITE_WATCH_FOLDER || './watch', watchInterval: 30, maxFileAge: 24, allowedTypes: ['pdf', 'png', 'jpg', 'jpeg', 'tiff', 'bmp', 'txt', 'doc', 'docx'], isActive: true, strategy: 'hybrid' }; useEffect(() => { fetchQueueStats(); const interval = setInterval(fetchQueueStats, 30000); // Refresh every 30 seconds return () => clearInterval(interval); }, []); const fetchQueueStats = async (): Promise => { try { setLoading(true); const response = await queueService.getStats(); setQueueStats(response.data); setLastRefresh(new Date()); setError(null); } catch (err) { console.error('Error fetching queue stats:', err); setError('Failed to fetch queue statistics'); } finally { setLoading(false); } }; const requeueFailedJobs = async (): Promise => { try { setRequeuingFailed(true); const response = await queueService.requeueFailedItems(); const requeued = response.data.requeued_count || 0; if (requeued > 0) { // Show success message setError(null); // Refresh stats to see updated counts await fetchQueueStats(); } } catch (err) { console.error('Error requeuing failed jobs:', err); setError('Failed to requeue failed jobs'); } finally { setRequeuingFailed(false); } }; 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]; }; const formatDuration = (minutes: number | null | undefined): string => { if (!minutes) return 'N/A'; if (minutes < 60) return `${Math.round(minutes)}m`; const hours = Math.floor(minutes / 60); const mins = Math.round(minutes % 60); return `${hours}h ${mins}m`; }; const getStatusColor = (status: string): 'success' | 'error' | 'warning' | 'default' => { switch (status) { case 'active': return 'success'; case 'error': return 'error'; case 'pending': return 'warning'; default: return 'default'; } }; const getStatusIcon = (status: string): JSX.Element => { switch (status) { case 'active': return ; case 'error': return ; case 'pending': return ; default: return ; } }; return ( Watch Folder {queueStats && queueStats.failed_count > 0 && ( )} {error && ( {error} )} {/* Watch Folder Configuration */} Watch Folder Configuration Watched Directory {watchConfig.watchFolder} Status Watch Strategy {watchConfig.strategy} Scan Interval {watchConfig.watchInterval} seconds Max File Age {watchConfig.maxFileAge} hours Supported File Types {watchConfig.allowedTypes.map((type) => ( ))} {/* Queue Statistics */} {queueStats && ( Processing Queue {queueStats.pending_count} Pending {queueStats.processing_count} Processing {queueStats.failed_count} Failed {queueStats.completed_today} Completed Today Average Wait Time {formatDuration(queueStats.avg_wait_time_minutes)} Oldest Pending Item {formatDuration(queueStats.oldest_pending_minutes)} {lastRefresh && ( Last updated: {lastRefresh.toLocaleTimeString()} )} )} {/* Processing Information */} How Watch Folder Works The watch folder system automatically monitors the configured directory for new files and processes them for OCR. Processing Pipeline: 1. File Detection: New files are detected using hybrid watching (inotify + polling) 2. Validation: Files are checked for supported format and size limits 3. Deduplication: System prevents processing of duplicate files 4. Storage: Files are moved to the document storage system 5. OCR Queue: Documents are queued for OCR processing with priority The system uses a hybrid watching strategy that automatically detects filesystem type and chooses the optimal monitoring approach (inotify for local filesystems, polling for network mounts). ); }; export default WatchFolderPage;