const API_BASE = window.location.hostname === 'localhost' ? 'http://localhost:8000' : '' // ══════════════════════════════════════════════════════════════════════════════ // 1. CONSTANTS AND CONFIGURATION // ══════════════════════════════════════════════════════════════════════════════ const GROUPS = [ { id: 'deterministic', label: 'Group 1 — Deterministic', strategies: ['greedy', 'beam_search'] }, { id: 'sampling', label: 'Group 2 — Pure Sampling', strategies: ['sampling', 'temperature'] }, { id: 'basic-constrained', label: 'Group 3A — Basic Constrained', strategies: ['top_k', 'top_p'] }, { id: 'hybrid-constrained', label: 'Group 3B — Hybrid Constrained', strategies: ['top_k_top_p', 'temp_top_k', 'temp_top_p', 'temp_top_k_top_p'] } ] const STRATEGY_META = { greedy: { label: 'Greedy', group: 'deterministic' }, beam_search: { label: 'Beam Search', group: 'deterministic' }, sampling: { label: 'Random Sampling', group: 'sampling' }, temperature: { label: 'Temperature', group: 'sampling' }, top_k: { label: 'Top-k', group: 'basic-constrained' }, top_p: { label: 'Top-p', group: 'basic-constrained' }, top_k_top_p: { label: 'Top-k + Top-p', group: 'hybrid-constrained' }, temp_top_k: { label: 'Temp + Top-k', group: 'hybrid-constrained' }, temp_top_p: { label: 'Temp + Top-p', group: 'hybrid-constrained' }, temp_top_k_top_p: { label: 'Temp + Top-k + Top-p', group: 'hybrid-constrained' }, } const allOutputs = {} let analysisReady = false // ══════════════════════════════════════════════════════════════════════════════ // 2. BACKEND HEALTH CHECK // ══════════════════════════════════════════════════════════════════════════════ async function checkBackendHealth() { try { await fetch(`${API_BASE}/health`) } catch { console.warn('Backend not reachable.') } } checkBackendHealth() // ══════════════════════════════════════════════════════════════════════════════ // 3. LIVE TOKEN COUNTER // ══════════════════════════════════════════════════════════════════════════════ let encoder = null try { encoder = tiktoken.get_encoding('cl100k_base') } catch (error) { console.warn('Tiktoken failed to load, falling back to approximation:', error) } function countTokens(text) { if (!text || text.trim() === '') return 0 if (encoder) return encoder.encode(text).length return Math.max(1, Math.ceil(text.length / 4)) } const promptInput = document.getElementById('prompt-input') const tokenCountDisplay = document.getElementById('token-count') promptInput.addEventListener('input', function(event) { const text = event.target.value tokenCountDisplay.textContent = countTokens(text) }) // ══════════════════════════════════════════════════════════════════════════════ // 4. COLLECT PARAMS AND VALIDATE // ══════════════════════════════════════════════════════════════════════════════ const runAllBtn = document.getElementById('run-all-btn') const runAnalysisBtn = document.getElementById('run-analysis-btn') function collectParams() { return { prompt: promptInput.value.trim(), max_tokens: parseInt(document.getElementById('max-tokens').value, 10), beam_size: parseInt(document.getElementById('param-beam-size').value, 10), top_k: parseInt(document.getElementById('param-top-k').value, 10), top_p: parseFloat(document.getElementById('param-top-p').value), temperature: parseFloat(document.getElementById('param-temperature').value), tktp_k: parseInt(document.getElementById('param-tktp-k').value, 10), tktp_p: parseFloat(document.getElementById('param-tktp-p').value), ttk_temp: parseFloat(document.getElementById('param-ttk-temp').value), ttk_k: parseInt(document.getElementById('param-ttk-k').value, 10), ttp_temp: parseFloat(document.getElementById('param-ttp-temp').value), ttp_p: parseFloat(document.getElementById('param-ttp-p').value), ttkp_temp: parseFloat(document.getElementById('param-ttkp-temp').value), ttkp_k: parseInt(document.getElementById('param-ttkp-k').value, 10), ttkp_p: parseFloat(document.getElementById('param-ttkp-p').value), } } function validateParams(params) { if (!params.prompt) { alert('Please enter a prompt before running.') return false } if (isNaN(params.max_tokens) || params.max_tokens < 1) { alert('Max tokens must be a valid number greater than 0.') return false } if (isNaN(params.top_p) || params.top_p < 0 || params.top_p > 1) { alert('Top-p must be between 0 and 1.') return false } if (isNaN(params.temperature) || params.temperature < 0) { alert('Temperature must be a valid positive number.') return false } return true } // ══════════════════════════════════════════════════════════════════════════════ // 5. CARD STATE MANAGEMENT // ══════════════════════════════════════════════════════════════════════════════ function setCardState(strategy, state) { /* Sets the visual state of a strategy card. The streaming display inside the card also shows the current state until actual tokens arrive. */ const card = document.querySelector(`[data-strategy="${strategy}"]`) if (!card) return card.classList.remove('pending', 'running', 'done', 'error') card.classList.add(state) const streamDisplay = document.getElementById(`stream-${strategy}`) if (!streamDisplay) return if (state === 'pending') { streamDisplay.innerHTML = ` Pending... ` } if (state === 'running') { streamDisplay.innerHTML = ` Running... ` } if (state === 'error') { streamDisplay.innerHTML = ` Failed ` } } function setAllCardsPending() { Object.keys(STRATEGY_META).forEach(function(strategy) { setCardState(strategy, 'pending') const card = document.querySelector(`[data-strategy="${strategy}"]`) if (!card) return card.querySelector('.col-token-count').textContent = '—' card.querySelector('.col-latency').textContent = '—' card.querySelector('.col-toks').textContent = '—' card.querySelector('.col-finish').textContent = '—' const groupOutput = document.getElementById(`output-text-${strategy}`) if (groupOutput) { groupOutput.textContent = 'Waiting for results...' groupOutput.style.color = '' } }) } // ══════════════════════════════════════════════════════════════════════════════ // 6. RUN ALL — MAIN ORCHESTRATOR // ══════════════════════════════════════════════════════════════════════════════ async function runAll() { const params = collectParams() if (!validateParams(params)) return runAllBtn.disabled = true runAllBtn.textContent = '⏳ Running...' runAnalysisBtn.disabled = true analysisReady = false setAllCardsPending() clearGroupPanels() try { for (const group of GROUPS) { await runGroup(group, params) } updateTokenPerSec(Object.values(allOutputs)) analysisReady = true runAnalysisBtn.disabled = false runAnalysisBtn.textContent = '🔬 Run Analysis' } catch (error) { console.error('Run All failed:', error) } runAllBtn.disabled = false runAllBtn.textContent = '⚡ Run All Strategies' } async function runGroup(group, params) { for (const strategy of group.strategies) { await runStrategy(strategy, params) const output = allOutputs[strategy] if (output && !output.error && output.tokens) { await streamTokensForStrategy(strategy, output.tokens) } } group.strategies.forEach(function(strategy) { const output = allOutputs[strategy] if (!output || output.error) return const groupOutputText = document.getElementById(`output-text-${strategy}`) if (groupOutputText) { groupOutputText.textContent = output.text groupOutputText.style.color = '' } }) const groupOutputs = group.strategies .map(function(s) { return allOutputs[s] }) .filter(function(o) { return o && !o.error }) if (groupOutputs.length >= 2) { await computeWithinGroupSimilarity(group.id, groupOutputs) } } async function runStrategy(strategy, params) { setCardState(strategy, 'running') try { const response = await fetch(`${API_BASE}/api/run-strategy`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ strategy, params }), }) if (!response.ok) { const err = await response.json() throw new Error(err.detail || 'Strategy failed') } const output = await response.json() allOutputs[strategy] = output populateCard(strategy, output) } catch (error) { console.error(`Strategy ${strategy} failed:`, error) allOutputs[strategy] = { strategy, error: error.message } setCardState(strategy, 'error') const groupOutput = document.getElementById(`output-text-${strategy}`) if (groupOutput) { groupOutput.textContent = `Error: ${error.message}` groupOutput.style.color = 'var(--red)' } } } runAllBtn.addEventListener('click', runAll) // ══════════════════════════════════════════════════════════════════════════════ // 7. POPULATE CARD // ══════════════════════════════════════════════════════════════════════════════ function populateCard(strategy, output) { const card = document.querySelector(`[data-strategy="${strategy}"]`) if (!card) return if (output.error) { const groupOutput = document.getElementById(`output-text-${strategy}`) if (groupOutput) { groupOutput.textContent = `Error: ${output.error}` groupOutput.style.color = 'var(--red)' } return } card.querySelector('.col-token-count').textContent = output.token_count card.querySelector('.col-latency').textContent = `${Math.round(output.latency_ms)}ms` card.querySelector('.col-finish').textContent = output.finish_reason const tokPerSec = (output.token_count / output.latency_ms) * 1000 card.querySelector('.col-toks').textContent = `${tokPerSec.toFixed(1)}` } // ══════════════════════════════════════════════════════════════════════════════ // 8. STREAMING VISUALIZER // ══════════════════════════════════════════════════════════════════════════════ function streamTokensForStrategy(strategy, tokens) { return new Promise(function(resolve) { const display = document.getElementById(`stream-${strategy}`) if (!display) { setCardState(strategy, 'done') resolve() return } display.innerHTML = '' const DELAY_PER_TOKEN = 60 if (tokens.length === 0) { setCardState(strategy, 'done') resolve() return } tokens.forEach(function(token, index) { setTimeout(function() { const span = document.createElement('span') span.textContent = token + ' ' span.classList.add('token-new') display.appendChild(span) display.scrollTop = display.scrollHeight if (index === tokens.length - 1) { setTimeout(function() { setCardState(strategy, 'done') resolve() }, 200) } }, index * DELAY_PER_TOKEN) }) }) } // ══════════════════════════════════════════════════════════════════════════════ // 9. CLEAR GROUP PANELS // ══════════════════════════════════════════════════════════════════════════════ function clearGroupPanels() { GROUPS.forEach(function(group) { group.strategies.forEach(function(strategy) { const streamDisplay = document.getElementById(`stream-${strategy}`) if (streamDisplay) streamDisplay.innerHTML = '' }) const simGrid = document.getElementById(`similarity-grid-${group.id}`) if (simGrid) simGrid.innerHTML = '
Waiting for results...
' const simAvg = document.getElementById(`similarity-avg-${group.id}`) if (simAvg) simAvg.innerHTML = '' }) document.getElementById('attention-display').innerHTML = 'Waiting for Run Analysis...
' document.getElementById('consistency-display').innerHTML = 'Select a strategy and click Run Consistency.
' document.getElementById('dashboard-table-wrap').innerHTML = 'Waiting for Run Analysis...
' document.getElementById('similarity-cross').innerHTML = 'Waiting for Run Analysis...
' document.getElementById('similarity-avg-cross').innerHTML = '' } // ══════════════════════════════════════════════════════════════════════════════ // 10. WITHIN-GROUP SIMILARITY // ══════════════════════════════════════════════════════════════════════════════ async function computeWithinGroupSimilarity(groupId, outputs) { const simGrid = document.getElementById(`similarity-grid-${groupId}`) const simAvg = document.getElementById(`similarity-avg-${groupId}`) if (!simGrid) return simGrid.innerHTML = 'Computing similarity...
' try { const texts = {} outputs.forEach(function(output) { texts[output.strategy] = output.text }) const response = await fetch(`${API_BASE}/api/similarity`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ texts }), }) if (!response.ok) throw new Error('Similarity request failed') const data = await response.json() renderSimilarityCards(simGrid, simAvg, data.scores) } catch (error) { simGrid.innerHTML = `Similarity failed: ${error.message}
` } } function renderSimilarityCards(gridEl, avgEl, scores) { if (!scores || scores.length === 0) { gridEl.innerHTML = 'No similarity data.
' return } let html = '' let total = 0 scores.forEach(function(item) { const labelA = STRATEGY_META[item.strategy_a] ? STRATEGY_META[item.strategy_a].label : item.strategy_a const labelB = STRATEGY_META[item.strategy_b] ? STRATEGY_META[item.strategy_b].label : item.strategy_b const percentage = (item.score * 100).toFixed(1) total += item.score const scoreColor = item.score > 0.8 ? 'var(--green)' : item.score > 0.5 ? 'var(--text-accent)' : 'var(--red)' html += `${labelA}
vs
${labelB}
${item.score.toFixed(2)}
No data yet.
' return } let html = `| Strategy | Group | Latency | Tokens | Tok/s | Finish |
|---|---|---|---|---|---|
| ${label} | ${group} | ${Math.round(output.latency_ms)}ms | ${output.token_count} | ${tokPerSec} | ${output.finish_reason} |
Running 5 rounds...
' consistencyBtn.disabled = true consistencyBtn.textContent = '⏳ Running...' try { const runs = await Promise.all( Array(5).fill(0).map(async function(_, i) { const response = await fetch(`${API_BASE}/api/run-strategy`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ strategy, params }), }) if (!response.ok) throw new Error(`Run ${i + 1} failed`) const output = await response.json() return { run_number: i + 1, output } }) ) const meta = STRATEGY_META[strategy] const label = meta ? meta.label : strategy const texts = runs.map(function(r) { return r.output.error ? '' : r.output.text }) const allIdentical = texts.every(function(t) { return t === texts[0] }) let html = `${label} — 5 runs
${allIdentical ? 'All outputs identical — deterministic strategy.
' : 'Outputs vary across runs — sampling strategy.
' }Consistency failed: ${error.message}
` console.error('Consistency error:', error) } consistencyBtn.disabled = false consistencyBtn.textContent = 'Run 5×' } document.getElementById('consistency-run-btn') .addEventListener('click', runConsistency) document.getElementById('consistency-strategy-select') .addEventListener('change', function() { document.getElementById('consistency-display').innerHTML = 'Click Run 5× to test this strategy.
' }) // ══════════════════════════════════════════════════════════════════════════════ // 14. ATTENTION HEATMAP // ══════════════════════════════════════════════════════════════════════════════ async function extractAndRenderAttention(prompt) { const attentionDisplay = document.getElementById('attention-display') attentionDisplay.innerHTML = 'Computing attention matrix...
' try { const response = await fetch(`${API_BASE}/api/attention`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ word: '', prompt }), }) if (!response.ok) throw new Error('Attention request failed') const data = await response.json() renderAttentionHeatmap(data) } catch (error) { attentionDisplay.innerHTML = `Attention failed: ${error.message}
` } } function renderAttentionHeatmap(data) { const display = document.getElementById('attention-display') if (!data || !data.attention_matrix) { display.innerHTML = 'No attention data available.
' return } const tokens = data.tokens function buildTable(matrix, title, subtitle) { let html = `${title}
${subtitle}
| ` tokens.forEach(function(token) { html += ` | ${token} | ` }) html += `
|---|---|
| ${tokens[rowIndex]} | ` const rowMax = Math.max(...row) row.forEach(function(weight) { const normalized = rowMax > 0 ? weight / rowMax : 0 const intensity = Math.min(5, Math.round(normalized * 5)) html += `` }) html += ` |
Model: Qwen2.5-0.5B — ${data.num_layers} layers × ${data.num_heads} heads = ${data.num_layers * data.num_heads} attention matrices
Computing cross-group similarity...
' try { const texts = {} representatives.forEach(function(strategy) { const output = allOutputs[strategy] if (output && !output.error) { texts[strategy] = output.text } }) if (Object.keys(texts).length < 2) { gridEl.innerHTML = 'Not enough outputs to compare.
' return } const response = await fetch(`${API_BASE}/api/similarity`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ texts }), }) if (!response.ok) throw new Error('Cross-group similarity failed') const data = await response.json() renderSimilarityCards(gridEl, avgEl, data.scores) } catch (error) { gridEl.innerHTML = `Cross-group similarity failed: ${error.message}
` } } // ══════════════════════════════════════════════════════════════════════════════ // 16. RUN ANALYSIS // ══════════════════════════════════════════════════════════════════════════════ async function runAnalysis() { if (!analysisReady) return runAnalysisBtn.disabled = true runAnalysisBtn.textContent = '⏳ Analysing...' await extractAndRenderAttention(promptInput.value.trim()) populateDashboard(Object.values(allOutputs)) await computeCrossGroupSimilarity() runAnalysisBtn.disabled = false runAnalysisBtn.textContent = '🔬 Run Analysis' } runAnalysisBtn.addEventListener('click', runAnalysis)