import { useState, useRef, useCallback } from 'react'; import { predictFromFile, predictFromURL, predictFromBase64 } from '../services/api'; import { usePredictionHistory } from '../hooks/usePredictionHistory'; function PredictPage() { const [activeTab, setActiveTab] = useState('upload'); const [imagePreview, setImagePreview] = useState(null); const [selectedFile, setSelectedFile] = useState(null); const [imageUrl, setImageUrl] = useState(''); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [dragOver, setDragOver] = useState(false); const [cameraActive, setCameraActive] = useState(false); const fileInputRef = useRef(null); const videoRef = useRef(null); const canvasRef = useRef(null); const streamRef = useRef(null); const { history, addPrediction } = usePredictionHistory(); // File upload handlers const handleFileSelect = (e) => { const file = e.target.files?.[0]; if (file) processFile(file); }; const processFile = (file) => { if (!file.type.startsWith('image/')) { setError('Please select an image file'); return; } setSelectedFile(file); setError(null); const reader = new FileReader(); reader.onload = (e) => setImagePreview(e.target.result); reader.readAsDataURL(file); }; const handleDrop = (e) => { e.preventDefault(); setDragOver(false); const file = e.dataTransfer.files?.[0]; if (file) processFile(file); }; // Camera handlers const startCamera = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: { ideal: 640 }, height: { ideal: 480 } } }); streamRef.current = stream; if (videoRef.current) { videoRef.current.srcObject = stream; videoRef.current.play(); } setCameraActive(true); setError(null); } catch (err) { setError('Could not access camera. Please check permissions.'); } }; const stopCamera = () => { if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); } setCameraActive(false); }; const capturePhoto = () => { if (!videoRef.current || !canvasRef.current) return; const canvas = canvasRef.current; const video = videoRef.current; canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext('2d').drawImage(video, 0, 0); const dataUrl = canvas.toDataURL('image/jpeg', 0.85); setImagePreview(dataUrl); stopCamera(); }; // Clear const clearImage = () => { setImagePreview(null); setSelectedFile(null); setResult(null); setError(null); if (fileInputRef.current) fileInputRef.current.value = ''; }; // Predict const handlePredict = useCallback(async () => { setLoading(true); setError(null); setResult(null); try { let response; if (activeTab === 'upload' && selectedFile) { response = await predictFromFile(selectedFile); } else if (activeTab === 'url' && imageUrl) { response = await predictFromURL(imageUrl); } else if (activeTab === 'camera' && imagePreview) { response = await predictFromBase64(imagePreview); } else { setError('Please provide an image first'); setLoading(false); return; } setResult(response); addPrediction(response, imagePreview); } catch (err) { setError(err.message || 'Prediction failed'); } finally { setLoading(false); } }, [activeTab, selectedFile, imageUrl, imagePreview, addPrediction]); const getConfidenceClass = (conf) => { if (conf >= 0.75) return 'high'; if (conf >= 0.5) return 'medium'; return 'low'; }; const canPredict = (activeTab === 'upload' && selectedFile) || (activeTab === 'url' && imageUrl.trim()) || (activeTab === 'camera' && imagePreview); return (

Predict Breed

Upload an image, use your camera, or paste a URL to identify the breed.

{/* Left: Input panel */}
{/* Upload tab */} {activeTab === 'upload' && ( <>
fileInputRef.current?.click()} onDragOver={(e) => { e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={handleDrop}>
๐Ÿ“ค

Click to upload or drag & drop

JPG, PNG, WebP โ€” max 10MB

)} {/* Camera tab */} {activeTab === 'camera' && ( <> {!cameraActive && !imagePreview && (
๐Ÿ“ท

Click to open camera

Allow camera access when prompted

)} {cameraActive && (
)}
{/* Right: Results panel */}
{loading && (

Analyzing image...

)} {result && !loading && (

{result.predicted_breed}

{(result.confidence * 100).toFixed(1)}%
{/* Confidence bar */}
Confidence {(result.confidence * 100).toFixed(1)}%
{/* Top K predictions */} {result.top_k && result.top_k.length > 1 && (

Top Predictions

{result.top_k.map((item, idx) => (
{idx + 1} {item.breed} {(item.confidence * 100).toFixed(1)}%
))}
)} {/* Breed info */} {result.breed_info && ( <>

Breed Details

Region
{result.breed_info.region}
Type
{result.breed_info.animal_type}
Milk Yield
{result.breed_info.avg_milk_liters_per_day} L/day
Lifespan
{result.breed_info.lifespan_years} years
Primary Use
{result.breed_info.primary_use}
Inference
{result.inference_time_ms} ms
{result.breed_info.description && (

{result.breed_info.description}

)} )} {/* Warning */} {result.warning && (
โš ๏ธ {result.warning}
)}
)} {/* History */} {!result && !loading && history.length > 0 && (

Recent Predictions

{history.slice(0, 5).map((entry) => (
{entry.predictedBreed} {(entry.confidence * 100).toFixed(1)}% ยท {new Date(entry.timestamp).toLocaleDateString()}
))}
)}
); } export default PredictPage;