|
49 | 49 | return; |
50 | 50 | } |
51 | 51 |
|
52 | | - const summaryCells = summaryGrid ? Array.from(summaryGrid.querySelectorAll('dd')) : []; |
53 | | - const valueTableBody = valueTable; |
54 | | - let activeSampleId = ''; |
55 | | - |
56 | | - function formatDecimal(value) { |
57 | | - if (!Number.isFinite(value)) { |
58 | | - return '0.000'; |
59 | | - } |
60 | | - const rounded = Math.round(value * 1000) / 1000; |
61 | | - return rounded.toFixed(3); |
62 | | - } |
63 | | - |
64 | | - function formatRange(min, max) { |
65 | | - if (!Number.isFinite(min) || !Number.isFinite(max)) { |
66 | | - return '— / —'; |
67 | | - } |
68 | | - return `${formatDecimal(min)} / ${formatDecimal(max)}`; |
69 | | - } |
70 | | - |
71 | | - function calculateStats(vector) { |
72 | | - const values = Array.isArray(vector) ? vector.map((entry) => Number(entry)).filter((entry) => Number.isFinite(entry)) : []; |
73 | | - const length = values.length; |
74 | | - |
75 | | - if (!length) { |
76 | | - return { |
77 | | - values: [], |
78 | | - dimensions: 0, |
79 | | - magnitude: 0, |
80 | | - mean: 0, |
81 | | - deviation: 0, |
82 | | - min: null, |
83 | | - max: null, |
84 | | - zeroShare: 0, |
85 | | - }; |
86 | | - } |
87 | | - |
88 | | - let sum = 0; |
89 | | - let sumSquares = 0; |
90 | | - let min = values[0]; |
91 | | - let max = values[0]; |
92 | | - let zeroCount = 0; |
93 | | - |
94 | | - values.forEach((value) => { |
95 | | - sum += value; |
96 | | - sumSquares += value * value; |
97 | | - if (value < min) { |
98 | | - min = value; |
99 | | - } |
100 | | - if (value > max) { |
101 | | - max = value; |
102 | | - } |
103 | | - if (Math.abs(value) < 1e-9) { |
104 | | - zeroCount += 1; |
105 | | - } |
106 | | - }); |
107 | | - |
108 | | - const mean = sum / length; |
109 | | - let variance = 0; |
110 | | - values.forEach((value) => { |
111 | | - const delta = value - mean; |
112 | | - variance += delta * delta; |
113 | | - }); |
114 | | - variance /= length; |
115 | | - |
116 | | - return { |
117 | | - values, |
118 | | - dimensions: length, |
119 | | - magnitude: Math.sqrt(sumSquares), |
120 | | - mean, |
121 | | - deviation: Math.sqrt(variance), |
122 | | - min, |
123 | | - max, |
124 | | - zeroShare: length ? zeroCount / length : 0, |
125 | | - }; |
126 | | - } |
127 | | - |
128 | | - function updateSummary(stats) { |
129 | | - if (!summaryCells.length) { |
130 | | - return; |
131 | | - } |
132 | | - |
133 | | - const entries = [ |
134 | | - stats.dimensions.toLocaleString(), |
135 | | - formatDecimal(stats.magnitude), |
136 | | - formatDecimal(stats.mean), |
137 | | - formatDecimal(stats.deviation), |
138 | | - formatRange(stats.min, stats.max), |
139 | | - `${Math.round(stats.zeroShare * 100)}%`, |
140 | | - ]; |
141 | | - |
142 | | - entries.forEach((value, index) => { |
143 | | - if (summaryCells[index]) { |
144 | | - summaryCells[index].textContent = value; |
145 | | - } |
146 | | - }); |
147 | | - } |
148 | | - |
149 | | - function renderExtrema(listElement, entries, placeholder) { |
150 | | - if (!listElement) { |
151 | | - return; |
152 | | - } |
153 | | - |
154 | | - listElement.innerHTML = ''; |
155 | | - |
156 | | - if (!entries.length) { |
157 | | - const item = document.createElement('li'); |
158 | | - const text = document.createElement('span'); |
159 | | - text.className = 'placeholder'; |
160 | | - text.textContent = placeholder; |
161 | | - item.appendChild(text); |
162 | | - listElement.appendChild(item); |
163 | | - return; |
164 | | - } |
165 | | - |
166 | | - entries.forEach((entry) => { |
167 | | - const item = document.createElement('li'); |
168 | | - item.textContent = `#${entry.index}${formatDecimal(entry.value)}`; |
169 | | - listElement.appendChild(item); |
170 | | - }); |
171 | | - } |
172 | | - |
173 | | - function renderValueRows(values) { |
174 | | - if (!valueTableBody) { |
175 | | - return; |
176 | | - } |
177 | | - |
178 | | - valueTableBody.innerHTML = ''; |
179 | | - |
180 | | - if (!values.length) { |
181 | | - const row = document.createElement('tr'); |
182 | | - const cell = document.createElement('td'); |
183 | | - cell.colSpan = 3; |
184 | | - cell.innerHTML = '<p class="placeholder">Enter an embedding vector to populate this table.</p>'; |
185 | | - row.appendChild(cell); |
186 | | - valueTableBody.appendChild(row); |
187 | | - return; |
188 | | - } |
189 | | - |
190 | | - const maxAbs = values.reduce((max, value) => { |
191 | | - const magnitude = Math.abs(value); |
192 | | - return magnitude > max ? magnitude : max; |
193 | | - }, 0); |
194 | | - |
195 | | - values.forEach((value, index) => { |
196 | | - const row = document.createElement('tr'); |
197 | | - const indexCell = document.createElement('td'); |
198 | | - indexCell.textContent = `#${index + 1}`; |
199 | | - |
200 | | - const valueCell = document.createElement('td'); |
201 | | - const valueSpan = document.createElement('span'); |
202 | | - valueSpan.textContent = formatDecimal(value); |
203 | | - valueCell.appendChild(valueSpan); |
204 | | - |
205 | | - const normalizedCell = document.createElement('td'); |
206 | | - const normalizedSpan = document.createElement('span'); |
207 | | - const normalized = maxAbs > 0 ? Math.abs(value) / maxAbs : 0; |
208 | | - normalizedSpan.textContent = formatDecimal(normalized); |
209 | | - normalizedCell.appendChild(normalizedSpan); |
210 | | - |
211 | | - row.append(indexCell, valueCell, normalizedCell); |
212 | | - valueTableBody.appendChild(row); |
213 | | - }); |
214 | | - } |
215 | | - |
216 | | - function updateInsights(vector) { |
217 | | - const stats = calculateStats(vector); |
218 | | - updateSummary(stats); |
219 | | - |
220 | | - const entries = stats.values.map((value, index) => ({ index: index + 1, value })); |
221 | | - const positive = entries |
222 | | - .filter((entry) => entry.value > 0) |
223 | | - .sort((a, b) => b.value - a.value) |
224 | | - .slice(0, 3); |
225 | | - const negative = entries |
226 | | - .filter((entry) => entry.value < 0) |
227 | | - .sort((a, b) => a.value - b.value) |
228 | | - .slice(0, 3); |
229 | | - |
230 | | - renderExtrema(positiveList, positive, 'Positive values appear here.'); |
231 | | - renderExtrema(negativeList, negative, 'Negative values appear here.'); |
232 | | - renderValueRows(stats.values); |
233 | | - } |
234 | | - |
235 | | - function formatVectorForInput(values) { |
236 | | - return values.map((value) => formatDecimal(value)).join(', '); |
237 | | - } |
238 | | - |
239 | | - function setSampleMeta(description) { |
240 | | - if (!sampleMeta) { |
241 | | - return; |
242 | | - } |
243 | | - |
244 | | - sampleMeta.innerHTML = ''; |
245 | | - const paragraph = document.createElement('p'); |
246 | | - paragraph.textContent = description || 'Choose a sample to populate its description.'; |
247 | | - sampleMeta.appendChild(paragraph); |
248 | | - } |
249 | | - |
250 | | - function highlightSampleButtons() { |
251 | | - if (!sampleList) { |
252 | | - return; |
253 | | - } |
254 | | - |
255 | | - const buttons = sampleList.querySelectorAll('.sample-button'); |
256 | | - buttons.forEach((button) => { |
257 | | - const isActive = button.dataset.sampleId === activeSampleId; |
258 | | - button.classList.toggle('is-active', isActive); |
259 | | - button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); |
260 | | - }); |
261 | | - } |
262 | | - |
263 | | - function parseVectorInput(input) { |
264 | | - if (typeof input !== 'string') { |
265 | | - return []; |
266 | | - } |
267 | | - |
268 | | - return input |
269 | | - .split(/[,\s]+/) |
270 | | - .map((chunk) => Number.parseFloat(chunk)) |
271 | | - .filter((value) => Number.isFinite(value)); |
272 | | - } |
273 | | - |
274 | | - function selectSample(sample) { |
275 | | - if (!sample) { |
276 | | - activeSampleId = ''; |
277 | | - highlightSampleButtons(); |
278 | | - updateInsights([]); |
279 | | - setSampleMeta('Choose a sample to populate its description.'); |
280 | | - return; |
281 | | - } |
282 | | - |
283 | | - activeSampleId = sample.id; |
284 | | - highlightSampleButtons(); |
285 | | - const vector = Array.isArray(sample.vector) ? sample.vector : []; |
286 | | - updateInsights(vector); |
287 | | - setSampleMeta(sample.description || ''); |
288 | | - |
289 | | - if (embeddingInput) { |
290 | | - embeddingInput.value = formatVectorForInput(vector); |
291 | | - } |
292 | | - } |
293 | | - |
294 | | - function renderSampleToolbar() { |
295 | | - if (!sampleList) { |
296 | | - return; |
297 | | - } |
298 | | - |
299 | | - sampleList.innerHTML = ''; |
300 | | - |
301 | | - if (!samples.length) { |
302 | | - const placeholder = document.createElement('p'); |
303 | | - placeholder.className = 'placeholder'; |
304 | | - placeholder.textContent = 'No curated samples available.'; |
305 | | - sampleList.appendChild(placeholder); |
306 | | - return; |
307 | | - } |
308 | | - |
309 | | - const fragment = document.createDocumentFragment(); |
310 | | - samples.slice(0, 3).forEach((sample, index) => { |
311 | | - const button = document.createElement('button'); |
312 | | - button.type = 'button'; |
313 | | - button.className = 'sample-button'; |
314 | | - button.dataset.sampleId = sample.id; |
315 | | - button.textContent = sample.label || `Sample ${index + 1}`; |
316 | | - button.addEventListener('click', () => { |
317 | | - selectSample(sample); |
318 | | - }); |
319 | | - fragment.appendChild(button); |
320 | | - }); |
321 | | - |
322 | | - sampleList.appendChild(fragment); |
323 | | - } |
324 | | - |
325 | | - function setupSamples() { |
326 | | - if (!sampleList || !sampleMeta || !summaryGrid || !valueTableBody) { |
327 | | - return; |
328 | | - } |
329 | | - |
330 | | - renderSampleToolbar(); |
331 | | - if (samples.length) { |
332 | | - selectSample(samples[0]); |
333 | | - } else { |
334 | | - updateInsights([]); |
335 | | - } |
336 | | - } |
337 | | - |
338 | | - function setupManualInput() { |
339 | | - if (!updateButton || !embeddingInput) { |
340 | | - return; |
341 | | - } |
342 | | - |
343 | | - updateButton.addEventListener('click', () => { |
344 | | - const values = parseVectorInput(embeddingInput.value); |
345 | | - if (!values.length) { |
346 | | - activeSampleId = ''; |
347 | | - highlightSampleButtons(); |
348 | | - updateInsights([]); |
349 | | - setSampleMeta('Enter numbers separated by commas or spaces to update the insights.'); |
350 | | - return; |
351 | | - } |
352 | | - |
353 | | - activeSampleId = ''; |
354 | | - highlightSampleButtons(); |
355 | | - updateInsights(values); |
356 | | - setSampleMeta('Custom embedding applied.'); |
357 | | - }); |
358 | | - } |
359 | | - |
360 | 52 | const datasetCache = new Map(); |
361 | 53 | let activeDatasetId = ''; |
362 | 54 | let activeRecordId = ''; |
|
394 | 86 | const NEGATIVE_PLACEHOLDER = 'Negative values appear here.'; |
395 | 87 | const SAMPLE_PLACEHOLDER = 'Choose a sample to populate its description.'; |
396 | 88 | let activeSampleId = ''; |
397 | | - let currentVectorValues = []; |
398 | 89 |
|
399 | 90 | function safeText(value) { |
400 | 91 | return typeof value === 'string' ? value.trim() : ''; |
|
715 | 406 |
|
716 | 407 | if (!values.length) { |
717 | 408 | const row = document.createElement('tr'); |
718 | | - const indexCell = document.createElement('td'); |
719 | | - indexCell.textContent = '#1'; |
720 | | - const valueCell = document.createElement('td'); |
721 | | - const valueSpan = document.createElement('span'); |
722 | | - valueSpan.textContent = '0.000'; |
723 | | - valueCell.appendChild(valueSpan); |
724 | | - const normalizedCell = document.createElement('td'); |
725 | | - const normalizedSpan = document.createElement('span'); |
726 | | - normalizedSpan.textContent = '0.000'; |
727 | | - normalizedCell.appendChild(normalizedSpan); |
728 | | - row.append(indexCell, valueCell, normalizedCell); |
| 409 | + const cell = document.createElement('td'); |
| 410 | + cell.colSpan = 3; |
| 411 | + const message = document.createElement('p'); |
| 412 | + message.className = 'placeholder'; |
| 413 | + message.textContent = 'Enter an embedding vector to populate this table.'; |
| 414 | + cell.appendChild(message); |
| 415 | + row.appendChild(cell); |
729 | 416 | valueTable.appendChild(row); |
730 | 417 | return; |
731 | 418 | } |
|
777 | 464 |
|
778 | 465 | function updateSummaryForVector(values) { |
779 | 466 | const stats = computeVectorStats(values); |
780 | | - currentVectorValues = stats.values; |
781 | 467 | renderSummary(stats); |
782 | 468 |
|
783 | 469 | const entries = stats.values.map((value, index) => ({ index: index + 1, value })); |
|
2054 | 1740 | applyFilter(); |
2055 | 1741 | }); |
2056 | 1742 |
|
2057 | | - setupSamples(); |
2058 | | - setupManualInput(); |
2059 | 1743 | populateDatasetSelect(); |
2060 | 1744 |
|
2061 | 1745 | if (datasetSelect.value) { |
|
0 commit comments