// Store full texts in a module-level array — avoids HTML attribute escaping bugs let _p2FullTexts = []; function scoreBar(val, color) { const pct = (val * 100).toFixed(0); return `
${val.toFixed(3)}
`; } function rankArrow(delta) { if (delta > 0) return `▲${delta}`; if (delta < 0) return `▼${Math.abs(delta)}`; return ``; } function rowColor(rerank, passed) { if (!passed) return "rgba(239,68,68,0.06)"; if (rerank >= 0.7) return "rgba(16,185,129,0.08)"; if (rerank >= 0.4) return "rgba(245,158,11,0.07)"; return "rgba(99,102,241,0.05)"; } function borderColor(rerank, passed) { if (!passed) return "#fca5a5"; if (rerank >= 0.7) return "#6ee7b7"; if (rerank >= 0.4) return "#fcd34d"; return "#c7d2fe"; } function escHtml(s) { return String(s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function renderRetrievalTable(rows) { const container = document.getElementById("retrieval-panel"); if (!rows || !rows.length) { container.innerHTML = ""; return; } _p2FullTexts = rows.map(r => r.text_full || ""); const topRow = [...rows].sort((a, b) => b.rerank_score - a.rerank_score)[0]; const tableRows = rows.map((r, i) => { const isTop = r.idx === topRow.idx; const bg = rowColor(r.rerank_score, r.passed_threshold); const border = borderColor(r.rerank_score, r.passed_threshold); const dropped = !r.passed_threshold ? `DROPPED` : ""; const winner = isTop ? `★ top` : ""; const preview = escHtml((r.text_preview || "").trim()); return ` #${r.pre_rank + 1} #${r.post_rank + 1} ${winner}${dropped}
${preview}
${scoreBar(r.vector_score, "#0ea5e9")} ${scoreBar(r.bm25_score, "#10b981")} ${scoreBar(r.hybrid_score, "#f59e0b")} ${scoreBar(r.rerank_score, r.passed_threshold ? "#6366f1" : "#ef4444")} ${rankArrow(r.rank_delta)} `; }).join(""); container.innerHTML = `
📊 Retrieval Comparison
Vector BM25 Hybrid Reranker | passed dropped
${tableRows}
Original Rank Rerank Chunk Vector BM25 Hybrid Reranker Δ
`; container.querySelectorAll(".p2-preview-cell").forEach(cell => { const tooltip = cell.querySelector(".p2-tooltip"); const idx = parseInt(cell.dataset.idx); tooltip.textContent = _p2FullTexts[idx] || ""; cell.addEventListener("mouseenter", () => tooltip.classList.add("visible")); cell.addEventListener("mouseleave", () => tooltip.classList.remove("visible")); cell.addEventListener("mousemove", e => { const spaceBelow = window.innerHeight - e.clientY; tooltip.style.left = "0px"; if (spaceBelow < 220) { tooltip.style.bottom = "100%"; tooltip.style.top = "auto"; } else { tooltip.style.top = "100%"; tooltip.style.bottom = "auto"; } }); }); }