const docBody = document.body;
window.APP_STATE = {
ready: docBody.dataset.appReady === "true",
loading: docBody.dataset.appLoading === "true",
error: docBody.dataset.appError || null,
};
const form = document.getElementById("upload-form");
const fileInput = document.getElementById("file-input");
const dropZone = document.getElementById("drop-zone");
const fileMeta = document.getElementById("file-meta");
const submitBtn = document.getElementById("submit-btn");
const loadingEl = document.getElementById("loading");
const errorEl = document.getElementById("error");
const resultsEl = document.getElementById("results");
const resultsMeta = document.getElementById("results-meta");
const resultsGrid = document.getElementById("results-grid");
const statusBadge = document.getElementById("status-badge");
const header = document.querySelector(".site-header");
let selectedFile = null;
/* ---------- Header shadow on scroll ---------- */
function onScroll() {
if (window.scrollY > 10) header.classList.add("scrolled");
else header.classList.remove("scrolled");
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- Scroll reveal ---------- */
const revealObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("in");
revealObserver.unobserve(entry.target);
}
});
},
{ threshold: 0.12 }
);
document.querySelectorAll(".reveal").forEach((el) => revealObserver.observe(el));
/* ---------- Active nav link on scroll ---------- */
const navLinks = Array.from(document.querySelectorAll(".nav-link"));
const sections = navLinks
.map((link) => document.querySelector(link.getAttribute("href")))
.filter(Boolean);
const navObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("id");
navLinks.forEach((l) =>
l.classList.toggle("active", l.getAttribute("href") === `#${id}`)
);
}
});
},
{ rootMargin: "-45% 0px -50% 0px" }
);
sections.forEach((s) => navObserver.observe(s));
/* ---------- Expandable model cards ---------- */
document.querySelectorAll(".model-card-head").forEach((head) => {
head.addEventListener("click", () => {
const card = head.closest(".model-card");
const open = card.classList.toggle("open");
head.setAttribute("aria-expanded", String(open));
});
});
/* ---------- Upload helpers ---------- */
function setError(message) {
if (!message) {
errorEl.classList.add("hidden");
errorEl.textContent = "";
return;
}
errorEl.textContent = message;
errorEl.classList.remove("hidden");
}
function updateFileMeta(file) {
selectedFile = file;
fileMeta.textContent = `${file.name} · ${(file.size / 1024).toFixed(1)} KB`;
fileMeta.classList.remove("hidden");
submitBtn.disabled = !window.APP_STATE.ready;
}
dropZone.addEventListener("click", () => fileInput.click());
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
dropZone.classList.add("dragover");
});
dropZone.addEventListener("dragleave", () => dropZone.classList.remove("dragover"));
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("dragover");
const file = e.dataTransfer.files[0];
if (file) updateFileMeta(file);
});
fileInput.addEventListener("change", () => {
if (fileInput.files[0]) updateFileMeta(fileInput.files[0]);
});
/* ---------- Submit ---------- */
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (!selectedFile) return;
if (!window.APP_STATE.ready) {
setError("Models are not ready yet. Please wait for startup to finish.");
return;
}
setError("");
resultsEl.classList.add("hidden");
loadingEl.classList.remove("hidden");
submitBtn.disabled = true;
const payload = new FormData();
payload.append("file", selectedFile);
try {
const res = await fetch("/api/summarize", { method: "POST", body: payload });
const data = await res.json();
if (!res.ok) throw new Error(data.detail || "Summarization failed.");
renderResults(data);
resultsEl.classList.remove("hidden");
resultsEl.scrollIntoView({ behavior: "smooth", block: "nearest" });
} catch (err) {
setError(err.message);
} finally {
loadingEl.classList.add("hidden");
submitBtn.disabled = !window.APP_STATE.ready;
}
});
/* ---------- Render results ---------- */
function renderResults(data) {
resultsMeta.innerHTML = [
["File", data.filename],
["Methods", data.method_count],
["Statements", data.statement_count],
["Tokens", data.token_count],
["Total time", `${data.total_elapsed_ms} ms`],
]
.map(([k, v]) => `${k}: ${escapeHtml(String(v))}`)
.join("");
const summaries = data.summaries;
const withText = summaries.filter((s) => s.summary && s.summary.trim());
const fastest = summaries.reduce((a, b) => (b.elapsed_ms < a.elapsed_ms ? b : a));
const longest = withText.reduce(
(a, b) => (b.summary.length > (a ? a.summary.length : -1) ? b : a),
null
);
resultsGrid.innerHTML = summaries
.map((s, i) => {
const accent = s.accent || "#5b9cff";
const isEmpty = !s.summary || !s.summary.trim();
const badges = [];
if (s === fastest) badges.push(`⚡ Fastest`);
if (longest && s === longest) badges.push(`Most detail`);
const isCodet5 = s.model_id === "codet5" && s.methods && s.methods.length > 0;
const bodyHtml = isEmpty
? "(no output produced)"
: isCodet5
? renderMethodSummaries(s.methods)
: escapeHtml(s.summary);
const bodyClass = isEmpty ? "empty" : isCodet5 ? "method-list" : "";
return `
${escapeHtml(s.model)}