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' ? (
<>
{runMeta.cacheStatus ? (
Cache {runMeta.cacheStatus}
{runMeta.cacheEntries ? ` | ${runMeta.cacheEntries} entr${runMeta.cacheEntries === '1' ? 'y' : 'ies'}` : ''}
) : (
Ready for a demo run
)}
{requestError ? {requestError}
: null}
{isSubmitting ? (
Running document query...
) : null}
{!isSubmitting && !requestError && answers.length === 0 ? (
Submit a PDF URL or upload a PDF to see grounded answers with source excerpts.
) : null}
{answers.map((answer, index) => (
Question
{answer.question}
{ANSWER_STATUS_LABELS[answer.status] || answer.status || 'Unknown'}
{answer.claim_verifications?.length ? (
Claim Verification
{answer.claim_verifications.length}
{answer.claim_verifications.map((verification, verificationIndex) => (
{verification.claim}
{CLAIM_VERDICT_LABELS[verification.verdict] || verification.verdict || 'Unknown'}
{verification.rationale}
Supporting Sources
{verification.sources?.length || 0}
{verification.sources?.length ? (
{verification.sources.map((source, sourceIndex) => (
Page {source.page}
Chunk {source.chunk_id}
{source.excerpt}
))}
) : (
No supporting sources were selected for this claim.
)}
))}
) : null}
Sources
{answer.sources?.length || 0}
{answer.sources?.length ? (
{answer.sources.map((source, sourceIndex) => (
Page {source.page}
Chunk {source.chunk_id}
{source.excerpt}
))}
) : (
No source excerpts were returned for this answer.
)}
))}
>
) : (
History
{selectedHistoryDocument ? 'Document queries' : 'Documents'}
{selectedHistoryDocument ? (
) : null}
{selectedHistoryDocument ? (
Selected document
{formatDocumentTitle(selectedHistoryDocument)}
{selectedHistoryDocument.source_type} | {selectedHistoryDocument.chunk_count} chunks |{' '}
{selectedHistoryDocument.query_count} queries
) : null}
{historyError ? {historyError}
: null}
{historyLoading ? (
Loading history...
) : null}
{!historyLoading && !historyError && !selectedHistoryDocument && historyDocuments.length === 0 ? (
No persisted documents found for the current user.
) : null}
{!historyLoading && !historyError && selectedHistoryDocument && historyQueries.length === 0 ? (
No persisted queries found for this document.
) : null}
{!selectedHistoryDocument ? (
{historyDocuments.map((document) => (
))}
) : (
{historyQueries.map((query) => (
Question
{query.question}
{query.is_abstained ? 'Abstained' : ANSWER_STATUS_LABELS[query.status] || query.status || 'Unknown'}
{query.answer}
{formatDateTime(query.created_at)}
{Number.isFinite(query.latency_ms) ? ` | ${Math.round(query.latency_ms)} ms` : ''}
))}
)}
)}
);
}
export default App;