import React, { useState } from 'react'; import { FileText, AlertCircle } from 'lucide-react'; import FileUpload from './components/FileUpload'; import ProgressIndicator from './components/ProgressIndicator'; import ResultCard from './components/ResultCard'; import ImagePreview from './components/ImagePreview'; import { convertFileToImages, dataUrlToBlob } from './utils/fileConverter'; import { processSingleInvoice } from './utils/api'; function App() { const [processing, setProcessing] = useState(false); const [results, setResults] = useState([]); const [progress, setProgress] = useState({ total: 0, completed: 0, current: '' }); const [error, setError] = useState(null); const [imageDataMap, setImageDataMap] = useState({}); const [previewImages, setPreviewImages] = useState([]); const [processingIndex, setProcessingIndex] = useState(null); const [resolutionMap, setResolutionMap] = useState({}); const [resultResolutionMap, setResultResolutionMap] = useState({}); const [enhancedMap, setEnhancedMap] = useState({}); // Track which images are enhanced const [reasoningMap, setReasoningMap] = useState({}); // Track which images use reasoning mode const handleFilesSelected = async (files) => { setProcessing(false); setResults([]); setError(null); setImageDataMap({}); setPreviewImages([]); setResolutionMap({}); setEnhancedMap({}); // Reset enhanced state setReasoningMap({}); // Reset reasoning state try { // Step 1: Convert all files to images and show previews const allImages = []; const imageData = {}; const previews = []; for (const file of files) { try { const images = await convertFileToImages(file); images.forEach((img, idx) => { const key = `${file.name}_${idx}`; allImages.push({ key: key, dataUrl: img.dataUrl, filename: img.filename, originalFile: img.originalFile, pageNumber: img.pageNumber, }); imageData[key] = img.dataUrl; previews.push({ key: key, dataUrl: img.dataUrl, filename: img.filename, }); }); } catch (err) { console.error(`Error converting ${file.name}:`, err); setError(`Failed to convert ${file.name}: ${err.message}`); } } setImageDataMap(imageData); setPreviewImages(previews); // Initialize resolution map with 100% for all images const initialResolutions = {}; previews.forEach(p => { initialResolutions[p.key] = { dataUrl: p.dataUrl, resolution: 100 }; }); setResolutionMap(initialResolutions); } catch (err) { console.error('Processing error:', err); setError(`Processing failed: ${err.message}`); } }; const handleProcessImages = async () => { setProcessing(true); setResults([]); setError(null); setProgress({ total: previewImages.length, completed: 0, current: '' }); try { for (let i = 0; i < previewImages.length; i++) { const preview = previewImages[i]; setProcessingIndex(i); setProgress(prev => ({ ...prev, current: preview.filename })); try { // Use resolution-adjusted image if available const processData = resolutionMap[preview.key] || { dataUrl: preview.dataUrl, resolution: 100 }; const blob = dataUrlToBlob(processData.dataUrl); const isEnhanced = enhancedMap[preview.key] || false; const reasoningMode = reasoningMap[preview.key] ? "reason" : "simple"; const result = await processSingleInvoice(blob, preview.filename, isEnhanced, reasoningMode); const resultWithMetadata = { ...result, filename: preview.filename, originalFile: preview.filename, index: i, success: true, key: preview.key, processedImageData: processData.dataUrl, processedResolution: processData.resolution }; setResults(prev => [...prev, resultWithMetadata]); } catch (error) { const errorResult = { filename: preview.filename, index: i, success: false, error: error.response?.data?.detail || error.message, key: preview.key }; setResults(prev => [...prev, errorResult]); } setProgress(prev => ({ ...prev, completed: i + 1 })); } } catch (err) { console.error('Processing error:', err); setError(`Processing failed: ${err.message}`); } finally { setProcessing(false); setProcessingIndex(null); setProgress(prev => ({ ...prev, current: '' })); } }; const handleReprocess = async (result, resolution, adjustedDataUrl) => { const index = results.findIndex(r => r.key === result.key); if (index === -1) return; setProcessingIndex(index); try { // Use resolution-adjusted image from ResultCard const blob = dataUrlToBlob(adjustedDataUrl || imageDataMap[result.key]); const isEnhanced = enhancedMap[result.key] || false; const reasoningMode = reasoningMap[result.key] ? "reason" : "simple"; const newResult = await processSingleInvoice(blob, result.filename, isEnhanced, reasoningMode); const resultWithMetadata = { ...newResult, filename: result.filename, originalFile: result.originalFile, index: index, success: true, key: result.key, processedImageData: adjustedDataUrl || imageDataMap[result.key], processedResolution: resolution }; setResults(prev => { const newResults = [...prev]; newResults[index] = resultWithMetadata; return newResults; }); } catch (error) { setError(`Reprocessing failed: ${error.message}`); } finally { setProcessingIndex(null); } }; const handleResolutionChange = (key, dataUrl, resolution) => { setResolutionMap(prev => ({ ...prev, [key]: { dataUrl, resolution } })); }; const handleEnhanceToggle = (key) => { setEnhancedMap(prev => ({ ...prev, [key]: !prev[key] })); }; const handleReasoningModeToggle = (key) => { setReasoningMap(prev => ({ ...prev, [key]: !prev[key] })); }; return (
{/* Header */}

Invoice Information Extractor

Extract text, detect signatures and stamps from invoices using AI

{/* Main Content */}
{/* Upload Section */}
{/* Image Previews with Resolution Sliders */} {previewImages.length > 0 && !processing && results.length === 0 && (

Preview & Adjust Resolution

{previewImages.length} {previewImages.length === 1 ? 'Image' : 'Images'}
{previewImages.map((preview, idx) => ( handleReasoningModeToggle(preview.key)} useReasoning={reasoningMap[preview.key] || false} imageData={preview.dataUrl} fileName={preview.filename} onResolutionChange={(dataUrl, resolution) => handleResolutionChange(preview.key, dataUrl, resolution) } onEnhanceToggle={() => handleEnhanceToggle(preview.key)} isEnhanced={enhancedMap[preview.key] || false} /> ))}
)} {/* Error Display */} {error && (

Error

{error}

)} {/* Progress Indicator */} {processing && (
{/* Show image being processed with scanning animation */} {processingIndex !== null && previewImages[processingIndex] && (

Processing: {previewImages[processingIndex].filename}

Processing
)}
)} {/* Results Section */} {results.length > 0 && (

Processing Results

{results.length} {results.length === 1 ? 'Document' : 'Documents'}
{results.map((result, index) => { const imageKey = result.key || `${result.originalFile}_${index}`; const originalImage = imageDataMap[imageKey] || imageDataMap[Object.keys(imageDataMap)[index]]; const processedImage = result.processedImageData || originalImage; return ( ); })}
)} {/* Info Cards */} {!processing && results.length === 0 && (

Multiple Formats

Upload images or PDFs. PDFs are automatically converted to images.

Batch Processing

Process multiple documents at once and see results one by one.

Visual Detection

Automatically detect and highlight signatures and stamps on documents.

)}
{/* Footer */}

By Team DevBytes ⚡

); } export default App;