const analyzeBtn = document.getElementById("analyzeBtn");
const codeInput = document.getElementById("codeInput");
const statusEl = document.getElementById("status");
const indexInfoEl = document.getElementById("indexInfo");
const progressEl = document.getElementById("progress");
const progressBarEl = progressEl ? progressEl.querySelector(".progress-bar") : null;
const showIdenticalEl = document.getElementById("showIdentical");
const identicalLabelEl = document.getElementById("identicalLabel");
const excludeInputEl = document.getElementById("excludeModelInput");
const excludeListEl = document.getElementById("excludeModelList");
const modelOptionsEl = document.getElementById("modelOptions");
const copyAgentReportBtn = document.getElementById("copyAgentReport");
const reconstructionSummaryEl = document.getElementById("reconstructionSummary");
const moduleListEl = document.getElementById("moduleList");
const flowComparisonEl = document.getElementById("flowComparison");
const astQueryEl = document.getElementById("astQuery");
const astMatchEl = document.getElementById("astMatch");
const astQuerySummaryEl = document.getElementById("astQuerySummary");
const astMatchSummaryEl = document.getElementById("astMatchSummary");
const overallEl = document.getElementById("overall");
let lastAgentReport = "";
let lastAnalysis = null;
let availableModels = new Set();
let excludedModels = new Set();
if (showIdenticalEl) {
showIdenticalEl.disabled = true;
}
if (excludeInputEl) {
excludeInputEl.disabled = true;
}
function setStatus(message) {
if (statusEl) {
statusEl.textContent = message;
}
}
function setProgress(visible) {
if (!progressEl) return;
progressEl.classList.toggle("is-hidden", !visible);
}
function setProgressValue(progress, total) {
if (!progressBarEl) return;
const safeTotal = total && total > 0 ? total : 1;
const ratio = Math.max(0, Math.min(1, progress / safeTotal));
progressBarEl.style.width = `${(ratio * 100).toFixed(1)}%`;
}
function renderIndexInfo(info) {
if (!indexInfoEl) return;
if (!info) {
indexInfoEl.textContent = "";
return;
}
const requested = info.requested_granularity || "method";
const resolved = info.resolved_granularity || requested;
const origin = info.index_origin;
const dir = info.index_dir;
const pieces = [`Using ${resolved} index`];
if (requested !== resolved) {
pieces.push(`(fallback from ${requested})`);
}
if (origin === "hub") {
pieces.push("from Hub");
} else if (origin) {
pieces.push(`from ${origin}`);
}
if (dir) {
pieces.push(`@ ${dir}`);
}
indexInfoEl.textContent = pieces.join(" ");
}
function renderExcludedModels() {
if (!excludeListEl) return;
excludeListEl.innerHTML = "";
Array.from(excludedModels).sort().forEach((name) => {
const chip = document.createElement("span");
chip.className = "chip";
chip.textContent = name;
const removeBtn = document.createElement("button");
removeBtn.type = "button";
removeBtn.textContent = "×";
removeBtn.addEventListener("click", () => {
excludedModels.delete(name);
renderExcludedModels();
applyExclusions();
});
chip.appendChild(removeBtn);
excludeListEl.appendChild(chip);
});
}
function applyExclusions() {
if (!lastAnalysis) return;
const showAll = showIdenticalEl?.checked;
const baseByClass = showAll ? lastAnalysis.by_class_all : lastAnalysis.by_class;
const baseOverall = showAll ? lastAnalysis.overall_all : lastAnalysis.overall;
if (!excludedModels.size) {
renderBlueprint(baseByClass);
renderOverall(baseOverall);
renderSummaryPill(baseOverall);
return;
}
const excluded = new Set(Array.from(excludedModels).map((name) => name.toLowerCase()));
const filteredByClass = {};
Object.entries(baseByClass || {}).forEach(([qcls, matches]) => {
const filtered = (matches || []).filter((row) => {
const root = row.relative_path?.split("/")[0]?.toLowerCase();
return root && !excluded.has(root);
});
if (filtered.length) {
filteredByClass[qcls] = filtered;
}
});
const filteredOverall = (baseOverall || []).filter((row) => {
const root = row.relative_path?.split("/")[0]?.toLowerCase();
return root && !excluded.has(root);
});
renderBlueprint(filteredByClass);
renderOverall(filteredOverall);
renderSummaryPill(filteredOverall);
}
async function loadModels() {
if (!modelOptionsEl) return;
try {
const response = await fetch("/api/models");
if (!response.ok) return;
const data = await response.json();
const models = data.models || [];
availableModels = new Set(models);
modelOptionsEl.innerHTML = "";
models.forEach((name) => {
const option = document.createElement("option");
option.value = name;
modelOptionsEl.appendChild(option);
});
} catch (error) {
}
}
function formatSummary(summary) {
if (!summary || !summary.summary) return "No structural summary.";
const nodes = (summary.summary.node_counts || [])
.map((item) => `${item.name}(${item.count})`)
.join(", ");
const calls = (summary.summary.calls || [])
.map((item) => `${item.name}(${item.count})`)
.join(", ");
const flow = summary.flow ? `Flow: ${summary.flow}` : "";
const parts = [];
if (flow) parts.push(flow);
if (nodes) parts.push(`Nodes: ${nodes}`);
if (calls) parts.push(`Calls: ${calls}`);
return parts.join(" · ") || "No structural summary.";
}
function escapeHtml(text) {
if (!text) return "";
return text
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/\"/g, """)
.replace(/'/g, "'");
}
function diffLCS(text1, text2) {
const lines1 = text1 ? text1.split("\n") : [];
const lines2 = text2 ? text2.split("\n") : [];
const n = lines1.length;
const m = lines2.length;
const dp = Array.from({ length: n + 1 }, () => Array(m + 1).fill(0));
for (let i = 1; i <= n; i += 1) {
for (let j = 1; j <= m; j += 1) {
if (lines1[i - 1] === lines2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
let i = n;
let j = m;
const result = [];
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && lines1[i - 1] === lines2[j - 1]) {
result.unshift({ type: "same", text: lines1[i - 1] });
i -= 1;
j -= 1;
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
result.unshift({ type: "add", text: lines2[j - 1] });
j -= 1;
} else {
result.unshift({ type: "del", text: lines1[i - 1] });
i -= 1;
}
}
return result;
}
function renderDiff(text1, text2) {
const diff = diffLCS(text1, text2);
return diff
.map((part) => {
const cls = part.type === "add" ? "diff-add" : part.type === "del" ? "diff-del" : "";
const safe = escapeHtml(part.text || " ");
return `
`;
})
.join("");
}
function setAst(queryAst, matchAst, querySummary, matchSummary) {
if (astQueryEl) {
astQueryEl.textContent = queryAst || "AST not found.";
}
if (astMatchEl) {
astMatchEl.textContent = matchAst || "AST not found.";
}
if (astQuerySummaryEl) {
astQuerySummaryEl.textContent = formatSummary(querySummary);
}
if (astMatchSummaryEl) {
astMatchSummaryEl.textContent = formatSummary(matchSummary);
}
if (flowComparisonEl) {
const queryFlow = querySummary?.flow ? querySummary.flow : "unavailable";
const matchFlow = matchSummary?.flow ? matchSummary.flow : "unavailable";
flowComparisonEl.textContent = `Selected flow:\n${queryFlow}\n\nMatch flow:\n${matchFlow}`;
}
const diffContainer = document.querySelector(".code-diff-view");
if (diffContainer) {
diffContainer.innerHTML = "";
if (queryAst && matchAst) {
const diffWrapper = document.createElement("div");
diffWrapper.className = "diff-wrapper";
diffWrapper.innerHTML = renderDiff(queryAst, matchAst);
diffContainer.appendChild(diffWrapper);
} else {
const left = document.createElement("pre");
left.className = "code-block";
left.textContent = queryAst || "";
const right = document.createElement("pre");
right.className = "code-block";
right.textContent = matchAst || "";
diffContainer.appendChild(left);
diffContainer.appendChild(right);
}
}
}
async function loadAst(symbol, matchIdentifier) {
const code = codeInput.value.trim();
if (!code || !symbol) return;
if (astQueryEl) astQueryEl.textContent = "Loading AST...";
if (astMatchEl) astMatchEl.textContent = "Loading AST...";
const response = await fetch("/api/ast", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, symbol, match_identifier: matchIdentifier }),
});
if (!response.ok) {
setAst("Failed to load AST.", "Failed to load AST.");
return;
}
const data = await response.json();
setAst(data.query_ast, data.match_ast, data.query_summary, data.match_summary);
}
function renderOverall(overall) {
if (!overallEl) return;
overallEl.innerHTML = "";
if (!overall || overall.length === 0) {
overallEl.textContent = "No aggregate matches yet.";
return;
}
const slice = overall.slice(0, 6);
for (const entry of slice) {
const div = document.createElement("div");
div.className = "overall-item";
const title = document.createElement("div");
title.className = "overall-title";
title.textContent = entry.relative_path;
const meta = document.createElement("div");
meta.className = "overall-meta";
const parts = [`score ${entry.score.toFixed(4)}`];
if (entry.count) {
parts.push(`${entry.count} hits`);
}
meta.textContent = parts.join(" · ");
div.appendChild(title);
div.appendChild(meta);
overallEl.appendChild(div);
}
}
function renderSummaryPill(overall) {
if (!reconstructionSummaryEl) return;
if (!overall || overall.length === 0) {
reconstructionSummaryEl.textContent = "No primary ancestor found.";
return;
}
const best = overall[0];
reconstructionSummaryEl.textContent = `Primary ancestor: ${best.relative_path} · ${best.score.toFixed(4)}`;
}
function renderBlueprint(byClass) {
if (!moduleListEl) return;
moduleListEl.innerHTML = "";
if (!byClass || Object.keys(byClass).length === 0) {
moduleListEl.textContent = "No class-level suggestions.";
return;
}
const sortedClasses = Object.entries(byClass).sort((a, b) => {
const scoreA = a[1]?.[0]?.score || 0;
const scoreB = b[1]?.[0]?.score || 0;
return scoreB - scoreA;
});
sortedClasses.forEach(([qcls, matches], index) => {
if (!matches || matches.length === 0) return;
const top = matches[0];
const item = document.createElement("div");
item.className = `blueprint-item ${top.score > 0.9 ? "high-match" : ""}`;
const title = document.createElement("div");
title.className = "blueprint-name";
title.textContent = qcls;
const suggestion = document.createElement("div");
suggestion.className = "blueprint-suggestion";
suggestion.textContent = `→ ${top.class_name}`;
const meta = document.createElement("div");
meta.className = "blueprint-meta";
const coverage = top.coverage_pct !== undefined
? `${Math.round(top.coverage_pct * 100)}% coverage`
: `${top.coverage} defs`;
meta.textContent = `${top.relative_path} · ${top.score.toFixed(4)} · ${coverage}`;
const evidence = document.createElement("div");
evidence.className = "blueprint-evidence";
evidence.textContent = (top.top_contributors || [])
.map((c) => `${c.query.split(".").pop()} ← ${c.match.split(":").pop()}`)
.slice(0, 2)
.join(" · ");
item.appendChild(title);
item.appendChild(suggestion);
item.appendChild(meta);
if (evidence.textContent) {
item.appendChild(evidence);
}
item.addEventListener("click", () => {
document.querySelectorAll(".blueprint-item").forEach((el) => el.classList.remove("is-active"));
item.classList.add("is-active");
const matchIdentifier = top.identifier;
const activeName = document.getElementById("activeModuleName");
if (activeName) {
activeName.textContent = `${qcls} vs ${top.class_name}`;
}
loadAst(qcls, matchIdentifier);
});
moduleListEl.appendChild(item);
if (index === 0) {
item.click();
}
});
}
async function copyAgentReport() {
if (!lastAgentReport) {
setStatus("Run an analysis first.");
return;
}
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(lastAgentReport);
setStatus("Agent report copied.");
} else {
setStatus("Clipboard unavailable in this context.");
}
}
if (copyAgentReportBtn) {
copyAgentReportBtn.addEventListener("click", () => {
copyAgentReport().catch((error) => {
setStatus(`Copy failed: ${error.message || error}`);
});
});
}
if (excludeInputEl) {
excludeInputEl.addEventListener("keydown", (event) => {
if (event.key !== "Enter" && event.key !== "Tab") return;
const value = excludeInputEl.value.trim();
if (!value) return;
if (!availableModels.has(value)) {
setStatus(`Unknown model: ${value}`);
event.preventDefault();
return;
}
excludedModels.add(value);
excludeInputEl.value = "";
renderExcludedModels();
applyExclusions();
event.preventDefault();
});
}
loadModels();
analyzeBtn.addEventListener("click", async () => {
const code = codeInput.value.trim();
if (!code) {
setStatus("Paste some code first.");
return;
}
renderIndexInfo(null);
setStatus("Analyzing... this can take a bit on first run.");
setProgress(true);
setProgressValue(0, 1);
analyzeBtn.disabled = true;
try {
const payload = {
code,
top_k: Number(document.getElementById("topK").value || 5),
granularity: document.getElementById("granularity").value,
precision: "float32",
exclude_models: Array.from(excludedModels),
};
const response = await fetch("/api/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
const detail = await response.text();
throw new Error(detail || "Request failed");
}
const start = await response.json();
const jobId = start.job_id;
if (!jobId) {
throw new Error("Missing job id.");
}
const pollInterval = 600;
const poller = setInterval(async () => {
try {
const progressRes = await fetch(`/api/progress/${jobId}`);
if (!progressRes.ok) {
throw new Error("Failed to read progress.");
}
const progressData = await progressRes.json();
const done = progressData.progress || 0;
const total = progressData.total || 1;
setProgressValue(done, total);
const message = progressData.message ? ` · ${progressData.message}` : "";
const errorDetail = progressData.error ? ` · ${progressData.error}` : "";
setStatus(`Analyzing... ${done}/${total}${message}${errorDetail}`);
if (progressData.status === "done") {
clearInterval(poller);
const resultRes = await fetch(`/api/result/${jobId}`);
if (!resultRes.ok) {
const detail = await resultRes.text();
throw new Error(detail || "Failed to fetch result.");
}
const data = await resultRes.json();
lastAnalysis = data;
lastAgentReport = data.agent_report || "";
setAst("Select a module to compare.", "Select a module to compare.", null, null);
renderBlueprint(data.by_class);
renderOverall(data.overall);
renderSummaryPill(data.overall);
renderIndexInfo(data.index_info);
if (identicalLabelEl) {
const filtered = data.identical_filtered || 0;
const label = identicalLabelEl.querySelector("span");
if (label) {
label.textContent = `Show identical matches (${filtered})`;
}
}
if (showIdenticalEl) {
showIdenticalEl.disabled = false;
}
if (excludeInputEl) {
excludeInputEl.disabled = false;
}
setStatus("Done.");
setProgress(false);
analyzeBtn.disabled = false;
} else if (progressData.status === "error") {
clearInterval(poller);
const detail = progressData.error ? ` ${progressData.error}` : "";
setStatus(`Analysis failed.${detail}`);
setProgress(false);
analyzeBtn.disabled = false;
}
} catch (error) {
clearInterval(poller);
setStatus(`Error: ${error.message || error}`);
setProgress(false);
analyzeBtn.disabled = false;
}
}, pollInterval);
} catch (error) {
setStatus(`Error: ${error.message || error}`);
setProgress(false);
analyzeBtn.disabled = false;
} finally {
}
});
if (showIdenticalEl) {
showIdenticalEl.addEventListener("change", () => {
if (!lastAnalysis) return;
applyExclusions();
});
}