feat(document_details): add "expand" to OCR text box

This commit is contained in:
perf3ct 2025-07-14 21:44:29 +00:00
parent a393bd030f
commit 418eb48ee4
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
1 changed files with 192 additions and 3 deletions

View File

@ -21,6 +21,8 @@ import {
Container,
Fade,
Skeleton,
TextField,
InputAdornment,
} from '@mui/material';
import Grid from '@mui/material/GridLegacy';
import {
@ -46,6 +48,8 @@ import {
History as HistoryIcon,
Speed as SpeedIcon,
MoreVert as MoreIcon,
OpenInFull as ExpandIcon,
Close as CloseIcon,
} from '@mui/icons-material';
import { documentService, OcrResponse, type Document } from '../services/api';
import DocumentViewer from '../components/DocumentViewer';
@ -77,6 +81,7 @@ const DocumentDetailsPage: React.FC = () => {
const [processedImageLoading, setProcessedImageLoading] = useState<boolean>(false);
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null);
const [documentLabels, setDocumentLabels] = useState<LabelData[]>([]);
const [ocrSearchTerm, setOcrSearchTerm] = useState<string>(''); const [expandedOcrText, setExpandedOcrText] = useState<boolean>(false);
const [availableLabels, setAvailableLabels] = useState<LabelData[]>([]);
const [showLabelDialog, setShowLabelDialog] = useState<boolean>(false);
const [labelsLoading, setLabelsLoading] = useState<boolean>(false);
@ -723,9 +728,32 @@ const DocumentDetailsPage: React.FC = () => {
}}
>
<CardContent sx={{ p: 4 }}>
<Typography variant="h5" sx={{ mb: 3, fontWeight: 700 }}>
🔍 Extracted Text (OCR)
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h5" sx={{ fontWeight: 700 }}>
🔍 Extracted Text (OCR)
</Typography>
{ocrData?.ocr_text && (
<Tooltip title="Expand to view full text with search">
<IconButton
onClick={() => setExpandedOcrText(true)}
sx={{
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
'&:hover': {
backgroundColor: theme.palette.primary.dark,
},
borderRadius: 2,
px: 2,
}}
>
<ExpandIcon sx={{ mr: 1 }} />
<Typography variant="button" sx={{ fontSize: '0.75rem' }}>
Expand
</Typography>
</IconButton>
</Tooltip>
)}
</Box>
{ocrLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', py: 6 }}>
@ -1071,6 +1099,167 @@ const DocumentDetailsPage: React.FC = () => {
</DialogActions>
</Dialog>
{/* Expanded OCR Text Dialog with Search */}
<Dialog
open={expandedOcrText}
onClose={() => {
setExpandedOcrText(false);
setOcrSearchTerm('');
}}
maxWidth="lg"
fullWidth
PaperProps={{
sx: {
height: '90vh',
backgroundColor: theme.palette.background.paper,
}
}}
>
<DialogTitle>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h5" sx={{ fontWeight: 600 }}>
🔍 Extracted Text (OCR) - Full View
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{ocrData && (
<Stack direction="row" spacing={1}>
{ocrData.ocr_confidence && (
<Chip
label={`${Math.round(ocrData.ocr_confidence)}% confidence`}
color="primary"
size="small"
/>
)}
{ocrData.ocr_word_count && (
<Chip
label={`${ocrData.ocr_word_count} words`}
color="secondary"
size="small"
/>
)}
</Stack>
)}
<IconButton
onClick={() => {
setExpandedOcrText(false);
setOcrSearchTerm('');
}}
sx={{
backgroundColor: theme.palette.action.hover,
'&:hover': {
backgroundColor: theme.palette.action.selected,
},
}}
>
<CloseIcon />
</IconButton>
</Box>
</Box>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
{/* Search Bar */}
<Box sx={{ p: 3, borderBottom: `1px solid ${theme.palette.divider}` }}>
<TextField
fullWidth
variant="outlined"
placeholder="Search within extracted text..."
value={ocrSearchTerm}
onChange={(e) => setOcrSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
endAdornment: ocrSearchTerm && (
<InputAdornment position="end">
<IconButton
size="small"
onClick={() => setOcrSearchTerm('')}
>
<CloseIcon fontSize="small" />
</IconButton>
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2,
},
}}
/>
{ocrSearchTerm && (
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
{(() => {
const text = ocrData?.ocr_text || '';
const matches = text.toLowerCase().split(ocrSearchTerm.toLowerCase()).length - 1;
return matches > 0 ? `${matches} match${matches === 1 ? '' : 'es'} found` : 'No matches found';
})()}
</Typography>
)}
</Box>
{/* OCR Text Content */}
<Box sx={{ p: 3, height: 'calc(100% - 120px)', overflow: 'auto' }}>
{ocrLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', py: 6 }}>
<CircularProgress />
<Typography variant="body2" sx={{ ml: 2 }}>
Loading OCR text...
</Typography>
</Box>
) : (
<>
{ocrData && ocrData.ocr_error && (
<Alert severity="error" sx={{ mb: 2 }}>
OCR Error: {ocrData.ocr_error}
</Alert>
)}
<Paper
sx={{
p: 3,
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 2,
minHeight: 400,
}}
>
<Typography
variant="body1"
sx={{
fontFamily: '"Inter", monospace',
whiteSpace: 'pre-wrap',
lineHeight: 1.8,
fontSize: '1rem',
color: ocrData?.ocr_text ? 'text.primary' : 'text.secondary',
}}
dangerouslySetInnerHTML={{
__html: ocrData?.ocr_text ? (
ocrSearchTerm
? ocrData.ocr_text.replace(
new RegExp(`(${ocrSearchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'),
'<mark style="background-color: #ffeb3b; color: #000; padding: 2px 4px; border-radius: 2px;">$1</mark>'
)
: ocrData.ocr_text
) : 'No OCR text available for this document.'
}}
/>
</Paper>
{ocrData && (ocrData.ocr_processing_time_ms || ocrData.ocr_completed_at) && (
<Box sx={{ mt: 3, pt: 2, borderTop: `1px solid ${theme.palette.divider}` }}>
<Typography variant="caption" color="text.secondary">
{ocrData.ocr_processing_time_ms && `Processing time: ${ocrData.ocr_processing_time_ms}ms`}
{ocrData.ocr_processing_time_ms && ocrData.ocr_completed_at && ' • '}
{ocrData.ocr_completed_at && `Completed: ${new Date(ocrData.ocr_completed_at).toLocaleString()}`}
</Typography>
</Box>
)}
</>
)}
</Box>
</DialogContent>
</Dialog>
{/* Document View Dialog */}
<Dialog
open={showViewDialog}