import React, { useState, useEffect, useRef } from 'react'; import { Send, Sparkles, AlertTriangle, CheckCircle, Shield, Database, Zap, ArrowRight, RefreshCw, Star, Info, Lock, Check, LogOut, Mail, Key, Upload, FileText, MessageSquare, Trash2, Library, ChevronRight, Activity, FilePlus, Edit2, Copy, User, Eye, EyeOff } from 'lucide-react'; import './App.css'; const API_URL = process.env.REACT_APP_API_URL || (window.location.hostname === 'localhost' ? 'http://localhost:5000' : ''); function App() { const [token, setToken] = useState(localStorage.getItem('token') || ''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [loginError, setLoginError] = useState(''); // Auth view mode ('login' or 'register') const [authMode, setAuthMode] = useState('login'); // Registration States const [regEmail, setRegEmail] = useState(''); const [regPassword, setRegPassword] = useState(''); const [regConfirmPassword, setRegConfirmPassword] = useState(''); const [regName, setRegName] = useState(''); const [regAvatar, setRegAvatar] = useState('👨💻'); const [regError, setRegError] = useState(''); // Password Visibility States const [showLoginPassword, setShowLoginPassword] = useState(false); const [showRegPassword, setShowRegPassword] = useState(false); const [showRegConfirmPassword, setShowRegConfirmPassword] = useState(false); // Navigation Sidebar const [activeTab, setActiveTab] = useState('workspace'); // 'workspace', 'library', 'telemetry' // RAG States const [documents, setDocuments] = useState([]); const [selectedProjectId, setSelectedProjectId] = useState(''); const [queryAllDocs, setQueryAllDocs] = useState(false); const [activeTelemetry, setActiveTelemetry] = useState(null); // Chat Threads States const [chatThreads, setChatThreads] = useState([]); const [activeChatId, setActiveChatId] = useState(''); const [messages, setMessages] = useState([]); const [query, setQuery] = useState(''); const [loading, setLoading] = useState(false); // User Profile details const [userStatus, setUserStatus] = useState({ isPremium: false, name: '', queryCount: 0, totalStorageBytes: 0, uploadedDocuments: [] }); // Library Viewer & File Upload & Copy Paste States const [selectedDocContent, setSelectedDocContent] = useState(null); const [viewingDocId, setViewingDocId] = useState(''); const [uploadTitle, setUploadTitle] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const [copyPasteText, setCopyPasteText] = useState(''); const [ingestMode, setIngestMode] = useState('upload'); // 'upload', 'copypaste' const [uploadLoading, setUploadLoading] = useState(false); const [uploadStatus, setUploadStatus] = useState({ type: '', text: '' }); const [copiedId, setCopiedId] = useState(''); const [docSearchQuery, setDocSearchQuery] = useState(''); // Razorpay Simulation Overlay States const [showRzpModal, setShowRzpModal] = useState(false); const [rzpOrderDetails, setRzpOrderDetails] = useState(null); const [rzpPaymentStep, setRzpPaymentStep] = useState('methods'); // 'methods', 'card', 'upi', 'processing' const [rzpCardNumber, setRzpCardNumber] = useState(''); const [rzpCardExpiry, setRzpCardExpiry] = useState(''); const [rzpCardCvv, setRzpCardCvv] = useState(''); const [rzpUpiId, setRzpUpiId] = useState(''); const [rzpActiveOptions, setRzpActiveOptions] = useState(null); // Account Settings States const [profileName, setProfileName] = useState(''); const [profileEmail, setProfileEmail] = useState(''); const [profilePhone, setProfilePhone] = useState(''); const [profilePhoto, setProfilePhoto] = useState('👨💻'); const [profileSaveLoading, setProfileSaveLoading] = useState(false); const [profileSaveStatus, setProfileSaveStatus] = useState({ type: '', text: '' }); const messagesEndRef = useRef(null); // Initialize and load datasets useEffect(() => { if (token) { fetchUserStatus(); fetchDocuments(); } }, [token]); // Synchronize profile states with loaded user details useEffect(() => { if (userStatus) { setProfileName(userStatus.name || ''); setProfileEmail(userStatus.email || ''); setProfilePhone(userStatus.phoneNumber || ''); setProfilePhoto(userStatus.profilePhoto || '👨💻'); } }, [userStatus]); // Dynamically load Razorpay SDK script useEffect(() => { const script = document.createElement("script"); script.src = "https://checkout.razorpay.com/v1/checkout.js"; script.async = true; document.body.appendChild(script); return () => { document.body.removeChild(script); }; }, []); // Load chat threads when project (document) changes useEffect(() => { if (token && selectedProjectId) { fetchChatThreads(selectedProjectId); } else { setChatThreads([]); setActiveChatId(''); setMessages([]); } }, [selectedProjectId, token]); // Load message history when active chat thread changes useEffect(() => { if (activeChatId) { const activeThread = chatThreads.find(t => t.chatId === activeChatId); if (activeThread) { setMessages(activeThread.messages || []); const lastAiMsg = [...(activeThread.messages || [])].reverse().find(m => m.sender === 'ai'); if (lastAiMsg && lastAiMsg.telemetry) { setActiveTelemetry(lastAiMsg.telemetry); } } } else { setMessages([]); } }, [activeChatId, chatThreads]); useEffect(() => { const params = new URLSearchParams(window.location.search); if (params.get('payment') === 'success') { alert("🎉 Thank you! Your account has been upgraded to Premium."); window.history.replaceState({}, document.title, "/"); if (token) fetchUserStatus(); } else if (params.get('payment') === 'cancel') { alert("❌ Payment was cancelled. Feel free to upgrade anytime!"); window.history.replaceState({}, document.title, "/"); } }, [token]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const handleCopyToClipboard = (text, msgIdx) => { navigator.clipboard.writeText(text); setCopiedId(msgIdx); setTimeout(() => setCopiedId(''), 2000); }; const renderFormattedMessage = (text) => { if (!text) return null; const codeBlockRegex = /```([\s\S]*?)```/g; const parts = []; let lastIndex = 0; let match; while ((match = codeBlockRegex.exec(text)) !== null) { if (match.index > lastIndex) { parts.push({ type: 'text', content: text.substring(lastIndex, match.index) }); } parts.push({ type: 'code', content: match[1] }); lastIndex = codeBlockRegex.lastIndex; } if (lastIndex < text.length) { parts.push({ type: 'text', content: text.substring(lastIndex) }); } return parts.map((part, idx) => { if (part.type === 'code') { return (
{part.content.trim()}
);
} else {
const lines = part.content.split('\n');
return lines.map((line, lineIdx) => {
const isListItem = line.trim().startsWith('- ') || line.trim().startsWith('* ');
const content = line.replace(/^[\s*-]+/, '');
const formatBold = (str) => {
const boldRegex = /\*\*([\s\S]*?)\*\*/g;
const boldParts = [];
let bLastIndex = 0;
let bMatch;
while ((bMatch = boldRegex.exec(str)) !== null) {
if (bMatch.index > bLastIndex) {
boldParts.push(str.substring(bLastIndex, bMatch.index));
}
boldParts.push({bMatch[1]});
bLastIndex = boldRegex.lastIndex;
}
if (bLastIndex < str.length) {
boldParts.push(str.substring(bLastIndex));
}
return boldParts.length > 0 ? boldParts : str;
};
if (isListItem) {
return (
{formatBold(line)}
); } }); } }); }; const handleLogin = async (e, devEmail = null) => { if (e) e.preventDefault(); setLoginError(''); const targetEmail = devEmail || email; const targetPassword = devEmail ? 'password123' : password; try { const res = await fetch(`${API_URL}/api/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: targetEmail, password: targetPassword }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Login failed"); localStorage.setItem('token', data.token); setToken(data.token); setEmail(''); setPassword(''); } catch (err) { setLoginError(err.message); } }; const getPasswordStrength = (pwd) => { if (!pwd) return { score: 0, label: 'Empty', color: '#718096' }; let score = 0; if (pwd.length >= 6) score += 1; if (/\d/.test(pwd)) score += 1; if (/[!@#$%^&*(),.?":{}|<>]/.test(pwd)) score += 1; if (score === 1) return { score: 33, label: 'Weak', color: '#ef4444' }; if (score === 2) return { score: 66, label: 'Medium', color: '#f59e0b' }; if (score === 3) return { score: 100, label: 'Strong', color: '#22c55e' }; return { score: 0, label: 'Empty', color: '#718096' }; }; const handleRegisterSubmit = async (e) => { e.preventDefault(); setRegError(''); if (regPassword !== regConfirmPassword) { setRegError("Passwords do not match!"); return; } const strength = getPasswordStrength(regPassword); if (strength.label === 'Weak' || regPassword.length < 6) { setRegError("Password must be at least 6 characters, contain a number, and a special character."); return; } try { const res = await fetch(`${API_URL}/api/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: regEmail, password: regPassword, name: regName, profilePhoto: regAvatar }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Registration failed"); localStorage.setItem('token', data.token); setToken(data.token); setRegEmail(''); setRegPassword(''); setRegConfirmPassword(''); setRegName(''); setAuthMode('login'); } catch (err) { setRegError(err.message); } }; const handleLogout = () => { localStorage.removeItem('token'); setToken(''); setUserStatus({ isPremium: false, name: '', queryCount: 0, totalStorageBytes: 0, uploadedDocuments: [] }); setActiveTelemetry(null); setDocuments([]); setSelectedProjectId(''); setChatThreads([]); setActiveChatId(''); setMessages([]); }; const fetchUserStatus = async () => { try { const res = await fetch(`${API_URL}/api/user-status`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); if (res.status === 401 || res.status === 403) handleLogout(); else setUserStatus(data); } catch (err) { console.error("Failed to connect to Gateway server."); } }; const fetchDocuments = async () => { try { const res = await fetch(`${API_URL}/api/documents`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); if (Array.isArray(data)) { setDocuments(data); if (data.length > 0 && !selectedProjectId) setSelectedProjectId(data[0].id); } else { setDocuments([]); } } catch (err) { console.error("Failed to fetch documents list."); setDocuments([]); } }; const fetchChatThreads = async (projId) => { try { const res = await fetch(`${API_URL}/api/projects/${projId}/chats`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); setChatThreads(data); if (data.length > 0) setActiveChatId(data[0].chatId); else handleCreateChatThread(projId, "General Discussion"); } catch (err) { console.error("Failed to load chat threads."); } }; const handleCreateChatThread = async (projId, threadTitle) => { try { const res = await fetch(`${API_URL}/api/projects/${projId}/chats`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ title: threadTitle }) }); const newThread = await res.json(); setChatThreads(prev => [...prev, newThread]); setActiveChatId(newThread.chatId); } catch (err) { console.error("Failed to create new chat thread."); } }; const handleDeleteChatThread = async (e, threadId) => { e.stopPropagation(); try { await fetch(`${API_URL}/api/projects/${selectedProjectId}/chats/${threadId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); setChatThreads(prev => prev.filter(t => t.chatId !== threadId)); if (activeChatId === threadId) setActiveChatId(''); } catch (err) { console.error("Failed to delete chat thread."); } }; const handleRenameChatThread = async (e, threadId, currentTitle) => { e.stopPropagation(); const titleInput = prompt("Rename conversation thread to:", currentTitle); if (titleInput === null) return; const newTitle = titleInput.trim() || currentTitle; if (newTitle === currentTitle) return; try { const res = await fetch(`${API_URL}/api/projects/${selectedProjectId}/chats/${threadId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ title: newTitle }) }); const data = await res.json(); if (res.ok) { setChatThreads(prev => prev.map(t => t.chatId === threadId ? { ...t, chatTitle: data.chatTitle } : t)); } } catch (err) { console.error("Failed to rename thread."); } }; const handleDeleteDocument = async (e, docId) => { e.stopPropagation(); if (!window.confirm("Are you sure you want to delete this document from the database? This will permanently wipe its text vectors and conversation history.")) return; try { const res = await fetch(`${API_URL}/api/documents/${docId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); if (res.ok) { alert("🗑️ Document deleted successfully!"); if (viewingDocId === docId) { setSelectedDocContent(null); setViewingDocId(''); } if (selectedProjectId === docId) { setSelectedProjectId(''); } fetchDocuments(); fetchUserStatus(); } else { alert(`Error: ${data.error || "Failed to delete document"}`); } } catch (err) { alert("Failed to connect to Gateway server during deletion."); } }; const handleExportChat = () => { if (messages.length === 0) { alert("No messages to export!"); return; } let mdContent = `# Chat Export - ${documents.find(d => d.id === selectedProjectId)?.title || "Workspace"}\n`; mdContent += `Exported on: ${new Date().toLocaleString()}\n\n---\n\n`; messages.forEach(msg => { const senderName = msg.sender === 'user' ? 'User' : 'AI Assistant'; mdContent += `### 💬 ${senderName}\n${msg.text}\n\n`; if (msg.telemetry) { mdContent += `*Audit Trace: Execution Speed: ${msg.telemetry.execution_time_sec?.toFixed(2)}s | NLI Fact-check: ${msg.telemetry.success ? 'PASSED' : 'BLOCKED'}*\n\n`; } mdContent += `---\n\n`; }); const blob = new Blob([mdContent], { type: 'text/markdown;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `chat_export_${selectedProjectId || 'session'}.md`); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const handleSendQuery = async (e) => { e.preventDefault(); if (!query.trim() || !activeChatId) return; const userMessage = { sender: 'user', text: query, timestamp: new Date().toISOString() }; setMessages(prev => [...prev, userMessage]); setQuery(''); setLoading(true); try { const response = await fetch(`${API_URL}/api/query`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ query: userMessage.text, projectId: queryAllDocs ? 'all' : selectedProjectId, chatId: activeChatId }) }); const data = await response.json(); if (response.status === 403 && data.is_blocked) { setMessages(prev => [...prev, { sender: 'ai', text: data.message, isBlocked: true, timestamp: new Date().toISOString() }]); } else if (data.error) { setMessages(prev => [...prev, { sender: 'ai', text: `Error: ${data.error}`, timestamp: new Date().toISOString() }]); } else { const aiResponse = { sender: 'ai', text: data.answer, telemetry: data.telemetry, timestamp: new Date().toISOString() }; setMessages(prev => [...prev, aiResponse]); setActiveTelemetry(data.telemetry); fetchUserStatus(); } } catch (err) { setMessages(prev => [...prev, { sender: 'ai', text: "Connection failed.", timestamp: new Date().toISOString() }]); } finally { setLoading(false); } }; const handleFileUpload = async (e) => { e.preventDefault(); if (!selectedFile || !uploadTitle.trim()) return; setUploadLoading(true); setUploadStatus({ type: '', text: '' }); const docId = `doc_${uploadTitle.toLowerCase().replace(/[^a-z0-9]/g, '_')}`; const formData = new FormData(); formData.append('file', selectedFile); formData.append('doc_id', docId); formData.append('title', uploadTitle); try { const res = await fetch(`${API_URL}/api/ingest-file`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }); const data = await res.json(); if (!res.ok) throw new Error(data.message || data.error || "Failed to upload."); setUploadStatus({ type: 'success', text: `Success: "${uploadTitle}" indexed.` }); setUploadTitle(''); setSelectedFile(null); const fileInput = document.getElementById('device-file-input'); if (fileInput) fileInput.value = ''; fetchDocuments(); fetchUserStatus(); } catch (err) { setUploadStatus({ type: 'error', text: err.message }); } finally { setUploadLoading(false); } }; const handleCopyPasteIngest = async (e) => { e.preventDefault(); if (!uploadTitle.trim() || !copyPasteText.trim()) return; setUploadLoading(true); setUploadStatus({ type: '', text: '' }); const docId = `doc_${uploadTitle.toLowerCase().replace(/[^a-z0-9]/g, '_')}`; try { const res = await fetch(`${API_URL}/api/ingest`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ doc_id: docId, title: uploadTitle, text: copyPasteText }) }); const data = await res.json(); if (!res.ok) throw new Error(data.message || data.error || "Failed to index text."); setUploadStatus({ type: 'success', text: `Success: "${uploadTitle}" indexed.` }); setUploadTitle(''); setCopyPasteText(''); fetchDocuments(); fetchUserStatus(); } catch (err) { setUploadStatus({ type: 'error', text: err.message }); } finally { setUploadLoading(false); } }; const handleViewDocContent = async (docId) => { try { const res = await fetch(`${API_URL}/api/documents/${docId}`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); if (data.text) { setSelectedDocContent(data.text); setViewingDocId(docId); } } catch (err) { alert("Failed to load document text."); } }; const handleUpgrade = async () => { try { const res = await fetch(`${API_URL}/api/create-razorpay-order`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }); const order = await res.json(); if (res.status !== 200 || !order.id) { alert("Failed to create payment order: " + (order.error || "Gateway error")); return; } const options = { key: order.key_id, amount: order.amount, currency: order.currency, name: "VigilantRAG Premium", description: "Premium Plan Upgrade (Unlock Unlimited Document search, factuality guard)", order_id: order.id, handler: async function (response) { try { const verifyRes = await fetch(`${API_URL}/api/verify-razorpay-payment`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ razorpay_order_id: response.razorpay_order_id, razorpay_payment_id: response.razorpay_payment_id, razorpay_signature: response.razorpay_signature, isMock: order.isMock }) }); const verifyData = await verifyRes.json(); if (verifyRes.ok) { alert("🎉 Upgrade successful! Welcome to VigilantRAG Premium."); fetchUserStatus(); } else { alert(`Verification failed: ${verifyData.error}`); } } catch (err) { alert("Payment verification connection error."); } }, prefill: { name: userStatus.name || "Aryan", email: "user@example.com" }, theme: { color: "#8b5cf6" } }; if (order.isMock) { console.log("⚠️ Dev Mode: Initializing custom Razorpay overlay simulation."); setRzpOrderDetails(order); setRzpActiveOptions(options); setRzpPaymentStep('methods'); setShowRzpModal(true); return; } if (window.Razorpay) { const rzp = new window.Razorpay(options); rzp.open(); } else { alert("Razorpay payment SDK failed to load. Please check your internet connection."); } } catch (err) { alert("Failed to initiate payment gateway."); } }; const handleSaveProfile = async (e) => { e.preventDefault(); setProfileSaveLoading(true); setProfileSaveStatus({ type: '', text: '' }); try { const res = await fetch(`${API_URL}/api/user-profile`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ name: profileName, email: profileEmail, phoneNumber: profilePhone, profilePhoto: profilePhoto }) }); const data = await res.json(); if (res.ok) { setProfileSaveStatus({ type: 'success', text: '🎉 Profile changes saved successfully!' }); fetchUserStatus(); } else { setProfileSaveStatus({ type: 'error', text: data.error || 'Failed to update profile details.' }); } } catch (err) { setProfileSaveStatus({ type: 'error', text: 'Gateway server connection failure.' }); } finally { setProfileSaveLoading(false); } }; const handleCancelSubscription = async () => { const confirmCancel = window.confirm("Are you sure you want to cancel your Premium subscription? \n\nThis will downgrade your account to the Free Plan and lower your document limits."); if (!confirmCancel) return; try { const res = await fetch(`${API_URL}/api/cancel-subscription`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { alert("🛡️ Subscription cancelled successfully. Account downgraded to Free."); fetchUserStatus(); } else { alert("Subscription cancellation request failed."); } } catch (err) { alert("Billing server connection error."); } }; if (!token) { return (Access your self-correcting RAG workspace
Sign up to start searching and verifying your documents
No projects found.
)}Ask anything about your document project. The AI will search, analyze, and fact-check its answers automatically.
{msg.text}
} {msg.sender === 'ai' && (No documents found.
)}{selectedDocContent}
Index documents directly from your device, or copy-paste raw text contents. They will be partitioned into semantic chunks and embedded instantly.
Check how the AI analyzed your files and fact-checked its own response to prevent errors.
The time taken by the AI to search your files and construct this answer.
The number of matching paragraphs located in your document library for this search query.
{activeTelemetry.use_reranking === false ? ( "Upgrade to Premium to enable advanced semantic semantic re-ranking check." ) : ( `The scanned paragraphs have a ${Math.round(activeTelemetry.retrieved_attempts?.[0]?.top_score * 100)}% match level to the meaning of your question.` )}
{activeTelemetry.use_nli_guard === false ? ( "Upgrade to Premium to auto-audit response drafts against raw documents." ) : ( activeTelemetry.success ? "Passed! The AI verified that the answer is 100% supported by the text in your files." : "Audit failed! A potential hallucination was blocked, and the AI was prompted to rewrite it." )}
Draft Response: "{attempt.response_draft}"
Run a search query in your Workspace, then click "Inspect AI Logic" to trace the fact-checking process.
Choose Avatar Emoji
{userStatus.isPremium ? 'You have complete access to multi-document search and AI NLI hallucination check guards.' : 'You are on the basic plan. Upload limits are capped at 3 documents.' }
{userStatus.isPremium ? ( ) : ( )}{rzpActiveOptions?.description || "Premium Plan Upgrade"}
Contacting banking networks securely. Please do not close or go back.
Upgrading your account to premium plan. Loading workspace...