mirror of
				https://github.com/allenai/olmocr.git
				synced 2025-10-31 18:15:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			445 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE html>
 | |
| <html lang="en">
 | |
| <head>
 | |
|     <meta charset="UTF-8">
 | |
|     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | |
|     <title>Text Evaluation Review</title>
 | |
|     <style>
 | |
|         body {
 | |
|             font-family: Arial, sans-serif;
 | |
|             background-color: #f9f9f9;
 | |
|             margin: 0;
 | |
|             padding: 20px;
 | |
|         }
 | |
|         h1 {
 | |
|             text-align: center;
 | |
|             font-size: 2em;
 | |
|             color: #333;
 | |
|         }
 | |
|         .container {
 | |
|             width: 100%;
 | |
|             max-width: 1600px;
 | |
|             margin: 0 auto;
 | |
|         }
 | |
|         .entry {
 | |
|             display: grid;
 | |
|             grid-template-columns: 1fr 1fr 1fr;
 | |
|             grid-gap: 20px;
 | |
|             margin-bottom: 20px;
 | |
|             padding: 20px;
 | |
|             background-color: #fff;
 | |
|             border-radius: 8px;
 | |
|             box-shadow: 0 0 10px rgba(0,0,0,0.1);
 | |
|             transition: background-color 0.3s ease;
 | |
|         }
 | |
|         .text-block {
 | |
|             padding: 10px;
 | |
|             background-color: #f1f1f1;
 | |
|             border-radius: 6px;
 | |
|             min-height: 100px;
 | |
|             display: flex;
 | |
|             flex-direction: column;
 | |
|             justify-content: space-between;
 | |
|             cursor: pointer;
 | |
|             position: relative;
 | |
|         }
 | |
|         .text-block:hover {
 | |
|             background-color: #e0e0e0;
 | |
|         }
 | |
|         .text-block.selected {
 | |
|             background-color: lightgreen;
 | |
|             border: 2px solid black;
 | |
|         }
 | |
|         .alignment {
 | |
|             font-size: 0.9em;
 | |
|             color: #777;
 | |
|             margin-top: 10px;
 | |
|         }
 | |
|         .reveal-box {
 | |
|             position: fixed;
 | |
|             top: 20px;
 | |
|             right: 20px;
 | |
|             padding: 15px;
 | |
|             background-color: white;
 | |
|             border: 1px solid #ccc;
 | |
|             border-radius: 8px;
 | |
|             box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
 | |
|             z-index: 1000;
 | |
|             width: 200px;
 | |
|         }
 | |
|         .reveal-box input {
 | |
|             margin-right: 10px;
 | |
|         }
 | |
|         .reveal-info {
 | |
|             margin-top: 10px;
 | |
|             font-size: 0.9em;
 | |
|             color: #333;
 | |
|         }
 | |
|         .revealed .gold {
 | |
|             background-color: #fff9e6;
 | |
|         }
 | |
|         .revealed .eval {
 | |
|             background-color: #e6f3ff;
 | |
|         }
 | |
|         .revealed .text-block.selected {
 | |
|             border: 2px solid black;
 | |
|         }
 | |
|         .entry > div:first-child img {
 | |
|             width: 100%;
 | |
|             height: auto;
 | |
|             object-fit: cover;
 | |
|             border-radius: 6px;
 | |
|             cursor: pointer;
 | |
|             transition: transform 0.3s ease, box-shadow 0.3s ease;
 | |
|         }
 | |
|         /* Full-screen preview mode */
 | |
|         .full-screen img {
 | |
|             position: fixed;
 | |
|             top: 50%;
 | |
|             left: 50%;
 | |
|             transform: translate(-50%, -50%);
 | |
|             width: unset !important;
 | |
|             max-width: 90vw;
 | |
|             height: auto;
 | |
|             max-height: 90vh;
 | |
|             z-index: 1001;
 | |
|             box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
 | |
|         }
 | |
|         .overlay {
 | |
|             position: fixed;
 | |
|             top: 0;
 | |
|             left: 0;
 | |
|             width: 100vw;
 | |
|             height: 100vh;
 | |
|             background-color: rgba(0, 0, 0, 0.7);
 | |
|             z-index: 1000;
 | |
|             display: none;
 | |
|         }
 | |
|         .overlay.active {
 | |
|             display: block;
 | |
|         }
 | |
|         /* Voting Buttons Styles */
 | |
|         .voting-buttons {
 | |
|             margin-top: 10px;
 | |
|             display: flex;
 | |
|             flex-direction: column;
 | |
|             gap: 5px;
 | |
|         }
 | |
|         .voting-buttons button {
 | |
|             padding: 8px 12px;
 | |
|             border: none;
 | |
|             border-radius: 4px;
 | |
|             cursor: pointer;
 | |
|             background-color: #007BFF;
 | |
|             color: white;
 | |
|             transition: background-color 0.3s ease, border 0.3s ease;
 | |
|         }
 | |
