'use client'; import React, { useEffect, useMemo, useState } from 'react'; import { AlertCircle, CheckCircle2, Clock3, MessageSquare, RefreshCw, Send, Trash2 } from 'lucide-react'; import { FaqEntry, FaqReviewItem, lmsApi } from '@/lib/api'; type QueueStatus = 'pending' | 'answered' | 'published' | 'dismissed'; const statusOptions: Array<{ value: QueueStatus | ''; label: string }> = [ { value: '', label: 'Todos' }, { value: 'pending', label: 'Pendientes' }, { value: 'answered', label: 'Respondidas' }, { value: 'published', label: 'Publicadas en FAQ' }, { value: 'dismissed', label: 'Descartadas' }, ]; export default function AdminFaqReviewPage() { const [status, setStatus] = useState('pending'); const [items, setItems] = useState([]); const [faqEntries, setFaqEntries] = useState([]); const [loading, setLoading] = useState(true); const [workingId, setWorkingId] = useState(null); const [importing, setImporting] = useState(false); const [formState, setFormState] = useState>({}); const pendingCount = useMemo(() => items.filter((i) => i.status === 'pending').length, [items]); const loadData = async (selectedStatus: QueueStatus | '' = status) => { setLoading(true); try { const [queue, entries] = await Promise.all([ lmsApi.getFaqReviewQueue(selectedStatus || undefined, 100, 0), lmsApi.listFaqEntries(undefined, 20, 0), ]); setItems(queue.items); setFaqEntries(entries); } catch (err) { console.error('Error loading FAQ moderation data', err); alert('No se pudo cargar la cola de FAQ.'); } finally { setLoading(false); } }; useEffect(() => { loadData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleImportCandidates = async () => { setImporting(true); try { const result = await lmsApi.importFaqCandidates(100); alert(`Importadas: ${result.imported}. Omitidas: ${result.skipped}.`); await loadData(); } catch (err) { console.error('Error importing FAQ candidates', err); alert('No se pudieron importar candidatos desde chats.'); } finally { setImporting(false); } }; const setLocalForm = (itemId: string, field: 'answer' | 'note' | 'tags', value: string) => { setFormState((prev) => ({ ...prev, [itemId]: { answer: prev[itemId]?.answer ?? '', note: prev[itemId]?.note ?? '', tags: prev[itemId]?.tags ?? '', [field]: value, }, })); }; const submitAnswer = async (item: FaqReviewItem, publishToFaq: boolean) => { const local = formState[item.id] ?? { answer: '', note: '', tags: '' }; const answer = local.answer.trim(); if (!answer) { alert('Debes escribir una respuesta humana.'); return; } const tags = local.tags .split(',') .map((t) => t.trim()) .filter(Boolean); setWorkingId(item.id); try { await lmsApi.answerFaqReviewItem(item.id, { human_answer: answer, reviewer_note: local.note.trim() || undefined, publish_to_faq: publishToFaq, tags: tags.length ? tags : undefined, }); await loadData(); } catch (err) { console.error('Error answering FAQ review item', err); alert('No se pudo guardar la respuesta.'); } finally { setWorkingId(null); } }; const dismissItem = async (item: FaqReviewItem) => { const local = formState[item.id] ?? { answer: '', note: '', tags: '' }; setWorkingId(item.id); try { await lmsApi.dismissFaqReviewItem(item.id, local.note.trim() || undefined); await loadData(); } catch (err) { console.error('Error dismissing FAQ review item', err); alert('No se pudo descartar la consulta.'); } finally { setWorkingId(null); } }; return (

Moderación FAQ desde IA

Revisa consultas de alumnos sin buen contexto RAG, responde manualmente y publícalas al FAQ.

Pendientes: {pendingCount} FAQ publicadas: {faqEntries.length}
{loading ? (
Cargando cola de revisión...
) : items.length === 0 ? (
No hay consultas para revisar con el filtro actual.
) : (
{items.map((item) => { const local = formState[item.id] ?? { answer: item.human_answer ?? '', note: item.reviewer_note ?? '', tags: '', }; const isWorking = workingId === item.id; return (
Estado: {item.status} {item.rag_context_found ? 'con RAG' : 'sin RAG útil'} Alumno: {item.student_name || 'N/D'} ({item.student_email || 'sin email'})

Pregunta del alumno

{item.question_text}

Respuesta actual IA

{item.ai_response || 'Sin respuesta registrada'}