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 (
Upload an image, use your camera, or paste a URL to identify the breed.
Click to upload or drag & drop
JPG, PNG, WebP โ max 10MB
Click to open camera
Allow camera access when prompted
Analyzing image...
{result.breed_info.description}
)} > )} {/* Warning */} {result.warning && (