llm_as_judge_output / index.html
CCRss's picture
changes
33cc1cf
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Caption Judge – Human Label UI</title>
<style>
:root {
--bg: #f5f7fa;
--card: #ffffff;
--primary: #2563eb;
--danger: #dc2626;
--warn: #eab308;
--text: #111827;
--muted: #6b7280;
--model: #10b981;
--truth: #8b5cf6;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
background: var(--bg);
color: var(--text);
display: flex;
flex-direction: column;
min-height: 100vh;
}
header {
background: var(--card);
padding: 1rem 1.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
gap: 1rem;
position: sticky;
top: 0;
z-index: 10;
flex-wrap: wrap;
}
header h1 { font-size: 1.25rem; margin: 0; }
main {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem 1rem 4rem;
width: 100%;
}
#card {
width: 100%;
max-width: 900px;
background: var(--card);
border-radius: 1rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
padding: 2rem;
display: none;
}
.kv-pair {
margin-bottom: 1.25rem;
position: relative;
}
.kv-pair .label {
font-weight: 600;
display: flex;
align-items: center;
margin-bottom: 0.25rem;
}
.label-tag {
display: inline-block;
font-size: 0.75rem;
padding: 0.2rem 0.5rem;
border-radius: 0.25rem;
color: white;
margin-left: 0.5rem;
}
.model-tag {
background-color: var(--model);
}
.truth-tag {
background-color: var(--truth);
}
.kv-pair .value {
padding: 0.75rem 1rem;
background: #f9fafb;
border-radius: 0.375rem;
white-space: pre-wrap;
word-break: break-word;
}
.model-value {
border-left: 4px solid var(--model);
}
.truth-value {
border-left: 4px solid var(--truth);
}
.question-value {
border-left: 4px solid var(--primary);
}
.label-btn {
border: none;
padding: 0.6rem 1.5rem;
font-size: 1rem;
border-radius: 0.5rem;
cursor: pointer;
color: #fff;
margin-right: 0.75rem;
transition: transform 0.1s ease;
}
.label-btn:active { transform: scale(0.97); }
#trueBtn { background: var(--primary); }
#partialBtn { background: var(--warn); color: #000; }
#falseBtn { background: var(--danger); }
#progressWrap {
width: 100%;
max-width: 300px;
height: 8px;
background: #e5e7eb;
border-radius: 4px;
overflow: hidden;
}
#progressBar {
height: 100%;
width: 0%;
background: var(--primary);
transition: width 0.3s ease;
}
#controls { margin-top: 1.5rem; }
footer {
padding: 1rem;
text-align: center;
font-size: 0.85rem;
color: var(--muted);
}
#loader { display: none; }
#fileInput {
margin-right: 10px;
}
.judgement-question {
font-weight: 500;
margin-bottom: 1rem;
padding: 0.5rem;
background: #f0f4f8;
border-radius: 0.5rem;
text-align: center;
}
</style>
</head>
<body>
<header>
<h1>Caption Judge – Human Label UI</h1>
<input type="file" id="fileInput" accept=".jsonl" />
<div id="progressWrap"><div id="progressBar"></div></div>
<span id="counter" class="muted"></span>
</header>
<main>
<div id="loader">Loading&nbsp;dataset…</div>
<div id="card">
<div class="kv-pair">
<span class="label">Question</span>
<div id="question" class="value question-value"></div>
</div>
<div class="kv-pair">
<span class="label">
Model Caption
<span class="label-tag model-tag">AI GENERATED</span>
</span>
<div id="generated" class="value model-value"></div>
</div>
<div class="kv-pair">
<span class="label">
Ground Truth
<span class="label-tag truth-tag">CORRECT ANSWER</span>
</span>
<div id="reference" class="value truth-value"></div>
</div>
<div class="judgement-question">
Is the model caption accurate compared to the ground truth?
</div>
<div id="controls">
<button id="trueBtn" class="label-btn" title="Y key">TRUE (Y)</button>
<button id="partialBtn" class="label-btn" title="P key">PARTIAL (P)</button>
<button id="falseBtn" class="label-btn" title="X key">FALSE (X)</button>
</div>
</div>
</main>
<footer>
Keyboard shortcuts: <strong>Y</strong> = True, <strong>P</strong> = Partial, <strong>X</strong> = False &middot;
When finished a download dialog will appear with your labels.
</footer>
<script>
const fileInput = document.getElementById('fileInput');
const loader = document.getElementById('loader');
const card = document.getElementById('card');
const questionEl = document.getElementById('question');
const generatedEl = document.getElementById('generated');
const referenceEl = document.getElementById('reference');
const trueBtn = document.getElementById('trueBtn');
const falseBtn = document.getElementById('falseBtn');
const partialBtn = document.getElementById('partialBtn');
const progressBar = document.getElementById('progressBar');
const counter = document.getElementById('counter');
let dataset = [];
let index = 0;
let labels = [];
let currentFileName = '';
const updateProgress = () => {
counter.textContent = dataset.length ? `${index + 1}/${dataset.length}` : '';
progressBar.style.width = dataset.length ? `${((index + 1) / dataset.length) * 100}%` : '0%';
};
const showEntry = () => {
if (index >= dataset.length) {
finish();
return;
}
const entry = dataset[index];
questionEl.textContent = entry.original_question || 'β€”';
generatedEl.textContent = entry.generated_text || 'β€”';
referenceEl.textContent = entry.ground_truth_text || 'β€”';
updateProgress();
};
const label = (value) => {
const entry = dataset[index];
labels.push({ id: entry.id, label: value });
index += 1;
showEntry();
};
trueBtn.onclick = () => label('TRUE');
falseBtn.onclick = () => label('FALSE');
partialBtn.onclick = () => label('PARTIAL');
document.addEventListener('keydown', (e) => {
if (!dataset.length) return;
if (['y', 'Y'].includes(e.key)) label('TRUE');
if (['x', 'X'].includes(e.key)) label('FALSE');
if (['p', 'P'].includes(e.key)) label('PARTIAL');
});
const finish = () => {
card.style.display = 'none';
counter.textContent = 'βœ“ Done';
progressBar.style.width = '100%';
const blob = new Blob(labels.map(l => JSON.stringify(l) + '\n'), { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'labels_' + currentFileName.replace(/\.jsonl$/, '') + '.jsonl';
a.click();
URL.revokeObjectURL(url);
alert('All items labelled! A labels file has been downloaded.');
};
const parseJSONL = (text) => text.trim().split(/\n+/).map(line => JSON.parse(line));
fileInput.addEventListener('change', async (event) => {
if (!event.target.files.length) return;
const file = event.target.files[0];
if (!file.name.endsWith('.jsonl')) {
alert('Please select a JSONL file.');
return;
}
currentFileName = file.name;
loader.style.display = 'block';
card.style.display = 'none';
try {
const text = await file.text();
dataset = parseJSONL(text);
// randomize order for fairness
for (let i = dataset.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[dataset[i], dataset[j]] = [dataset[j], dataset[i]];
}
index = 0;
labels = [];
loader.style.display = 'none';
card.style.display = 'block';
showEntry();
} catch (err) {
alert('Failed to load dataset: ' + err.message);
loader.style.display = 'none';
}
});
</script>
</body>
</html>