Skip to main content
Glama
DocumentView.js13 kB
import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Box, Typography, Paper, Button, Grid, Card, CardContent, Divider, Alert, CircularProgress, Chip, List, ListItem, ListItemText, ListItemIcon, Accordion, AccordionSummary, AccordionDetails, Tab, Tabs } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import HistoryIcon from '@mui/icons-material/History'; import { fetchDocument, processDocument } from '../services/api'; function DocumentView() { const { documentId } = useParams(); const navigate = useNavigate(); const [document, setDocument] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [processing, setProcessing] = useState(false); const [activeTab, setActiveTab] = useState(0); useEffect(() => { const loadDocument = async () => { try { const data = await fetchDocument(documentId, true); setDocument(data); } catch (err) { setError(`Error loading document: ${err.message || 'Unknown error'}`); } finally { setLoading(false); } }; loadDocument(); }, [documentId]); const handleProcessDocument = async () => { setProcessing(true); setError(null); try { const data = await processDocument(documentId); setDocument(data); } catch (err) { setError(`Processing failed: ${err.message || 'Unknown error'}`); } finally { setProcessing(false); } }; const handleTabChange = (event, newValue) => { setActiveTab(newValue); }; const getConfidenceColor = (confidence) => { if (confidence >= 0.9) return 'success'; if (confidence >= 0.7) return 'info'; if (confidence >= 0.5) return 'warning'; return 'error'; }; const formatDate = (dateString) => { if (!dateString) return 'N/A'; return new Date(dateString).toLocaleString(); }; if (loading) { return ( <Box sx={{ display: 'flex', justifyContent: 'center', my: 4 }}> <CircularProgress /> </Box> ); } if (error) { return ( <Alert severity="error" sx={{ mb: 3 }}> {error} </Alert> ); } if (!document) { return ( <Alert severity="warning" sx={{ mb: 3 }}> Document not found </Alert> ); } return ( <Box> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}> <Typography variant="h4"> Document Details </Typography> <Button variant="outlined" onClick={() => navigate('/')}> Back to Dashboard </Button> </Box> <Paper sx={{ p: 3, mb: 3 }}> <Grid container spacing={2}> <Grid item xs={12} md={6}> <Typography variant="h6" gutterBottom> Document Information </Typography> <Typography variant="body1"> <strong>Document ID:</strong> {document.document_id} </Typography> <Typography variant="body1"> <strong>Filename:</strong> {document.metadata?.filename || 'N/A'} </Typography> <Typography variant="body1"> <strong>Type:</strong> {document.metadata?.document_type || 'Unknown'} </Typography> <Typography variant="body1"> <strong>Last Modified:</strong> {formatDate(document.metadata?.last_modified)} </Typography> <Typography variant="body1"> <strong>File Size:</strong> {document.metadata?.file_size ? `${(document.metadata.file_size / 1024).toFixed(2)} KB` : 'N/A'} </Typography> </Grid> <Grid item xs={12} md={6}> <Typography variant="h6" gutterBottom> Processing Status </Typography> {document.processing_history && document.processing_history.length > 0 ? ( <Box> <Chip label={document.processing_history[document.processing_history.length - 1]?.status || 'Unknown'} color={document.processing_history[document.processing_history.length - 1]?.status === 'completed' ? 'success' : 'default'} sx={{ mb: 2 }} /> {document.metadata?.document_type ? ( <Typography variant="body1"> This document has been classified as a <strong>{document.metadata.document_type}</strong>. </Typography> ) : ( <Box sx={{ mt: 2 }}> <Typography variant="body1" color="text.secondary"> This document has not been processed yet. </Typography> <Button variant="contained" onClick={handleProcessDocument} disabled={processing} sx={{ mt: 1 }} > {processing ? 'Processing...' : 'Process Document'} </Button> </Box> )} </Box> ) : ( <Box> <Typography variant="body1" color="text.secondary"> This document has not been processed yet. </Typography> <Button variant="contained" onClick={handleProcessDocument} disabled={processing} sx={{ mt: 1 }} > {processing ? 'Processing...' : 'Process Document'} </Button> </Box> )} </Grid> </Grid> </Paper> <Box sx={{ mb: 3 }}> <Tabs value={activeTab} onChange={handleTabChange} aria-label="document tabs"> <Tab label="Extracted Data" /> <Tab label="Processing History" /> <Tab label="Document Text" /> </Tabs> </Box> {/* Extracted Data Tab */} {activeTab === 0 && ( <Paper sx={{ p: 3 }}> <Typography variant="h6" gutterBottom> Extracted Data </Typography> {Object.keys(document.extracted_data || {}).length === 0 ? ( <Typography color="text.secondary"> No data has been extracted from this document yet. </Typography> ) : ( <Grid container spacing={2}> {Object.entries(document.extracted_data || {}).map(([key, value]) => { // Skip line_items for separate display if (key === 'line_items') return null; const confidence = document.confidence_scores?.[key] || 0; return ( <Grid item xs={12} sm={6} md={4} key={key}> <Card variant="outlined"> <CardContent> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}> <Typography variant="subtitle1" component="div" sx={{ textTransform: 'capitalize' }}> {key.replace(/_/g, ' ')} </Typography> <Chip label={`${(confidence * 100).toFixed(0)}%`} size="small" color={getConfidenceColor(confidence)} /> </Box> <Typography variant="body1" sx={{ mt: 1, wordBreak: 'break-word' }}> {typeof value === 'object' ? JSON.stringify(value) : String(value)} </Typography> </CardContent> </Card> </Grid> ); })} </Grid> )} {/* Line Items Section */} {document.extracted_data?.line_items && document.extracted_data.line_items.length > 0 && ( <Box sx={{ mt: 4 }}> <Typography variant="h6" gutterBottom> Line Items </Typography> <Accordion> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography> {document.extracted_data.line_items.length} Line Items </Typography> </AccordionSummary> <AccordionDetails> <Box component="pre" sx={{ backgroundColor: '#f5f5f5', p: 2, borderRadius: 1, overflow: 'auto', maxHeight: 300 }}> {JSON.stringify(document.extracted_data.line_items, null, 2)} </Box> </AccordionDetails> </Accordion> </Box> )} </Paper> )} {/* Processing History Tab */} {activeTab === 1 && ( <Paper sx={{ p: 3 }}> <Typography variant="h6" gutterBottom> Processing History </Typography> {!document.processing_history || document.processing_history.length === 0 ? ( <Typography color="text.secondary"> No processing history available. </Typography> ) : ( <List> {document.processing_history.map((entry, index) => ( <React.Fragment key={index}> <ListItem alignItems="flex-start"> <ListItemIcon> <HistoryIcon /> </ListItemIcon> <ListItemText primary={ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Typography variant="subtitle1"> {entry.processor} </Typography> <Chip label={entry.status} size="small" color={entry.status === 'completed' ? 'success' : entry.status === 'failed' ? 'error' : 'default'} /> </Box> } secondary={ <Box> <Typography variant="body2" color="text.secondary"> {formatDate(entry.timestamp)} </Typography> {entry.details && Object.keys(entry.details).length > 0 && ( <Accordion sx={{ mt: 1 }}> <AccordionSummary expandIcon={<ExpandMoreIcon />}> <Typography variant="body2">Details</Typography> </AccordionSummary> <AccordionDetails> <Box component="pre" sx={{ backgroundColor: '#f5f5f5', p: 2, borderRadius: 1, overflow: 'auto', fontSize: '0.8rem' }}> {JSON.stringify(entry.details, null, 2)} </Box> </AccordionDetails> </Accordion> )} </Box> } /> </ListItem> {index < document.processing_history.length - 1 && <Divider variant="inset" component="li" />} </React.Fragment> ))} </List> )} </Paper> )} {/* Document Text Tab */} {activeTab === 2 && ( <Paper sx={{ p: 3 }}> <Typography variant="h6" gutterBottom> Document Text </Typography> {!document.text ? ( <Typography color="text.secondary"> Document text is not available. Try retrieving the document with the include_text parameter set to true. </Typography> ) : ( <Box sx={{ backgroundColor: '#f5f5f5', p: 3, borderRadius: 1, whiteSpace: 'pre-wrap', overflow: 'auto', maxHeight: 500, fontFamily: 'monospace' }} > {document.text} </Box> )} </Paper> )} </Box> ); } export default DocumentView;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arifazim/MCP_Document_Classifer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server