import { useEffect, useRef, useState } from 'react'; import './App.css'; const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || '').trim().replace(/\/$/, ''); const API_BASE_LABEL = API_BASE_URL || window.location.origin; function apiUrl(path) { return `${API_BASE_URL}${path}`; } const MODE_OPTIONS = [ { value: 'url', label: 'PDF URL' }, { value: 'upload', label: 'Upload PDF' }, ]; const VIEW_OPTIONS = [ { value: 'query', label: 'Query' }, { value: 'history', label: 'History' }, ]; const ANSWER_STATUS_LABELS = { ok: 'OK', no_context: 'No Context', error: 'Error', }; const CLAIM_VERDICT_LABELS = { supported: 'Supported', weakly_supported: 'Weakly Supported', unsupported: 'Unsupported', }; function parseQuestions(text) { return text .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean); } async function readResponseBody(response) { const contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { return response.json(); } const text = await response.text(); if (!text) { return null; } try { return JSON.parse(text); } catch { return text; } } function getErrorMessage(payload, fallbackMessage) { if (!payload) { return fallbackMessage; } if (typeof payload === 'string') { return payload; } if (typeof payload.detail === 'string') { return payload.detail; } if (Array.isArray(payload.detail)) { return payload.detail .map((item) => { if (typeof item === 'string') { return item; } if (item && typeof item.msg === 'string') { return item.msg; } return null; }) .filter(Boolean) .join(' '); } if (typeof payload.message === 'string') { return payload.message; } return fallbackMessage; } function formatFileSize(bytes) { if (!Number.isFinite(bytes) || bytes <= 0) { return ''; } if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(1)} KB`; } return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; } function formatDateTime(value) { if (!value) { return 'Unknown date'; } const date = new Date(value); if (Number.isNaN(date.getTime())) { return 'Unknown date'; } return date.toLocaleString(); } function formatDocumentTitle(document) { return document.filename || document.source_url || `Document ${document.id.slice(0, 8)}`; } function App() { const fileInputRef = useRef(null); const [activeView, setActiveView] = useState('query'); const [mode, setMode] = useState('url'); const [documentUrl, setDocumentUrl] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const [questionsText, setQuestionsText] = useState(''); const [token, setToken] = useState(''); const [showToken, setShowToken] = useState(false); const [answers, setAnswers] = useState([]); const [formError, setFormError] = useState(''); const [requestError, setRequestError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [runMeta, setRunMeta] = useState({ cacheStatus: '', cacheEntries: '' }); const [historyDocuments, setHistoryDocuments] = useState([]); const [historyQueries, setHistoryQueries] = useState([]); const [selectedHistoryDocument, setSelectedHistoryDocument] = useState(null); const [historyLoading, setHistoryLoading] = useState(false); const [historyError, setHistoryError] = useState(''); const [health, setHealth] = useState({ loading: true, error: '', data: null, }); const questions = parseQuestions(questionsText); async function refreshHealth() { setHealth((current) => ({ ...current, loading: true, error: '', })); try { const response = await fetch(apiUrl('/health')); const payload = await readResponseBody(response); if (!response.ok) { throw new Error(getErrorMessage(payload, `Health check failed with status ${response.status}.`)); } setHealth({ loading: false, error: '', data: payload, }); } catch (error) { setHealth({ loading: false, error: error instanceof Error ? error.message : 'Unable to reach the backend.', data: null, }); } } useEffect(() => { void refreshHealth(); }, []); function getAuthHeader() { return { Authorization: `Bearer ${token.trim()}` }; } function validateHistoryAccess() { if (!token.trim()) { setHistoryError('API token is required.'); return false; } return true; } async function loadHistoryDocuments() { if (!validateHistoryAccess()) { return; } setHistoryLoading(true); setHistoryError(''); setSelectedHistoryDocument(null); setHistoryQueries([]); try { const response = await fetch(apiUrl('/history/documents'), { headers: getAuthHeader(), }); const payload = await readResponseBody(response); if (!response.ok) { throw new Error(getErrorMessage(payload, `History request failed with status ${response.status}.`)); } setHistoryDocuments(Array.isArray(payload?.documents) ? payload.documents : []); } catch (error) { setHistoryError(error instanceof Error ? error.message : 'Unable to load history.'); } finally { setHistoryLoading(false); } } async function loadHistoryQueries(document) { if (!validateHistoryAccess()) { return; } setHistoryLoading(true); setHistoryError(''); setSelectedHistoryDocument(document); setHistoryQueries([]); try { const response = await fetch(apiUrl(`/history/documents/${encodeURIComponent(document.id)}/queries`), { headers: getAuthHeader(), }); const payload = await readResponseBody(response); if (!response.ok) { throw new Error(getErrorMessage(payload, `History request failed with status ${response.status}.`)); } setHistoryQueries(Array.isArray(payload?.queries) ? payload.queries : []); } catch (error) { setHistoryError(error instanceof Error ? error.message : 'Unable to load query history.'); } finally { setHistoryLoading(false); } } function handleViewChange(nextView) { setActiveView(nextView); setHistoryError(''); if (nextView === 'history' && historyDocuments.length === 0 && token.trim()) { void loadHistoryDocuments(); } } function handleModeChange(nextMode) { setMode(nextMode); setFormError(''); setRequestError(''); } function handleFileChange(event) { const nextFile = event.target.files?.[0] || null; setFormError(''); if (!nextFile) { setSelectedFile(null); return; } const isPdf = nextFile.type === 'application/pdf' || nextFile.name.toLowerCase().endsWith('.pdf'); if (!isPdf) { setSelectedFile(null); setFormError('Please choose a PDF file.'); event.target.value = ''; return; } setSelectedFile(nextFile); } function handleReset() { setMode('url'); setDocumentUrl(''); setSelectedFile(null); setQuestionsText(''); setToken(''); setShowToken(false); setAnswers([]); setFormError(''); setRequestError(''); setRunMeta({ cacheStatus: '', cacheEntries: '' }); setHistoryDocuments([]); setHistoryQueries([]); setSelectedHistoryDocument(null); setHistoryError(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } } function validateForm() { if (!token.trim()) { return 'API token is required.'; } if (mode === 'url' && !documentUrl.trim()) { return 'PDF URL is required.'; } if (mode === 'upload' && !selectedFile) { return 'Please choose a PDF file to upload.'; } if (questions.length === 0) { return 'Enter at least one question.'; } return ''; } async function handleSubmit(event) { event.preventDefault(); const validationMessage = validateForm(); if (validationMessage) { setFormError(validationMessage); return; } setFormError(''); setRequestError(''); setAnswers([]); setRunMeta({ cacheStatus: '', cacheEntries: '' }); setIsSubmitting(true); try { const authHeader = getAuthHeader(); let response; if (mode === 'url') { response = await fetch(apiUrl('/hackrx/run'), { method: 'POST', headers: { ...authHeader, 'Content-Type': 'application/json', }, body: JSON.stringify({ documents: documentUrl.trim(), questions, }), }); } else { const formData = new FormData(); formData.append('file', selectedFile); formData.append('questions_json', JSON.stringify(questions)); response = await fetch(apiUrl('/hackrx/upload-run'), { method: 'POST', headers: authHeader, body: formData, }); } const payload = await readResponseBody(response); if (!response.ok) { throw new Error(getErrorMessage(payload, `Request failed with status ${response.status}.`)); } setAnswers(Array.isArray(payload?.answers) ? payload.answers : []); setRunMeta({ cacheStatus: response.headers.get('X-Document-Cache') || '', cacheEntries: response.headers.get('X-Cache-Entries') || '', }); await refreshHealth(); } catch (error) { setRequestError(error instanceof Error ? error.message : 'Request failed.'); } finally { setIsSubmitting(false); } } const healthStatusClass = health.loading ? 'checking' : health.data?.status === 'healthy' ? 'healthy' : 'unhealthy'; const healthStatusLabel = health.loading ? 'Checking' : health.data?.status === 'healthy' ? 'Healthy' : 'Unhealthy'; return (

Intelligent Document Query Engine

Ask questions over PDF documents

Ask questions over PDF documents using retrieval-augmented generation.

{VIEW_OPTIONS.map((option) => ( ))}
{activeView === 'query' ? ( <>

Query setup

Document input

{questions.length} question{questions.length === 1 ? '' : 's'}
{MODE_OPTIONS.map((option) => ( ))}
{mode === 'url' ? ( ) : ( )}