Molbap HF Staff commited on
Commit
df3ff34
·
verified ·
1 Parent(s): ceed98e

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. static/app.js +129 -99
  2. static/index.html +25 -12
  3. static/styles.css +52 -37
static/app.js CHANGED
@@ -2,9 +2,13 @@ const analyzeBtn = document.getElementById("analyzeBtn");
2
  const codeInput = document.getElementById("codeInput");
3
  const statusEl = document.getElementById("status");
4
  const indexInfoEl = document.getElementById("indexInfo");
5
- const graphEl = document.getElementById("graph");
6
  const matchesEl = document.getElementById("matches");
7
  const overallEl = document.getElementById("overall");
 
 
 
 
 
8
 
9
  function setStatus(message) {
10
  statusEl.textContent = message;
@@ -35,6 +39,90 @@ function renderIndexInfo(info) {
35
  indexInfoEl.textContent = pieces.join(" ");
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  function renderOverall(overall) {
39
  overallEl.innerHTML = "";
40
  if (!overall || overall.length === 0) {
@@ -45,7 +133,21 @@ function renderOverall(overall) {
45
  for (const entry of slice) {
46
  const div = document.createElement("div");
47
  div.className = "overall-item";
48
- div.textContent = `${entry.relative_path} (${entry.score.toFixed(4)})`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  overallEl.appendChild(div);
50
  }
51
  }
@@ -61,20 +163,35 @@ function renderMatches(results) {
61
  const card = document.createElement("div");
62
  card.className = "match-card";
63
  const title = document.createElement("h3");
64
- title.textContent = symbol;
 
65
  card.appendChild(title);
66
  const list = document.createElement("div");
67
  list.className = "match-list";
68
  const matches = results[symbol].embedding || [];
 
 
 
69
  for (const match of matches) {
70
  const row = document.createElement("div");
71
  row.className = "match-row";
72
  const left = document.createElement("span");
73
- left.textContent = `${match.match_name} (${match.score.toFixed(4)})`;
74
  const right = document.createElement("span");
75
- right.textContent = match.relative_path;
 
 
 
76
  row.appendChild(left);
77
  row.appendChild(right);
 
 
 
 
 
 
 
 
78
  list.appendChild(row);
79
  }
80
  card.appendChild(list);
@@ -82,99 +199,6 @@ function renderMatches(results) {
82
  }
83
  }
84
 
85
- function renderGraph(graph) {
86
- graphEl.innerHTML = "";
87
- const width = graphEl.clientWidth;
88
- const height = graphEl.clientHeight;
89
- const svg = d3
90
- .select(graphEl)
91
- .append("svg")
92
- .attr("width", width)
93
- .attr("height", height);
94
-
95
- const nodes = graph.nodes.map((node) => ({ ...node }));
96
- const links = graph.edges.map((edge) => ({ ...edge }));
97
-
98
- const color = (type) => {
99
- if (type === "class") return "#d6572b";
100
- if (type === "method") return "#2b6fd6";
101
- if (type === "function") return "#1b8d57";
102
- return "#666";
103
- };
104
-
105
- const simulation = d3
106
- .forceSimulation(nodes)
107
- .force("link", d3.forceLink(links).id((d) => d.id).distance(80))
108
- .force("charge", d3.forceManyBody().strength(-220))
109
- .force("center", d3.forceCenter(width / 2, height / 2));
110
-
111
- const link = svg
112
- .append("g")
113
- .attr("stroke", "#333")
114
- .attr("stroke-opacity", 0.4)
115
- .selectAll("line")
116
- .data(links)
117
- .join("line")
118
- .attr("stroke-width", (d) => (d.type === "contains" ? 1.5 : 1));
119
-
120
- const node = svg
121
- .append("g")
122
- .attr("stroke", "#fff")
123
- .attr("stroke-width", 1.5)
124
- .selectAll("circle")
125
- .data(nodes)
126
- .join("circle")
127
- .attr("r", (d) => (d.type === "class" ? 9 : 6))
128
- .attr("fill", (d) => color(d.type))
129
- .call(drag(simulation));
130
-
131
- const labels = svg
132
- .append("g")
133
- .selectAll("text")
134
- .data(nodes)
135
- .join("text")
136
- .text((d) => d.label)
137
- .attr("font-size", 11)
138
- .attr("fill", "#2b1e13")
139
- .attr("dx", 12)
140
- .attr("dy", 3);
141
-
142
- node.append("title").text((d) => d.id);
143
-
144
- simulation.on("tick", () => {
145
- link
146
- .attr("x1", (d) => d.source.x)
147
- .attr("y1", (d) => d.source.y)
148
- .attr("x2", (d) => d.target.x)
149
- .attr("y2", (d) => d.target.y);
150
-
151
- node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
152
-
153
- labels.attr("x", (d) => d.x).attr("y", (d) => d.y);
154
- });
155
-
156
- function drag(sim) {
157
- function dragstarted(event, d) {
158
- if (!event.active) sim.alphaTarget(0.3).restart();
159
- d.fx = d.x;
160
- d.fy = d.y;
161
- }
162
-
163
- function dragged(event, d) {
164
- d.fx = event.x;
165
- d.fy = event.y;
166
- }
167
-
168
- function dragended(event, d) {
169
- if (!event.active) sim.alphaTarget(0);
170
- d.fx = null;
171
- d.fy = null;
172
- }
173
-
174
- return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);
175
- }
176
- }
177
-
178
  analyzeBtn.addEventListener("click", async () => {
179
  const code = codeInput.value.trim();
180
  if (!code) {
@@ -201,7 +225,13 @@ analyzeBtn.addEventListener("click", async () => {
201
  throw new Error(detail || "Request failed");
202
  }
203
  const data = await response.json();
204
- renderGraph(data.graph);
 
 
 
 
 
 
205
  renderOverall(data.overall);
206
  renderMatches(data.results);
207
  renderIndexInfo(data.index_info);
 
2
  const codeInput = document.getElementById("codeInput");
3
  const statusEl = document.getElementById("status");
4
  const indexInfoEl = document.getElementById("indexInfo");
 
5
  const matchesEl = document.getElementById("matches");
6
  const overallEl = document.getElementById("overall");
7
+ const byClassEl = document.getElementById("byClass");
8
+ const astQueryEl = document.getElementById("astQuery");
9
+ const astMatchEl = document.getElementById("astMatch");
10
+ const astQuerySummaryEl = document.getElementById("astQuerySummary");
11
+ const astMatchSummaryEl = document.getElementById("astMatchSummary");
12
 
13
  function setStatus(message) {
14
  statusEl.textContent = message;
 
39
  indexInfoEl.textContent = pieces.join(" ");
40
  }
41
 
42
+ function formatSummary(summary) {
43
+ if (!summary) return "No structural summary.";
44
+ const nodes = (summary.node_counts || [])
45
+ .map((item) => `${item.name}(${item.count})`)
46
+ .join(", ");
47
+ const calls = (summary.calls || []).map((item) => `${item.name}(${item.count})`).join(", ");
48
+ const parts = [];
49
+ if (nodes) parts.push(`Nodes: ${nodes}`);
50
+ if (calls) parts.push(`Calls: ${calls}`);
51
+ return parts.join(" · ") || "No structural summary.";
52
+ }
53
+
54
+ function setAst(queryAst, matchAst, querySummary, matchSummary) {
55
+ if (astQueryEl) {
56
+ astQueryEl.textContent = queryAst || "AST not found.";
57
+ }
58
+ if (astMatchEl) {
59
+ astMatchEl.textContent = matchAst || "AST not found.";
60
+ }
61
+ if (astQuerySummaryEl) {
62
+ astQuerySummaryEl.textContent = formatSummary(querySummary);
63
+ }
64
+ if (astMatchSummaryEl) {
65
+ astMatchSummaryEl.textContent = formatSummary(matchSummary);
66
+ }
67
+ }
68
+
69
+ async function loadAst(symbol, matchIdentifier) {
70
+ const code = codeInput.value.trim();
71
+ if (!code || !symbol) return;
72
+ if (astQueryEl) astQueryEl.textContent = "Loading AST...";
73
+ if (astMatchEl) astMatchEl.textContent = "Loading AST...";
74
+ const response = await fetch("/api/ast", {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/json" },
77
+ body: JSON.stringify({ code, symbol, match_identifier: matchIdentifier }),
78
+ });
79
+ if (!response.ok) {
80
+ setAst("Failed to load AST.", "Failed to load AST.");
81
+ return;
82
+ }
83
+ const data = await response.json();
84
+ setAst(data.query_ast, data.match_ast, data.query_summary, data.match_summary);
85
+ }
86
+
87
+ function renderByClass(byClass) {
88
+ if (!byClassEl) return;
89
+ byClassEl.innerHTML = "";
90
+ if (!byClass || Object.keys(byClass).length === 0) {
91
+ byClassEl.textContent = "No class-level suggestions.";
92
+ return;
93
+ }
94
+ for (const qcls of Object.keys(byClass)) {
95
+ const card = document.createElement("div");
96
+ card.className = "match-card";
97
+ const title = document.createElement("h3");
98
+ title.textContent = qcls;
99
+ card.appendChild(title);
100
+ for (const row of byClass[qcls]) {
101
+ const line = document.createElement("div");
102
+ line.className = "match-row";
103
+ const left = document.createElement("span");
104
+ left.textContent = `${row.class_name} · ${Number(row.score).toFixed(4)} · ${row.coverage} defs`;
105
+ const right = document.createElement("span");
106
+ right.textContent = row.relative_path;
107
+ line.appendChild(left);
108
+ line.appendChild(right);
109
+ line.style.cursor = "pointer";
110
+ line.addEventListener("click", () => loadAst(qcls, row.top_contributors?.[0]?.match || row.identifier));
111
+ card.appendChild(line);
112
+ if (row.top_contributors && row.top_contributors.length) {
113
+ const expl = document.createElement("div");
114
+ expl.className = "helper";
115
+ expl.textContent = row.top_contributors
116
+ .map((c) => `${c.query} <- ${c.match.split(":").pop()} (${Number(c.score).toFixed(4)})`)
117
+ .slice(0, 3)
118
+ .join(" · ");
119
+ card.appendChild(expl);
120
+ }
121
+ }
122
+ byClassEl.appendChild(card);
123
+ }
124
+ }
125
+
126
  function renderOverall(overall) {
127
  overallEl.innerHTML = "";
128
  if (!overall || overall.length === 0) {
 
133
  for (const entry of slice) {
134
  const div = document.createElement("div");
135
  div.className = "overall-item";
136
+ const title = document.createElement("div");
137
+ title.className = "overall-title";
138
+ title.textContent = entry.relative_path;
139
+ const meta = document.createElement("div");
140
+ meta.className = "overall-meta";
141
+ const parts = [`score ${entry.score.toFixed(4)}`];
142
+ if (entry.count) {
143
+ parts.push(`${entry.count} hits`);
144
+ }
145
+ if (entry.best_score !== undefined && entry.best_score !== null) {
146
+ parts.push(`best ${Number(entry.best_score).toFixed(4)}`);
147
+ }
148
+ meta.textContent = parts.join(" · ");
149
+ div.appendChild(title);
150
+ div.appendChild(meta);
151
  overallEl.appendChild(div);
152
  }
153
  }
 
163
  const card = document.createElement("div");
164
  card.className = "match-card";
165
  const title = document.createElement("h3");
166
+ const kind = results[symbol].kind || "definition";
167
+ title.textContent = `${symbol} (${kind})`;
168
  card.appendChild(title);
169
  const list = document.createElement("div");
170
  list.className = "match-list";
171
  const matches = results[symbol].embedding || [];
172
+ const defaultMatch = matches[0]?.identifier;
173
+ title.style.cursor = "pointer";
174
+ title.addEventListener("click", () => loadAst(symbol, defaultMatch));
175
  for (const match of matches) {
176
  const row = document.createElement("div");
177
  row.className = "match-row";
178
  const left = document.createElement("span");
179
+ left.textContent = `${match.match_name} · ${match.score.toFixed(4)}`;
180
  const right = document.createElement("span");
181
+ const line = match.line ? `:${match.line}` : "";
182
+ const location = `${match.relative_path}${line}`;
183
+ right.textContent = location;
184
+ right.title = "Click to copy";
185
  row.appendChild(left);
186
  row.appendChild(right);
187
+ row.style.cursor = "pointer";
188
+ row.addEventListener("click", () => loadAst(symbol, match.identifier));
189
+ right.addEventListener("click", (event) => {
190
+ event.stopPropagation();
191
+ if (navigator.clipboard && window.isSecureContext) {
192
+ navigator.clipboard.writeText(location);
193
+ }
194
+ });
195
  list.appendChild(row);
196
  }
197
  card.appendChild(list);
 
199
  }
200
  }
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  analyzeBtn.addEventListener("click", async () => {
203
  const code = codeInput.value.trim();
204
  if (!code) {
 
225
  throw new Error(detail || "Request failed");
226
  }
227
  const data = await response.json();
228
+ setAst(
229
+ "Select a definition to load its AST.",
230
+ "Select a definition to load its top match AST.",
231
+ null,
232
+ null
233
+ );
234
+ renderByClass(data.by_class);
235
  renderOverall(data.overall);
236
  renderMatches(data.results);
237
  renderIndexInfo(data.index_info);
static/index.html CHANGED
@@ -5,7 +5,6 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>Modular Model Graph</title>
7
  <link rel="stylesheet" href="/static/styles.css" />
8
- <script src="https://d3js.org/d3.v7.min.js"></script>
9
  </head>
10
  <body>
11
  <div class="page">
@@ -45,31 +44,45 @@
45
  <section class="grid">
46
  <div class="panel">
47
  <div class="panel-header">
48
- <h2>Graph</h2>
49
- <div class="legend">
50
- <span class="dot class">Class</span>
51
- <span class="dot method">Method</span>
52
- <span class="dot function">Function</span>
53
- <span class="dot call">Call edge</span>
 
 
 
 
 
 
 
54
  </div>
55
  </div>
56
- <div id="graph" class="graph"></div>
57
  </div>
58
 
59
  <div class="panel">
60
  <div class="panel-header">
61
- <h2>Closest Models</h2>
62
  </div>
63
- <div id="overall" class="overall"></div>
 
64
  </div>
65
  </section>
66
 
67
  <section class="panel">
68
  <div class="panel-header">
69
- <h2>Matches by Symbol</h2>
70
  </div>
71
- <div id="matches" class="matches"></div>
 
72
  </section>
 
 
 
 
 
 
73
  </div>
74
 
75
  <script src="/static/app.js"></script>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>Modular Model Graph</title>
7
  <link rel="stylesheet" href="/static/styles.css" />
 
8
  </head>
9
  <body>
10
  <div class="page">
 
44
  <section class="grid">
45
  <div class="panel">
46
  <div class="panel-header">
47
+ <h2>AST Compare</h2>
48
+ </div>
49
+ <p class="helper">Click a definition below to compare its AST with the top match.</p>
50
+ <div class="ast-grid">
51
+ <div>
52
+ <p class="ast-label">Selected</p>
53
+ <pre id="astQuery" class="ast-view">Select a definition to load its AST.</pre>
54
+ <p id="astQuerySummary" class="ast-summary">No structural summary.</p>
55
+ </div>
56
+ <div>
57
+ <p class="ast-label">Top Match</p>
58
+ <pre id="astMatch" class="ast-view">Select a definition to load its top match AST.</pre>
59
+ <p id="astMatchSummary" class="ast-summary">No structural summary.</p>
60
  </div>
61
  </div>
 
62
  </div>
63
 
64
  <div class="panel">
65
  <div class="panel-header">
66
+ <h2>Suggested Components</h2>
67
  </div>
68
+ <p class="helper">Class-level matches aggregated from method-level similarities.</p>
69
+ <div id="byClass" class="matches"></div>
70
  </div>
71
  </section>
72
 
73
  <section class="panel">
74
  <div class="panel-header">
75
+ <h2>Closest Files</h2>
76
  </div>
77
+ <p class="helper">Normalized by best match per definition.</p>
78
+ <div id="overall" class="overall"></div>
79
  </section>
80
+
81
+ <details class="panel">
82
+ <summary>Matches by Definition</summary>
83
+ <p class="helper">Each definition from your paste with its closest matches.</p>
84
+ <div id="matches" class="matches"></div>
85
+ </details>
86
  </div>
87
 
88
  <script src="/static/app.js"></script>
static/styles.css CHANGED
@@ -67,6 +67,12 @@ h1 {
67
  margin-bottom: 24px;
68
  }
69
 
 
 
 
 
 
 
70
  .panel-header {
71
  display: flex;
72
  justify-content: space-between;
@@ -142,51 +148,39 @@ textarea {
142
  gap: 24px;
143
  }
144
 
145
- .graph {
146
- width: 100%;
147
- height: 480px;
148
- border-radius: 18px;
149
- background: #fff;
150
- border: 1px solid #e3d6c8;
151
  }
152
 
153
- .legend {
154
- display: flex;
155
- gap: 10px;
156
- align-items: center;
157
- flex-wrap: wrap;
158
  font-size: 12px;
159
  color: var(--muted);
 
 
160
  }
161
 
162
- .dot {
163
- display: inline-flex;
164
- align-items: center;
165
- gap: 6px;
166
- }
167
-
168
- .dot::before {
169
- content: "";
170
- width: 10px;
171
- height: 10px;
172
- border-radius: 50%;
173
- background: var(--muted);
174
- }
175
-
176
- .dot.class::before {
177
- background: var(--accent);
178
- }
179
-
180
- .dot.method::before {
181
- background: var(--accent-2);
182
- }
183
-
184
- .dot.function::before {
185
- background: var(--accent-3);
186
  }
187
 
188
- .dot.call::before {
189
- background: #333;
 
 
190
  }
191
 
192
  .overall {
@@ -203,6 +197,16 @@ textarea {
203
  border: 1px solid #eadccd;
204
  }
205
 
 
 
 
 
 
 
 
 
 
 
206
  .matches {
207
  display: grid;
208
  gap: 16px;
@@ -234,8 +238,19 @@ textarea {
234
  gap: 12px;
235
  }
236
 
 
 
 
 
 
 
 
237
  @media (max-width: 960px) {
238
  .grid {
239
  grid-template-columns: 1fr;
240
  }
 
 
 
 
241
  }
 
67
  margin-bottom: 24px;
68
  }
69
 
70
+ .panel summary {
71
+ font-size: 20px;
72
+ font-weight: 600;
73
+ cursor: pointer;
74
+ }
75
+
76
  .panel-header {
77
  display: flex;
78
  justify-content: space-between;
 
148
  gap: 24px;
149
  }
150
 
151
+ .ast-grid {
152
+ display: grid;
153
+ grid-template-columns: repeat(2, minmax(0, 1fr));
154
+ gap: 16px;
155
+ margin-top: 12px;
 
156
  }
157
 
158
+ .ast-label {
159
+ margin: 0 0 6px;
 
 
 
160
  font-size: 12px;
161
  color: var(--muted);
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.08em;
164
  }
165
 
166
+ .ast-view {
167
+ margin: 0;
168
+ padding: 12px;
169
+ border-radius: 12px;
170
+ border: 1px solid #e3d6c8;
171
+ background: #fff;
172
+ font-family: "Space Grotesk", monospace;
173
+ font-size: 12px;
174
+ line-height: 1.4;
175
+ max-height: 520px;
176
+ overflow: auto;
177
+ white-space: pre-wrap;
 
 
 
 
 
 
 
 
 
 
 
 
178
  }
179
 
180
+ .ast-summary {
181
+ margin: 8px 0 0;
182
+ font-size: 12px;
183
+ color: var(--muted);
184
  }
185
 
186
  .overall {
 
197
  border: 1px solid #eadccd;
198
  }
199
 
200
+ .overall-title {
201
+ font-weight: 600;
202
+ }
203
+
204
+ .overall-meta {
205
+ font-size: 12px;
206
+ color: var(--muted);
207
+ margin-top: 4px;
208
+ }
209
+
210
  .matches {
211
  display: grid;
212
  gap: 16px;
 
238
  gap: 12px;
239
  }
240
 
241
+
242
+ .helper {
243
+ margin: 8px 0 0;
244
+ font-size: 12px;
245
+ color: var(--muted);
246
+ }
247
+
248
  @media (max-width: 960px) {
249
  .grid {
250
  grid-template-columns: 1fr;
251
  }
252
+
253
+ .ast-grid {
254
+ grid-template-columns: 1fr;
255
+ }
256
  }