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> |