const WASM_PATHS = [
'../../pkg/edgevec.js', '/pkg/edgevec.js', '../pkg/edgevec.js', './pkg/edgevec.js' ];
const WRAPPER_PATHS = [
'../../pkg/edgevec-wrapper.js',
'/pkg/edgevec-wrapper.js',
'../pkg/edgevec-wrapper.js',
'./pkg/edgevec-wrapper.js'
];
let wasmModule = null;
let wrapperModule = null;
let index = null;
let startTime = Date.now();
let totalInserted = 0;
function initBrowserInfo() {
const info = wrapperModule.getBrowserInfo();
const modeEl = document.getElementById('browserMode');
if (info.mode === 'modern') {
modeEl.textContent = 'Modern';
modeEl.classList.add('supported');
log(`Browser supports BigUint64Array - using optimal batch delete`, 'success');
} else {
modeEl.textContent = 'Compat';
modeEl.classList.remove('supported');
log(`Safari 14 compat mode - IDs limited to 2^53`, 'warn');
if (info.recommendation) {
log(info.recommendation, 'warn');
}
}
}
function log(msg, level = 'info') {
const now = new Date();
const elapsed = now - startTime;
const timestamp = new Date(elapsed).toISOString().substr(11, 8);
const logDiv = document.getElementById('terminalOutput');
const line = document.createElement('div');
line.className = 'log-line';
line.innerHTML = `
<span class="log-time">[${timestamp}]</span>
<span class="log-level ${level}">${level.toUpperCase()}</span>
<span class="log-message">${msg}</span>
`;
logDiv.appendChild(line);
logDiv.scrollTop = logDiv.scrollHeight;
}
function updateStats() {
if (!index) return;
try {
const liveCount = index.liveCount();
const deletedCount = index.deletedCount();
const total = liveCount + deletedCount;
const ratio = index.tombstoneRatio();
document.getElementById('totalVectors').textContent = total.toLocaleString();
document.getElementById('liveCount').textContent = liveCount.toLocaleString();
document.getElementById('deletedCount').textContent = deletedCount.toLocaleString();
document.getElementById('tombstoneRatio').textContent = `${(ratio * 100).toFixed(1)}%`;
const hasLive = liveCount > 0;
document.getElementById('batchDeleteBtn').disabled = !hasLive;
document.getElementById('deleteAllBtn').disabled = !hasLive;
document.getElementById('compactBtn').disabled = !index.needsCompaction();
const warning = index.compactionWarning();
if (warning) {
log(warning, 'warn');
}
} catch (e) {
log(`Stats update error: ${e.message}`, 'error');
}
}
async function setupIndex() {
const count = parseInt(document.getElementById('vectorCount').value);
try {
log(`Creating new EdgeVec index (128 dimensions)...`, 'info');
const config = new wasmModule.EdgeVecConfig(128);
config.m = 16;
config.ef_construction = 200;
index = new wasmModule.EdgeVec(config);
log(`Inserting ${count.toLocaleString()} random vectors...`, 'info');
const insertStart = performance.now();
totalInserted = 0;
for (let i = 0; i < count; i++) {
const vec = new Float32Array(128);
for (let j = 0; j < 128; j++) {
vec[j] = Math.random() * 2 - 1;
}
index.insert(vec);
totalInserted++;
if (totalInserted % 100 === 0) {
log(` Inserted ${totalInserted}/${count}...`, 'info');
}
}
const insertTime = (performance.now() - insertStart).toFixed(0);
log(`Setup complete: ${totalInserted} vectors in ${insertTime}ms`, 'success');
log(`Average: ${(parseFloat(insertTime) / totalInserted).toFixed(3)}ms per vector`, 'info');
updateStats();
} catch (e) {
log(`Setup failed: ${e.message}`, 'error');
console.error(e);
}
}
async function batchDelete() {
const batchSize = parseInt(document.getElementById('batchSize').value);
if (!index) {
log('No index - run Setup first', 'error');
return;
}
const liveCount = index.liveCount();
if (liveCount === 0) {
log('No live vectors to delete', 'warn');
return;
}
try {
const actualSize = Math.min(batchSize, liveCount);
log(`Generating ${actualSize} random IDs to delete...`, 'info');
const ids = [];
const used = new Set();
const totalCount = index.liveCount() + index.deletedCount();
while (ids.length < actualSize) {
const id = Math.floor(Math.random() * totalCount) + 1;
if (!used.has(id)) {
used.add(id);
ids.push(id);
}
}
log(`Calling softDeleteBatch with ${ids.length} IDs...`, 'info');
const deleteStart = performance.now();
const result = wrapperModule.softDeleteBatch(index, ids);
const deleteTime = (performance.now() - deleteStart).toFixed(3);
log(`Batch delete complete in ${deleteTime}ms`, 'success');
log(` Deleted: ${result.deleted}`, 'success');
log(` Already deleted: ${result.alreadyDeleted}`, 'info');
log(` Invalid IDs: ${result.invalidIds}`, result.invalidIds > 0 ? 'warn' : 'info');
log(` Total/Unique: ${result.total}/${result.uniqueCount}`, 'info');
const resultCard = document.getElementById('lastBatchResult');
resultCard.style.display = 'block';
document.getElementById('resultDeleted').textContent = result.deleted;
document.getElementById('resultAlreadyDeleted').textContent = result.alreadyDeleted;
document.getElementById('resultInvalidIds').textContent = result.invalidIds;
document.getElementById('resultTotalUnique').textContent = `${result.total} / ${result.uniqueCount}`;
document.getElementById('resultDuration').textContent = `${deleteTime}ms`;
const badge = document.getElementById('resultBadge');
if (result.allValid()) {
badge.textContent = 'All Valid';
badge.className = 'result-badge badge-success';
} else if (result.anyDeleted()) {
badge.textContent = 'Partial Success';
badge.className = 'result-badge badge-warning';
} else {
badge.textContent = 'No Deletions';
badge.className = 'result-badge badge-error';
}
updateStats();
} catch (e) {
log(`Batch delete failed: ${e.message}`, 'error');
console.error(e);
}
}
async function deleteAllLive() {
if (!index) {
log('No index - run Setup first', 'error');
return;
}
const liveCount = index.liveCount();
if (liveCount === 0) {
log('No live vectors to delete', 'warn');
return;
}
try {
log(`Deleting all ${liveCount} live vectors...`, 'warn');
const totalCount = index.liveCount() + index.deletedCount();
const allIds = Array.from({ length: totalCount }, (_, i) => i + 1);
const deleteStart = performance.now();
const result = wrapperModule.softDeleteBatch(index, allIds);
const deleteTime = (performance.now() - deleteStart).toFixed(3);
log(`Delete all complete in ${deleteTime}ms`, 'success');
log(` Deleted: ${result.deleted}`, 'success');
log(` Already deleted: ${result.alreadyDeleted}`, 'info');
updateStats();
} catch (e) {
log(`Delete all failed: ${e.message}`, 'error');
console.error(e);
}
}
async function compactIndex() {
if (!index) {
log('No index - run Setup first', 'error');
return;
}
if (!index.needsCompaction()) {
log('Compaction not needed (tombstone ratio below threshold)', 'info');
return;
}
try {
log('Starting compaction...', 'warn');
const result = index.compact();
log(`Compaction complete in ${result.durationMs}ms`, 'success');
log(` Tombstones removed: ${result.tombstonesRemoved}`, 'success');
log(` New size: ${result.newSize}`, 'info');
const compactCard = document.getElementById('compactionResult');
compactCard.style.display = 'block';
document.getElementById('compactRemoved').textContent = result.tombstonesRemoved;
document.getElementById('compactNewSize').textContent = result.newSize;
document.getElementById('compactDuration').textContent = `${result.durationMs}ms`;
updateStats();
} catch (e) {
log(`Compaction failed: ${e.message}`, 'error');
console.error(e);
}
}
function resetDemo() {
log('Resetting demo...', 'info');
if (index) {
index.free();
index = null;
}
totalInserted = 0;
document.getElementById('totalVectors').textContent = '0';
document.getElementById('liveCount').textContent = '0';
document.getElementById('deletedCount').textContent = '0';
document.getElementById('tombstoneRatio').textContent = '0%';
document.getElementById('lastBatchResult').style.display = 'none';
document.getElementById('compactionResult').style.display = 'none';
const logDiv = document.getElementById('terminalOutput');
logDiv.innerHTML = '';
startTime = Date.now();
log('Demo reset complete', 'success');
updateStats();
}
async function initDemo() {
try {
log('Initializing WASM module...', 'info');
for (const path of WASM_PATHS) {
try {
log(` Trying: ${path}`, 'info');
wasmModule = await import(path);
log(` Loaded WASM from: ${path}`, 'success');
break;
} catch (e) {
log(` Failed: ${e.message}`, 'warn');
}
}
if (!wasmModule) {
throw new Error('Could not load WASM module from any path');
}
for (const path of WRAPPER_PATHS) {
try {
log(` Trying wrapper: ${path}`, 'info');
wrapperModule = await import(path);
log(` Loaded wrapper from: ${path}`, 'success');
break;
} catch (e) {
log(` Failed: ${e.message}`, 'warn');
}
}
if (!wrapperModule) {
throw new Error('Could not load wrapper module from any path');
}
await wasmModule.default();
log('WASM module initialized successfully', 'success');
initBrowserInfo();
document.getElementById('setupBtn').addEventListener('click', setupIndex);
document.getElementById('batchDeleteBtn').addEventListener('click', batchDelete);
document.getElementById('deleteAllBtn').addEventListener('click', deleteAllLive);
document.getElementById('compactBtn').addEventListener('click', compactIndex);
document.getElementById('resetBtn').addEventListener('click', resetDemo);
log('Demo ready - click "Setup Index" to begin', 'success');
} catch (e) {
log(`Initialization failed: ${e.message}`, 'error');
log('', 'error');
log('═══════════════════════════════════════════════════', 'error');
log(' HOW TO FIX: Start server from PROJECT ROOT', 'error');
log('═══════════════════════════════════════════════════', 'error');
log('', 'error');
log('1. Open terminal in the edgevec project root folder', 'info');
log('2. Run: python -m http.server 8080', 'info');
log('3. Open: http://localhost:8080/wasm/examples/index.html', 'info');
log('', 'error');
log('DO NOT start server from wasm/examples/ folder!', 'warn');
log('The WASM module is at /pkg/ which requires root access.', 'warn');
console.error(e);
document.getElementById('wasmStatus').textContent = 'Failed';
document.getElementById('wasmStatus').classList.remove('supported');
}
}
initDemo();