// Minimal App State let presets = []; let activePreset = null; const $ = (id) => document.getElementById(id); const els = { presetSelect: $('preset-select'), btnRun: $('btn-run'), summaryBar: $('summary-bar'), sumSpeedup: $('sum-speedup'), sumTimes: $('sum-times'), // Left: Parallel Constrained timerParallel: $('timer-parallel'), parallelOutput: $('parallel-output'), bodyParallel: $('body-parallel'), // Right: Naive timerNaive: $('timer-naive'), streamOutput: $('stream-output'), bodyNaive: $('body-naive'), badgeNaiveHallucinated: $('badge-naive-hallucinated'), }; // Synchronized scrolling for side-by-side comparison let isSyncingNaive = false; let isSyncingParallel = false; function setupScrollSync() { if (els.bodyNaive && els.bodyParallel) { els.bodyNaive.addEventListener('scroll', () => { if (isSyncingNaive) return; isSyncingParallel = true; els.bodyParallel.scrollTop = els.bodyNaive.scrollTop; els.bodyParallel.scrollLeft = els.bodyNaive.scrollLeft; requestAnimationFrame(() => { isSyncingParallel = false; }); }); els.bodyParallel.addEventListener('scroll', () => { if (isSyncingParallel) return; isSyncingNaive = true; els.bodyNaive.scrollTop = els.bodyParallel.scrollTop; els.bodyNaive.scrollLeft = els.bodyParallel.scrollLeft; requestAnimationFrame(() => { isSyncingNaive = false; }); }); } } function escapeHtml(str) { if (typeof str !== 'string') str = String(str); return str .replace(/&/g, '&') .replace(//g, '>'); } function escapeAttr(str) { if (typeof str !== 'string') str = String(str); return str .replace(/&/g, '&') .replace(/"/g, '"'); } function formatValue(val) { if (val === null || val === undefined) { return 'null'; } if (typeof val === 'boolean') { return `${val}`; } if (typeof val === 'number') { return `${val}`; } if (typeof val === 'string') { return `"${escapeHtml(val)}"`; } if (Array.isArray(val)) { return `[${val.map(formatValue).join(', ')}]`; } return `${escapeHtml(JSON.stringify(val))}`; } // Single-line JSON formatters so keys line up line-for-line across both panels function formatParallelJson(obj) { if (!obj || typeof obj !== 'object') return '
{ }
'; const schemaKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : Object.keys(obj); const keys = schemaKeys.filter(k => k in obj); Object.keys(obj).forEach(k => { if (!keys.includes(k)) keys.push(k); }); const lines = ['
{
']; keys.forEach((key, idx) => { const isLast = idx === keys.length - 1; const comma = isLast ? '' : ','; const val = obj[key]; let inner = ''; if (val && typeof val === 'object' && 'value' in val && 'prob' in val) { const valHtml = formatValue(val.value); const probNum = typeof val.prob === 'number' ? val.prob.toFixed(4) : escapeHtml(val.prob); inner = ` "${escapeHtml(key)}": { "value": ${valHtml}, "prob": ${probNum} }${comma}`; } else { inner = ` "${escapeHtml(key)}": ${formatValue(val)}${comma}`; } lines.push(`
${inner}
`); }); lines.push('
}
'); return lines.join(''); } function formatNaiveJson(obj) { if (!obj || typeof obj !== 'object') return '
{ }
'; const schemaKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : Object.keys(obj); const keys = schemaKeys.filter(k => k in obj); Object.keys(obj).forEach(k => { if (!keys.includes(k)) keys.push(k); }); const lines = ['
{
']; keys.forEach((key, idx) => { const isLast = idx === keys.length - 1; const comma = isLast ? '' : ','; const val = obj[key]; const inner = ` "${escapeHtml(key)}": ${formatValue(val)}${comma}`; lines.push(`
${inner}
`); }); lines.push('
}
'); return lines.join(''); } function highlightStreamJson(text) { if (!text) return ''; const lines = text.split('\n'); const rendered = lines.map((line, idx) => { const isLastLine = idx === lines.length - 1; let lineHtml = escapeHtml(line); if (lineHtml.includes('":')) { lineHtml = lineHtml.replace(/"([^"]+)"(\s*:)/g, '"$1"$2'); } else if (isLastLine && /^(\s*)"([^"]*)$/.test(lineHtml)) { lineHtml = lineHtml.replace(/^(\s*)"([^"]*)$/, '$1"$2'); } else if (isLastLine && /^(\s*)"([^"]+)"(\s*)$/.test(lineHtml)) { lineHtml = lineHtml.replace(/^(\s*)"([^"]+)"(\s*)$/, '$1"$2"$3'); } return lineHtml; }); return rendered.join('\n'); } function setupRowHoverSync() { document.addEventListener('mouseover', (e) => { const row = e.target.closest('.json-row'); if (row && row.dataset.key) { const key = row.dataset.key; document.querySelectorAll(`.json-row[data-key="${CSS.escape(key)}"]`).forEach(el => { el.classList.add('row-hover'); }); } }); document.addEventListener('mouseout', (e) => { const row = e.target.closest('.json-row'); if (row && row.dataset.key) { const key = row.dataset.key; document.querySelectorAll(`.json-row[data-key="${CSS.escape(key)}"]`).forEach(el => { el.classList.remove('row-hover'); }); } }); } // Initialize async function init() { setupScrollSync(); setupRowHoverSync(); try { const res = await fetch('/api/presets'); presets = await res.json(); els.presetSelect.innerHTML = ''; presets.forEach((p) => { const opt = document.createElement('option'); opt.value = p.id; opt.textContent = `${p.title}`; els.presetSelect.appendChild(opt); }); if (presets.length > 0) { activePreset = presets[0]; } } catch (e) { console.error('Error loading presets:', e); } els.presetSelect.addEventListener('change', (e) => { activePreset = presets.find(p => p.id === e.target.value); reset(); }); els.btnRun.addEventListener('click', runComparison); } function reset() { els.timerNaive.textContent = '0.0 ms'; els.timerParallel.textContent = '0.0 ms'; els.streamOutput.innerHTML = 'Click "Run Comparison" to start...'; els.parallelOutput.innerHTML = 'Click "Run Comparison" to start...'; els.summaryBar.classList.add('hidden'); if (els.badgeNaiveHallucinated) { els.badgeNaiveHallucinated.classList.add('hidden'); els.badgeNaiveHallucinated.textContent = ''; } } // Execute Parallel Constrained Generation async function runParallel(payload) { els.parallelOutput.innerHTML = '// Evaluating parallel forward pass across all schema fields...'; const t0 = performance.now(); let running = true; function tickParallel() { if (!running) return; const ms = performance.now() - t0; els.timerParallel.textContent = `${ms.toFixed(1)} ms`; requestAnimationFrame(tickParallel); } requestAnimationFrame(tickParallel); try { const res = await fetch('/api/run-parallel', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await res.json(); running = false; const elapsed = data.elapsed_ms || (performance.now() - t0); els.timerParallel.textContent = `${elapsed.toFixed(1)} ms`; renderParallel(data); return data; } catch (err) { running = false; throw err; } } function renderParallel(data) { if (data.parsed_json) { els.parallelOutput.innerHTML = formatParallelJson(data.parsed_json); } } // Stream Naive async function streamNaive(payload) { els.streamOutput.textContent = ''; const t0 = performance.now(); let running = true; function tickNaive() { if (!running) return; const ms = performance.now() - t0; els.timerNaive.textContent = `${ms.toFixed(1)} ms`; requestAnimationFrame(tickNaive); } requestAnimationFrame(tickNaive); let tokenCount = 0; let finalResult = null; try { const response = await fetch('/api/stream-naive', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); let text = ''; while (true) { const { value, done } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const ev = JSON.parse(line.substring(6)); if (ev.type === 'token') { text += ev.token; tokenCount = ev.token_count; els.streamOutput.innerHTML = highlightStreamJson(text); els.streamOutput.scrollTop = els.streamOutput.scrollHeight; } else if (ev.type === 'done') { finalResult = ev.result; } } catch (e) {} } } } } finally { running = false; } if (finalResult) { els.timerNaive.textContent = `${finalResult.elapsed_ms.toFixed(1)} ms`; let parsedObj = finalResult.parsed_json; if (parsedObj) { els.streamOutput.innerHTML = formatNaiveJson(parsedObj); } else { try { const cleaned = text.substring(text.indexOf('{'), text.lastIndexOf('}') + 1); parsedObj = JSON.parse(cleaned); els.streamOutput.innerHTML = formatNaiveJson(parsedObj); } catch (e) { els.streamOutput.innerHTML = highlightStreamJson(text); } } // Check for missing / hallucinated fields const expectedKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : []; let missingCount = 0; if (parsedObj && typeof parsedObj === 'object') { const parsedKeys = Object.keys(parsedObj); const missing = expectedKeys.filter(k => !parsedKeys.includes(k)); const extra = parsedKeys.filter(k => !expectedKeys.includes(k)); const invalidEnums = finalResult.invalid_enums || []; missingCount = missing.length + extra.length + invalidEnums.length; } else { missingCount = expectedKeys.length; } if (missingCount > 0 && els.badgeNaiveHallucinated) { els.badgeNaiveHallucinated.textContent = `${missingCount} field${missingCount > 1 ? 's' : ''} hallucinated`; els.badgeNaiveHallucinated.classList.remove('hidden'); } } return finalResult; } // Side-by-Side Comparison Runner async function runComparison() { if (!activePreset) return; reset(); els.btnRun.disabled = true; els.btnRun.textContent = 'Running...'; const payload = { context: activePreset.context, schema: activePreset.schema }; try { // 1. Run Parallel Constrained inference first const parallelData = await runParallel(payload); // 2. Stream Naive right after const naiveData = await streamNaive(payload); // 3. Update summary pill if (parallelData && naiveData) { const speedup = (naiveData.elapsed_ms / Math.max(parallelData.elapsed_ms, 1.0)).toFixed(1); els.sumSpeedup.textContent = `${speedup}x FASTER`; els.sumTimes.textContent = `${parallelData.elapsed_ms.toFixed(1)} ms vs ${naiveData.elapsed_ms.toFixed(1)} ms`; els.summaryBar.classList.remove('hidden'); } } catch (err) { console.error('Run failed:', err); } finally { els.btnRun.disabled = false; els.btnRun.innerHTML = '⚡ Run Comparison'; } } document.addEventListener('DOMContentLoaded', init);