Skip to content

Commit e871db4

Browse files
authored
Merge pull request #107 from DenisValeev/codex/fix-embedding-explorer-test-failure
2 parents 5ef21bd + 6b37dc4 commit e871db4

3 files changed

Lines changed: 689 additions & 11 deletions

File tree

apps/embedding-explorer/app.js

Lines changed: 341 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
(function () {
2+
const samples = Array.isArray(window.embeddingExplorerSamples)
3+
? window.embeddingExplorerSamples
4+
: [];
25
const sources = Array.isArray(window.embeddingSources) ? window.embeddingSources : [];
6+
7+
const sampleList = document.querySelector('[data-sample-list]');
8+
const sampleMeta = document.querySelector('[data-sample-meta]');
9+
const summaryGrid = document.querySelector('[data-summary-grid]');
10+
const embeddingInput = document.querySelector('[data-embedding-input]');
11+
const updateButton = document.querySelector('[data-update-button]');
12+
const positiveList = document.querySelector('[data-positive-list]');
13+
const negativeList = document.querySelector('[data-negative-list]');
14+
const valueTable = document.querySelector('[data-value-table]');
15+
316
const datasetSelect = document.querySelector('[data-dataset-select]');
417
const datasetInfo = document.querySelector('[data-dataset-info]');
518
const recordList = document.querySelector('[data-record-list]');
@@ -8,15 +21,334 @@
821
const previewCanvas = document.querySelector('[data-preview-canvas]');
922
const previewMeta = document.querySelector('[data-preview-meta]');
1023

11-
if (
12-
!datasetSelect ||
13-
!datasetInfo ||
14-
!recordList ||
15-
!recordStatus ||
16-
!filterInput ||
17-
!previewCanvas ||
18-
!previewMeta
19-
) {
24+
const hasSampleElements =
25+
sampleList &&
26+
sampleMeta &&
27+
summaryGrid &&
28+
embeddingInput &&
29+
updateButton &&
30+
positiveList &&
31+
negativeList &&
32+
valueTable;
33+
34+
const hasDatasetElements =
35+
datasetSelect &&
36+
datasetInfo &&
37+
recordList &&
38+
recordStatus &&
39+
filterInput &&
40+
previewCanvas &&
41+
previewMeta;
42+
43+
if (!hasSampleElements && !hasDatasetElements) {
44+
return;
45+
}
46+
47+
if (hasSampleElements) {
48+
let activeSampleId = '';
49+
50+
function formatNumber(value, decimals) {
51+
if (!Number.isFinite(value)) {
52+
return (0).toFixed(decimals);
53+
}
54+
return value.toFixed(decimals);
55+
}
56+
57+
function parseVector(raw) {
58+
if (!raw) {
59+
return [];
60+
}
61+
62+
return raw
63+
.split(/[\s,]+/)
64+
.map((token) => Number.parseFloat(token))
65+
.filter((value) => Number.isFinite(value));
66+
}
67+
68+
function computeSummary(vector) {
69+
const count = vector.length;
70+
if (!count) {
71+
return {
72+
count: 0,
73+
magnitude: 0,
74+
mean: 0,
75+
stdDev: 0,
76+
min: 0,
77+
max: 0,
78+
zeroShare: 0,
79+
};
80+
}
81+
82+
const sum = vector.reduce((total, value) => total + value, 0);
83+
const sumSquares = vector.reduce((total, value) => total + value * value, 0);
84+
const mean = sum / count;
85+
const magnitude = Math.sqrt(sumSquares);
86+
const variance =
87+
vector.reduce((total, value) => {
88+
const diff = value - mean;
89+
return total + diff * diff;
90+
}, 0) / count;
91+
const stdDev = Math.sqrt(variance);
92+
const min = Math.min(...vector);
93+
const max = Math.max(...vector);
94+
const zeroShare = (vector.filter((value) => value === 0).length / count) * 100;
95+
96+
return { count, magnitude, mean, stdDev, min, max, zeroShare };
97+
}
98+
99+
function updateSummary(vector) {
100+
const summaryValues = summaryGrid.querySelectorAll('dd');
101+
if (summaryValues.length < 6) {
102+
return;
103+
}
104+
105+
const stats = computeSummary(vector);
106+
summaryValues[0].textContent = stats.count.toLocaleString();
107+
summaryValues[1].textContent = formatNumber(stats.magnitude, 3);
108+
summaryValues[2].textContent = formatNumber(stats.mean, 3);
109+
summaryValues[3].textContent = formatNumber(stats.stdDev, 3);
110+
if (vector.length) {
111+
summaryValues[4].textContent = `${formatNumber(stats.min, 3)} / ${formatNumber(stats.max, 3)}`;
112+
summaryValues[5].textContent = `${Math.round(stats.zeroShare)}%`;
113+
} else {
114+
summaryValues[4].textContent = '— / —';
115+
summaryValues[5].textContent = '0%';
116+
}
117+
}
118+
119+
function renderExtremaList(target, items, emptyMessage) {
120+
target.innerHTML = '';
121+
if (!items.length) {
122+
const item = document.createElement('li');
123+
const span = document.createElement('span');
124+
span.className = 'placeholder';
125+
span.textContent = emptyMessage;
126+
item.appendChild(span);
127+
target.appendChild(item);
128+
return;
129+
}
130+
131+
const fragment = document.createDocumentFragment();
132+
items.forEach((entry) => {
133+
const li = document.createElement('li');
134+
li.textContent = `#${entry.index}${formatNumber(entry.value, 3)}`;
135+
fragment.appendChild(li);
136+
});
137+
target.appendChild(fragment);
138+
}
139+
140+
function updateExtrema(vector) {
141+
const entries = vector.map((value, index) => ({
142+
value,
143+
index: index + 1,
144+
}));
145+
146+
const positives = entries
147+
.filter((entry) => entry.value > 0)
148+
.sort((a, b) => b.value - a.value)
149+
.slice(0, 5);
150+
151+
const negatives = entries
152+
.filter((entry) => entry.value < 0)
153+
.sort((a, b) => a.value - b.value)
154+
.slice(0, 5);
155+
156+
renderExtremaList(positiveList, positives, 'No positive values found.');
157+
renderExtremaList(negativeList, negatives, 'No negative values found.');
158+
}
159+
160+
function updateValueTable(vector) {
161+
const body = valueTable;
162+
if (!body) {
163+
return;
164+
}
165+
166+
body.innerHTML = '';
167+
168+
if (!vector.length) {
169+
const emptyRow = document.createElement('tr');
170+
const idCell = document.createElement('td');
171+
idCell.textContent = '#1';
172+
const valueCell = document.createElement('td');
173+
const valueSpan = document.createElement('span');
174+
valueSpan.textContent = '0.000';
175+
valueCell.appendChild(valueSpan);
176+
const normCell = document.createElement('td');
177+
const normSpan = document.createElement('span');
178+
normSpan.textContent = '0.000';
179+
normCell.appendChild(normSpan);
180+
emptyRow.append(idCell, valueCell, normCell);
181+
body.appendChild(emptyRow);
182+
return;
183+
}
184+
185+
const fragment = document.createDocumentFragment();
186+
const maxAbs = vector.reduce((largest, value) => Math.max(largest, Math.abs(value)), 0);
187+
188+
vector.forEach((value, index) => {
189+
const row = document.createElement('tr');
190+
191+
const idCell = document.createElement('td');
192+
idCell.textContent = `#${index + 1}`;
193+
194+
const valueCell = document.createElement('td');
195+
const valueSpan = document.createElement('span');
196+
valueSpan.textContent = formatNumber(value, 3);
197+
valueCell.appendChild(valueSpan);
198+
199+
const normCell = document.createElement('td');
200+
const normSpan = document.createElement('span');
201+
const normalized = maxAbs ? value / maxAbs : 0;
202+
normSpan.textContent = formatNumber(normalized, 3);
203+
normCell.appendChild(normSpan);
204+
205+
row.append(idCell, valueCell, normCell);
206+
fragment.appendChild(row);
207+
});
208+
209+
body.appendChild(fragment);
210+
}
211+
212+
function applyVector(vector) {
213+
const cleanVector = vector.filter((value) => Number.isFinite(value));
214+
updateSummary(cleanVector);
215+
updateExtrema(cleanVector);
216+
updateValueTable(cleanVector);
217+
}
218+
219+
function formatVectorForInput(vector) {
220+
return vector.map((value) => (Number.isFinite(value) ? value.toFixed(4) : '0.0000')).join(', ');
221+
}
222+
223+
function setSampleMeta(sample, stats) {
224+
sampleMeta.innerHTML = '';
225+
const description = document.createElement('p');
226+
description.textContent = sample?.description || 'No description provided for this sample.';
227+
sampleMeta.appendChild(description);
228+
229+
if (stats) {
230+
const detail = document.createElement('p');
231+
detail.className = 'panel-sub';
232+
detail.textContent = `${stats.count.toLocaleString()} dimensions · magnitude ${formatNumber(stats.magnitude, 3)}`;
233+
sampleMeta.appendChild(detail);
234+
}
235+
}
236+
237+
function clearSampleButtons() {
238+
const buttons = sampleList.querySelectorAll('.sample-button');
239+
buttons.forEach((button) => {
240+
button.dataset.active = 'false';
241+
button.setAttribute('aria-pressed', 'false');
242+
});
243+
}
244+
245+
function setActiveSample(sampleId) {
246+
const sample = samples.find((entry) => entry.id === sampleId);
247+
if (!sample) {
248+
return;
249+
}
250+
251+
const vector = Array.isArray(sample.vector) ? sample.vector.map((value) => Number(value)) : [];
252+
const stats = computeSummary(vector);
253+
activeSampleId = sampleId;
254+
255+
applyVector(vector);
256+
setSampleMeta(sample, stats);
257+
258+
const buttons = sampleList.querySelectorAll('.sample-button');
259+
buttons.forEach((button) => {
260+
const isActive = button.dataset.sampleId === sampleId;
261+
button.dataset.active = isActive ? 'true' : 'false';
262+
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
263+
});
264+
265+
if (embeddingInput) {
266+
embeddingInput.value = formatVectorForInput(vector);
267+
}
268+
}
269+
270+
function renderSampleButtons() {
271+
sampleList.innerHTML = '';
272+
273+
if (!samples.length) {
274+
const placeholder = document.createElement('p');
275+
placeholder.className = 'placeholder';
276+
placeholder.textContent = 'No sample embeddings are configured.';
277+
sampleList.appendChild(placeholder);
278+
sampleMeta.innerHTML = '<p class="placeholder">Add sample embeddings to explore them here.</p>';
279+
applyVector([]);
280+
return;
281+
}
282+
283+
const fragment = document.createDocumentFragment();
284+
samples.forEach((sample) => {
285+
const button = document.createElement('button');
286+
button.type = 'button';
287+
button.className = 'sample-button';
288+
button.dataset.sampleId = sample.id;
289+
button.dataset.active = 'false';
290+
button.setAttribute('aria-pressed', 'false');
291+
button.textContent = sample.label;
292+
fragment.appendChild(button);
293+
});
294+
295+
sampleList.appendChild(fragment);
296+
setActiveSample(samples[0].id);
297+
}
298+
299+
function updateFromInput() {
300+
const vector = parseVector(embeddingInput.value);
301+
if (!vector.length) {
302+
activeSampleId = '';
303+
clearSampleButtons();
304+
sampleMeta.innerHTML = '<p class="placeholder">Enter at least one number to generate insights.</p>';
305+
applyVector([]);
306+
return;
307+
}
308+
309+
activeSampleId = '';
310+
clearSampleButtons();
311+
const stats = computeSummary(vector);
312+
applyVector(vector);
313+
314+
sampleMeta.innerHTML = '';
315+
const message = document.createElement('p');
316+
message.textContent = 'Custom vector entered manually.';
317+
const detail = document.createElement('p');
318+
detail.className = 'panel-sub';
319+
detail.textContent = `${stats.count.toLocaleString()} dimensions · magnitude ${formatNumber(stats.magnitude, 3)}`;
320+
sampleMeta.append(message, detail);
321+
}
322+
323+
sampleList.addEventListener('click', (event) => {
324+
const button = event.target.closest('.sample-button');
325+
if (!button) {
326+
return;
327+
}
328+
329+
const sampleId = button.dataset.sampleId;
330+
if (!sampleId || sampleId === activeSampleId) {
331+
return;
332+
}
333+
334+
setActiveSample(sampleId);
335+
});
336+
337+
updateButton.addEventListener('click', () => {
338+
updateFromInput();
339+
});
340+
341+
embeddingInput.addEventListener('keydown', (event) => {
342+
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
343+
event.preventDefault();
344+
updateFromInput();
345+
}
346+
});
347+
348+
renderSampleButtons();
349+
}
350+
351+
if (!hasDatasetElements) {
20352
return;
21353
}
22354

0 commit comments

Comments
 (0)