'use client'; import React, { useEffect, useMemo, useState } from 'react'; import { AlertCircle, BrainCircuit, Calendar, CheckCircle2, Filter, RefreshCw, ShieldAlert, X } from 'lucide-react'; import { AiAuditItem, lmsApi } from '@/lib/api'; type ReviewFilter = 'all' | 'pending' | 'reviewed'; // Todas las señales que el backend puede emitir const ALL_SIGNALS = [ 'missing_rag_context', 'high_output_tokens', 'long_response', 'absolute_claim_language', 'citation_without_rag', ] as const; type RiskSignal = (typeof ALL_SIGNALS)[number]; function formatSignal(signal: string) { return signal .replaceAll('_', ' ') .replace(/\b\w/g, (c) => c.toUpperCase()); } function todayMinus(days: number): string { const d = new Date(); d.setDate(d.getDate() - days); return d.toISOString().slice(0, 10); } export default function AdminAiAuditPage() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [workingId, setWorkingId] = useState(null); const [notes, setNotes] = useState>({}); // Filtros de estado const [filter, setFilter] = useState('pending'); // Filtros avanzados (aplicados en cliente) const [signalFilter, setSignalFilter] = useState(''); const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); const [minRisk, setMinRisk] = useState(1); const [filtersOpen, setFiltersOpen] = useState(false); const loadData = async (nextFilter: ReviewFilter = filter) => { setLoading(true); try { const reviewed = nextFilter === 'all' ? undefined : nextFilter === 'reviewed'; const response = await lmsApi.getAiAuditLogs(reviewed, 200, 0); setItems(response.items); } catch (err) { console.error('Error loading AI audit logs', err); alert('No se pudieron cargar los casos de auditoría IA.'); } finally { setLoading(false); } }; useEffect(() => { loadData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const markReview = async (item: AiAuditItem, reviewed: boolean) => { setWorkingId(item.id); try { await lmsApi.reviewAiAuditLog(item.id, { reviewed, reviewer_note: notes[item.id]?.trim() || undefined, }); await loadData(); } catch (err) { console.error('Error updating AI audit review', err); alert('No se pudo actualizar el estado de revisión.'); } finally { setWorkingId(null); } }; const resetAdvancedFilters = () => { setSignalFilter(''); setDateFrom(''); setDateTo(''); setMinRisk(1); }; const filtered = useMemo(() => { return items.filter((item) => { if (signalFilter && !item.risk_signals.includes(signalFilter)) return false; if (item.risk_score < minRisk) return false; if (dateFrom && item.created_at < dateFrom) return false; if (dateTo && item.created_at > dateTo + 'T23:59:59Z') return false; return true; }); }, [items, signalFilter, minRisk, dateFrom, dateTo]); const pendingCount = useMemo(() => filtered.filter((i) => !i.reviewed).length, [filtered]); const reviewedCount = useMemo(() => filtered.filter((i) => i.reviewed).length, [filtered]); const activeFilterCount = [signalFilter, dateFrom, dateTo, minRisk > 1].filter(Boolean).length; return (

Auditoría IA

Detección temprana de posibles alucinaciones en respuestas del tutor.

{/* Panel de filtros avanzados */} {filtersOpen && (
Filtros avanzados {activeFilterCount > 0 && ( )}
{/* Señal de riesgo */}
{/* Score mínimo */}
setMinRisk(Number(e.target.value))} className="w-full accent-indigo-600" />
15
{/* Fecha desde */}
setDateFrom(e.target.value)} className="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm dark:border-white/20 dark:bg-slate-700" /> {dateFrom && ( )}
{[7, 30, 90].map((d) => ( ))}
{/* Fecha hasta */}
setDateTo(e.target.value)} className="w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm dark:border-white/20 dark:bg-slate-700" /> {dateTo && ( )}
)}
Pendientes: {pendingCount} Revisados: {reviewedCount} {activeFilterCount > 0 && ( Mostrando {filtered.length} de {items.length} )}
{loading ? (
Cargando casos de auditoría...
) : filtered.length === 0 ? (
{items.length === 0 ? 'No hay casos de riesgo con el filtro de estado actual.' : `Los filtros avanzados no arrojaron resultados. ${items.length} caso(s) excluidos.`}
) : (
{filtered.map((item) => { const isWorking = workingId === item.id; const noteValue = notes[item.id] ?? item.reviewer_note ?? ''; return (
Riesgo: {item.risk_score} {item.reviewed ? 'Revisado' : 'Pendiente'} Alumno: {item.student_name || 'N/D'} Modelo: {item.model} Tokens salida: {item.output_tokens}
{item.risk_signals.map((signal) => ( {formatSignal(signal)} ))}
{item.response_excerpt || 'Sin extracto de respuesta'}
setNotes((prev) => ({ ...prev, [item.id]: e.target.value }))} className="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm dark:border-white/20 dark:bg-slate-800" placeholder="Nota del revisor" />
{item.reviewed && item.reviewed_by_name && (

Revisado por: {item.reviewed_by_name}

)}
); })}
)}
); }