Spaces:
Sleeping
Sleeping
| 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, """) | |
| .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 `<div class="diff-row ${cls}"><div class="code-line">${safe}</div></div>`; | |
| }) | |
| .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(); | |
| }); | |
| } | |