|         .voting-buttons button:hover {
 | |
|             background-color: #0056b3;
 | |
|         }
 | |
|         .voting-buttons button.invalid {
 | |
|             background-color: #dc3545;
 | |
|         }
 | |
|         .voting-buttons button.invalid:hover {
 | |
|             background-color: #a71d2a;
 | |
|         }
 | |
|         .voting-buttons button.both-good {
 | |
|             background-color: #28a745;
 | |
|         }
 | |
|         .voting-buttons button.both-good:hover {
 | |
|             background-color: #1e7e34;
 | |
|         }
 | |
|         .voting-buttons button.both-bad {
 | |
|             background-color: #ffc107;
 | |
|             color: #212529;
 | |
|         }
 | |
|         .voting-buttons button.both-bad:hover {
 | |
|             background-color: #e0a800;
 | |
|         }
 | |
|         /* Selected State for Voting Buttons */
 | |
|         .voting-buttons button.selected {
 | |
|             border: 3px solid #000;
 | |
|         }
 | |
|         /* for diffs */
 | |
|         .added {
 | |
|             background-color: #d4fcdc;
 | |
|         }
 | |
|         .removed {
 | |
|             background-color: #fcd4d4;
 | |
|             text-decoration: line-through;
 | |
|         }
 | |
| 
 | |
|         /* Diff Toggle Styles */
 | |
|         body.diffed .right-text {
 | |
|             display: none;
 | |
|         }
 | |
|         body.diffed .diff-text {
 | |
|             display: block;
 | |
|         }
 | |
|         .diff-text {
 | |
|             display: none;
 | |
|         }
 | |
|     </style>
 | |
| </head>
 | |
| <body>
 | |
|     <h1>Text Evaluation Review</h1>
 | |
| 
 | |
|     <!-- Floating Reveal Box -->
 | |
|     <div class="reveal-box">
 | |
|         <div>
 | |
|             <input type="checkbox" id="diff-toggle" />
 | |
|             <label for="diff-toggle">Toggle diff</label>
 | |
|         </div>
 | |
| 
 | |
|         <div>
 | |
|             <input type="checkbox" id="reveal-toggle" />
 | |
|             <label for="reveal-toggle">Reveal Gold/Eval</label>
 | |
|         </div>
 | |
|         <div class="reveal-info" id="vote-info">Votes</div>
 | |
|     </div>
 | |
| 
 | |
|     <div class="container">
 | |
|         {% for entry in entries %}
 | |
|         <div class="entry {{ entry.gold_class }} {{ entry.eval_class }}" data-entry-id="{{ entry.key }}" data-left-metadata="{{ entry.left_metadata }}" data-right-metadata="{{ entry.right_metadata }}">
 | |
|             <div class="image-container">
 | |
|                 <img src="data:image/png;base64,{{ entry.page_image }}" alt="Render">
 | |
| 
 | |
|                 <div class="alignment">Alignment: {{ entry.alignment }}</div>
 | |
|                 <a href="{{entry.signed_pdf_link}}#page={{ entry.page }}" target="_blank">{{ entry.s3_path }} (Page {{ entry.page }})</a>
 | |
|                 
 | |
|                 <!-- Voting Buttons -->
 | |
|                 <div class="voting-buttons">
 | |
|                     <button class="both-good" data-vote="both_good">Both Good</button>
 | |
|                     <button class="both-bad" data-vote="both_bad">Both Bad</button>
 | |
|                     <button class="invalid" data-vote="invalid_pdf">Invalid PDF</button>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <div class="text-block {{ entry.left_class }}" data-choice="left">
 | |
|                 <div>{{ entry.left_text|safe }}</div>
 | |
|             </div>
 | |
|             <!-- Updated Right Text-Block with separate divs for right_text and diff_text -->
 | |
|             <div class="text-block {{ entry.right_class }}" data-choice="right">
 | |
|                 <div class="right-text">{{ entry.right_text|safe }}</div>
 | |
|                 <div class="diff-text">{{ entry.diff_text|safe }}</div>
 | |
|             </div>
 | |
|         </div>
 | |
|         {% endfor %}
 | |
|     </div>
 | |
| 
 | |
|     <!-- Overlay for full-screen preview -->
 | |
|     <div class="overlay"></div>
 | |
| 
 | |
|     <script>
 | |
