import { useState, useCallback, useEffect } from 'react'; import { Search, ChevronLeft, ChevronRight, FolderOpen, Upload, AlertTriangle } from 'lucide-react'; import { Panel } from '../components/Panel'; import { FileUpload } from '../components/FileUpload'; import { ResultsCard } from '../components/ResultsCard'; import { PreprocessingBadge } from '../components/PreprocessingBadge'; import { FeedbackSection } from '../components/FeedbackSection'; import { SessionHistory } from '../components/SessionHistory'; import { useImageContext } from '../lib/ImageContext'; import { classifyImage, getFilePreview, getFileType, isDicomFile, createSession, recordImageAnalyzed, type ClassificationResult, type PreprocessingInfo } from '../lib/api'; interface ClassificationPageProps { onFeedbackUpdate?: () => void; } export function ClassificationPage({ onFeedbackUpdate }: ClassificationPageProps) { const imageContext = useImageContext(); // Session state const [sessionId, setSessionId] = useState(''); const [feedbackRefresh, setFeedbackRefresh] = useState(0); // File state const [file, setFile] = useState(null); const [preview, setPreview] = useState(null); const [isLoadingPreview, setIsLoadingPreview] = useState(false); // Multiple files state const [files, setFiles] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); // Settings & results const [topK, setTopK] = useState(5); const [results, setResults] = useState(null); const [preprocessingInfo, setPreprocessingInfo] = useState(null); const [processedImage, setProcessedImage] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // Image view tab const [imageTab, setImageTab] = useState<'input' | 'processed'>('input'); // Initialize session useEffect(() => { const initSession = async () => { try { const session = await createSession(); setSessionId(session.session_id); } catch (err) { console.error('Failed to create session:', err); } }; initSession(); }, []); const loadPreview = useCallback(async (selectedFile: File) => { if (!isDicomFile(selectedFile.name)) { setPreview(URL.createObjectURL(selectedFile)); return; } setIsLoadingPreview(true); try { const response = await getFilePreview(selectedFile); if (response.success) { setPreview(`data:image/png;base64,${response.preview}`); } } catch (err) { console.error('Failed to load DICOM preview:', err); setPreview(null); } finally { setIsLoadingPreview(false); } }, []); const handleSingleUpload = useCallback((uploadedFile: File) => { setFile(uploadedFile); setFiles([]); setCurrentIndex(0); setResults(null); setPreprocessingInfo(null); setProcessedImage(null); setError(null); setImageTab('input'); loadPreview(uploadedFile); }, [loadPreview]); const handleFolderUpload = useCallback((e: React.ChangeEvent) => { const fileList = e.target.files; if (!fileList) return; const validFiles = Array.from(fileList).filter(f => f.type.startsWith('image/') || isDicomFile(f.name) ).sort((a, b) => a.name.localeCompare(b.name)); if (validFiles.length > 0) { setFiles(validFiles); setCurrentIndex(0); setFile(validFiles[0]); setResults(null); setPreprocessingInfo(null); setProcessedImage(null); setError(null); setImageTab('input'); loadPreview(validFiles[0]); } }, [loadPreview]); const navigateImage = useCallback((direction: 'prev' | 'next') => { if (files.length === 0) return; let newIndex = currentIndex; if (direction === 'prev' && currentIndex > 0) { newIndex = currentIndex - 1; } else if (direction === 'next' && currentIndex < files.length - 1) { newIndex = currentIndex + 1; } if (newIndex !== currentIndex) { setCurrentIndex(newIndex); setFile(files[newIndex]); setResults(null); setPreprocessingInfo(null); setProcessedImage(null); setImageTab('input'); loadPreview(files[newIndex]); } }, [files, currentIndex, loadPreview]); const handleClassify = async () => { if (!file) return; setIsLoading(true); setError(null); try { const response = await classifyImage(file, topK); setResults(response.predictions); setPreprocessingInfo(response.preprocessing); const processedImageData = response.preprocessing.processed_image_base64 ? `data:image/png;base64,${response.preprocessing.processed_image_base64}` : null; if (processedImageData) { setProcessedImage(processedImageData); } setImageTab('processed'); imageContext.setFile(file, preview); imageContext.setClassificationResults(response.predictions, processedImageData); if (sessionId) { await recordImageAnalyzed(sessionId); } } catch (err) { setError(err instanceof Error ? err.message : 'Classification failed'); setResults(null); setPreprocessingInfo(null); } finally { setIsLoading(false); } }; const handleFeedbackSubmitted = () => { setFeedbackRefresh(prev => prev + 1); onFeedbackUpdate?.(); }; const fileType = file ? getFileType(file.name) : null; const displayImage = imageTab === 'processed' && processedImage ? processedImage : preview; return (
{/* Left Panel - Image (60%) */}
{/* Compact Header */}
{/* Image Tab Toggle */}
{/* File info */} {file && ( {file.name} )} {/* DICOM badge */} {fileType === 'dicom' && ( DICOM )}
{/* Folder navigation */} {files.length > 1 && (
{currentIndex + 1}/{files.length}
)}
{/* Image Display - fills remaining space */}
{displayImage ? ( Ultrasound ) : ( )}
{/* Compact Control Bar */}
{/* Upload/Folder buttons */}
{/* Top-K selector */}
Top
{/* Classify Button */}
{/* Error row */} {error && (
{error}
)}
{/* Right Panel - Results (40%) */}
{/* Preprocessing Badge */} {(fileType || preprocessingInfo) && ( )} {/* Results Card */} {/* Feedback Section */} {results && results.length > 0 && file && ( imageContext.setCorrectedView(correctedLabel)} /> )} {/* Session History */} {sessionId && ( )}
); }