import { useState, useRef, useCallback, useEffect } from 'react' import { Upload, Eraser, Loader2, X, Eye, EyeOff, Image as ImageIcon } from 'lucide-react' import { ImageClassificationWorkerInput, WorkerMessage, ImageExample } from '../../types' import { useModel } from '../../contexts/ModelContext' import { useImageClassification } from '../../contexts/ImageClassificationContext' // Sample images for quick testing (placeholder URLs) const SAMPLE_IMAGES = [ { name: 'Cat', url: 'https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?w=300&h=300&fit=crop' }, { name: 'Dog', url: 'https://images.unsplash.com/photo-1552053831-71594a27632d?w=300&h=300&fit=crop' }, { name: 'Car', url: 'https://images.unsplash.com/photo-1605559424843-9e4c228bf1c2?w=300&h=300&fit=crop' }, { name: 'Flower', url: 'https://images.unsplash.com/photo-1490750967868-88aa4486c946?w=300&h=300&fit=crop' } ] function ImageClassification() { const { examples, selectedExample, setSelectedExample, addExample, removeExample, updateExample, clearExamples, config } = useImageClassification() const [isClassifying, setIsClassifying] = useState(false) const [dragOver, setDragOver] = useState(false) const [progress, setProgress] = useState(null) const { activeWorker, status, modelInfo, hasBeenLoaded, selectedQuantization } = useModel() const fileInputRef = useRef(null) const dropZoneRef = useRef(null) const classifyImage = useCallback( async (example: ImageExample) => { if (!modelInfo || !activeWorker || !example.url) return updateExample(example.id, { isLoading: true }) setIsClassifying(true) setProgress(0) const message: ImageClassificationWorkerInput = { type: 'classify', image: example.url, model: modelInfo.id, dtype: selectedQuantization ?? 'fp32', config } activeWorker.postMessage(message) }, [modelInfo, activeWorker, selectedQuantization, config, updateExample] ) const handleFileSelect = useCallback( (files: FileList | null) => { if (!files) return Array.from(files).forEach((file) => { if (file.type.startsWith('image/')) { addExample(file) } }) }, [addExample] ) const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault() setDragOver(true) }, []) const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault() setDragOver(false) }, []) const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault() setDragOver(false) handleFileSelect(e.dataTransfer.files) }, [handleFileSelect] ) const handleClassifyAll = useCallback(() => { const imagesToClassify = examples.filter( (ex) => !ex.predictions && !ex.isLoading ) imagesToClassify.forEach((example) => { classifyImage(example) }) }, [examples, classifyImage]) const handleSelectExample = useCallback( (example: ImageExample) => { setSelectedExample(example) }, [setSelectedExample] ) const handleLoadSampleImages = useCallback(async () => { const existstingImages = new Set(examples.map((ex) => ex.name)) for (const sample of SAMPLE_IMAGES) { if (existstingImages.has(sample.name)) continue try { const response = await fetch(sample.url) const blob = await response.blob() const file = new File([blob], sample.name, { type: blob.type }) addExample(file) } catch (error) { console.error(`Failed to load sample image ${sample.name}:`, error) } } }, [addExample, examples]) useEffect(() => { if (!activeWorker) return const onMessageReceived = (e: MessageEvent) => { const { status, output, progress: workerProgress } = e.data if (status === 'progress' && workerProgress !== undefined) { setProgress(workerProgress) } else if (status === 'output' && output?.predictions) { // Find the example that was being processed const processingExample = examples.find((ex) => ex.isLoading) if (processingExample) { updateExample(processingExample.id, { predictions: output.predictions, isLoading: false }) } setIsClassifying(false) setProgress(null) } else if (status === 'error') { // Clear loading state for all examples examples.forEach((ex) => { if (ex.isLoading) { updateExample(ex.id, { isLoading: false }) } }) setIsClassifying(false) setProgress(null) } } activeWorker.addEventListener('message', onMessageReceived) return () => activeWorker.removeEventListener('message', onMessageReceived) }, [activeWorker, examples, updateExample]) const busy = status !== 'ready' || isClassifying return (

Image Classification

{/* Left Panel - Image Upload and List */}
{/* Upload Area */}
hasBeenLoaded && fileInputRef.current?.click()} >

{dragOver ? 'Drop images here' : 'Click to upload or drag and drop images'}

Supports JPG, PNG, GIF, WebP

handleFileSelect(e.target.files)} className="hidden" disabled={!hasBeenLoaded} />
{/* Classify Button */} {examples.some((ex) => !ex.predictions) && (
)} {/* Images List */}

Images ({examples.length})

{examples.length === 0 ? (
No images uploaded yet. Upload some images above to get started.
) : (
{examples.map((example) => (
handleSelectExample(example)} >
{example.name}
{example.name}
{example.isLoading ? (
Classifying...
) : example.predictions ? (
✓ Classified
) : (
Not classified
)}
))}
)}
{/* Right Panel - Preview and Results */}
{/* Image Preview */} {selectedExample && (

Selected Image

{selectedExample.name}
{selectedExample.name}
)} {/* Classification Results */}

Classification Results {selectedExample && ` - ${selectedExample.name}`}

{!selectedExample ? (
Select an image to see classification results
) : selectedExample.isLoading ? (
Classifying image...
{progress !== null && (
{Math.round(progress * 100)}% complete
)}
) : !selectedExample.predictions ? (
) : (
{selectedExample.predictions.map((prediction, index) => { const confidencePercent = (prediction.score * 100).toFixed( 1 ) const isTopPrediction = index === 0 return (
{prediction.label}
{confidencePercent}%
0.5 ? 'bg-blue-500' : 'bg-gray-400' }`} style={{ width: `${Math.max(prediction.score * 100, 2)}%` }} />
) })}
)}
{!hasBeenLoaded && (
Please load an image classification model first to start classifying images
)} {hasBeenLoaded && examples.length === 0 && (
💡 Tip: Click "Load Samples" to try with example images, or upload your own images above
)}
) } export default ImageClassification