import { useState, useRef } from "react"; export interface PredictionResult { filename?: string; success: boolean; error?: string; prediction?: string; confidence?: number; probabilities?: { name: string; probability: number }[]; details?: { gram_stain: string; shape: string; pathogenicity: string; }; previewUrl?: string; } const PRECAUTIONS: Record = { "Escherichia coli": "Indicator of fecal contamination. \n\nPrecautions/Actions: Boil water immediately before consumption. Source trace to find sewage leaks. Do not use for washing open wounds.", "Pseudomonas aeruginosa": "Opportunistic pathogen resistant to many sanitizers. \n\nPrecautions/Actions: Ensure water chlorination levels are adequate. Can cause severe infections in immunocompromised individuals. Avoid contact with eyes or ears.", "Enterococcus faecalis": "Indicates prolonged fecal contamination. Very resilient. \n\nPrecautions/Actions: Shock chlorinate the water system. Discontinue use for drinking until negative tests are returned.", "Clostridium perfringens": "Spore-forming bacteria, highly resistant to standard disinfection. \n\nPrecautions/Actions: Indicates remote or past fecal contamination. UV filtration or extreme heat treatment may be required.", "Listeria monocytogenes": "Dangerous to pregnant women and immunocompromised individuals. \n\nPrecautions/Actions: Do not use water for food preparation or drinking. Pasteurization/boiling is required." }; interface UploadZoneProps { onResultsGenerated?: (results: PredictionResult[] | null) => void; } export const UploadZone = ({ onResultsGenerated }: UploadZoneProps) => { const [files, setFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); const [progress, setProgress] = useState(0); const [localResults, setLocalResults] = useState(null); const [selectedResult, setSelectedResult] = useState(null); const [error, setError] = useState(null); const fileInputRef = useRef(null); const handleFiles = async (selectedFiles: FileList | File[]) => { const fileArray = Array.from(selectedFiles); if (fileArray.length === 0) return; setFiles(fileArray); // Generate preview URLs const previewUrls = fileArray.map(f => URL.createObjectURL(f)); setLocalResults(null); if (onResultsGenerated) onResultsGenerated(null); setSelectedResult(null); setError(null); setIsUploading(true); setProgress(20); const formData = new FormData(); fileArray.forEach(f => { formData.append("files", f); }); try { setProgress(60); const apiUrl = import.meta.env.VITE_API_URL || "http://localhost:5000"; const response = await fetch(`${apiUrl}/predict_batch`, { method: "POST", body: formData, }); if (!response.ok) { throw new Error("Failed to analyze images"); } setProgress(90); const data = await response.json(); // Attach preview URLs to results for display if (data.results && Array.isArray(data.results)) { const resultsWithPreviews = data.results.map((r: any) => { const matchIndex = fileArray.findIndex(f => f.name === r.filename); return { ...r, previewUrl: matchIndex >= 0 ? previewUrls[matchIndex] : null }; }); setLocalResults(resultsWithPreviews); if (onResultsGenerated) onResultsGenerated(resultsWithPreviews); } setProgress(100); } catch (err: any) { setError(err.message || "An error occurred"); } finally { setIsUploading(false); } }; const onFileDrop = (e: React.DragEvent) => { e.preventDefault(); if (e.dataTransfer.files.length > 0) { handleFiles(e.dataTransfer.files); } }; const onFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleFiles(e.target.files); } }; return (

Upload Zone

e.preventDefault()} onDrop={onFileDrop} onClick={() => fileInputRef.current?.click()} className="cursor-target border-2 border-dashed border-primary/30 rounded-3xl p-12 flex flex-col items-center justify-center text-center bg-background-light/50 dark:bg-background-dark/50 backdrop-blur-md hover:bg-primary/10 transition-colors group cursor-pointer h-64" >
cloud_upload

Drag & drop images

Supports JPG, PNG up to 20MB.

{/* Upload Progress */} {isUploading && (
Processing {files.length} images {progress}%

sync Analyzing morphological features...

)} {error && (
error {error}
)}
{localResults && (

Analysis Results

{localResults.length} processed
{localResults.map((res, index) => (
res.success && setSelectedResult(res)} className={`bg-white/80 dark:bg-slate-800/80 backdrop-blur-md p-6 rounded-3xl border border-slate-200 dark:border-slate-700 shadow-xl flex flex-col ${res.success ? 'cursor-pointer hover:scale-[1.02] hover:border-primary/50 transition-all' : ''}`} title={res.success ? "Click for detailed summary" : ""} >
{res.previewUrl ? ( {`Upload ) : (
image
)}

{res.filename}

{res.success && res.prediction ? ( <>

{res.prediction}

80 ? 'bg-green-500/10 text-green-500' : 'bg-yellow-500/10 text-yellow-500'}`}> {(res.confidence || 0) > 80 ? 'verified' : 'warning'} {(res.confidence || 0).toFixed(1)}% Conf
) : (
error Failed
)}
{res.success && res.details && (
Morphology {res.details.shape}
Stain {res.details.gram_stain}
)}
))}
)}
{/* Modal Dialog Box */} {selectedResult && (
setSelectedResult(null)}>
e.stopPropagation()} >

Detailed Classification Summary

{selectedResult.previewUrl ? ( Analyzed bacteria ) : (
image
)}
Predicted Species

{selectedResult.prediction}

Confidence {(selectedResult.confidence || 0).toFixed(1)}%
Risk Level {selectedResult.details?.pathogenicity || 'Unknown'}
Microbiology Specifics
  • Gram Stain: {selectedResult.details?.gram_stain}
  • Morphology: {selectedResult.details?.shape}
  • Pathogenicity: {selectedResult.details?.pathogenicity}
Precautions & Actions
warning

{selectedResult.prediction ? (PRECAUTIONS[selectedResult.prediction] || "Standard water safety protocols apply. Heat above 70°C before any consumption.") : "Unknown"}

)}
); };