|         document.addEventListener('DOMContentLoaded', () => {
 | |
|             fetchDataAndUpdatePage();
 | |
| 
 | |
|             // Toggle the full-screen image preview
 | |
|             const overlay = document.querySelector('.overlay');
 | |
|             document.querySelectorAll('.image-container img').forEach(img => {
 | |
|                 img.addEventListener('click', () => {
 | |
|                     const entry = img.closest('.entry');
 | |
|                     entry.classList.toggle('full-screen');
 | |
|                     overlay.classList.toggle('active');
 | |
|                 });
 | |
|             });
 | |
| 
 | |
|             overlay.addEventListener('click', () => {
 | |
|                 document.querySelectorAll('.full-screen').forEach(entry => {
 | |
|                     entry.classList.remove('full-screen');
 | |
|                 });
 | |
|                 overlay.classList.remove('active');
 | |
|             });
 | |
| 
 | |
|             // Handle Reveal Gold/Eval Toggle
 | |
|             document.getElementById('reveal-toggle').addEventListener('change', (e) => {
 | |
|                 document.body.classList.toggle('revealed', e.target.checked);
 | |
|                 updateReveal();
 | |
|             });
 | |
| 
 | |
|             // Handle Diff Toggle
 | |
|             document.getElementById('diff-toggle').addEventListener('change', (e) => {
 | |
|                 document.body.classList.toggle('diffed', e.target.checked);
 | |
|                 toggleDiff(e.target.checked);
 | |
|             });
 | |
| 
 | |
|             // Handle text-block selections
 | |
|             document.querySelectorAll('.text-block').forEach(block => {
 | |
|                 block.addEventListener('click', () => selectChoice(block));
 | |
|             });
 | |
| 
 | |
|             // Handle voting buttons
 | |
|             document.querySelectorAll('.voting-buttons button').forEach(button => {
 | |
|                 button.addEventListener('click', () => handleVote(button));
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         // Utility function to sanitize s3_path for use as a key
 | |
|         function sanitizeKey(key) {
 | |
|             return key.replace(/[^a-zA-Z0-9-_]/g, '_');
 | |
|         }
 | |
| 
 | |
|         async function fetchDataAndUpdatePage() {
 | |
|             let datastore = await fetchDatastore();
 | |
| 
 | |
|             document.querySelectorAll('.entry').forEach(entry => {
 | |
|                 const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
 | |
|                 const leftBlock = entry.querySelector('.text-block[data-choice="left"]');
 | |
|                 const rightBlock = entry.querySelector('.text-block[data-choice="right"]');
 | |
|                 const voteButtons = entry.querySelectorAll('.voting-buttons button');
 | |
| 
 | |
|                 if (datastore[entryKey]) {
 | |
|                     const choice = datastore[entryKey];
 | |
|                     if (choice === 'left' || choice === 'right') {
 | |
|                         const selectedBlock = choice === 'left' ? leftBlock : rightBlock;
 | |
|                         selectChoice(selectedBlock, false);
 | |
|                     } else {
 | |
|                         // Handle additional voting choices
 | |
|                         handleAdditionalVote(entry, choice, false);
 | |
|                     }
 | |
|                 }
 | |
|             });
 | |
| 
 | |
|             // Ensure diff state is consistent on load
 | |
|             const diffToggle = document.getElementById('diff-toggle');
 | |
|             toggleDiff(diffToggle.checked);
 | |
|         }
 | |
| 
 | |
|         async function selectChoice(block, save = true) {
 | |
|             let datastore = await fetchDatastore();
 | |
| 
 | |
|             const entry = block.closest('.entry');
 | |
|             const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
 | |
|             const blocks = entry.querySelectorAll('.text-block');
 | |
| 
 | |
|             blocks.forEach(b => b.classList.remove('selected'));
 | |
|             block.classList.add('selected');
 | |
| 
 | |
|             datastore[entryKey] = block.getAttribute('data-choice');
 | |
| 
 | |
|             const numVotes = Object.keys(datastore).length;
 | |
|             document.getElementById("vote-info").innerText = `Total Votes: ${numVotes}`;
 | |
| 
 | |
|             if (save) {
 | |
|                 putDatastore(datastore); // Save entire datastore
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         async function handleVote(button, save = true) {
 | |
|             let datastore = await fetchDatastore();
 | |
| 
 | |
|             const entry = button.closest('.entry');
 | |
|             const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
 | |
|             const choice = button.getAttribute('data-vote');
 | |
| 
 | |
|             // Deselect any selected voting buttons within this entry
 | |
|             const voteButtons = entry.querySelectorAll('.voting-buttons button');
 | |
|             voteButtons.forEach(b => b.classList.remove('selected'));
 | |
| 
 | |
|             // Select the clicked button
 | |
|             button.classList.add('selected');
 | |
| 
 | |
|             // Deselect any selected text-blocks
 | |
|             const textBlocks = entry.querySelectorAll('.text-block');
 | |
|             textBlocks.forEach(b => b.classList.remove('selected'));
 | |
| 
 | |
|             datastore[entryKey] = choice;
 | |
| 
 | |
|             const numVotes = Object.keys(datastore).length;
 | |
|             document.getElementById("vote-info").innerText = `Total Votes: ${numVotes}`;
 | |
| 
 | |
|             if (save) {
 | |
|                 putDatastore(datastore); // Save entire datastore
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         async function handleAdditionalVote(entry, choice, save = true) {
 | |
|             let datastore = await fetchDatastore();
 | |
| 
 | |
|             const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
 | |
| 
 | |
|             // Select the appropriate voting button based on the choice
 | |
|             const voteButton = entry.querySelector(`.voting-buttons button[data-vote="${choice}"]`);
 | |
|             if (voteButton) {
 | |
|                 // Deselect other voting buttons
 | |
|                 const voteButtons = entry.querySelectorAll('.voting-buttons button');
 | |
|                 voteButtons.forEach(b => b.classList.remove('selected'));
 | |
| 
 | |
|                 // Select the current button
 | |
|                 voteButton.classList.add('selected');
 | |
|             }
 | |
| 
 | |
|             datastore[entryKey] = choice;
 | |
| 
 | |
|             if (save) {
 | |
|                 putDatastore(datastore);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         async function updateReveal() {
 | |
|             let datastore = await fetchDatastore();
 | |
|             let goldVotes = 0;
 | |
|             let evalVotes = 0;
 | |
|             let bothGoodVotes = 0;
 | |
|             let bothBadVotes = 0;
 | |
|             let invalidPdfVotes = 0;
 | |
| 
 | |
|             document.querySelectorAll('.entry').forEach(entry => {
 | |
|                 const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
 | |
|                 const leftBlock = entry.querySelector('.text-block[data-choice="left"]');
 | |
|                 const rightBlock = entry.querySelector('.text-block[data-choice="right"]');
 | |
| 
 | |
|                 const vote = datastore[entryKey];
 | |
|                 if (vote === 'left') {
 | |
|                     if (leftBlock.classList.contains('gold')) {
 | |
|                         goldVotes++;
 | |
|                     } else {
 | |
|                         evalVotes++;
 | |
|                     }
 | |
|                 } else if (vote === 'right') {
 | |
|                     if (rightBlock.classList.contains('gold')) {
 | |
|                         goldVotes++;
 | |
|                     } else {
 | |
|                         evalVotes++;
 | |
|                     }
 | |
|                 } else if (vote === 'both_good') {
 | |
|                     bothGoodVotes++;
 | |
|                 } else if (vote === 'both_bad') {
 | |
|                     bothBadVotes++;
 | |
|                 } else if (vote === 'invalid_pdf') {
 | |
|                     invalidPdfVotes++;
 | |
|                 }
 | |
|             });
 | |
| 
 | |
|             const totalVotes = goldVotes + evalVotes + bothGoodVotes + bothBadVotes + invalidPdfVotes;
 | |
|             const goldPercentage = totalVotes > 0 ? Math.round((goldVotes / totalVotes) * 100) : 0;
 | |
|             const evalPercentage = totalVotes > 0 ? Math.round((evalVotes / totalVotes) * 100) : 0;
 | |
|             const bothGoodPercentage = totalVotes > 0 ? Math.round((bothGoodVotes / totalVotes) * 100) : 0;
 | |
|             const bothBadPercentage = totalVotes > 0 ? Math.round((bothBadVotes / totalVotes) * 100) : 0;
 | |
|             const invalidPdfPercentage = totalVotes > 0 ? Math.round((invalidPdfVotes / totalVotes) * 100) : 0;
 | |
| 
 | |
|             document.getElementById("vote-info").innerText = `Gold: ${goldPercentage}% | Eval: ${evalPercentage}% | Both Good: ${bothGoodPercentage}% | Both Bad: ${bothBadPercentage}% | Invalid PDF: ${invalidPdfPercentage}%`;
 | |
| 
 | |
|             document.querySelectorAll('.entry').forEach(entry => {
 | |
|                 const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
 | |
|                 const vote = datastore[entryKey];
 | |
|                 if (vote === 'left' || vote === 'right') {
 | |
|                     const selectedBlock = vote === 'left' ? entry.querySelector('.text-block[data-choice="left"]') : entry.querySelector('.text-block[data-choice="right"]');
 | |
|                     selectedBlock.classList.add('selected');
 | |
|                 }
 | |
|                 // Additional votes already handled in handleAdditionalVote
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // Function to toggle diff text
 | |
|         function toggleDiff(isDiffed) {
 | |
|             if (isDiffed) {
 | |
|                 document.body.classList.add('diffed');
 | |
|             } else {
 | |
|                 document.body.classList.remove('diffed');
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     </script>
 | |
| </body>
 | |
| </html>
 | 
