Deploy Bot
πŸš€ Deploy OCR/LAD/RAD Intelligence Platform
f3c700a
// App.js - Main React Component
import React, { useState, useEffect, useRef, useCallback } from 'react';
import './App.css';
import { w3cwebsocket as W3CWebSocket } from "websocket";
// Document Processing Component (Left Zone)
const DocumentPanel = ({ onDocumentSelect, onExtract, currentDocument, isProcessing }) => {
const [dragActive, setDragActive] = useState(false);
const fileInputRef = useRef(null);
const handleDrag = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true);
} else if (e.type === "dragleave") {
setDragActive(false);
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
handleFileSelect(e.dataTransfer.files[0]);
}
};
const handleFileSelect = async (file) => {
// Upload file
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
const result = await response.json();
onDocumentSelect(result);
} else {
console.error('Upload failed');
}
} catch (error) {
console.error('Upload error:', error);
}
};
const handleFileInput = (e) => {
if (e.target.files && e.target.files[0]) {
handleFileSelect(e.target.files[0]);
}
};
const handleDelete = async () => {
if (currentDocument) {
try {
await fetch(`/api/documents/${currentDocument.document_id}`, {
method: 'DELETE',
});
onDocumentSelect(null);
} catch (error) {
console.error('Delete error:', error);
}
}
};
const renderThumbnail = () => {
if (!currentDocument) {
return <div className="thumbnail-placeholder">Document</div>;
}
// Use server-generated thumbnail for all document types
const thumbnailUrl = `/api/documents/${currentDocument.document_id}/thumbnail`;
return (
<img
src={thumbnailUrl}
alt={`Miniature de ${currentDocument.filename}`}
className="thumbnail-image"
onError={(e) => {
// Fallback if thumbnail generation fails
e.target.style.display = 'none';
e.target.nextSibling.style.display = 'flex';
}}
/>
);
};
return (
<div className="panel document-panel">
<div className="panel-header">
<h2>Document Processing</h2>
<p>Upload and intelligent analysis of your documents</p>
</div>
<div className="document-content">
{!currentDocument ? (
<div
className={`upload-zone ${dragActive ? 'drag-active' : ''}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
<div className="upload-icon">+</div>
<h3>Drop your document here</h3>
<p>or click to browse your files</p>
<div className="supported-formats">
PDF β€’ JPG β€’ PNG β€’ GIF β€’ WebP β€’ BMP β€’ TIFF
</div>
<input
ref={fileInputRef}
type="file"
accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.bmp,.tiff"
onChange={handleFileInput}
style={{ display: 'none' }}
/>
</div>
) : (
<div className="document-preview fade-in">
<div className="document-info">
<div className="document-header">
<h3>{currentDocument.filename}</h3>
<div className="document-meta">
<div className="file-size">
{(currentDocument.file_size / 1024).toFixed(1)} KB
</div>
<span className={`status status-${currentDocument.status}`}>
{currentDocument.status}
</span>
</div>
</div>
{/* Document thumbnail with PDF support */}
<div className="document-thumbnail">
{renderThumbnail()}
<div className="thumbnail-placeholder" style={{ display: 'none' }}>
πŸ“„ {currentDocument.filename}
</div>
</div>
{/* Horizontal action buttons */}
<div className="action-buttons">
<button
className="btn btn-primary"
onClick={() => onExtract(currentDocument.document_id)}
disabled={isProcessing}
>
{isProcessing ? (
<>
<span className="spinner"></span>
Analyzing...
</>
) : (
'Extract'
)}
</button>
<button
className="btn btn-secondary"
onClick={() => fileInputRef.current?.click()}
disabled={isProcessing}
>
Change
</button>
<button
className="btn btn-danger"
onClick={handleDelete}
disabled={isProcessing}
>
Delete
</button>
</div>
</div>
<input
ref={fileInputRef}
type="file"
accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.bmp,.tiff"
onChange={handleFileInput}
style={{ display: 'none' }}
/>
</div>
)}
</div>
</div>
);
};
// Enhanced Results Component (Right Zone)
const ResultsPanel = ({ extractionResult }) => {
const [copiedText, setCopiedText] = useState('');
const getMetricData = (result, key) => {
if (!result || result[key] === undefined || result[key] === null) {
return { value: 0, display: 'N/A', quality: 'poor' };
}
const value = typeof result[key] === 'number' ? result[key] : parseFloat(result[key]) || 0;
// Keep decimal precision for more realistic display
const percentage = Math.round(value * 1000) / 10; // One decimal place
let quality = 'poor';
if (percentage >= 90) quality = 'excellent';
else if (percentage >= 75) quality = 'good';
else if (percentage >= 50) quality = 'average';
return {
value: percentage,
display: `${percentage.toFixed(1)}%`,
quality,
color: quality === 'excellent' ? '#34C759' :
quality === 'good' ? '#007AFF' :
quality === 'average' ? '#FF9500' : '#FF3B30'
};
};
const copyToClipboard = async () => {
const text = formatExtractedText(extractionResult);
try {
await navigator.clipboard.writeText(text);
setCopiedText('Copied!');
setTimeout(() => setCopiedText(''), 2000);
} catch (err) {
setCopiedText('Error');
setTimeout(() => setCopiedText(''), 2000);
}
};
const formatExtractedText = (result) => {
if (!result) return '';
let text = '';
// Header with metadata
text += `=====================================\n`;
text += ` DOCUMENT ANALYSIS REPORT\n`;
text += `=====================================\n\n`;
// Document information
if (result.document_type) {
text += `DOCUMENT TYPE: ${result.document_type}\n`;
}
if (result.confidence_level) {
text += `CONFIDENCE LEVEL: ${(result.confidence_level * 100).toFixed(1)}%\n`;
}
if (result.processing_time) {
text += `PROCESSING TIME: ${result.processing_time}\n`;
}
text += `\n`;
// Analysis scores
text += `ANALYSIS SCORES:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
const imageQuality = getMetricData(result, 'image_quality_score');
const businessLogic = getMetricData(result, 'business_logic_score');
const infoRelevance = getMetricData(result, 'information_relevance_score');
text += ` β€’ Image quality..........: ${imageQuality.display}\n`;
text += ` β€’ Business logic.........: ${businessLogic.display}\n`;
text += ` β€’ Information relevance..: ${infoRelevance.display}\n\n`;
// OCR content
if (result.ocr_text) {
text += `EXTRACTED TEXT:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
text += `${result.ocr_text}\n\n`;
}
// Structured data
if (result.key_fields && result.key_fields.length > 0) {
text += `IDENTIFIED KEY FIELDS:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
result.key_fields.forEach((field, index) => {
text += ` ${index + 1}. ${field.field}: ${field.value}\n`;
if (field.confidence) {
text += ` Confidence: ${(field.confidence * 100).toFixed(1)}%\n`;
}
});
text += '\n';
}
// Dates
if (result.dates && result.dates.length > 0) {
text += `DETECTED DATES:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
result.dates.forEach((date, index) => {
text += ` ${index + 1}. ${date.date_type}: ${date.date_value}\n`;
if (date.confidence) {
text += ` Confidence: ${(date.confidence * 100).toFixed(1)}%\n`;
}
});
text += '\n';
}
// Amounts
if (result.amounts && result.amounts.length > 0) {
text += `IDENTIFIED AMOUNTS:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
result.amounts.forEach((amount, index) => {
text += ` ${index + 1}. ${amount.amount_type}: ${amount.amount_value}`;
if (amount.currency) text += ` ${amount.currency}`;
text += '\n';
if (amount.confidence) {
text += ` Confidence: ${(amount.confidence * 100).toFixed(1)}%\n`;
}
});
text += '\n';
}
// Named entities
if (result.entities && result.entities.length > 0) {
text += `RECOGNIZED ENTITIES:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
const groupedEntities = {};
result.entities.forEach(entity => {
if (!groupedEntities[entity.entity_type]) {
groupedEntities[entity.entity_type] = [];
}
groupedEntities[entity.entity_type].push(entity);
});
Object.entries(groupedEntities).forEach(([type, entities]) => {
text += ` ${type}:\n`;
entities.forEach(entity => {
text += ` β€’ ${entity.entity_value}`;
if (entity.confidence) {
text += ` (${(entity.confidence * 100).toFixed(1)}%)`;
}
text += '\n';
});
});
text += '\n';
}
// Analysis summary
if (result.summary) {
text += `ANALYSIS SUMMARY:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
if (result.summary.total_fields) {
text += ` β€’ Extracted fields: ${result.summary.total_fields}\n`;
}
if (result.summary.pages_processed) {
text += ` β€’ Pages processed: ${result.summary.pages_processed}\n`;
}
if (result.summary.accuracy_score) {
text += ` β€’ Accuracy score: ${(result.summary.accuracy_score * 100).toFixed(1)}%\n`;
}
}
// AI feedback
if (result.supervisor_feedback) {
text += `\nAI ANALYSIS:\n`;
text += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
text += `${result.supervisor_feedback}\n`;
}
text += `\n=====================================\n`;
text += `Generated on ${new Date().toLocaleString('en-US')}\n`;
text += `=====================================`;
return text;
};
const imageQuality = getMetricData(extractionResult, 'image_quality_score');
const businessLogic = getMetricData(extractionResult, 'business_logic_score');
const infoRelevance = getMetricData(extractionResult, 'information_relevance_score');
return (
<div className="panel results-panel">
<div className="panel-header">
<h2>Analysis Results</h2>
<p>Performance metrics and extracted data</p>
</div>
<div className="results-content">
{/* Enhanced metrics */}
<div className="analysis-metrics">
<div className="metric-card">
<div className="metric-label">Image Quality</div>
<div className={`metric-value metric-value ${imageQuality.quality}`}>
{imageQuality.display}
</div>
<div className="metric-bar">
<div
className="metric-bar-fill"
style={{
width: `${imageQuality.value}%`,
background: imageQuality.color
}}
></div>
</div>
</div>
<div className="metric-card">
<div className="metric-label">Business Logic</div>
<div className={`metric-value ${businessLogic.quality}`}>
{businessLogic.display}
</div>
<div className="metric-bar">
<div
className="metric-bar-fill"
style={{
width: `${businessLogic.value}%`,
background: businessLogic.color
}}
></div>
</div>
</div>
<div className="metric-card">
<div className="metric-label">Info Relevance</div>
<div className={`metric-value ${infoRelevance.quality}`}>
{infoRelevance.display}
</div>
<div className="metric-bar">
<div
className="metric-bar-fill"
style={{
width: `${infoRelevance.value}%`,
background: infoRelevance.color
}}
></div>
</div>
</div>
</div>
{/* Enhanced extracted content zone */}
<div className="extracted-content">
<div className="content-header">
<h3>Extracted Data</h3>
<button
className="copy-button"
onClick={copyToClipboard}
title="Copy to clipboard"
>
{copiedText || 'Copy'}
</button>
</div>
<div className="extracted-text">
{extractionResult ? formatExtractedText(extractionResult) : ''}
</div>
</div>
</div>
</div>
);
};
// Real Terminal with actual system logs
const Terminal = ({ logs }) => {
const terminalRef = useRef(null);
useEffect(() => {
if (terminalRef.current) {
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
}
}, [logs]);
return (
<div className="terminal-section">
<div className="terminal-header">
<div className="terminal-title">
System Terminal
</div>
<div className="terminal-controls">
<button className="terminal-btn close"></button>
<button className="terminal-btn minimize"></button>
<button className="terminal-btn maximize"></button>
</div>
</div>
<div className="terminal-content" ref={terminalRef}>
<div className="terminal-body">
{logs.map((log, index) => {
let logClass = 'log-info';
let content = '';
if (log.type === 'log') {
logClass = 'log-message';
content = log.content;
} else {
content = log.content;
if (log.type === 'error') logClass = 'log-error';
else if (log.type === 'success') logClass = 'log-success';
else if (log.type === 'warning') logClass = 'log-warning';
else if (log.type === 'system') logClass = 'log-system';
}
return <div key={index} className={logClass}>{content}</div>;
})}
</div>
</div>
</div>
);
};
// Main App Component
const App = () => {
const [currentDocument, setCurrentDocument] = useState(null);
const [extractionResult, setExtractionResult] = useState(null);
const [isProcessing, setIsProcessing] = useState(false);
const [logs, setLogs] = useState([]);
const [isConnected, setIsConnected] = useState(false);
const wsClient = useRef(null);
const addLog = useCallback((log) => {
setLogs(prevLogs => [...prevLogs, log].slice(-100)); // Keep last 100 logs
}, []);
useEffect(() => {
const connectWebSocket = () => {
// Determine WebSocket protocol based on browser's protocol
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
// Construct the WebSocket URL based on the browser's host
const wsUrl = `${wsProtocol}//${window.location.host}/ws`;
console.log(`Attempting to connect to WebSocket at: ${wsUrl}`);
const client = new W3CWebSocket(wsUrl);
wsClient.current = client;
client.onopen = () => {
console.log("WebSocket Client Connected");
setIsConnected(true);
addLog({ type: 'system', content: `[${new Date().toLocaleTimeString()}] System Connected to backend.` });
};
client.onmessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === 'log') {
// This is a real log from the backend
addLog({ type: 'log', content: data.content });
} else {
// This is a direct message (e.g., upload success)
addLog({ type: data.type, content: `[${data.timestamp}] ${data.message}` });
}
};
client.onerror = (error) => {
console.error("WebSocket Error: ", error);
setIsConnected(false);
addLog({ type: 'error', content: `[${new Date().toLocaleTimeString()}] WebSocket connection error. See browser console for details.` });
};
client.onclose = () => {
console.log("WebSocket Client Disconnected");
setIsConnected(false);
addLog({ type: 'system', content: `[${new Date().toLocaleTimeString()}] System Disconnected. Attempting to reconnect in 5s...` });
// Implement reconnection logic
setTimeout(connectWebSocket, 5000);
};
};
connectWebSocket();
// Cleanup on component unmount
return () => {
if (wsClient.current) {
wsClient.current.close();
}
};
}, [addLog]); // Re-run effect if addLog changes
const handleDocumentSelect = (document) => {
setCurrentDocument(document);
setExtractionResult(null);
addLog({ type: 'system', content: `[${new Date().toLocaleTimeString()}] Document "${document.filename}" uploaded successfully (${(document.file_size / 1024).toFixed(1)} KB)` });
addLog({ type: 'info', content: 'Preparing multi-agent analysis pipeline...' });
};
const handleExtract = async (documentId) => {
if (!documentId) return;
setIsProcessing(true);
setExtractionResult(null); // Clear previous results
try {
// Start the extraction process
const response = await fetch(`/api/extract/${documentId}`, {
method: 'POST',
});
if (response.ok) {
const result = await response.json();
console.log('Extraction result:', result); // Debug log
setExtractionResult(result);
// Final success message
addLog({ type: 'system', content: 'Analysis results loaded successfully' });
} else {
const errorText = await response.text();
console.error('Extraction error response:', errorText);
addLog({ type: 'error', content: `Error during analysis: ${response.status}` });
}
} catch (error) {
console.error('Extraction error:', error);
addLog({ type: 'error', content: `Network error: ${error.message}` });
} finally {
setIsProcessing(false);
}
};
return (
<div className="app-container">
{/* Header */}
<header className="app-header">
<div className="app-title">
OCR/LAD/RAD Intelligence Platform
</div>
<div className={`connection-status ${isConnected ? 'connected' : ''}`}>
<div className="status-light"></div>
{isConnected ? 'System Connected' : 'System Disconnected'}
</div>
</header>
{/* Main content - 2 columns */}
<main className="main-content">
<DocumentPanel
onDocumentSelect={handleDocumentSelect}
onExtract={handleExtract}
currentDocument={currentDocument}
isProcessing={isProcessing}
/>
<ResultsPanel
extractionResult={extractionResult}
/>
</main>
{/* Terminal at bottom with real system logs */}
<Terminal logs={logs} />
</div>
);
};
export default App;