const form = document.querySelector("#uploadForm"); const input = document.querySelector("#pdfInput"); const fileName = document.querySelector("#fileName"); const resultText = document.querySelector("#resultText"); const routeValue = document.querySelector("#routeValue"); const pageCount = document.querySelector("#pageCount"); const directText = document.querySelector("#directText"); const pageDetails = document.querySelector("#pageDetails"); const copyButton = document.querySelector("#copyButton"); const submitButton = form.querySelector('button[type="submit"]'); const uploadMessage = document.querySelector("#uploadMessage"); const fileCard = document.querySelector("#fileCard"); const uploadedFileName = document.querySelector("#uploadedFileName"); const uploadedFileMeta = document.querySelector("#uploadedFileMeta"); const pdfPreviewCard = document.querySelector("#pdfPreviewCard"); const pdfPreview = document.querySelector("#pdfPreview"); const docStatus = document.querySelector("#docStatus"); const indexSteps = document.querySelector("#indexSteps"); const activeDocument = document.querySelector("#activeDocument"); const dashboardStatus = document.querySelector("#dashboardStatus"); const chunkComparison = document.querySelector("#chunkComparison"); const bestChunking = document.querySelector("#bestChunking"); const hybridStats = document.querySelector("#hybridStats"); const pipelineViz = document.querySelector("#pipelineViz"); const evaluationCards = document.querySelector("#evaluationCards"); const hallucinationCard = document.querySelector("#hallucinationCard"); const bestRecommendation = document.querySelector("#bestRecommendation"); const cragStatus = document.querySelector("#cragStatus"); const queryForm = document.querySelector("#queryForm"); const queryInput = document.querySelector("#queryInput"); const confidenceBadge = document.querySelector("#confidenceBadge"); const answerBox = document.querySelector("#answerBox"); const citations = document.querySelector("#citations"); const retrievedChunks = document.querySelector("#retrievedChunks"); const queryHistory = document.querySelector("#queryHistory"); const historyCount = document.querySelector("#historyCount"); const feedbackForm = document.querySelector("#feedbackForm"); const feedbackRating = document.querySelector("#feedbackRating"); const feedbackComment = document.querySelector("#feedbackComment"); const unrelatedPdfAnswer = "Sorry, I can't give any answer because your question is not related to the PDF."; const timelineSteps = [ "Text Cleaning", "Metadata Extraction", "Parent Chunks", "Child Chunks", "Embeddings", "Qdrant Index", "BM25 Index", "Ready", ]; const pageTexts = new Map(); const queryHistoryItems = []; let directPages = 0; let ocrPages = 0; let currentDocumentId = ""; let currentFileName = ""; let currentFileSize = 0; let lastQuery = ""; let chunkCounts = {}; let chunkScores = {}; let completedTimeline = new Set(); let currentTimelineStep = ""; let activeRunId = 0; let activeQueryId = 0; let pdfPreviewUrl = ""; let uploadController = null; let indexController = null; let queryController = null; input.addEventListener("change", () => { const file = input.files[0]; fileName.textContent = file?.name || "Choose or drop a PDF"; if (file) { handleNewFileSelected(file); } }); citations.addEventListener("click", (event) => { const button = event.target.closest("[data-page]"); if (!button) { return; } scrollToExtractedPage(button.dataset.page); }); queryHistory.addEventListener("click", (event) => { const button = event.target.closest("button"); if (!button) { return; } queryInput.value = button.textContent.trim(); queryInput.focus(); }); form.addEventListener("dragover", (event) => { event.preventDefault(); form.classList.add("is-dragging"); }); form.addEventListener("dragleave", () => { form.classList.remove("is-dragging"); }); form.addEventListener("drop", (event) => { event.preventDefault(); form.classList.remove("is-dragging"); const file = event.dataTransfer.files[0]; if (file) { input.files = event.dataTransfer.files; fileName.textContent = file.name; handleNewFileSelected(file); } }); form.addEventListener("submit", async (event) => { event.preventDefault(); const file = input.files[0]; if (!file) { setError("Please select a PDF first."); return; } const runId = startUploadRun(file.name); setLoading(); const data = new FormData(); data.append("file", file); try { const response = await fetch("/api/pdf-to-text-stream", { method: "POST", body: data, signal: uploadController.signal, }); if (!isActiveRun(runId)) { return; } if (!response.ok) { throw new Error(await readErrorMessage(response, "PDF processing failed.")); } await readNdjson(response, (event) => handlePdfEvent(event, runId)); } catch (error) { if (error.name !== "AbortError" && isActiveRun(runId)) { setError(error.message); } } finally { if (isActiveRun(runId)) { submitButton.disabled = false; submitButton.textContent = "Upload PDF"; } } }); queryForm.addEventListener("submit", async (event) => { event.preventDefault(); const query = queryInput.value.trim(); const documentId = currentDocumentId; const queryRunId = activeRunId; if (!currentDocumentId) { answerBox.textContent = ""; showUploadMessage("PDF abhi ready nahi hai. Pehle PDF successfully upload/index hone do.", true); return; } if (!query) { answerBox.textContent = "Question likho, phir Ask dabao."; return; } lastQuery = query; answerBox.textContent = "Finding answer from your PDF..."; citations.innerHTML = ""; retrievedChunks.innerHTML = ""; renderConfidenceBadge(); renderHallucinationCard(); activeQueryId += 1; const queryId = activeQueryId; if (queryController) { queryController.abort(); } queryController = new AbortController(); try { const response = await fetch("/api/query", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ document_id: documentId, query }), signal: queryController.signal, }); const payload = await readJsonResponse(response, "Query failed."); if ( !isActiveRun(queryRunId) || queryId !== activeQueryId || documentId !== currentDocumentId || (payload.document_id && payload.document_id !== currentDocumentId) ) { return; } if (!response.ok) { throw new Error(payload.detail || "Query failed."); } renderAnswer(payload); } catch (error) { if (error.name !== "AbortError" && isActiveRun(queryRunId) && queryId === activeQueryId) { answerBox.textContent = error.message; } } }); feedbackForm.addEventListener("submit", async (event) => { event.preventDefault(); if (!currentDocumentId || !lastQuery) { answerBox.textContent = "Feedback ke liye pehle query run karo."; return; } const response = await fetch("/api/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ document_id: currentDocumentId, query: lastQuery, rating: Number(feedbackRating.value), comment: feedbackComment.value, }), }); const payload = await response.json(); answerBox.textContent += `\n\nFeedback saved: ${payload.feedback_id}`; feedbackComment.value = ""; }); copyButton.addEventListener("click", async () => { const text = resultText.textContent; if (!text || text.startsWith("Upload a PDF")) { return; } await navigator.clipboard.writeText(text); copyButton.textContent = "Copied"; setTimeout(() => { copyButton.textContent = "Copy"; }, 1200); }); function startUploadRun(fileNameValue) { activeRunId += 1; activeQueryId += 1; if (uploadController) { uploadController.abort(); } if (indexController) { indexController.abort(); } if (queryController) { queryController.abort(); } uploadController = new AbortController(); indexController = null; queryController = null; currentFileName = fileNameValue; currentFileSize = input.files[0]?.size || 0; currentDocumentId = ""; return activeRunId; } function isActiveRun(runId) { return runId === activeRunId; } function handleNewFileSelected(file) { activeRunId += 1; activeQueryId += 1; if (uploadController) { uploadController.abort(); } if (indexController) { indexController.abort(); } if (queryController) { queryController.abort(); } uploadController = null; indexController = null; queryController = null; currentDocumentId = ""; currentFileName = file.name; currentFileSize = file.size; lastQuery = ""; queryInput.value = ""; pageTexts.clear(); directPages = 0; ocrPages = 0; pageDetails.innerHTML = ""; resultText.textContent = "Upload the selected PDF to see extracted text here."; routeValue.textContent = "Waiting"; pageCount.textContent = "0"; directText.textContent = "-"; completedTimeline = new Set(); currentTimelineStep = ""; activeDocument.textContent = "New PDF selected. Upload to index."; dashboardStatus.textContent = "Upload required"; docStatus.textContent = "Waiting"; answerBox.textContent = "New PDF selected. Click Upload PDF, then ask questions from this file."; citations.innerHTML = ""; retrievedChunks.innerHTML = ""; chunkComparison.innerHTML = ""; bestChunking.textContent = "Evaluation Pending"; chunkCounts = {}; chunkScores = {}; renderIndexTimeline(); resetHybridStats(); resetEvaluationCards(); renderConfidenceBadge(); resetCragStatus(); renderHallucinationCard(); bestRecommendation.textContent = "Index a PDF to compare chunking strategies."; showUploadMessage("New PDF selected. Upload it to replace the active document."); submitButton.disabled = false; submitButton.textContent = "Upload PDF"; renderSelectedFile(file, "Selected"); } function setLoading() { pageTexts.clear(); directPages = 0; ocrPages = 0; currentDocumentId = ""; lastQuery = ""; queryInput.value = ""; feedbackComment.value = ""; completedTimeline = new Set(); currentTimelineStep = "Text Cleaning"; submitButton.disabled = true; submitButton.textContent = "Uploading"; routeValue.textContent = "Processing"; pageCount.textContent = "-"; directText.textContent = "-"; pageDetails.innerHTML = ""; resultText.textContent = ""; docStatus.textContent = "Waiting"; dashboardStatus.textContent = "Waiting for PDF"; renderIndexTimeline(); activeDocument.textContent = currentFileName ? `Indexing: ${currentFileName}` : "Not ready"; answerBox.textContent = "Upload and index the PDF, then ask your question."; renderConfidenceBadge(); showUploadMessage("Reading PDF. Please wait..."); citations.innerHTML = ""; retrievedChunks.innerHTML = ""; chunkComparison.innerHTML = ""; bestChunking.textContent = "Evaluation Pending"; resetHybridStats(); resetEvaluationCards(); resetCragStatus(); renderHallucinationCard(); bestRecommendation.textContent = "Index a PDF to compare chunking strategies."; chunkCounts = {}; chunkScores = {}; if (input.files[0]) { renderSelectedFile(input.files[0], "Processing Upload"); } } function setError(message) { const cleanMessage = String(message || "PDF processing failed."); routeValue.textContent = "Error"; pageCount.textContent = "0"; directText.textContent = "-"; pageDetails.innerHTML = ""; resultText.textContent = cleanMessage; answerBox.textContent = ""; renderConfidenceBadge(); showUploadMessage(cleanUploadError(cleanMessage), true); dashboardStatus.textContent = "Error"; activeDocument.textContent = "Not ready"; submitButton.disabled = false; submitButton.textContent = "Upload PDF"; } async function readNdjson(response, handler) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; const dispatchLine = (line) => { const trimmed = line.trim(); if (!trimmed) { return; } let event; try { event = JSON.parse(trimmed); } catch (error) { throw new Error("Processing stream ended unexpectedly. Please upload the PDF again."); } if (event.type === "error") { throw new Error(event.message || "Processing failed."); } handler(event); }; while (true) { const { value, done } = await reader.read(); if (done) { break; } buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop(); for (const line of lines) { dispatchLine(line); } } buffer += decoder.decode(); dispatchLine(buffer); } async function readJsonResponse(response, fallbackMessage) { const text = await response.text(); if (!text.trim()) { throw new Error(`${fallbackMessage} Empty response from server.`); } try { return JSON.parse(text); } catch (error) { throw new Error(`${fallbackMessage} Server returned an invalid response.`); } } async function readErrorMessage(response, fallbackMessage) { let text = ""; try { text = await response.text(); } catch (error) { return `${fallbackMessage} HTTP ${response.status}.`; } if (!text.trim()) { return `${fallbackMessage} HTTP ${response.status}: empty response from server.`; } try { const payload = JSON.parse(text); return payload.detail || payload.message || `${fallbackMessage} HTTP ${response.status}.`; } catch (error) { return text.slice(0, 500); } } function handlePdfEvent(event, runId) { if (!isActiveRun(runId)) { return; } if (event.type === "start") { pageCount.textContent = `0 / ${event.page_count}`; updateFileCardMeta(event.page_count, "Processing Upload"); directText.textContent = "Checking"; resultText.textContent = "Processing page 1..."; showUploadMessage(`Reading PDF pages: 0 / ${event.page_count}`); return; } if (event.type === "page") { if (String(event.text || "").trim()) { pageTexts.set(event.page_number, event.text || ""); } routeValue.textContent = event.route; pageCount.textContent = `${Math.max(pageTexts.size, event.page_number)} / ${event.page_count}`; updateFileCardMeta(event.page_count, "Processing Upload"); if (event.direct_text_found) { directPages += 1; } else { ocrPages += 1; } directText.textContent = directStatus(); appendPageChip(event); renderPageText(); showUploadMessage(`Reading PDF pages: ${event.page_number} / ${event.page_count}`); dashboardStatus.textContent = "Extracting PDF text"; return; } if (event.type === "done") { routeValue.textContent = "Completed"; pageCount.textContent = `${event.page_count} / ${event.page_count}`; updateFileCardMeta(event.page_count, "Uploaded Successfully"); if (!resultText.textContent.trim()) { resultText.textContent = "No text was extracted."; return; } indexExtractedText(runId).catch((error) => { if (error.name !== "AbortError" && isActiveRun(runId)) { docStatus.textContent = "error"; appendIndexStep("Indexing failed", error.message); showUploadMessage(cleanUploadError(error.message), true); } }); return; } if (event.type === "error") { setError(event.message || "PDF processing failed."); } } async function indexExtractedText(runId) { if (!isActiveRun(runId)) { return; } docStatus.textContent = "processing"; dashboardStatus.textContent = "Indexing"; currentTimelineStep = "Text Cleaning"; renderIndexTimeline(); showUploadMessage("Preparing PDF for questions..."); const fileNameForRun = currentFileName || "uploaded.pdf"; const textForRun = resultText.textContent; indexController = new AbortController(); const response = await fetch("/api/index-text-stream", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ file_name: fileNameForRun, text: textForRun, }), signal: indexController.signal, }); if (!isActiveRun(runId)) { return; } if (!response.ok) { throw new Error(await readErrorMessage(response, "Indexing failed.")); } await readNdjson(response, (event) => handleIndexEvent(event, runId, fileNameForRun)); } function handleIndexEvent(event, runId, fileNameForRun) { if (!isActiveRun(runId)) { return; } if (event.type === "status") { docStatus.textContent = event.status; appendIndexStep(event.step); return; } if (event.type === "step") { appendIndexStep(event.step); captureIndexDetail(event.step, event.detail); return; } if (event.type === "done") { currentDocumentId = event.document_id || ""; docStatus.textContent = "Indexed"; dashboardStatus.textContent = "Ready"; activeDocument.textContent = fileNameForRun; appendIndexStep(event.step); captureIndexSummary(event.summary); answerBox.textContent = "Ask a question from this PDF."; showUploadMessage("PDF ready. Ask your question.", false, true); queryInput.focus(); return; } if (event.type === "error") { docStatus.textContent = "error"; appendIndexStep("Error", event.message); showUploadMessage(cleanUploadError(event.message), true); } } function appendPageChip(page) { const confidence = page.confidence === null || page.confidence === undefined ? "" : ` - ${(page.confidence * 100).toFixed(1)}%`; const chip = document.createElement("div"); chip.className = "page-chip"; chip.dataset.page = String(page.page_number); chip.textContent = `Page ${page.page_number}: ${page.engine}${confidence}`; pageDetails.appendChild(chip); } function appendIndexStep(step) { const labelValue = timelineLabel(step); completedTimeline.add(timelineStepKey(labelValue)); const index = timelineSteps.findIndex((item) => timelineStepKey(item) === timelineStepKey(labelValue)); if (index >= 0) { const next = timelineSteps[index + 1]; currentTimelineStep = next && !completedTimeline.has(timelineStepKey(next)) ? next : ""; } renderIndexTimeline(); } function renderPageText() { const orderedPages = [...pageTexts.keys()].sort((a, b) => a - b); resultText.textContent = orderedPages .filter((pageNumber) => String(pageTexts.get(pageNumber) || "").trim()) .map((pageNumber) => `--- Page ${pageNumber} ---\n${pageTexts.get(pageNumber)}`) .join("\n\n"); } function renderAnswer(payload) { addQueryHistory(payload.query || lastQuery); answerBox.textContent = payload.answer || unrelatedPdfAnswer; dashboardStatus.textContent = payload.status === "answered" ? "Answered" : "No answer"; renderConfidenceBadge(payload.retrieval_grade || payload.status, payload.evaluation || {}); citations.innerHTML = ""; for (const citation of payload.citations || []) { const card = document.createElement("div"); card.className = "citation-card"; const heading = inferHeading(citation.preview); const pageNumber = findPageForText(citation.preview) || citation.page_number || citation.parent_index + 1; card.innerHTML = `
PDF ${escapeHtml(currentFileName || payload.file_name || "Uploaded PDF")}
Heading: ${escapeHtml(heading)} Parent Chunk: ${citation.parent_index + 1}

${escapeHtml(citation.preview)}

`; citations.appendChild(card); } renderHybridStats(payload.retrieval_stats || {}); renderEvaluationCards(payload.evaluation || {}); renderCragStatus(payload.retrieval_grade || payload.status); renderHallucinationCard(payload.evaluation || {}, payload.retrieval_grade || payload.status); renderRetrievedChunks(payload.hits || []); } function directStatus() { if (directPages > 0 && ocrPages > 0) { return "Mixed"; } if (directPages > 0) { return "Yes"; } if (ocrPages > 0) { return "No"; } return "Checking"; } function escapeHtml(value) { return String(value || "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function captureIndexDetail(step, detail) { if (step.includes("Child Chunks") && detail && typeof detail === "object") { chunkCounts = detail; renderChunkComparison(); } if (step === "Chunk Size Optimization" && detail && typeof detail === "object") { chunkScores = detail.scores || {}; bestChunking.textContent = bestPerformerLabel(detail.best_chunking); bestRecommendation.innerHTML = recommendationHtml(detail.best_chunking, detail.reason); renderChunkComparison(); } } function captureIndexSummary(summary) { if (!summary) { return; } if (summary.chunk_strategy_counts) { chunkCounts = summary.chunk_strategy_counts; } const recommendation = summary.chunking_recommendation || {}; chunkScores = recommendation.scores || chunkScores; bestChunking.textContent = bestPerformerLabel(recommendation.best_chunking); bestRecommendation.innerHTML = recommendationHtml(recommendation.best_chunking, recommendation.reason); renderChunkComparison(); } function renderChunkComparison() { const strategies = ["fixed", "recursive", "semantic"]; chunkComparison.innerHTML = `
ChunkingChunksScore
${strategies .map((strategy) => { const winner = bestChunking.textContent.toLowerCase().includes(strategy) ? " winner" : ""; return `
${label(strategy)} ${chunkCounts[strategy] || 0} ${chunkScores[strategy] ?? "-"}
`; }) .join("")} `; } function renderHybridStats(stats) { renderPipelineStats(stats); hybridStats.innerHTML = `
Vector Results${stats.vector_results ?? "-"}
BM25 Results${stats.bm25_results ?? "-"}
Merged${stats.merged ?? "-"}
Final Reranked${stats.final_reranked ?? "-"}
`; } function resetHybridStats() { renderHybridStats({}); } function renderEvaluationCards(metrics) { evaluationCards.innerHTML = `
Context Precision${metricScore(metrics.context_precision)}
Context Recall${metricScore(metrics.context_recall)}
Faithfulness${metricScore(metrics.faithfulness)}
Answer Relevancy${metricScore(metrics.answer_relevancy)}
`; } function resetEvaluationCards() { renderEvaluationCards({}); } function renderRetrievedChunks(hits) { if (!hits.length) { retrievedChunks.innerHTML = '
No retrieved chunks yet.
'; return; } retrievedChunks.innerHTML = hits .map( (hit, index) => `
#${index + 1} ${label(hit.strategy)} Chunk
Similarity: ${score(hit.vector_score)} BM25: ${bm25Score(hit.bm25_raw_score, hit.bm25_score)} Reranker: ${score(hit.rerank_score)}

Chunk Text${escapeHtml(hit.child_text)}

Similarity Score: ${score(hit.vector_score)} BM25 Score: ${bm25Score(hit.bm25_raw_score, hit.bm25_score)} Reranker Score: ${score(hit.rerank_score)}
` ) .join(""); } function score(value) { return value === undefined || value === null ? "-" : Number(value).toFixed(2); } function metricScore(value) { return value === undefined || value === null ? "Not Evaluated Yet" : Number(value).toFixed(2); } function bm25Score(raw, normalized) { if (raw !== undefined && raw !== null) { return Number(raw).toFixed(2); } return score(normalized); } function label(value) { return String(value || "") .replace(/^./, (char) => char.toUpperCase()); } function inferHeading(preview) { const text = String(preview || "").trim(); if (!text) { return "Document context"; } const firstLine = text.split(/\n|\. /)[0].trim(); const words = firstLine.split(/\s+/).slice(0, 5).join(" "); return words || "Document context"; } function timelineLabel(step) { const labels = { "Document Status: processing": "Processing Started", "Text Cleaning": "Text Cleaning", "Metadata": "Metadata Extraction", "Parent Chunks": "Parent Chunks", "Fixed / Recursive / Semantic Child Chunks": "Child Chunks", "Chunk Size Optimization": "Chunk Size Optimization", "Embeddings": "Embeddings", "Embedding Evaluation": "Embedding Evaluation", "Qdrant Store": "Qdrant Index", "BM25 Index": "BM25 Index", "Document Status: indexed": "Ready", }; return labels[step] || step; } function renderCragStatus(grade) { const good = grade === "good" || grade === "answered"; cragStatus.className = `crag-box ${good ? "good" : "bad"}`; cragStatus.innerHTML = good ? "GoodRetrieved context passed CRAG grading." : "Low ConfidenceQuery rewrite or no-answer fallback was used."; } function resetCragStatus() { cragStatus.className = "crag-box neutral"; cragStatus.innerHTML = "WaitingRun a query to grade retrieval quality."; } function renderSelectedFile(file, status) { currentFileName = file.name; currentFileSize = file.size; uploadedFileName.textContent = file.name; uploadedFileMeta.textContent = `0 Pages / ${formatBytes(file.size)}`; fileCard.querySelector("em").textContent = status; fileCard.classList.remove("is-hidden"); if (pdfPreviewUrl) { URL.revokeObjectURL(pdfPreviewUrl); } pdfPreviewUrl = URL.createObjectURL(file); pdfPreview.src = `${pdfPreviewUrl}#page=1&toolbar=0&navpanes=0&scrollbar=0`; pdfPreviewCard.classList.remove("is-hidden"); } function updateFileCardMeta(totalPages, status) { if (!currentFileName) { return; } uploadedFileName.textContent = currentFileName; uploadedFileMeta.textContent = `${totalPages || pageTexts.size || 0} Pages / ${formatBytes(currentFileSize)}`; fileCard.querySelector("em").textContent = status; fileCard.classList.remove("is-hidden"); } function formatBytes(bytes) { if (!bytes) { return "0 MB"; } const mb = bytes / (1024 * 1024); if (mb >= 1) { return `${mb.toFixed(1)} MB`; } return `${Math.max(bytes / 1024, 1).toFixed(0)} KB`; } function renderIndexTimeline() { indexSteps.innerHTML = timelineSteps .map((step) => { const key = timelineStepKey(step); const done = completedTimeline.has(key); const current = !done && currentTimelineStep && timelineStepKey(currentTimelineStep) === key; const state = done ? "done" : current ? "current" : "pending"; const marker = done ? "OK" : current ? "..." : ""; return `
${marker} ${escapeHtml(step)}
`; }) .join(""); } function timelineStepKey(value) { return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, ""); } function bestPerformerLabel(strategy) { return strategy ? `Best Performer: ${label(strategy)} Chunking` : "Evaluation Pending"; } function recommendationHtml(strategy, reason) { const strategyLabel = label(strategy || "semantic"); return ` Recommended Strategy ${strategyLabel} Chunking

Reason:

`; } function renderPipelineStats(stats = {}) { const hasStats = Object.keys(stats).length > 0; const vector = hasStats ? stats.vector_results ?? 0 : "Not Run"; const bm25 = hasStats ? stats.bm25_results ?? 0 : "Not Run"; const merged = hasStats ? stats.merged ?? 0 : "Not Run"; const final = hasStats ? stats.final_reranked ?? 0 : "Not Run"; pipelineViz.innerHTML = `
Query
Vector Search (${vector})
BM25 (${bm25})
Merged (${merged})
Reranked (${final})
Answer
`; } function renderConfidenceBadge(grade, metrics = {}) { if (!grade) { confidenceBadge.className = "confidence-badge neutral"; confidenceBadge.textContent = "Confidence pending"; return; } const faithfulness = Number(metrics.faithfulness || 0); const gradeGood = grade === "good" || grade === "answered"; const level = gradeGood && faithfulness >= 0.9 ? "high" : gradeGood && faithfulness >= 0.75 ? "medium" : "low"; const labelMap = { high: "High Confidence", medium: "Medium Confidence", low: "Low Confidence" }; confidenceBadge.className = `confidence-badge ${level}`; confidenceBadge.textContent = labelMap[level]; } function renderHallucinationCard(metrics = {}, grade) { const faithfulness = metrics.faithfulness; const precision = metrics.context_precision; const coverage = faithfulness === undefined || precision === undefined ? null : Math.round(((faithfulness + precision) / 2) * 100); const good = (grade === "good" || grade === "answered") && Number(faithfulness || 0) >= 0.75; hallucinationCard.className = `hallucination-card ${coverage === null ? "neutral" : good ? "good" : "bad"}`; hallucinationCard.innerHTML = ` Answer Source Coverage ${coverage === null ? "-" : `${coverage}%`} Faithfulness ${score(faithfulness)} ${coverage === null ? "Waiting for answer" : good ? "No Hallucination Detected" : "Needs review"} `; } function addQueryHistory(query) { const clean = String(query || "").trim(); if (!clean) { return; } const existingIndex = queryHistoryItems.findIndex((item) => item.toLowerCase() === clean.toLowerCase()); if (existingIndex >= 0) { queryHistoryItems.splice(existingIndex, 1); } queryHistoryItems.unshift(clean); queryHistoryItems.splice(5); renderQueryHistory(); } function renderQueryHistory() { historyCount.textContent = `${queryHistoryItems.length} saved`; if (!queryHistoryItems.length) { queryHistory.innerHTML = "No questions yet."; return; } queryHistory.innerHTML = queryHistoryItems .map((query) => ``) .join(""); } function findPageForText(preview) { const needle = String(preview || "").replace(/\s+/g, " ").trim().slice(0, 90); if (!needle) { return ""; } for (const [page, text] of pageTexts.entries()) { const haystack = String(text || "").replace(/\s+/g, " "); if (haystack.includes(needle)) { return page; } } return ""; } function scrollToExtractedPage(pageNumber) { const details = document.querySelector(".details-panel details"); details.open = true; document.querySelector(".details-panel").scrollIntoView({ behavior: "smooth", block: "start" }); pageDetails.querySelectorAll(".page-chip").forEach((chip) => { chip.classList.toggle("is-active", chip.dataset.page === String(pageNumber)); }); } function showUploadMessage(message, isError = false, isSuccess = false) { uploadMessage.textContent = message; uploadMessage.className = `upload-message${isError ? " error" : ""}${isSuccess ? " success" : ""}`; } function cleanUploadError(message) { const text = String(message || ""); const keyMatch = text.match(/Tried\s+(\d+)\s+active API key\(s\) out of\s+(\d+)\s+configured key\(s\);\s+(\d+)\s+key\(s\)/i); if (keyMatch) { const [, triedKeys, totalKeys, quotaKeys] = keyMatch; return `Gemini OCR ne ${triedKeys}/${totalKeys} API keys try ki. ${quotaKeys} key(s) quota/rate-limit de rahi hain. Agar yahi message aa raha hai to ya to sab keys exhausted hain, ya server/browser abhi old code use kar raha hai. Server restart aur browser hard refresh ke baad dobara try karo.`; } if (text.toLowerCase().includes("api key not valid") || text.toLowerCase().includes("invalid api key")) { return "Gemini API key valid nahi lag rahi. env file me GEMINI_API_KEY_1 se GEMINI_API_KEY_4 tak valid Google Gemini keys daalo, phir server restart karo."; } if (text.includes("RESOURCE_EXHAUSTED") || text.toLowerCase().includes("quota")) { return "Gemini OCR quota/rate-limit error de raha hai. App next keys try karta hai; agar phir bhi ye aa raha hai to configured keys exhausted/invalid ho sakti hain. Server restart aur hard refresh ke baad dobara try karo."; } if (text.includes("temporarily unavailable") || text.includes("503")) { return "Gemini OCR abhi busy hai. Thodi der baad try karo ya text-based PDF upload karo."; } if (text.toLowerCase().includes("password")) { return "PDF password-protected hai. Unlocked PDF upload karo."; } return text; } renderIndexTimeline(); resetHybridStats(); resetEvaluationCards(); resetCragStatus(); renderHallucinationCard(); renderRetrievedChunks([]); renderQueryHistory();