import { useState, useEffect, useCallback } from 'react'; import { CheckCircle, XCircle, SkipForward, Loader2, AlertCircle, Brain, RefreshCw } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; const API_BASE = import.meta.env.VITE_API_BASE || 'https://yahyoha-omnidiag.hf.space'; function UncertaintyBar({ score }) { const pct = Math.round((score ?? 0) * 100); return (
{pct}%
); } function ReviewCard({ item, onAnnotate, onSkip }) { const [busy, setBusy] = useState(false); async function handleAction(action) { setBusy(true); if (action === 'skip') { await onSkip(item.id); } else { await onAnnotate(item.id, action === 'positive' ? 1 : 0); } setBusy(false); } return (
{/* Header */}

{item.id.slice(0, 8)}…

{(item.disease || 'unknown').replace(/_/g, ' ')}

Model said

{item.model_prediction === 1 ? 'Positive' : 'Negative'}

{((item.confidence ?? 0) * 100).toFixed(1)}%

{/* Uncertainty */}

Uncertainty (entropy)

{/* Feature preview */} {item.features && (
Patient features ({Object.keys(item.features).length})
{Object.entries(item.features).slice(0, 12).map(([k, v]) => (
{k} {String(v)}
))}
)} {/* Actions */}
); } /** * Active Learning review queue panel. * Shows uncertain predictions awaiting expert annotation. */ export default function ReviewQueuePanel() { const { token } = useAuth(); const [items, setItems] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const headers = token ? { Authorization: `Bearer ${token}` } : {}; const load = useCallback(async (p = 1) => { setLoading(true); setError(''); try { const [queueRes, statsRes] = await Promise.all([ fetch(`${API_BASE}/api/v4/review/queue?page=${p}&limit=10`, { headers }), fetch(`${API_BASE}/api/v4/review/stats`, { headers }), ]); if (!queueRes.ok) throw new Error((await queueRes.json().catch(() => ({}))).detail || queueRes.statusText); const queueData = await queueRes.json(); const statsData = statsRes.ok ? await statsRes.json() : null; setItems(queueData.items || []); setTotalPages(queueData.pages || 1); setStats(statsData); setPage(p); } catch (err) { setError(err.message); } finally { setLoading(false); } }, [token]); useEffect(() => { load(1); }, [load]); async function handleAnnotate(id, label) { await fetch(`${API_BASE}/api/v4/review/${id}/annotate`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ label }), }); load(page); } async function handleSkip(id) { await fetch(`${API_BASE}/api/v4/review/${id}/skip`, { method: 'POST', headers, }); load(page); } return (
{/* Header */}

Review Queue

Active Learning
{/* Stats strip */} {stats && (
{[ { label: 'Pending', value: stats.pending, color: 'text-amber-600' }, { label: 'Reviewed', value: stats.reviewed, color: 'text-green-600' }, { label: 'Skipped', value: stats.skipped, color: 'text-gray-500' }, ].map(({ label, value, color }) => (

{value}

{label}

))}
)} {/* Error */} {error && (
{error}
)} {/* Items */} {loading ? (
) : items.length === 0 ? (

No pending items. Uncertain predictions will appear here automatically.

) : (
{items.map((item) => ( ))}
)} {/* Pagination */} {totalPages > 1 && (
{page} / {totalPages}
)}
); }