File size: 5,595 Bytes
2db7cb0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | // 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 `
<div class="p2-score-wrap" title="${val.toFixed(4)}">
<div class="p2-bar-bg">
<div class="p2-bar-fill" style="width:${pct}%;background:${color}"></div>
</div>
<span class="p2-score-num">${val.toFixed(3)}</span>
</div>`;
}
function rankArrow(delta) {
if (delta > 0) return `<span class="p2-arrow up" title="Moved up ${delta}">▲${delta}</span>`;
if (delta < 0) return `<span class="p2-arrow down" title="Moved down ${Math.abs(delta)}">▼${Math.abs(delta)}</span>`;
return `<span class="p2-arrow flat" title="No change">—</span>`;
}
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, """)
.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 ? `<span class="p2-dropped">DROPPED</span>` : "";
const winner = isTop ? `<span class="p2-winner" title="Highest combined rerank score">★ top</span>` : "";
const preview = escHtml((r.text_preview || "").trim());
return `
<tr class="p2-row" style="background:${bg};border-left:3px solid ${border}">
<td class="p2-td p2-rank">#${r.pre_rank + 1}</td>
<td class="p2-td p2-rank">#${r.post_rank + 1} ${winner}${dropped}</td>
<td class="p2-td p2-preview-cell" data-idx="${i}">
<div class="p2-preview">${preview}</div>
<div class="p2-tooltip"></div>
</td>
<td class="p2-td">${scoreBar(r.vector_score, "#0ea5e9")}</td>
<td class="p2-td">${scoreBar(r.bm25_score, "#10b981")}</td>
<td class="p2-td">${scoreBar(r.hybrid_score, "#f59e0b")}</td>
<td class="p2-td">${scoreBar(r.rerank_score, r.passed_threshold ? "#6366f1" : "#ef4444")}</td>
<td class="p2-td p2-delta">${rankArrow(r.rank_delta)}</td>
</tr>`;
}).join("");
container.innerHTML = `
<div class="p2-panel">
<div class="panel-title">📊 Retrieval Comparison</div>
<div class="p2-legend">
<span><span class="p2-dot" style="background:#0ea5e9"></span>Vector</span>
<span><span class="p2-dot" style="background:#10b981"></span>BM25</span>
<span><span class="p2-dot" style="background:#f59e0b"></span>Hybrid</span>
<span><span class="p2-dot" style="background:#6366f1"></span>Reranker</span>
<span class="p2-legend-sep">|</span>
<span><span class="p2-dot" style="background:#6ee7b7;border:1px solid #10b981"></span>passed</span>
<span><span class="p2-dot" style="background:#fca5a5;border:1px solid #ef4444"></span>dropped</span>
</div>
<div class="p2-table-wrap">
<table class="p2-table">
<thead>
<tr>
<th class="p2-th">Original Rank</th>
<th class="p2-th">Rerank</th>
<th class="p2-th">Chunk</th>
<th class="p2-th">Vector</th>
<th class="p2-th">BM25</th>
<th class="p2-th">Hybrid</th>
<th class="p2-th">Reranker</th>
<th class="p2-th">Δ</th>
</tr>
</thead>
<tbody>${tableRows}</tbody>
</table>
</div>
</div>`;
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";
}
});
});
} |