let _p3 = { coords: null, // [[x,y], ...] UMAP coords, local index sims: null, // [float, ...] query-doc similarities previews: null, // [str, ...] chunk previews docIndices: null, // [int, ...] global doc indices mmrSelected: null, // set of global doc indices — current MMR selection noMmrSelected: null, // set — pure relevance top-10 lambda: 0.7, sliderTimeout: null, }; function renderMMRPanel(mmrData) { const container = document.getElementById("mmr-panel"); if (!mmrData || !mmrData.umap_coords) { container.innerHTML = `
🔵 MMR Visualization

UMAP unavailable — install umap-learn

`; return; } _p3.coords = mmrData.umap_coords; _p3.sims = mmrData.sims; _p3.previews = mmrData.doc_previews; _p3.docIndices = mmrData.doc_indices; _p3.mmrSelected = new Set(mmrData.mmr_selected); _p3.noMmrSelected = new Set(mmrData.no_mmr_selected); _p3.lambda = 0.7; container.innerHTML = `
🔵 MMR Visualization
λ = 0.70 ← diversity    relevance →
MMR selected Rejected
Similarity Matrix — top 10 MMR candidates
`; _drawAll(); _drawSideBySide(); _drawSimMatrix(mmrData.sim_matrix); document.getElementById("p3-lambda").addEventListener("input", function () { _p3.lambda = parseFloat(this.value); document.getElementById("p3-lambda-val").textContent = _p3.lambda.toFixed(2); document.getElementById("p3-side-lambda").textContent = _p3.lambda.toFixed(2); clearTimeout(_p3.sliderTimeout); _p3.sliderTimeout = setTimeout(_rerrunMMR, 300); }); } function _drawAll() { const canvas = document.getElementById("p3-umap"); if (!canvas) return; const ctx = canvas.getContext("2d"); const W = canvas.width, H = canvas.height; const PAD = 24; ctx.clearRect(0, 0, W, H); _p3.coords.forEach(([x, y], i) => { const cx = PAD + x * (W - PAD * 2); const cy = PAD + y * (H - PAD * 2); const isSelected = _p3.mmrSelected.has(_p3.docIndices[i]); const sim = _p3.sims[i] || 0; if (isSelected) { ctx.beginPath(); ctx.arc(cx, cy, 11, 0, Math.PI * 2); ctx.fillStyle = "rgba(16,185,129,0.18)"; ctx.fill(); } ctx.beginPath(); ctx.arc(cx, cy, isSelected ? 8 : 5, 0, Math.PI * 2); ctx.fillStyle = isSelected ? "#10b981" : "#ef4444"; ctx.globalAlpha = isSelected ? 1 : 0.55 + sim * 0.45; ctx.fill(); ctx.globalAlpha = 1; if (isSelected) { ctx.font = "bold 9px Arial"; ctx.fillStyle = "#065f46"; ctx.fillText(i, cx + 10, cy + 4); } }); canvas.onmousemove = (e) => { const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; const PAD = 24; let hit = null; _p3.coords.forEach(([x, y], i) => { const cx = PAD + x * (W - PAD * 2); const cy = PAD + y * (H - PAD * 2); if (Math.hypot(mx - cx, my - cy) < 10) hit = i; }); const tip = document.getElementById("p3-tooltip"); if (hit !== null) { const isSelected = _p3.mmrSelected.has(_p3.docIndices[hit]); tip.innerHTML = ` ${isSelected ? "✅ Selected" : "❌ Rejected"} — chunk ${hit}
sim: ${(_p3.sims[hit] || 0).toFixed(3)}
${escP3(_p3.previews[hit] || "")}`; tip.style.display = "block"; tip.style.left = (mx + 12) + "px"; tip.style.top = (my + 12) + "px"; } else { tip.style.display = "none"; } }; canvas.onmouseleave = () => { const tip = document.getElementById("p3-tooltip"); if (tip) tip.style.display = "none"; }; } function _drawSideBySide() { const noMmrEl = document.getElementById("p3-no-mmr-list"); const mmrEl = document.getElementById("p3-mmr-list"); if (!noMmrEl || !mmrEl) return; function makeList(selectedSet) { return _p3.docIndices .map((docIdx, i) => ({ docIdx, i, sim: _p3.sims[i] || 0, preview: _p3.previews[i] || "" })) .filter(({ docIdx }) => selectedSet.has(docIdx)) .sort((a, b) => b.sim - a.sim) .map(({ i, sim, preview }) => `
${sim.toFixed(3)}
${escP3(preview)}…
`) .join(""); } noMmrEl.innerHTML = makeList(_p3.noMmrSelected); mmrEl.innerHTML = makeList(_p3.mmrSelected); } function _drawSimMatrix(simMatrix) { if (!simMatrix) return; const localSelected = _p3.docIndices .map((d, i) => ({ d, i })) .filter(({ d }) => _p3.mmrSelected.has(d)) .map(({ i }) => i) .slice(0, 10); const N = localSelected.length; const CELL = 32; const canvas = document.getElementById("p3-simmatrix"); if (!canvas || N === 0) return; canvas.width = N * CELL; canvas.height = N * CELL; const ctx = canvas.getContext("2d"); for (let r = 0; r < N; r++) { for (let c = 0; c < N; c++) { const ri = localSelected[r]; const ci = localSelected[c]; const val = (simMatrix[ri] && simMatrix[ri][ci] != null) ? simMatrix[ri][ci] : 0; ctx.fillStyle = simHeatColor(val); ctx.fillRect(c * CELL, r * CELL, CELL, CELL); ctx.font = "9px Arial"; ctx.fillStyle = val > 0.6 ? "#fff" : "#334155"; ctx.textAlign = "center"; ctx.fillText(val.toFixed(2), c * CELL + CELL / 2, r * CELL + CELL / 2 + 3); } } ctx.font = "bold 9px Arial"; ctx.fillStyle = "#64748b"; ctx.textAlign = "center"; for (let i = 0; i < N; i++) { ctx.fillText(localSelected[i], i * CELL + CELL / 2, N * CELL + 12); ctx.fillText(localSelected[i], -6, i * CELL + CELL / 2 + 3); } } function simHeatColor(v) { const t = Math.max(0, Math.min(1, v)); const r = Math.round(255 - t * (255 - 99)); const g = Math.round(255 - t * (255 - 102)); const b = Math.round(255 - t * (255 - 241)); return `rgb(${r},${g},${b})`; } async function _rerrunMMR() { const res = await fetch("/mmr_rerun", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ lambda: _p3.lambda }) }); const data = await res.json(); if (data.error) { console.warn("MMR rerun:", data.error); return; } _p3.mmrSelected = new Set(data.selected_indices); _drawAll(); _drawSideBySide(); } function escP3(s) { return String(s).replace(/&/g, "&").replace(//g, ">"); }