Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Token Uncertainty Visualization</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
:root { | |
--primary-color: #4a6fa5; | |
--secondary-color: #6c757d; | |
--success-color: #28a745; | |
--danger-color: #dc3545; | |
--warning-color: #ffc107; | |
--info-color: #17a2b8; | |
--light-bg: #f8f9fa; | |
--dark-bg: #343a40; | |
--border-radius: 4px; | |
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
--transition: all 0.3s ease; | |
} | |
/* Added new color classes for better visualization */ | |
.color-percentile-0 { background-color: #2ecc71; } /* 0-33%: Green */ | |
.color-percentile-1 { background-color: #f39c12; } /* 33-66%: Orange */ | |
.color-percentile-2 { background-color: #e74c3c; } /* 66-100%: Red */ | |
/* For probability (higher is better) */ | |
.color-percentile-prob-0 { background-color: #e74c3c; } /* 0-33%: Red (low probability) */ | |
.color-percentile-prob-1 { background-color: #f39c12; } /* 33-66%: Orange */ | |
.color-percentile-prob-2 { background-color: #2ecc71; } /* 66-100%: Green (high probability) */ | |
body { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
line-height: 1.6; | |
margin: 0; | |
padding: 0; | |
background-color: #f5f7fa; | |
color: #333; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
header { | |
background-color: var(--primary-color); | |
color: white; | |
padding: 20px 0; | |
text-align: center; | |
margin-bottom: 30px; | |
border-radius: 0 0 var(--border-radius) var(--border-radius); | |
box-shadow: var(--box-shadow); | |
} | |
h1 { | |
margin: 0; | |
font-size: 2.2rem; | |
} | |
.subtitle { | |
font-size: 1rem; | |
opacity: 0.9; | |
margin-top: 8px; | |
} | |
.card { | |
background-color: white; | |
border-radius: var(--border-radius); | |
box-shadow: var(--box-shadow); | |
overflow: hidden; | |
margin-bottom: 20px; | |
} | |
.card-header { | |
padding: 15px 20px; | |
background-color: var(--light-bg); | |
border-bottom: 1px solid #e9ecef; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
} | |
.card-title { | |
margin: 0; | |
font-size: 1.25rem; | |
color: var(--primary-color); | |
} | |
.card-body { | |
padding: 20px; | |
} | |
.controls { | |
display: flex; | |
gap: 15px; | |
margin-bottom: 20px; | |
align-items: center; | |
flex-wrap: wrap; | |
} | |
.control-group { | |
display: flex; | |
gap: 5px; | |
align-items: center; | |
} | |
label { | |
font-weight: 500; | |
font-size: 0.9rem; | |
} | |
input, select { | |
padding: 8px 12px; | |
border: 1px solid #ced4da; | |
border-radius: var(--border-radius); | |
font-size: 0.9rem; | |
} | |
button { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
padding: 8px 15px; | |
border-radius: var(--border-radius); | |
cursor: pointer; | |
font-size: 0.9rem; | |
transition: var(--transition); | |
} | |
button:hover { | |
background-color: #3a5a80; | |
} | |
.token-container { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 8px; | |
margin-bottom: 20px; | |
} | |
.token { | |
position: relative; | |
background-color: #e9ecef; | |
padding: 6px 12px; | |
border-radius: 20px; | |
font-size: 0.9rem; | |
cursor: pointer; | |
transition: var(--transition); | |
border: 1px solid transparent; | |
} | |
.token:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
} | |
.token.active { | |
border-color: var(--primary-color); | |
background-color: #e3f2fd; | |
} | |
.uncertainty-marker { | |
position: absolute; | |
bottom: -4px; | |
left: 0; | |
width: 100%; | |
height: 3px; | |
background-color: var(--danger-color); | |
opacity: 0.7; | |
transition: var(--transition); | |
} | |
.detail-panel { | |
display: none; | |
background-color: white; | |
border-radius: var(--border-radius); | |
padding: 20px; | |
box-shadow: var(--box-shadow); | |
margin-top: 20px; | |
} | |
.detail-panel.active { | |
display: block; | |
animation: fadeIn 0.3s ease; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.metric-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
gap: 15px; | |
margin-bottom: 20px; | |
} | |
.metric-item { | |
background-color: var(--light-bg); | |
padding: 10px; | |
border-radius: var(--border-radius); | |
text-align: center; | |
} | |
.metric-value { | |
font-weight: bold; | |
font-size: 1.25rem; | |
color: var(--primary-color); | |
margin: 5px 0; | |
} | |
.metric-label { | |
font-size: 0.8rem; | |
color: var(--secondary-color); | |
} | |
.heatmap-container { | |
margin-top: 20px; | |
} | |
.heatmap-row { | |
display: flex; | |
margin-bottom: 5px; | |
align-items: center; | |
} | |
.heatmap-label { | |
width: 120px; | |
font-size: 0.8rem; | |
margin-right: 10px; | |
color: var(--secondary-color); | |
} | |
.heatmap-bars { | |
flex-grow: 1; | |
display: flex; | |
} | |
.heatmap-bar { | |
height: 20px; | |
margin-right: 2px; | |
transition: var(--transition); | |
position: relative; | |
} | |
.heatmap-bar:hover { | |
transform: scaleY(1.2); | |
} | |
.heatmap-tooltip { | |
position: absolute; | |
bottom: 100%; | |
left: 50%; | |
transform: translateX(-50%); | |
background-color: var(--dark-bg); | |
color: white; | |
padding: 5px 10px; | |
border-radius: var(--border-radius); | |
font-size: 0.7rem; | |
white-space: nowrap; | |
opacity: 0; | |
pointer-events: none; | |
transition: var(--transition); | |
} | |
.heatmap-bar:hover .heatmap-tooltip { | |
opacity: 1; | |
bottom: 120%; | |
} | |
/* Color scales */ | |
.color-low { | |
background-color: #2ecc71; | |
} | |
.color-medium { | |
background-color: #f39c12; | |
} | |
.color-high { | |
background-color: #e74c3c; | |
} | |
/* Responsive design */ | |
@media (max-width: 768px) { | |
.metric-grid { | |
grid-template-columns: 1fr 1fr; | |
} | |
.controls { | |
flex-direction: column; | |
align-items: flex-start; | |
} | |
} | |
@media (max-width: 576px) { | |
.metric-grid { | |
grid-template-columns: 1fr; | |
} | |
} | |
/* Tabs */ | |
.tabs { | |
display: flex; | |
border-bottom: 1px solid #dee2e6; | |
margin-bottom: 15px; | |
overflow-x: auto; | |
} | |
.tab { | |
padding: 10px 15px; | |
cursor: pointer; | |
border: 1px solid transparent; | |
border-bottom: none; | |
border-radius: 4px 4px 0 0; | |
margin-right: 5px; | |
background-color: transparent; | |
transition: var(--transition); | |
flex-shrink: 0; | |
} | |
.tab:hover { | |
background-color: #f8f9fa; | |
} | |
.tab.active { | |
background-color: white; | |
border-color: #dee2e6 #dee2e6 white; | |
color: var(--primary-color); | |
font-weight: bold; | |
} | |
.chart-container { | |
height: 300px; | |
margin-top: 20px; | |
} | |
/* Token info panel */ | |
.token-info { | |
background-color: white; | |
border-radius: var(--border-radius); | |
padding: 15px; | |
box-shadow: var(--box-shadow); | |
margin-top: 15px; | |
} | |
.token-info-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 10px; | |
} | |
.token-info-title { | |
font-size: 1rem; | |
margin: 0; | |
color: var(--primary-color); | |
} | |
.token-info-content { | |
font-size: 0.9rem; | |
line-height: 1.5; | |
} | |
/* Legend */ | |
.legend { | |
display: flex; | |
justify-content: center; | |
margin: 15px 0; | |
flex-wrap: wrap; | |
gap: 15px; | |
} | |
.legend-item { | |
display: flex; | |
align-items: center; | |
font-size: 0.8rem; | |
} | |
.legend-color { | |
width: 16px; | |
height: 16px; | |
border-radius: 3px; | |
margin-right: 5px; | |
} | |
.toggle-btn { | |
background-color: transparent; | |
color: var(--secondary-color); | |
border: 1px solid #ced4da; | |
padding: 5px 10px; | |
font-size: 0.8rem; | |
} | |
.toggle-btn.active { | |
background-color: var(--primary-color); | |
color: white; | |
border-color: var(--primary-color); | |
} | |
.file-input-wrapper { | |
position: relative; | |
overflow: hidden; | |
display: inline-block; | |
} | |
.file-input-wrapper input[type="file"] { | |
font-size: 100px; | |
position: absolute; | |
left: 0; | |
top: 0; | |
opacity: 0; | |
} | |
.loading { | |
display: none; | |
text-align: center; | |
padding: 20px; | |
} | |
.loading.active { | |
display: block; | |
} | |
.spinner { | |
border: 4px solid rgba(0, 0, 0, 0.1); | |
border-radius: 50%; | |
border-top: 4px solid var(--primary-color); | |
width: 30px; | |
height: 30px; | |
animation: spin 1s linear infinite; | |
margin: 0 auto 10px; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.error-message { | |
color: var(--danger-color); | |
background-color: #f8d7da; | |
padding: 10px; | |
border-radius: var(--border-radius); | |
margin: 10px 0; | |
display: none; | |
} | |
.file-info { | |
font-size: 0.8rem; | |
color: var(--secondary-color); | |
margin-left: 10px; | |
} | |
/* Range slider */ | |
.range-slider { | |
width: 200px; | |
margin: 0 10px; | |
} | |
.range-value { | |
min-width: 30px; | |
text-align: center; | |
} | |
/* Progress bar */ | |
.progress-container { | |
width: 100%; | |
background-color: #e9ecef; | |
border-radius: 4px; | |
margin: 10px 0; | |
} | |
.progress-bar { | |
height: 20px; | |
background-color: var(--primary-color); | |
border-radius: 4px; | |
width: 0%; | |
transition: width 0.3s ease; | |
} | |
</style> | |
</head> | |
<body> | |
<header> | |
<div class="container"> | |
<h1>Token Uncertainty Visualization</h1> | |
<p class="subtitle">Analyze model confidence for JSONL files with question/answer data</p> | |
</div> | |
</header> | |
<div class="container"> | |
<div class="card"> | |
<div class="card-header"> | |
<h2 class="card-title">Data Selection</h2> | |
<div class="legend"> | |
<div class="legend-item"> | |
<div class="legend-color color-low"></div> | |
<span>Low Uncertainty</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color color-medium"></div> | |
<span>Medium Uncertainty</span> | |
</div> | |
<div class="legend-item"> | |
<div class="legend-color color-high"></div> | |
<span>High Uncertainty</span> | |
</div> | |
</div> | |
</div> | |
<div class="card-body"> | |
<div class="controls"> | |
<div class="control-group"> | |
<div class="file-input-wrapper"> | |
<button type="button" id="uploadBtn"> | |
<i class="fas fa-upload"></i> Upload JSONL File | |
</button> | |
<input type="file" id="fileInput" accept=".jsonl,.json"> | |
</div> | |
<span class="file-info" id="fileInfo">No file selected</span> | |
</div> | |
<div class="control-group"> | |
<label for="questionId">Question ID:</label> | |
<input type="number" id="questionId" min="0" value="0"> | |
<button id="loadQuestionBtn">Load</button> | |
</div> | |
<div class="control-group"> | |
<label for="autoToggle">Auto Load:</label> | |
<button id="autoToggle" class="toggle-btn">Off</button> | |
</div> | |
<div class="control-group"> | |
<label for="heatmapType">Heatmap:</label> | |
<select id="heatmapType"> | |
<option value="prob">Probability</option> | |
<option value="entropy">Entropy</option> | |
<option value="au">Aleatoric Uncertainty</option> | |
<option value="eu">Epistemic Uncertainty</option> | |
</select> | |
<button id="updateHeatmapBtn">Update</button> | |
</div> | |
</div> | |
<div class="progress-container" id="fileProgressContainer"> | |
<div class="progress-bar" id="fileProgressBar"></div> | |
</div> | |
<div class="error-message" id="errorMessage"></div> | |
<div class="loading" id="loadingIndicator"> | |
<div class="spinner"></div> | |
<p>Processing data...</p> | |
</div> | |
<div class="token-container" id="tokenContainer"> | |
<!-- Tokens will be inserted here by JavaScript --> | |
</div> | |
<div class="detail-panel" id="detailPanel"> | |
<div class="tabs"> | |
<button class="tab active" data-tab="metrics">Metrics</button> | |
<button class="tab" data-tab="probability">Probability</button> | |
<button class="tab" data-tab="entropy">Entropy</button> | |
<button class="tab" data-tab="au">Aleatoric Uncertainty</button> | |
<button class="tab" data-tab="eu">Epistemic Uncertainty</button> | |
<button class="tab" data-tab="logits">Top Logits</button> | |
<button class="tab" data-tab="rawData">Raw Data</button> | |
</div> | |
<div id="metricsTab" class="tab-content active"> | |
<div class="metric-grid"> | |
<div class="metric-item"> | |
<div class="metric-label">Probability</div> | |
<div class="metric-value" id="probValue">0.00</div> | |
<div class="metric-note">Confidence score (0-1)</div> | |
</div> | |
<div class="metric-item"> | |
<div class="metric-label">Entropy</div> | |
<div class="metric-value" id="entropyValue">0.00</div> | |
<div class="metric-note">Uncertainty measure</div> | |
</div> | |
<div class="metric-item"> | |
<div class="metric-label">AU</div> | |
<div class="metric-value" id="auValue">0.00</div> | |
<div class="metric-note">Aleatoric Uncertainty</div> | |
</div> | |
<div class="metric-item"> | |
<div class="metric-label">EU</div> | |
<div class="metric-value" id="euValue">0.00</div> | |
<div class="metric-note">Epistemic Uncertainty</div> | |
</div> | |
</div> | |
<div class="heatmap-container"> | |
<h4>Uncertainty Heatmap</h4> | |
<div class="heatmap-row"> | |
<div class="heatmap-label" id="currentHeatmapLabel">Probability</div> | |
<div class="heatmap-bars" id="currentHeatmap"></div> | |
</div> | |
</div> | |
</div> | |
<div id="probabilityTab" class="tab-content"> | |
<div class="token-info"> | |
<div class="token-info-header"> | |
<h3 class="token-info-title">Probability Distribution</h3> | |
</div> | |
<div class="token-info-content"> | |
<p>The model predicted this token with a probability of <strong id="probValue2">0.00</strong>.</p> | |
<p>Higher probability values indicate greater confidence in the predicted token.</p> | |
<div class="heatmap-container"> | |
<div class="heatmap-row"> | |
<div class="heatmap-label">Probability</div> | |
<div class="heatmap-bars" id="probHeatmap"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="entropyTab" class="tab-content"> | |
<div class="token-info"> | |
<div class="token-info-header"> | |
<h3 class="token-info-title">Entropy Analysis</h3> | |
</div> | |
<div class="token-info-content"> | |
<p>This token has an entropy value of <strong id="entropyValue2">0.00</strong>.</p> | |
<p>Higher entropy values indicate greater uncertainty in the prediction.</p> | |
<div class="heatmap-container"> | |
<div class="heatmap-row"> | |
<div class="heatmap-label">Entropy</div> | |
<div class="heatmap-bars" id="entropyHeatmap"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="auTab" class="tab-content"> | |
<div class="token-info"> | |
<div class="token-info-header"> | |
<h3 class="token-info-title">Aleatoric Uncertainty</h3> | |
</div> | |
<div class="token-info-content"> | |
<p>This token has an Aleatoric Uncertainty (AU) value of <strong id="auValue2">0.00</strong>.</p> | |
<p>AU represents the inherent noise in the data that cannot be reduced.</p> | |
<div class="heatmap-container"> | |
<div class="heatmap-row"> | |
<div class="heatmap-label">Aleatoric Uncertainty</div> | |
<div class="heatmap-bars" id="auHeatmap"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="euTab" class="tab-content"> | |
<div class="token-info"> | |
<div class="token-info-header"> | |
<h3 class="token-info-title">Epistemic Uncertainty</h3> | |
</div> | |
<div class="token-info-content"> | |
<p>This token has an Epistemic Uncertainty (EU) value of <strong id="euValue2">0.00</strong>.</p> | |
<p>EU represents uncertainty due to limited knowledge and can be reduced with more data.</p> | |
<div class="heatmap-container"> | |
<div class="heatmap-row"> | |
<div class="heatmap-label">Epistemic Uncertainty</div> | |
<div class="heatmap-bars" id="euHeatmap"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="logitsTab" class="tab-content"> | |
<div class="token-info"> | |
<div class="token-info-header"> | |
<h3 class="token-info-title">Top Alternative Predictions</h3> | |
</div> | |
<div class="token-info-content"> | |
<p>These are the other most likely tokens the model considered (logits):</p> | |
<table id="logitsTable" style="width:100%; font-size:0.8rem; border-collapse: collapse;"> | |
<thead> | |
<tr style="background-color: #f8f9fa; border-top: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6;"> | |
<th style="padding: 8px; text-align: left;">Rank</th> | |
<th style="padding: 8px; text-align: left;">Token ID</th> | |
<th style="padding: 8px; text-align: left;">Logit Value</th> | |
</tr> | |
</thead> | |
<tbody> | |
<!-- Will be populated by JavaScript --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
<div id="rawDataTab" class="tab-content"> | |
<div class="token-info"> | |
<div class="token-info-header"> | |
<h3 class="token-info-title">Complete Question Data</h3> | |
</div> | |
<div class="token-info-content"> | |
<p>Full JSON data for the selected question:</p> | |
<pre id="rawData" style="background-color: #f8f9fa; padding: 10px; border-radius: var(--border-radius); overflow-x: auto; max-height: 300px;"></pre> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM elements | |
const fileInput = document.getElementById('fileInput'); | |
const uploadBtn = document.getElementById('uploadBtn'); | |
const fileInfo = document.getElementById('fileInfo'); | |
const questionIdInput = document.getElementById('questionId'); | |
const loadQuestionBtn = document.getElementById('loadQuestionBtn'); | |
const autoToggle = document.getElementById('autoToggle'); | |
const heatmapTypeSelect = document.getElementById('heatmapType'); | |
const updateHeatmapBtn = document.getElementById('updateHeatmapBtn'); | |
const tokenContainer = document.getElementById('tokenContainer'); | |
const detailPanel = document.getElementById('detailPanel'); | |
const loadingIndicator = document.getElementById('loadingIndicator'); | |
const errorMessage = document.getElementById('errorMessage'); | |
const fileProgressBar = document.getElementById('fileProgressBar'); | |
const fileProgressContainer = document.getElementById('fileProgressContainer'); | |
// State variables | |
let jsonlData = null; | |
let currentQuestionIndex = 0; | |
let autoLoadEnabled = false; | |
let currentHeatmapType = 'prob'; | |
let questionDataCache = {}; | |
let sortedRanks = { | |
prob: [], | |
entropy: [], | |
au: [], | |
eu: [] | |
}; | |
// Initialize the application | |
init(); | |
function init() { | |
// Event listeners | |
fileInput.addEventListener('change', handleFileUpload); | |
uploadBtn.addEventListener('click', () => fileInput.click()); | |
loadQuestionBtn.addEventListener('click', loadCurrentQuestion); | |
autoToggle.addEventListener('click', toggleAutoLoad); | |
questionIdInput.addEventListener('change', handleQuestionIdChange); | |
heatmapTypeSelect.addEventListener('change', updateHeatmapSelection); | |
updateHeatmapBtn.addEventListener('click', updateCurrentHeatmap); | |
// Initialize with no progress bar visible | |
fileProgressContainer.style.display = 'none'; | |
} | |
function handleFileUpload(event) { | |
const file = event.target.files[0]; | |
if (!file) return; | |
fileInfo.textContent = file.name; | |
loadingIndicator.classList.add('active'); | |
errorMessage.style.display = 'none'; | |
// Show progress bar | |
fileProgressContainer.style.display = 'block'; | |
fileProgressBar.style.width = '0%'; | |
let lineCount = 0; | |
let processedLines = 0; | |
const reader = new FileReader(); | |
// First pass to count lines | |
const countReader = new FileReader(); | |
countReader.onload = function(e) { | |
const text = e.target.result; | |
lineCount = text.split('\n').filter(line => line.trim()).length; | |
// Now process the file | |
reader.onload = function(e) { | |
try { | |
// Parse JSONL file (each line is a separate JSON object) | |
const lines = e.target.result.split('\n').filter(line => line.trim()); | |
jsonlData = []; | |
lines.forEach((line, index) => { | |
try { | |
const data = JSON.parse(line); | |
jsonlData.push(data); | |
processedLines++; | |
// Update progress | |
const progress = Math.round((processedLines / lineCount) * 100); | |
fileProgressBar.style.width = `${progress}%`; | |
// Every 10% or when complete, update the UI | |
if (progress % 10 === 0 || progress === 100) { | |
setTimeout(() => { | |
// Force UI update | |
}, 0); | |
} | |
} catch (parseError) { | |
console.error(`Error parsing line ${index}:`, parseError); | |
} | |
}); | |
// Sort data by question_id for easier navigation | |
jsonlData.sort((a, b) => a.question_id - b.question_id); | |
// Update the question ID input range | |
if (jsonlData.length > 0) { | |
const minId = jsonlData[0].question_id; | |
const maxId = jsonlData[jsonlData.length - 1].question_id; | |
questionIdInput.min = minId; | |
questionIdInput.max = maxId; | |
questionIdInput.value = minId; | |
// Load the first question | |
loadCurrentQuestion(); | |
} | |
loadingIndicator.classList.remove('active'); | |
fileProgressContainer.style.display = 'none'; | |
} catch (error) { | |
showError("Error parsing JSONL file. Please check the file format."); | |
console.error(error); | |
loadingIndicator.classList.remove('active'); | |
fileProgressContainer.style.display = 'none'; | |
} | |
}; | |
reader.onerror = function() { | |
showError("Error reading file. Please try again."); | |
loadingIndicator.classList.remove('active'); | |
fileProgressContainer.style.display = 'none'; | |
}; | |
reader.readAsText(file); | |
}; | |
countReader.readAsText(file); | |
} | |
function handleQuestionIdChange() { | |
if (autoLoadEnabled) { | |
loadCurrentQuestion(); | |
} | |
} | |
function loadCurrentQuestion() { | |
if (!jsonlData || jsonlData.length === 0) { | |
showError("No data loaded. Please upload a JSONL file first."); | |
return; | |
} | |
const questionId = parseInt(questionIdInput.value); | |
const questionData = jsonlData.find(item => item.question_id === questionId); | |
if (questionData) { | |
currentQuestionIndex = questionId; | |
renderQuestionData(questionData); | |
} else { | |
showError(`Question with ID ${questionId} not found in the file.`); | |
} | |
} | |
function toggleAutoLoad() { | |
autoLoadEnabled = !autoLoadEnabled; | |
autoToggle.textContent = autoLoadEnabled ? 'On' : 'Off'; | |
autoToggle.classList.toggle('active', autoLoadEnabled); | |
} | |
function updateHeatmapSelection() { | |
currentHeatmapType = heatmapTypeSelect.value; | |
updateCurrentHeatmap(); | |
} | |
function updateCurrentHeatmap() { | |
if (!questionDataCache || !questionDataCache[currentHeatmapType]) return; | |
document.getElementById('currentHeatmapLabel').textContent = | |
currentHeatmapType === 'prob' ? 'Probability' : | |
currentHeatmapType === 'entropy' ? 'Entropy' : | |
currentHeatmapType === 'au' ? 'Aleatoric Uncertainty' : 'Epistemic Uncertainty'; | |
createPercentileHeatmap( | |
'currentHeatmap', | |
questionDataCache[currentHeatmapType], | |
sortedRanks[currentHeatmapType], | |
currentHeatmapType === 'prob' // Reverse for probability (higher is better) | |
); | |
// NEW: Update all token markers based on the current heatmap selection | |
updateTokenMarkers(); | |
} | |
function updateTokenMarkers() { | |
const tokens = document.querySelectorAll('.token'); | |
tokens.forEach(tokenEl => { | |
const index = parseInt(tokenEl.dataset.index); | |
const marker = tokenEl.querySelector('.uncertainty-marker'); | |
if (marker) { | |
const percentile = getPercentile(index, currentHeatmapType); | |
// Clear previous color classes | |
marker.className = 'uncertainty-marker'; | |
// Add appropriate color class based on percentile and metric type | |
if (currentHeatmapType === 'prob') { | |
// For probability - higher is better (red to green) | |
if (percentile < 0.33) marker.classList.add('color-percentile-prob-0'); | |
else if (percentile < 0.66) marker.classList.add('color-percentile-prob-1'); | |
else marker.classList.add('color-percentile-prob-2'); | |
} else { | |
// For entropy, AU, EU - higher is worse (green to red) | |
if (percentile < 0.33) marker.classList.add('color-percentile-0'); | |
else if (percentile < 0.66) marker.classList.add('color-percentile-1'); | |
else marker.classList.add('color-percentile-2'); | |
} | |
} | |
}); | |
} | |
function showError(message) { | |
errorMessage.textContent = message; | |
errorMessage.style.display = 'block'; | |
} | |
function renderQuestionData(data) { | |
// Cache the current question data | |
questionDataCache = data; | |
// Update the raw data display | |
document.getElementById('rawData').textContent = JSON.stringify(data, null, 2); | |
// Clear existing tokens | |
tokenContainer.innerHTML = ''; | |
// Check if the required data fields exist | |
if (!data.token_char_list || !data.prob || !data.entropy || !data.au || !data.eu) { | |
showError("The selected question doesn't contain the required token analysis data."); | |
detailPanel.classList.remove('active'); | |
return; | |
} | |
// Calculate sorted rankings for each metric | |
sortedRanks.prob = getSortedRanks(data.prob, false); // Higher is better | |
sortedRanks.entropy = getSortedRanks(data.entropy, true); // Higher is worse | |
sortedRanks.au = getSortedRanks(data.au, true); // Higher is worse | |
sortedRanks.eu = getSortedRanks(data.eu, true); // Higher is worse | |
// Initialize the token display | |
data.token_char_list.forEach((token, index) => { | |
const tokenEl = document.createElement('div'); | |
tokenEl.className = 'token'; | |
tokenEl.textContent = token; | |
tokenEl.dataset.index = index; | |
// Add uncertainty marker that will be styled dynamically | |
const marker = document.createElement('div'); | |
marker.className = 'uncertainty-marker'; | |
tokenEl.appendChild(marker); | |
tokenEl.addEventListener('click', () => showTokenDetails(index)); | |
tokenContainer.appendChild(tokenEl); | |
}); | |
// Create all heatmaps | |
createPercentileHeatmap('probHeatmap', data.prob, sortedRanks.prob, true); | |
createPercentileHeatmap('entropyHeatmap', data.entropy, sortedRanks.entropy, false); | |
createPercentileHeatmap('auHeatmap', data.au, sortedRanks.au, false); | |
createPercentileHeatmap('euHeatmap', data.eu, sortedRanks.eu, false); | |
// Set current heatmap to show probability by default | |
updateCurrentHeatmap(); | |
// Show details for first token by default | |
if (data.token_char_list.length > 0) { | |
showTokenDetails(0); | |
} else { | |
detailPanel.classList.remove('active'); | |
} | |
updateTokenMarkers(); | |
} | |
function getSortedRanks(values, higherIsWorse = true) { | |
// Create an array of indices and sort them based on values | |
const indices = Array.from({length: values.length}, (_, i) => i); | |
// Sort indices based on the values | |
indices.sort((a, b) => higherIsWorse ? | |
(values[b] - values[a]) : // Higher values come first for entropy/au/eu | |
(values[a] - values[b])); // Lower values come first for probability | |
// Create a rank array where rank[i] is the position of the i-th element in the sorted array | |
const ranks = new Array(values.length); | |
indices.forEach((originalIndex, sortedPos) => { | |
ranks[originalIndex] = sortedPos; | |
}); | |
return ranks; | |
} | |
function getPercentile(index, metricType) { | |
if (!sortedRanks[metricType] || sortedRanks[metricType].length <= index) { | |
return 0; | |
} | |
// Calculate percentile (0 to 1) based on rank | |
const rank = sortedRanks[metricType][index]; | |
const total = sortedRanks[metricType].length; | |
return rank / (total - 1); // Normalize to 0-1 range | |
} | |
function showTokenDetails(index) { | |
const data = questionDataCache; | |
if (!data || index >= (data.token_char_list?.length || 0)) { | |
return; | |
} | |
// Update active token styling | |
document.querySelectorAll('.token').forEach(token => token.classList.remove('active')); | |
const activeToken = document.querySelector(`.token[data-index="${index}"]`); | |
if (activeToken) { | |
activeToken.classList.add('active'); | |
// Scroll the selection into view if needed | |
activeToken.scrollIntoView({ | |
behavior: 'smooth', | |
block: 'nearest' | |
}); | |
} | |
// Show the detail panel if hidden | |
detailPanel.classList.add('active'); | |
// Update metrics | |
document.getElementById('probValue').textContent = data.prob[index].toFixed(4); | |
document.getElementById('probValue2').textContent = data.prob[index].toFixed(4); | |
document.getElementById('entropyValue').textContent = data.entropy[index].toFixed(4); | |
document.getElementById('entropyValue2').textContent = data.entropy[index].toFixed(4); | |
document.getElementById('auValue').textContent = data.au[index].toFixed(4); | |
document.getElementById('auValue2').textContent = data.au[index].toFixed(4); | |
document.getElementById('euValue').textContent = data.eu[index].toFixed(4); | |
document.getElementById('euValue2').textContent = data.eu[index].toFixed(4); | |
// Highlight the current token in all heatmaps | |
highlightHeatmapToken('probHeatmap', index, sortedRanks.prob, true); | |
highlightHeatmapToken('entropyHeatmap', index, sortedRanks.entropy, false); | |
highlightHeatmapToken('auHeatmap', index, sortedRanks.au, false); | |
highlightHeatmapToken('euHeatmap', index, sortedRanks.eu, false); | |
highlightHeatmapToken('currentHeatmap', index, sortedRanks[currentHeatmapType], currentHeatmapType === 'prob'); | |
// Update logits table if data exists | |
const logitsTable = document.querySelector('#logitsTable tbody'); | |
logitsTable.innerHTML = ''; | |
if (data.Token_logit_dict && data.Token_logit_dict[index]) { | |
const logits = data.Token_logit_dict[index]; | |
const topValues = logits.top_values || []; | |
const topIndices = logits.top_indices || []; | |
for (let i = 0; i < topValues.length; i++) { | |
const row = document.createElement('tr'); | |
row.style.borderBottom = '1px solid #dee2e6'; | |
const rankCell = document.createElement('td'); | |
rankCell.textContent = i + 1; | |
rankCell.style.padding = '8px'; | |
const idCell = document.createElement('td'); | |
idCell.textContent = topIndices[i] || 'N/A'; | |
idCell.style.padding = '8px'; | |
const valueCell = document.createElement('td'); | |
valueCell.textContent = topValues[i] ? topValues[i].toFixed(4) : 'N/A'; | |
valueCell.style.padding = '8px'; | |
row.appendChild(rankCell); | |
row.appendChild(idCell); | |
row.appendChild(valueCell); | |
logitsTable.appendChild(row); | |
} | |
} | |
} | |
function createPercentileHeatmap(containerId, values, ranks, reverseColor = false) { | |
const container = document.getElementById(containerId); | |
container.innerHTML = ''; | |
if (!values || !ranks || values.length !== ranks.length) return; | |
for (let i = 0; i < values.length; i++) { | |
const percentile = ranks[i] / (ranks.length - 1); // 0 to 1 | |
const color = getUncertaintyColor(percentile, reverseColor); | |
const value = values[i]; | |
const bar = createHeatmapBar(value, percentile, color, i); | |
container.appendChild(bar); | |
} | |
} | |
function highlightHeatmapToken(containerId, index, ranks, reverseColor = false) { | |
const container = document.getElementById(containerId); | |
if (!container || !container.children[index]) return; | |
// Reset all bars first | |
Array.from(container.children).forEach(bar => { | |
bar.style.opacity = '1'; | |
bar.style.border = 'none'; | |
}); | |
// Highlight the selected bar | |
const bar = container.children[index]; | |
bar.style.opacity = '1'; | |
bar.style.border = '2px solid var(--primary-color)'; | |
bar.style.boxSizing = 'border-box'; | |
// Scroll to make the bar visible if needed | |
bar.scrollIntoView({ | |
behavior: 'smooth', | |
block: 'nearest', | |
inline: 'center' | |
}); | |
} | |
function createHeatmapBar(value, percentile, color, index) { | |
const bar = document.createElement('div'); | |
bar.className = 'heatmap-bar'; | |
bar.style.width = '10px'; | |
bar.style.height = '20px'; | |
bar.style.backgroundColor = color; | |
bar.dataset.index = index; | |
const tooltip = document.createElement('div'); | |
tooltip.className = 'heatmap-tooltip'; | |
tooltip.textContent = `Token ${index}: ${value.toFixed(4)} (Top ${Math.round((1 - percentile) * 100)}%)`; | |
bar.appendChild(tooltip); | |
// Make bars clickable to select tokens | |
bar.addEventListener('click', function() { | |
showTokenDetails(index); | |
}); | |
return bar; | |
} | |
function getUncertaintyColor(percentile, higherIsBetter = false) { | |
// If higher is better (like probability), invert the percentile | |
const effectivePercentile = higherIsBetter ? (1 - percentile) : percentile; | |
// Returns a color based on the percentile (0-1) | |
if (effectivePercentile < 0.33) return '#2ecc71'; // Green - low uncertainty | |
if (effectivePercentile < 0.66) return '#f39c12'; // Orange - medium uncertainty | |
return '#e74c3c'; // Red - high uncertainty | |
} | |
// Tab switching functionality | |
const tabs = document.querySelectorAll('.tab'); | |
tabs.forEach(tab => { | |
tab.addEventListener('click', () => { | |
// Remove active class from all tabs and content | |
tabs.forEach(t => t.classList.remove('active')); | |
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); | |
// Add active class to clicked tab and corresponding content | |
tab.classList.add('active'); | |
const tabId = tab.dataset.tab + 'Tab'; | |
document.getElementById(tabId).classList.add('active'); | |
}); | |
}); | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: absolute; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">This website has been generated by <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
</html> |