const form = document.querySelector("#question-form");
const submitButton = document.querySelector("#submit-button");
const statusChip = document.querySelector("#status-chip");
const statusDetail = document.querySelector("#status-detail");
const authSummary = document.querySelector("#auth-summary");
const loginLink = document.querySelector("#login-link");
const logoutLink = document.querySelector("#logout-link");
const recentSessions = document.querySelector("#recent-sessions");
const answerOutput = document.querySelector("#answer-output");
const sessionIdOutput = document.querySelector("#session-id");
const sentenceList = document.querySelector("#sentence-list");
const traceCount = document.querySelector("#trace-count");
const heatmap = document.querySelector("#heatmap");
const selectionDetails = document.querySelector("#selection-details");
const metrics = document.querySelector("#metrics");
const topEdges = document.querySelector("#top-edges");
const exportJson = document.querySelector("#export-json");
const exportCsv = document.querySelector("#export-csv");
let activeSessionId = null;
let pollHandle = null;
let activePayload = null;
let selectedSentenceIdx = null;
let selectedCell = null;
let currentUser = null;
function currentSentences() {
return activePayload?.analysis?.sentences || activePayload?.session?.sentences || [];
}
function setStatus(label, detail) {
statusChip.textContent = label;
statusDetail.textContent = detail;
}
function clearSelection() {
selectedSentenceIdx = null;
selectedCell = null;
selectionDetails.textContent = "Choose a sentence or heatmap cell.";
metrics.innerHTML = "";
}
function setExportLinks(sessionId, enabled) {
exportJson.href = enabled ? `/api/sessions/${sessionId}/export.json` : "#";
exportCsv.href = enabled ? `/api/sessions/${sessionId}/export.csv` : "#";
exportJson.classList.toggle("is-disabled", !enabled);
exportCsv.classList.toggle("is-disabled", !enabled);
exportJson.setAttribute("aria-disabled", String(!enabled));
exportCsv.setAttribute("aria-disabled", String(!enabled));
}
function renderRecentSessions(sessions) {
recentSessions.innerHTML = "";
if (!currentUser?.authenticated) {
recentSessions.textContent = currentUser?.auth_required
? "Sign in to view your jobs."
: "Anonymous mode is enabled.";
return;
}
if (!sessions.length) {
recentSessions.textContent = "No jobs yet on this running Space instance.";
return;
}
sessions.forEach((session) => {
const item = document.createElement("button");
item.type = "button";
item.className = "session-card";
if (session.id === activeSessionId) {
item.classList.add("is-active");
}
item.innerHTML = `${session.status}${session.question}`;
item.addEventListener("click", () => {
startPolling(session.id);
});
recentSessions.appendChild(item);
});
}
async function loadRecentSessions() {
if (!currentUser?.authenticated && currentUser?.auth_required) {
renderRecentSessions([]);
return;
}
const response = await fetch("/api/sessions?limit=8");
if (!response.ok) {
renderRecentSessions([]);
return;
}
renderRecentSessions(await response.json());
}
function renderAuth() {
if (!currentUser) {
authSummary.textContent = "Checking sign-in status.";
return;
}
loginLink.hidden = currentUser.authenticated;
logoutLink.hidden = !currentUser.authenticated;
if (currentUser.authenticated) {
authSummary.textContent = `Signed in as ${currentUser.full_name || currentUser.username}.`;
return;
}
authSummary.textContent = currentUser.auth_required
? "Sign in with Hugging Face to run analysis jobs."
: "Anonymous access is enabled for this Space.";
}
function renderSession(session) {
sessionIdOutput.textContent = session.id ? `Session ${session.id.slice(0, 8)}` : "";
answerOutput.textContent = session.answer || "Waiting for generated answer.";
const sentences = currentSentences();
traceCount.textContent = sentences.length ? `${sentences.length} sentences` : "";
sentenceList.innerHTML = "";
sentences.forEach((sentence, index) => {
const item = document.createElement("li");
item.className = "sentence-item";
item.innerHTML = `${index} ${sentence}`;
item.addEventListener("click", () => {
selectedSentenceIdx = index;
selectedCell = null;
renderSelection();
});
sentenceList.appendChild(item);
});
}
function colorForValue(value, maxAbs) {
if (!maxAbs) {
return "rgba(224, 223, 218, 0.8)";
}
const normalized = Math.max(-1, Math.min(1, value / maxAbs));
if (normalized >= 0) {
const alpha = 0.12 + normalized * 0.88;
return `rgba(14, 90, 138, ${alpha.toFixed(3)})`;
}
const alpha = 0.12 + Math.abs(normalized) * 0.88;
return `rgba(215, 106, 52, ${alpha.toFixed(3)})`;
}
function renderHeatmap(result) {
const matrix = result?.suppression_matrix;
if (!matrix || !matrix.length) {
heatmap.className = "heatmap placeholder-box";
heatmap.textContent = "Analysis pending.";
return;
}
const flatValues = matrix.flat();
const maxAbs = Math.max(...flatValues.map((value) => Math.abs(value)));
heatmap.className = "heatmap";
heatmap.innerHTML = "";
const grid = document.createElement("div");
grid.className = "heatmap-grid";
grid.style.gridTemplateColumns = `repeat(${matrix.length}, 32px)`;
matrix.forEach((row, rowIndex) => {
row.forEach((value, colIndex) => {
const cell = document.createElement("button");
cell.type = "button";
cell.className = "heatmap-cell";
cell.style.background = colorForValue(value, maxAbs);
cell.title = `target ${rowIndex} ← source ${colIndex}: ${value.toFixed(4)}`;
if (selectedCell && selectedCell.row === rowIndex && selectedCell.col === colIndex) {
cell.classList.add("is-selected");
}
cell.addEventListener("click", () => {
selectedSentenceIdx = null;
selectedCell = { row: rowIndex, col: colIndex, value };
renderSelection();
});
grid.appendChild(cell);
});
});
heatmap.appendChild(grid);
}
function renderTopEdges(result) {
topEdges.innerHTML = "";
const edges = result?.top_edges || [];
if (!edges.length) {
return;
}
edges.slice(0, 5).forEach((edge) => {
const item = document.createElement("div");
item.className = "edge-card";
item.innerHTML = `${edge.source_sentence_idx} → ${edge.target_sentence_idx}${edge.score.toFixed(4)}`;
topEdges.appendChild(item);
});
}
function renderSelection() {
Array.from(sentenceList.children).forEach((item, index) => {
item.classList.toggle("is-active", selectedSentenceIdx === index);
});
const result = activePayload?.analysis;
const session = activePayload?.session;
if (!session) {
clearSelection();
return;
}
metrics.innerHTML = "";
if (selectedSentenceIdx != null && result) {
const outgoing = result.outgoing_importance[selectedSentenceIdx] ?? 0;
const incoming = result.incoming_importance[selectedSentenceIdx] ?? 0;
selectionDetails.innerHTML = `Sentence ${selectedSentenceIdx}
${currentSentences()[selectedSentenceIdx] || ""}`;
metrics.innerHTML = `