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 (
{/* 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 */}
{/* 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 }) => (
))}
)}
{/* Error */}
{error && (
)}
{/* Items */}
{loading ? (
) : items.length === 0 ? (
No pending items. Uncertain predictions will appear here automatically.
) : (
{items.map((item) => (
))}
)}
{/* Pagination */}
{totalPages > 1 && (
{page} / {totalPages}
)}
);
}