ilaa-chenjeri-15's picture
Initial commit: Explainable RAG system
2db7cb0
Raw
History Blame Contribute Delete
9.13 kB
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 = `<div class="p3-panel"><div class="panel-title">🔵 MMR Visualization</div><p style="color:#94a3b8;font-size:13px">UMAP unavailable — install umap-learn</p></div>`;
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 = `
<div class="p3-panel">
<div class="panel-title">🔵 MMR Visualization</div>
<!-- Lambda slider -->
<div class="p3-slider-row">
<span class="p3-slider-label">λ = <b id="p3-lambda-val">0.70</b></span>
<input id="p3-lambda" type="range" min="0" max="1" step="0.05" value="0.7" class="p3-slider">
<span class="p3-slider-hint">← diversity &nbsp;&nbsp; relevance →</span>
</div>
<!-- UMAP + side-by-side row -->
<div class="p3-top-row">
<!-- UMAP scatter -->
<div class="p3-scatter-wrap">
<div class="p3-section-label">UMAP Scatter — candidate chunks</div>
<canvas id="p3-umap" width="340" height="280" class="p3-canvas"></canvas>
<div class="p3-scatter-legend">
<span><span class="p3-dot-lg" style="background:#10b981"></span> MMR selected</span>
<span><span class="p3-dot-lg" style="background:#ef4444"></span> Rejected</span>
</div>
<div id="p3-tooltip" class="p3-scatter-tooltip"></div>
</div>
<!-- Side by side: No MMR vs MMR -->
<div class="p3-side-wrap">
<div class="p3-side-col">
<div class="p3-section-label" style="color:#f59e0b">Without MMR (top relevance)</div>
<div id="p3-no-mmr-list" class="p3-chunk-list"></div>
</div>
<div class="p3-side-col">
<div class="p3-section-label" style="color:#10b981">With MMR (λ=<span id="p3-side-lambda">0.70</span>)</div>
<div id="p3-mmr-list" class="p3-chunk-list"></div>
</div>
</div>
</div>
<!-- Similarity matrix -->
<div class="p3-section-label" style="margin-top:18px">Similarity Matrix — top 10 MMR candidates</div>
<div class="p3-matrix-wrap">
<canvas id="p3-simmatrix" class="p3-canvas"></canvas>
</div>
</div>`;
_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 = `
<b>${isSelected ? "✅ Selected" : "❌ Rejected"}</b> — chunk ${hit}<br>
sim: ${(_p3.sims[hit] || 0).toFixed(3)}<br>
<span style="color:#cbd5e1">${escP3(_p3.previews[hit] || "")}</span>`;
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 }) => `
<div class="p3-side-item">
<div class="p3-side-sim">${sim.toFixed(3)}</div>
<div class="p3-side-text">${escP3(preview)}…</div>
</div>`)
.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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}