cot-anc / app /frontend /app.js
BART-ender's picture
Deploy Thought Anchors
fda8fb3 verified
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 = `<strong>${session.status}</strong>${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 = `<strong>${index}</strong> ${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 = `<strong>${edge.source_sentence_idx}${edge.target_sentence_idx}</strong>${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 = `<strong>Sentence ${selectedSentenceIdx}</strong><br>${currentSentences()[selectedSentenceIdx] || ""}`;
metrics.innerHTML = `
<div class="metric-card"><strong>Outgoing impact</strong>${outgoing.toFixed(4)}</div>
<div class="metric-card"><strong>Incoming dependence</strong>${incoming.toFixed(4)}</div>
`;
return;
}
if (selectedCell) {
selectionDetails.innerHTML = `<strong>Edge ${selectedCell.col}${selectedCell.row}</strong><br>Influence score ${selectedCell.value.toFixed(4)}`;
metrics.innerHTML = `
<div class="metric-card"><strong>Source sentence</strong>${selectedCell.col}</div>
<div class="metric-card"><strong>Target sentence</strong>${selectedCell.row}</div>
`;
return;
}
selectionDetails.textContent = "Choose a sentence or heatmap cell.";
}
async function fetchSession(sessionId) {
const response = await fetch(`/api/sessions/${sessionId}/result`);
if (!response.ok) {
throw new Error(`Failed to fetch session ${sessionId}`);
}
return response.json();
}
function updateFromPayload(payload) {
activePayload = payload;
activeSessionId = payload.session.id;
renderSession(payload.session);
renderHeatmap(payload.analysis);
renderTopEdges(payload.analysis);
renderSelection();
setExportLinks(payload.session.id, payload.session.status === "completed");
const { status, error } = payload.session;
if (status === "queued") setStatus("Queued", "Waiting for a worker slot.");
if (status === "generating") setStatus("Generating", "The model is producing an answer and visible trace.");
if (status === "answer_ready") setStatus("Analysis pending", "Answer is ready. Attribution analysis is starting.");
if (status === "analyzing") setStatus("Analyzing", "Running forward and backward passes for sentence influence.");
if (status === "completed") setStatus("Completed", "Analysis finished.");
if (status === "failed") setStatus("Failed", error || "The session failed.");
if (["completed", "failed"].includes(status) && pollHandle) {
window.clearInterval(pollHandle);
pollHandle = null;
}
loadRecentSessions().catch(() => {});
}
async function startPolling(sessionId) {
activeSessionId = sessionId;
if (pollHandle) {
window.clearInterval(pollHandle);
}
const tick = async () => {
try {
const payload = await fetchSession(sessionId);
updateFromPayload(payload);
} catch (error) {
setStatus("Error", String(error));
window.clearInterval(pollHandle);
pollHandle = null;
}
};
await tick();
pollHandle = window.setInterval(tick, 2500);
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (!currentUser?.authenticated && currentUser?.auth_required) {
window.location.href = loginLink.href;
return;
}
submitButton.disabled = true;
clearSelection();
sentenceList.innerHTML = "";
heatmap.className = "heatmap placeholder-box";
heatmap.textContent = "Analysis pending.";
topEdges.innerHTML = "";
answerOutput.textContent = "Waiting for generated answer.";
sessionIdOutput.textContent = "";
traceCount.textContent = "";
setExportLinks("", false);
setStatus("Submitting", "Creating session.");
const payload = {
question: document.querySelector("#question").value,
max_new_tokens: Number(document.querySelector("#max-new-tokens").value),
max_trace_tokens: Number(document.querySelector("#max-trace-tokens").value),
max_sentences: Number(document.querySelector("#max-sentences").value),
};
try {
const response = await fetch("/api/sessions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(await response.text());
}
const session = await response.json();
await startPolling(session.id);
} catch (error) {
setStatus("Error", String(error));
} finally {
submitButton.disabled = false;
}
});
async function initialize() {
setExportLinks("", false);
try {
const response = await fetch("/api/me");
currentUser = await response.json();
} catch (_error) {
currentUser = {
authenticated: false,
auth_required: true,
login_url: "/oauth/huggingface/login",
logout_url: "/oauth/huggingface/logout",
};
}
renderAuth();
await loadRecentSessions();
}
initialize();