olmocr/scripts/eval/evalhtml_template.html

445 lines
16 KiB
HTML
Raw Normal View History

<!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%;
2025-01-16 18:00:12 +00:00
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;
2024-10-09 17:53:26 +00:00
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;
}
2024-10-09 17:53:26 +00:00
/* 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">
2024-10-09 17:53:26 +00:00
<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 %}
2025-01-14 22:57:17 +00:00
<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">
2024-10-09 17:53:26 +00:00
<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>
2024-10-09 17:53:26 +00:00
<!-- Updated Right Text-Block with separate divs for right_text and diff_text -->
<div class="text-block {{ entry.right_class }}" data-choice="right">
2024-10-09 17:53:26 +00:00
<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');
});
2024-10-09 17:53:26 +00:00
// Handle Reveal Gold/Eval Toggle
document.getElementById('reveal-toggle').addEventListener('change', (e) => {
document.body.classList.toggle('revealed', e.target.checked);
updateReveal();
});
2024-10-09 17:53:26 +00:00
// 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);
}
}
});
2024-10-09 17:53:26 +00:00
// 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
});
}
2024-10-09 17:53:26 +00:00
// Function to toggle diff text
function toggleDiff(isDiffed) {
if (isDiffed) {
document.body.classList.add('diffed');
} else {
document.body.classList.remove('diffed');
}
}
</script>
</body>
</html>