/** * RAG Questions Panel - Admin PDF Upload & RAG Training * * This panel allows admins to: * 1. Upload interview question PDFs * 2. Train RAG on uploaded PDFs with intelligent classification * 3. View RAG statistics and classification breakdown * 4. Search existing questions * 5. Manage vector collections * * INTELLIGENT CLASSIFICATION: * - Auto-detects branch (CSE, ECE, EEE, ME, CE, Architecture) * - Auto-detects domain (ML Engineer, VLSI Engineer, etc.) * - Optional manual override for specific branch/domain */ import React, { useState, useEffect, useCallback } from 'react'; import { Database, Upload, FileText, Trash2, Play, Search, RefreshCw, Loader2, CheckCircle, XCircle, AlertCircle, BookOpen, Brain, Zap, BarChart2, FolderOpen, Download, ChevronDown, ChevronRight, Filter, Clock, Hash, Tag, GitBranch, Briefcase, Settings, X, PieChart, FileCheck, Eye, AlertTriangle, Shield, Lock } from 'lucide-react'; import { getToken } from '../../services/api'; import './RAGQuestionsPanel.css'; const API_BASE = import.meta.env.VITE_API_URL || ''; function RAGQuestionsPanel() { // State for PDFs const [pdfs, setPdfs] = useState([]); const [pdfsLoading, setPdfsLoading] = useState(true); const [uploadProgress, setUploadProgress] = useState(null); // State for RAG stats const [ragStats, setRagStats] = useState(null); const [statsLoading, setStatsLoading] = useState(true); // State for collections const [collections, setCollections] = useState([]); // State for taxonomy (branches & domains) const [taxonomy, setTaxonomy] = useState({ branches: [], domains: [] }); const [taxonomyLoading, setTaxonomyLoading] = useState(true); // State for training const [trainingPdfId, setTrainingPdfId] = useState(null); const [trainAllLoading, setTrainAllLoading] = useState(false); const [trainResult, setTrainResult] = useState(null); const [showTrainingSummary, setShowTrainingSummary] = useState(false); const [selectedPdfSummary, setSelectedPdfSummary] = useState(null); const [summaryLoading, setSummaryLoading] = useState(false); // Training configuration const [trainConfig, setTrainConfig] = useState({ branch: '', // Empty = auto-detect domain: '', // Empty = auto-detect company: '', company_level: 'product', }); const [showTrainConfig, setShowTrainConfig] = useState(false); const [configPdfId, setConfigPdfId] = useState(null); // State for search const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState(null); const [searchLoading, setSearchLoading] = useState(false); const [searchFilters, setSearchFilters] = useState({ question_type: '', difficulty: '', branch: '', domain: '', }); // State for UI const [activeView, setActiveView] = useState('upload'); // 'upload', 'stats', 'search' const [error, setError] = useState(null); const [success, setSuccess] = useState(null); // State for delete functionality const [showDeleteModal, setShowDeleteModal] = useState(false); const [showClearAllModal, setShowClearAllModal] = useState(false); const [allQuestions, setAllQuestions] = useState([]); const [questionsLoading, setQuestionsLoading] = useState(false); const [selectedQuestions, setSelectedQuestions] = useState(new Set()); const [deleteLoading, setDeleteLoading] = useState(false); const [clearAllPassword, setClearAllPassword] = useState(''); const [confirmDataLoss, setConfirmDataLoss] = useState(false); const [confirmNoBackup, setConfirmNoBackup] = useState(false); const [clearAllLoading, setClearAllLoading] = useState(false); // Password lockout state const [lockoutStatus, setLockoutStatus] = useState({ is_locked: false, remaining_attempts: 5, time_remaining_seconds: null, stage: 0, }); const [lockoutCountdown, setLockoutCountdown] = useState(null); /** * Fetch taxonomy (branches & domains) */ const fetchTaxonomy = useCallback(async () => { setTaxonomyLoading(true); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/taxonomy`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); if (data.success) { setTaxonomy({ branches: data.branches || [], domains: data.domains || [], }); } } } catch (err) { console.error('Error fetching taxonomy:', err); } finally { setTaxonomyLoading(false); } }, []); /** * Get domains filtered by selected branch */ const getFilteredDomains = (branchId) => { if (!branchId) return taxonomy.domains; return taxonomy.domains.filter(d => d.branch === branchId); }; /** * Fetch uploaded PDFs */ const fetchPdfs = useCallback(async () => { setPdfsLoading(true); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/pdfs`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); if (data.success) { setPdfs(data.pdfs || []); } } } catch (err) { console.error('Error fetching PDFs:', err); setError('Failed to fetch PDFs'); } finally { setPdfsLoading(false); } }, []); /** * Fetch RAG statistics */ const fetchRagStats = useCallback(async () => { setStatsLoading(true); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/stats`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); if (data.success) { setRagStats(data); } } } catch (err) { console.error('Error fetching RAG stats:', err); } finally { setStatsLoading(false); } }, []); /** * Fetch collections */ const fetchCollections = useCallback(async () => { try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/collections`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); if (data.success) { setCollections(data.collections || []); } } } catch (err) { console.error('Error fetching collections:', err); } }, []); /** * Upload PDF */ const handleUpload = async (event) => { const file = event.target.files[0]; if (!file) return; if (!file.name.endsWith('.pdf')) { setError('Please upload a PDF file'); return; } setUploadProgress({ status: 'uploading', filename: file.name }); setError(null); setSuccess(null); try { const token = getToken(); const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_BASE}/api/admin/rag/upload-pdf`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const data = await response.json(); if (response.ok && data.success) { setUploadProgress({ status: 'success', filename: file.name }); setSuccess(`PDF "${file.name}" uploaded successfully!`); fetchPdfs(); } else { setUploadProgress({ status: 'error', filename: file.name }); setError(data.detail || 'Upload failed'); } } catch (err) { console.error('Upload error:', err); setUploadProgress({ status: 'error', filename: file.name }); setError('Upload failed: ' + err.message); } // Clear upload input event.target.value = ''; // Clear progress after 3 seconds setTimeout(() => setUploadProgress(null), 3000); }; /** * Train RAG on a specific PDF with intelligent classification */ const trainPdf = async (pdfId, config = null) => { const useConfig = config || trainConfig; setTrainingPdfId(pdfId); setTrainResult(null); setError(null); setShowTrainConfig(false); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/train`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ pdf_id: pdfId, branch: useConfig.branch || null, domain: useConfig.domain || null, company: useConfig.company || null, company_level: useConfig.company_level || 'product', }) }); const data = await response.json(); if (response.ok && data.success) { setTrainResult(data); setShowTrainingSummary(true); // Show detailed summary modal // Build success message with classification info let msg = `Training complete! Found ${data.questions_found} questions, stored ${data.questions_stored}.`; setSuccess(msg); fetchPdfs(); fetchRagStats(); fetchCollections(); } else { setError(data.detail || 'Training failed'); } } catch (err) { console.error('Training error:', err); setError('Training failed: ' + err.message); } finally { setTrainingPdfId(null); setConfigPdfId(null); // Reset config setTrainConfig({ branch: '', domain: '', company: '', company_level: 'product', }); } }; /** * Open training config dialog */ const openTrainConfig = (pdfId) => { setConfigPdfId(pdfId); setShowTrainConfig(true); setTrainConfig({ branch: '', domain: '', company: '', company_level: 'product', }); }; /** * Train all untrained PDFs */ const trainAllPdfs = async () => { setTrainAllLoading(true); setTrainResult(null); setError(null); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/train-all`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); const data = await response.json(); if (response.ok && data.success) { setTrainResult(data); setShowTrainingSummary(true); // Show detailed summary modal setSuccess(`Batch training complete! Processed ${data.pdfs_processed} PDFs, found ${data.total_questions_found} questions.`); fetchPdfs(); fetchRagStats(); fetchCollections(); } else { setError(data.detail || 'Batch training failed'); } } catch (err) { console.error('Batch training error:', err); setError('Batch training failed: ' + err.message); } finally { setTrainAllLoading(false); } }; /** * Delete PDF */ const deletePdf = async (pdfId) => { if (!window.confirm('Are you sure you want to delete this PDF?')) return; try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/pdfs/${pdfId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { setSuccess('PDF deleted successfully'); fetchPdfs(); } else { const data = await response.json(); setError(data.detail || 'Delete failed'); } } catch (err) { console.error('Delete error:', err); setError('Delete failed: ' + err.message); } }; /** * Fetch all questions for deletion management */ const fetchAllQuestions = async () => { setQuestionsLoading(true); setError(null); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/questions/list?limit=500`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); console.log('Fetched questions:', data); setAllQuestions(data.questions || []); } else { const errorData = await response.json().catch(() => ({})); console.error('Error response:', response.status, errorData); setError(`Failed to fetch questions: ${errorData.detail || response.statusText}`); } } catch (err) { console.error('Error fetching questions:', err); setError(`Failed to fetch questions: ${err.message}`); } finally { setQuestionsLoading(false); } }; /** * Open delete questions modal */ const openDeleteModal = () => { setShowDeleteModal(true); setSelectedQuestions(new Set()); fetchAllQuestions(); }; /** * Toggle question selection */ const toggleQuestionSelection = (chunkId) => { const newSelected = new Set(selectedQuestions); if (newSelected.has(chunkId)) { newSelected.delete(chunkId); } else { newSelected.add(chunkId); } setSelectedQuestions(newSelected); }; /** * Select all questions */ const selectAllQuestions = () => { if (selectedQuestions.size === allQuestions.length) { setSelectedQuestions(new Set()); } else { setSelectedQuestions(new Set(allQuestions.map(q => q.chunk_id))); } }; /** * Delete selected questions */ const deleteSelectedQuestions = async () => { if (selectedQuestions.size === 0) return; if (!window.confirm(`Are you sure you want to delete ${selectedQuestions.size} questions?`)) return; setDeleteLoading(true); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/questions/bulk`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ chunk_ids: Array.from(selectedQuestions) }) }); const data = await response.json(); if (response.ok && data.success) { setSuccess(`Successfully deleted ${data.deleted_count} questions`); setSelectedQuestions(new Set()); fetchAllQuestions(); fetchRagStats(); } else { setError(data.detail || 'Failed to delete questions'); } } catch (err) { console.error('Delete error:', err); setError('Failed to delete questions: ' + err.message); } finally { setDeleteLoading(false); } }; /** * Fetch lockout status */ const fetchLockoutStatus = async () => { try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/clear-all/status`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); setLockoutStatus(data); if (data.is_locked && data.time_remaining_seconds) { setLockoutCountdown(data.time_remaining_seconds); } } } catch (err) { console.error('Error fetching lockout status:', err); } }; /** * Open clear all modal */ const openClearAllModal = () => { setShowClearAllModal(true); setClearAllPassword(''); setConfirmDataLoss(false); setConfirmNoBackup(false); fetchLockoutStatus(); }; /** * Clear entire vector database */ const clearAllVectorDatabase = async () => { // Check lockout before even trying if (lockoutStatus.is_locked) { setError('You are currently locked out. Please wait.'); return; } setClearAllLoading(true); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/clear-all`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ password: clearAllPassword, confirm_data_loss: confirmDataLoss, confirm_no_backup: confirmNoBackup }) }); const data = await response.json(); if (response.ok && data.success) { setSuccess(`Vector database cleared! ${data.questions_deleted} questions deleted. ${data.pdfs_reset || 0} PDFs reset.`); setShowClearAllModal(false); setClearAllPassword(''); setConfirmDataLoss(false); setConfirmNoBackup(false); setLockoutStatus({ is_locked: false, remaining_attempts: 5, time_remaining_seconds: null, stage: 0 }); // Refresh ALL data fetchRagStats(); fetchCollections(); fetchPdfs(); // Also refresh PDFs to show untrained status setAllQuestions([]); // Clear local questions cache } else { // Handle lockout response (429) if (response.status === 429) { const timeRemaining = response.headers.get('X-Lockout-Remaining'); if (timeRemaining) { setLockoutCountdown(parseInt(timeRemaining)); setLockoutStatus(prev => ({ ...prev, is_locked: true, time_remaining_seconds: parseInt(timeRemaining), remaining_attempts: 0, })); } } else if (response.status === 403) { // Wrong password - update remaining attempts const remaining = response.headers.get('X-Remaining-Attempts'); if (remaining !== null) { setLockoutStatus(prev => ({ ...prev, remaining_attempts: parseInt(remaining), })); } } setError(data.detail || 'Failed to clear vector database'); setClearAllPassword(''); } } catch (err) { console.error('Clear error:', err); setError('Failed to clear vector database: ' + err.message); } finally { setClearAllLoading(false); } }; // Countdown timer effect for lockout React.useEffect(() => { if (lockoutCountdown && lockoutCountdown > 0) { const timer = setInterval(() => { setLockoutCountdown(prev => { if (prev <= 1) { clearInterval(timer); setLockoutStatus(prevStatus => ({ ...prevStatus, is_locked: false, })); fetchLockoutStatus(); // Refresh status from server return null; } return prev - 1; }); }, 1000); return () => clearInterval(timer); } }, [lockoutCountdown]); /** * View PDF Summary (for already trained PDFs) */ const viewPdfSummary = async (pdfId) => { setSummaryLoading(true); setSelectedPdfSummary(null); setShowTrainingSummary(true); try { const token = getToken(); const response = await fetch(`${API_BASE}/api/admin/rag/pdfs/${pdfId}/details`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); console.log('PDF Details Response:', data); console.log('Classification:', data.classification); console.log('Questions Detail:', data.classification?.questions_detail); // Transform to match trainResult format for the modal setSelectedPdfSummary({ pdfName: data.original_filename, questions_found: data.questions_extracted, questions_stored: data.questions_extracted, trained_at: data.trained_at, branch: data.branch, domain: data.domain, classification: data.classification, question_types: data.question_types, difficulties: data.difficulties, }); } else { const data = await response.json(); setError(data.detail || 'Failed to load PDF details'); setShowTrainingSummary(false); } } catch (err) { console.error('Error fetching PDF details:', err); setError('Failed to load PDF details: ' + err.message); setShowTrainingSummary(false); } finally { setSummaryLoading(false); } }; /** * Search questions */ const searchQuestions = async () => { if (!searchQuery.trim()) return; setSearchLoading(true); setSearchResults(null); try { const token = getToken(); const params = new URLSearchParams({ query: searchQuery, top_k: '10' }); if (searchFilters.question_type) { params.append('question_type', searchFilters.question_type); } if (searchFilters.difficulty) { params.append('difficulty', searchFilters.difficulty); } const response = await fetch(`${API_BASE}/api/admin/rag/search?${params}`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); setSearchResults(data); } } catch (err) { console.error('Search error:', err); setError('Search failed: ' + err.message); } finally { setSearchLoading(false); } }; // Initial fetch useEffect(() => { fetchPdfs(); fetchRagStats(); fetchCollections(); fetchTaxonomy(); }, [fetchPdfs, fetchRagStats, fetchCollections, fetchTaxonomy]); // Clear messages after delay useEffect(() => { if (success) { const timer = setTimeout(() => setSuccess(null), 5000); return () => clearTimeout(timer); } }, [success]); useEffect(() => { if (error) { const timer = setTimeout(() => setError(null), 5000); return () => clearTimeout(timer); } }, [error]); /** * Format file size */ const formatFileSize = (bytes) => { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }; /** * Format domain/branch name from snake_case to Title Case */ const formatName = (name) => { if (!name) return ''; return name .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }; /** * Get branch name from domain (lookup from taxonomy) */ const getBranchForDomain = (domainId) => { const domain = taxonomy.domains.find(d => d.id === domainId); if (domain) { const branch = taxonomy.branches.find(b => b.id === domain.branch); return branch ? branch.name : formatName(domain.branch); } return null; }; const untrainedPdfs = pdfs.filter(p => !p.is_trained); return (
Upload PDFs, train RAG, and manage interview questions
Loading PDFs...
No PDFs uploaded yet
Upload interview question PDFs to train RAGConfigure classification for this PDF. Leave empty for auto-detection.
Loading statistics...
Could not load RAG statistics
No collections yet
Loading summary...
{q.question_text}
No classification data available
This PDF may have been trained before classification was addedLoading questions...
No questions in vector database
Train a PDF first to add questions{q.content}
You are about to delete ALL questions from the vector database. This includes all questions extracted from PDFs.
Current database contains approximately {ragStats?.total_documents || 0} questions.
Too many failed attempts. Try again in: {lockoutCountdown >= 3600 ? `${Math.floor(lockoutCountdown / 3600)}h ${Math.floor((lockoutCountdown % 3600) / 60)}m` : lockoutCountdown >= 60 ? `${Math.floor(lockoutCountdown / 60)}m ${lockoutCountdown % 60}s` : `${lockoutCountdown}s` }