egor-bogomolov's picture
Show repo/commit links in simple problem view
f0b5b33
/* global problemIdx, datasetSlug, datasetName, hasGroundTruth, hasTasks */
function badgeClass(source) {
return 'badge-' + source.toLowerCase().replace(/[^a-z0-9]/g, '');
}
async function loadProblem() {
try {
const response = await fetch(`/api/${datasetSlug}/problem/${problemIdx}`);
const problem = await response.json();
if (problem.error) {
document.getElementById('problem-content').innerHTML =
'<div class="card"><p style="color: red;">Error: ' + problem.error + '</p></div>';
return;
}
renderProblem(problem);
} catch (error) {
document.getElementById('problem-content').innerHTML =
'<div class="card"><p style="color: red;">Error loading problem: ' + error.message + '</p></div>';
}
}
function renderProblem(problem) {
const container = document.getElementById('problem-content');
// Main problem info card (shared by all datasets)
let html = `
<div class="card">
<div class="problem-header">
<h2>${escapeHtml(problem.entry_point)}</h2>
<span class="badge ${badgeClass(problem.source)}">${escapeHtml(problem.source)}</span>
</div>
<div class="problem-meta">
<div class="meta-item">
<span class="meta-label">Task ID:</span>
<span class="meta-value">${escapeHtml(problem.task_id)}</span>
</div>
<div class="meta-item">
<span class="meta-label">Index:</span>
<span class="meta-value">${problem.idx}</span>
</div>
<div class="meta-item">
<span class="meta-label">Dataset:</span>
<span class="meta-value">${escapeHtml(datasetName)}</span>
</div>
${problem.inputs.length > 0 ? `
<div class="meta-item">
<span class="meta-label">Test Inputs:</span>
<span class="meta-value">${problem.inputs.length}</span>
</div>` : ''}
</div>
</div>
`;
// --- BigOBench view (problem description + per-solution code & complexity) ---
if (problem.solutions && problem.solutions.length > 0) {
// Problem description
if (problem.description) {
html += `
<div class="card">
<h2>Problem Statement</h2>
<pre class="code-block" style="white-space: pre-wrap;">${escapeHtml(problem.description)}</pre>
</div>
`;
}
// Each solution: code + complexity/language badges
problem.solutions.forEach((sol, i) => {
html += `
<div class="card">
<h2>Solution ${i + 1} <span style="font-size:0.8rem;color:#7f8c8d;font-weight:400;">${escapeHtml(sol.solution_id)}</span></h2>
<div class="complexity-badges" style="margin-bottom: 15px;">
`;
if (sol.language) {
html += `
<div class="complexity-item">
<span class="complexity-label">Language</span>
<span class="badge badge-info">${escapeHtml(sol.language)}</span>
</div>`;
}
if (sol.time_complexity) {
html += `
<div class="complexity-item">
<span class="complexity-label">Time</span>
<span class="complexity-value">${escapeHtml(sol.time_complexity)}</span>
</div>`;
}
if (sol.space_complexity) {
html += `
<div class="complexity-item">
<span class="complexity-label">Space</span>
<span class="complexity-value">${escapeHtml(sol.space_complexity)}</span>
</div>`;
}
html += `
</div>
<div class="code-with-tasks">
${sol.highlighted_code}
</div>
</div>
`;
});
// Navigation hint
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- DebugBench before/after view (buggy → fixed) ---
if (problem.buggy_code !== undefined && problem.fixed_code !== undefined) {
// Problem description
if (problem.description) {
html += `
<div class="card">
<h2>Problem Statement</h2>
<pre class="code-block" style="white-space: pre-wrap;">${escapeHtml(problem.description)}</pre>
</div>
`;
}
// Bug info
html += `
<div class="card">
<h2>Bug Information</h2>
<div class="bug-info">
<span class="bug-category">${escapeHtml(problem.bug_category || '')}</span>
<span class="bug-subtype">${escapeHtml(problem.bug_subtype || '')}</span>
</div>
<p style="margin-top: 10px;">${escapeHtml(problem.bug_explanation || '')}</p>
`;
if (problem.difficulty) {
html += `<p style="margin-top: 8px; color: #7f8c8d;">Difficulty: <strong>${escapeHtml(problem.difficulty)}</strong></p>`;
}
html += `</div>`;
// Unified diff view of buggy → fixed
const unifiedDiff = computeUnifiedDiff(problem.buggy_code, problem.fixed_code);
html += `
<div class="card">
<h2>Changes</h2>
<div class="diff-view">${renderComputedDiff(unifiedDiff)}</div>
</div>
`;
// Side-by-side buggy/fixed code
html += `
<div class="card">
<h2>Full Code Comparison</h2>
<div class="diff-container">
<div class="diff-panel">
<h3><span class="diff-label-buggy">Before</span></h3>
<div class="code-with-tasks">${problem.buggy_highlighted_code}</div>
</div>
<div class="diff-panel">
<h3><span class="diff-label-fixed">After</span></h3>
<div class="code-with-tasks">${problem.fixed_highlighted_code}</div>
</div>
</div>
</div>
`;
// Examples
if (problem.examples && problem.examples.length > 0) {
html += `<div class="card"><h2>Examples</h2>`;
problem.examples.forEach((ex, i) => {
html += `<pre class="code-block" style="margin-bottom: 10px; white-space: pre-wrap;">${escapeHtml(ex)}</pre>`;
});
html += `</div>`;
}
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- HumanEval-X / HumanEvalPack multi-language view ---
if (problem.lang_solutions && problem.lang_solutions.length > 0) {
// Check if this is HumanEvalPack (has buggy_code in solutions)
const hasBuggy = problem.lang_solutions.some(sol => sol.buggy_code);
// Bug info (HumanEvalPack only)
if (hasBuggy && (problem.bug_type || problem.failure_symptoms)) {
html += `
<div class="card">
<h2>Bug Information</h2>
<div class="bug-info">
${problem.bug_type ? `<span class="bug-category">${escapeHtml(problem.bug_type)}</span>` : ''}
${problem.failure_symptoms ? `<span class="bug-subtype">${escapeHtml(problem.failure_symptoms)}</span>` : ''}
</div>
</div>
`;
}
// Language tabs with code panels
html += `
<div class="card">
<h2>Source Code</h2>
`;
// Buggy/Canonical toggle for HumanEvalPack
if (hasBuggy) {
html += `
<div class="lang-tabs" id="code-mode-tabs" style="margin-bottom: 10px;">
<button class="lang-tab active" onclick="toggleCodeMode('canonical')">Canonical</button>
<button class="lang-tab" onclick="toggleCodeMode('buggy')">Buggy</button>
</div>
`;
}
html += `<div class="lang-tabs" id="lang-tabs">`;
problem.lang_solutions.forEach((sol, i) => {
const label = sol.language_label || sol.language;
html += `<button class="lang-tab ${i === 0 ? 'active' : ''}" onclick="showLangTab(${i})">${escapeHtml(label)}</button>`;
});
html += `</div>`;
problem.lang_solutions.forEach((sol, i) => {
html += `
<div class="lang-code-panel ${i === 0 ? 'active' : ''}" id="lang-panel-${i}">
<div class="code-with-tasks" id="lang-code-canonical-${i}">${sol.highlighted_code}</div>
${sol.buggy_code ? `<div class="code-with-tasks" id="lang-code-buggy-${i}" style="display:none;">${sol.buggy_highlighted_code}</div>` : ''}
</div>
`;
});
html += `</div>`;
// Test suite for current language
html += `<div class="card" id="lang-test-container">`;
if (problem.lang_solutions[0].test) {
html += `<h2>Test Suite</h2><pre class="code-block">${escapeHtml(problem.lang_solutions[0].test)}</pre>`;
}
html += `</div>`;
// Description
if (problem.description) {
html += `
<div class="card">
<h2>Problem Description</h2>
<div style="padding: 10px; line-height: 1.6; white-space: pre-wrap;">${escapeHtml(problem.description)}</div>
</div>
`;
}
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
window._currentCodeMode = 'canonical';
return;
}
// --- SWE-bench / CommitBench diff view (unified diff patch) ---
if (problem.patch !== undefined) {
// GitHub links bar (SWE-bench variants)
const ghLinks = [];
if (problem.repo_url) ghLinks.push(`<a href="${escapeHtml(problem.repo_url)}" target="_blank" class="gh-link">Repository</a>`);
if (problem.issue_url) ghLinks.push(`<a href="${escapeHtml(problem.issue_url)}" target="_blank" class="gh-link">Issue</a>`);
if (problem.commit_url) ghLinks.push(`<a href="${escapeHtml(problem.commit_url)}" target="_blank" class="gh-link">Base Commit</a>`);
if (ghLinks.length > 0) {
html += `<div class="card gh-links-bar">${ghLinks.join('')}</div>`;
}
// Metadata badges (version, date)
const metaBadges = [];
if (problem.version) metaBadges.push(`<span class="badge badge-info">v${escapeHtml(problem.version)}</span>`);
if (problem.created_at) metaBadges.push(`<span class="badge badge-info">${escapeHtml(problem.created_at.split('T')[0])}</span>`);
if (problem.commit_hash) metaBadges.push(`<span class="badge badge-info">${escapeHtml(problem.commit_hash.substring(0, 12))}</span>`);
if (problem.diff_languages) metaBadges.push(`<span class="badge badge-info">${escapeHtml(problem.diff_languages)}</span>`);
if (metaBadges.length > 0) {
html += `<div style="margin-bottom: 15px;">${metaBadges.join(' ')}</div>`;
}
// Problem statement (issue text / commit message)
if (problem.description) {
html += `
<div class="card">
<h2>${problem.issue_url ? 'Issue Description' : 'Description'}</h2>
<div class="issue-statement">${escapeHtml(problem.description)}</div>
</div>
`;
}
// Render unified diff with per-file sections
html += renderDiffFiles(problem.patch, 'Solution Patch');
// Test patch if available
if (problem.test_patch) {
html += renderDiffFiles(problem.test_patch, 'Test Patch');
}
// Failing tests
if (problem.fail_to_pass && problem.fail_to_pass.length > 0) {
html += `<div class="card"><h2>Tests: Fail → Pass</h2><ul class="test-id-list">`;
problem.fail_to_pass.forEach(t => {
html += `<li>${escapeHtml(t)}</li>`;
});
html += `</ul></div>`;
}
// Hints
if (problem.hints) {
html += `
<div class="card">
<h2>Hints</h2>
<div class="issue-statement">${escapeHtml(problem.hints)}</div>
</div>
`;
}
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- SAFIM Fill-in-the-Middle view ---
if (problem.fim_ground_truth !== undefined) {
// Tab bar: "With Gap" | "Completed" | "Completion Only"
html += `
<div class="card">
<h2>Fill-in-the-Middle</h2>
<div class="lang-tabs" id="fim-tabs">
<button class="lang-tab" onclick="showFimTab(0)">With Gap</button>
<button class="lang-tab active" onclick="showFimTab(1)">Completed</button>
<button class="lang-tab" onclick="showFimTab(2)">Completion Only</button>
</div>
<div class="lang-code-panel" id="fim-panel-0">
<div class="code-with-tasks">${problem.highlighted_code}</div>
</div>
<div class="lang-code-panel active" id="fim-panel-1">
<div class="fim-merged-legend">
<span class="coverage-swatch" style="background:#ffffcc; border:1px solid #ccc;"></span>
Inserted completion (lines ${problem.fim_gt_start_line}&ndash;${problem.fim_gt_end_line})
</div>
<div class="code-with-tasks">${problem.fim_merged_highlighted}</div>
</div>
<div class="lang-code-panel" id="fim-panel-2">
<div class="fim-answer">${problem.fim_ground_truth_highlighted || escapeHtml(problem.fim_ground_truth)}</div>
</div>
</div>
`;
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- Vulnerability view (BigVul, DiverseVul, PrimeVul) ---
if (problem.vulnerable_code !== undefined || problem.is_vulnerable !== undefined) {
// Vulnerability status and CWE info
const isVuln = problem.is_vulnerable;
html += `
<div class="card">
<h2>Vulnerability Information</h2>
<div style="margin-bottom: 10px;">
<span class="vuln-status ${isVuln ? 'vuln-status-vulnerable' : 'vuln-status-patched'}">
${isVuln ? 'Vulnerable' : 'Patched'}
</span>
${problem.cwe_id ? `<span class="cwe-badge">${escapeHtml(problem.cwe_id)}</span>` : ''}
${problem.cve_id ? `<span class="badge badge-info">${escapeHtml(problem.cve_id)}</span>` : ''}
${problem.project ? `<span class="badge badge-info">${escapeHtml(problem.project)}</span>` : ''}
</div>
${problem.description ? `<p style="margin-top: 10px; color: #555;">${escapeHtml(problem.description).substring(0, 500)}</p>` : ''}
</div>
`;
// Show code with vuln/patched side-by-side if both available
if (problem.vulnerable_code && problem.patched_code) {
const vulnDiff = computeUnifiedDiff(problem.vulnerable_code, problem.patched_code);
html += `
<div class="card">
<h2>Changes</h2>
<div class="diff-view">${renderComputedDiff(vulnDiff)}</div>
</div>
`;
html += `
<div class="card">
<h2>Full Code Comparison</h2>
<div class="diff-container">
<div class="diff-panel">
<h3><span class="diff-label-buggy">Vulnerable</span></h3>
<div class="code-with-tasks">${problem.vulnerable_highlighted_code}</div>
</div>
<div class="diff-panel">
<h3><span class="diff-label-fixed">Patched</span></h3>
<div class="code-with-tasks">${problem.patched_highlighted_code}</div>
</div>
</div>
</div>
`;
} else {
// Single code view
html += `
<div class="card">
<h2>Source Code</h2>
<div class="code-with-tasks">${problem.highlighted_code}</div>
</div>
`;
}
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// Source Code card
html += `
<div class="card">
<h2>Source Code</h2>
<div class="code-with-tasks" id="code-container">
${problem.highlighted_code}
</div>
</div>
`;
// --- Non-DREval (simple) view ---
if (!hasTasks) {
// GitHub / external links (e.g. LCA adapters)
const simpleLinks = [];
if (problem.repo_url) simpleLinks.push(`<a href="${escapeHtml(problem.repo_url)}" target="_blank" class="gh-link">Repository</a>`);
if (problem.issue_url) simpleLinks.push(`<a href="${escapeHtml(problem.issue_url)}" target="_blank" class="gh-link">Issue</a>`);
if (problem.commit_url) simpleLinks.push(`<a href="${escapeHtml(problem.commit_url)}" target="_blank" class="gh-link">Commit</a>`);
if (simpleLinks.length > 0) {
html += `<div class="card gh-links-bar">${simpleLinks.join('')}</div>`;
}
// Show description if available (e.g. LiveCodeBench, MBPP+, ClassEval)
if (problem.description) {
html += `
<div class="card">
<h2>Problem Description</h2>
<div style="padding: 10px; line-height: 1.6; white-space: pre-wrap;">${escapeHtml(problem.description)}</div>
</div>
`;
}
// Show difficulty, contest date, tags, rating if available
if (problem.difficulty || problem.contest_date || problem.tags || problem.cf_rating) {
let metaHtml = '';
if (problem.difficulty) {
metaHtml += `<span class="badge badge-info">Difficulty: ${escapeHtml(problem.difficulty)}</span>`;
}
if (problem.cf_rating) {
metaHtml += `<span class="badge badge-info">Rating: ${problem.cf_rating}</span>`;
}
if (problem.contest_date) {
metaHtml += `<span class="badge badge-info">Date: ${escapeHtml(problem.contest_date.split('T')[0])}</span>`;
}
if (problem.tags && problem.tags.length > 0) {
problem.tags.forEach(tag => {
metaHtml += `<span class="badge badge-info">${escapeHtml(tag)}</span>`;
});
}
html += `<div style="margin-bottom: 15px;">${metaHtml}</div>`;
}
// Show inputs/outputs if available
if (problem.inputs && problem.inputs.length > 0) {
html += `<div class="card"><h2>Inputs &amp; Outputs</h2>`;
problem.inputs.forEach((inp, i) => {
const out = (problem.outputs && problem.outputs[i]) || '';
html += `
<div class="io-section" style="margin-bottom: 15px;">
<div class="task-section">
<h3>Input ${i + 1}</h3>
<pre class="code-block">${escapeHtml(inp)}</pre>
</div>
<div class="task-section">
<h3>Output</h3>
<pre class="code-block">${escapeHtml(out)}</pre>
</div>
</div>
`;
});
html += `</div>`;
}
// Show test suite if available
if (problem.test) {
html += `
<div class="card">
<h2>Test Suite</h2>
<pre class="code-block">${escapeHtml(problem.test)}</pre>
</div>
`;
}
// Navigation hint
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- CRUXEval task view (tasks have given/predict fields, no task_items) ---
if (problem.tasks.length > 0 && problem.tasks[0].given !== undefined) {
// Task selector
html += `
<div class="card">
<h2>Tasks</h2>
<div class="task-selector" id="task-selector">
`;
problem.tasks.forEach((task, idx) => {
html += `
<button class="task-btn ${idx === 0 ? 'active' : ''}"
onclick="showCruxTask(${idx})">
${escapeHtml(task.name)}
</button>
`;
});
html += `
</div>
<div id="task-content"></div>
</div>
`;
// Navigation hint
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
window.currentProblem = problem;
showCruxTask(0);
return;
}
// --- DREval (full) view with tasks, coverage, arrows ---
// Rebuild html cleanly with coverage legend and SVG overlay
html = `
<div class="card">
<div class="problem-header">
<h2>${escapeHtml(problem.entry_point)}</h2>
<span class="badge ${badgeClass(problem.source)}">${escapeHtml(problem.source)}</span>
</div>
<div class="problem-meta">
<div class="meta-item">
<span class="meta-label">Task ID:</span>
<span class="meta-value">${escapeHtml(problem.task_id)}</span>
</div>
<div class="meta-item">
<span class="meta-label">Index:</span>
<span class="meta-value">${problem.idx}</span>
</div>
<div class="meta-item">
<span class="meta-label">Dataset:</span>
<span class="meta-value">${escapeHtml(datasetName)}</span>
</div>
<div class="meta-item">
<span class="meta-label">Test Inputs:</span>
<span class="meta-value">${problem.inputs.length}</span>
</div>
</div>
</div>
<div class="card">
<h2>Source Code</h2>
<div class="coverage-legend" id="coverage-legend">
<strong>Coverage:</strong>
<span class="coverage-legend-item">
<span class="coverage-swatch" style="background:#d4edda; border:1px solid #28a745;"></span>
Executed
</span>
<span class="coverage-legend-item">
<span class="coverage-swatch" style="background:#f8d7da; border:1px solid #dc3545;"></span>
Not executed
</span>
</div>
<div class="code-with-tasks" id="code-container">
${problem.highlighted_code}
<svg id="arrow-overlay" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6"
refX="8" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" class="exec-arrow-head"/>
</marker>
</defs>
</svg>
</div>
</div>
`;
// Task selector
html += `
<div class="card">
<h2>Test Cases & Tasks</h2>
<p>Select a test input to view associated reasoning tasks:</p>
<div class="task-selector" id="task-selector">
`;
problem.tasks.forEach((task, idx) => {
html += `
<button class="task-btn ${idx === 0 ? 'active' : ''}"
onclick="showTask(${idx})">
Input ${task.input_idx + 1}
</button>
`;
});
html += `
</div>
<div id="task-content"></div>
</div>
`;
// Navigation hint
html += `
<div class="navigation-hint">
<strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
</div>
`;
container.innerHTML = html;
// Store problem data globally
window.currentProblem = problem;
// Show first task by default
showTask(0);
}
function injectTaskMarkers(taskItems) {
const codePre = document.querySelector('.source .code pre');
// Save the pristine original innerHTML once, before any modification.
if (codePre && !window._codePreOriginalHtml) {
window._codePreOriginalHtml = codePre.innerHTML;
}
// Invalidate span cache (rebuilt lazily on next arrow draw)
window._linenoSpanCache = null;
// Store current task items so applyCoverage can re-add markers after wrapping.
window._currentTaskItems = taskItems || [];
// Reset code pre to original, then add markers from scratch.
if (codePre && window._codePreOriginalHtml) {
codePre.innerHTML = window._codePreOriginalHtml;
}
if (!taskItems || taskItems.length === 0) {
return;
}
// Group tasks by line number
const tasksByLine = {};
taskItems.forEach(item => {
if (!tasksByLine[item.lineno]) tasksByLine[item.lineno] = [];
tasksByLine[item.lineno].push(item.var);
});
// Inject task marker badges into the code pre
if (!codePre) return;
const codeLines = codePre.innerHTML.split('\n');
codePre.innerHTML = codeLines.map((line, idx) => {
const lineNum = idx + 1;
if (tasksByLine[lineNum] && line.trim() !== '') {
const vars = tasksByLine[lineNum];
return line + `<span class="task-marker" data-lineno="${lineNum}" data-vars="${escapeHtml(vars.join(', '))}">${escapeHtml(vars.join(', '))}</span>`;
}
return line;
}).join('\n');
}
function applyCoverage(coverageSet, totalLines) {
// Remove previous coverage classes from lineno spans.
document.querySelectorAll('td.linenos .normal').forEach(el => {
el.classList.remove('line-executed', 'line-not-executed');
});
if (!coverageSet) {
const legend = document.getElementById('coverage-legend');
if (legend) legend.style.display = 'none';
return;
}
const legend = document.getElementById('coverage-legend');
if (legend) legend.style.display = 'block';
// Color lineno spans only.
document.querySelectorAll('td.linenos .normal').forEach(span => {
const lineNum = parseInt(span.textContent.trim());
if (!isNaN(lineNum) && lineNum <= totalLines) {
span.classList.add(coverageSet.has(lineNum) ? 'line-executed' : 'line-not-executed');
}
});
}
// Global map: lineno -> list of next line numbers (1-indexed; -1 = end of trace)
window._nextLinesMap = {};
async function loadAndApplyGroundTruth(problemIdx, inputIdx, taskItems) {
// Show "loading" placeholders on all task items
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) { el.textContent = '…'; el.className = 'gt-answer loading'; }
});
// Clear next-lines data from previous input
window._nextLinesMap = {};
try {
const resp = await fetch(`/api/${datasetSlug}/problem/${problemIdx}/ground_truth/${inputIdx}`);
const gt = await resp.json();
if (gt.status !== 'ok') {
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) { el.textContent = gt.status === 'error' ? '(exec error)' : '(unavailable)'; el.className = 'gt-answer'; }
});
applyCoverage(null, 0);
return;
}
// Apply coverage highlighting
const coverageSet = new Set(gt.coverage);
applyCoverage(coverageSet, gt.total_lines);
// Fill in variable answers
const answerMap = {};
gt.variable_answers.forEach(a => {
answerMap[`${a.lineno}-${a.var}`] = a.answer_str;
});
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) {
const answer = answerMap[`${item.lineno}-${item.var}`] || '(not available)';
el.textContent = answer;
el.className = 'gt-answer';
}
});
// Store next-lines data for arrow visualization
if (gt.next_lines_answers) {
gt.next_lines_answers.forEach(a => {
window._nextLinesMap[a.lineno] = a.next_lines;
});
}
// Attach hover handlers to task-marker spans now that we have next-lines data
attachArrowHoverHandlers();
} catch (e) {
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) { el.textContent = '(error)'; el.className = 'gt-answer'; }
});
}
}
// Cache of lineNum → DOM span, rebuilt whenever injectTaskMarkers runs.
window._linenoSpanCache = null;
function buildLinenoSpanCache(container) {
const cache = {};
container.querySelectorAll('td.linenos .normal').forEach(span => {
const n = parseInt(span.textContent.trim());
if (!isNaN(n)) cache[n] = span;
});
window._linenoSpanCache = cache;
}
/**
* Get the bounding rect of the lineno span for a given 1-indexed line number,
* relative to the code container element. Uses a cached span map.
*/
function getLinenoSpanRect(lineNum, container) {
if (!window._linenoSpanCache) buildLinenoSpanCache(container);
const span = window._linenoSpanCache[lineNum];
if (!span) return null;
const spanRect = span.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
return {
top: spanRect.top - containerRect.top + container.scrollTop,
bottom: spanRect.bottom - containerRect.top + container.scrollTop,
left: spanRect.left - containerRect.left,
right: spanRect.right - containerRect.left,
width: spanRect.width,
height: spanRect.height,
midY: (spanRect.top + spanRect.bottom) / 2 - containerRect.top + container.scrollTop,
};
}
/**
* Draw arrows from sourceLine to each of the targetLines in the SVG overlay.
* Lines are 1-indexed. -1 means "end of execution" (no arrow drawn).
*/
function drawArrows(sourceLineNum, targetLineNums) {
const container = document.getElementById('code-container');
const svg = document.getElementById('arrow-overlay');
if (!container || !svg) return;
// Remove previous arrows (but keep defs)
svg.querySelectorAll('.arrow-path').forEach(el => el.remove());
const srcRect = getLinenoSpanRect(sourceLineNum, container);
if (!srcRect) return;
// Update SVG height to match container
svg.setAttribute('height', container.scrollHeight);
targetLineNums.forEach(targetLineNum => {
if (targetLineNum === -1) return; // end of trace — no arrow
const dstRect = getLinenoSpanRect(targetLineNum, container);
if (!dstRect) return;
// Start point: right edge of source lineno span, vertically centered
const x1 = srcRect.right + 2;
const y1 = srcRect.midY;
// End point: right edge of target lineno span, vertically centered
const x2 = dstRect.right + 2;
const y2 = dstRect.midY;
// Horizontal offset for the bezier control points — curves to the right
const curveOffset = Math.max(30, Math.abs(y2 - y1) * 0.4);
// Cubic bezier: both control points extend to the right of the lineno column
const cx1 = x1 + curveOffset;
const cy1 = y1;
const cx2 = x2 + curveOffset;
const cy2 = y2;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', `M ${x1} ${y1} C ${cx1} ${cy1}, ${cx2} ${cy2}, ${x2} ${y2}`);
path.setAttribute('class', 'exec-arrow arrow-path');
path.setAttribute('marker-end', 'url(#arrowhead)');
svg.appendChild(path);
});
}
/**
* Clear all arrows from the SVG overlay.
*/
function clearArrows() {
const svg = document.getElementById('arrow-overlay');
if (svg) {
svg.querySelectorAll('.arrow-path').forEach(el => el.remove());
}
}
// AbortController for the current set of marker hover listeners.
let _markerListenersAbort = null;
/**
* Attach mouseenter/mouseleave handlers to all .task-marker spans so that
* hovering shows execution-flow arrows to next lines.
*/
function attachArrowHoverHandlers() {
// Cancel any previously attached listeners without touching the DOM.
if (_markerListenersAbort) _markerListenersAbort.abort();
_markerListenersAbort = new AbortController();
const { signal } = _markerListenersAbort;
document.querySelectorAll('.task-marker').forEach(marker => {
marker.addEventListener('mouseenter', () => {
const lineNum = parseInt(marker.dataset.lineno);
if (!lineNum) return;
const nextLines = window._nextLinesMap[lineNum];
if (nextLines && nextLines.length > 0) {
drawArrows(lineNum, nextLines);
}
}, { signal });
marker.addEventListener('mouseleave', () => {
clearArrows();
}, { signal });
});
}
function showCruxTask(taskIdx) {
const problem = window.currentProblem;
const task = problem.tasks[taskIdx];
// Update active button
document.querySelectorAll('.task-btn').forEach((btn, idx) => {
btn.classList.toggle('active', idx === taskIdx);
});
const givenLabel = task.given === 'input' ? 'Input (given)' : 'Output (given)';
const predictLabel = task.predict === 'output' ? 'Output (predict)' : 'Input (predict)';
const givenValue = task.given === 'input' ? task.input : task.output;
const predictValue = task.predict === 'output' ? task.output : task.input;
const html = `
<div class="task-details">
<div class="task-section">
<p style="margin-bottom: 12px; color: #7f8c8d;">${escapeHtml(task.description)}</p>
</div>
<div class="io-section">
<div class="task-section">
<h3>${escapeHtml(givenLabel)}</h3>
<pre class="code-block">${escapeHtml(givenValue)}</pre>
</div>
<div class="task-section">
<h3>${escapeHtml(predictLabel)}</h3>
<pre class="code-block crux-answer">${escapeHtml(predictValue)}</pre>
</div>
</div>
</div>
`;
document.getElementById('task-content').innerHTML = html;
}
function showTask(taskIdx) {
const problem = window.currentProblem;
const task = problem.tasks[taskIdx];
// Update active button
const buttons = document.querySelectorAll('.task-btn');
buttons.forEach((btn, idx) => {
if (idx === taskIdx) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
// Inject task markers into the code
injectTaskMarkers(task.task_items);
// Clear previous coverage while new one loads
applyCoverage(null, 0);
// Render task content
const ioSection = task.test_class_code
? `<div class="io-section">
<div class="task-section">
<h3>Input</h3>
<pre class="code-block">${escapeHtml(task.input)}</pre>
</div>
</div>
<div class="task-section">
<h3>Test Class &mdash; <code>${escapeHtml(task.test_class_name)}</code></h3>
<pre class="code-block">${escapeHtml(task.test_class_code)}</pre>
</div>`
: `<div class="io-section">
<div class="task-section">
<h3>Input</h3>
<pre class="code-block">${escapeHtml(task.input)}</pre>
</div>
<div class="task-section">
<h3>Expected Output</h3>
<pre class="code-block">${escapeHtml(task.output)}</pre>
</div>
</div>`;
let html = `
<div class="task-details">
${ioSection}
`;
// Show task items with ground truth answer placeholders
if (task.task_items && task.task_items.length > 0) {
html += `
<div class="task-section">
<h3>Reasoning Tasks</h3>
<p style="margin-bottom: 10px; color: #7f8c8d;">
Variable state at each execution point (correct answer shown in
<span style="background:#17a2b8;color:white;padding:1px 6px;border-radius:3px;font-size:0.82rem;">teal</span>):
</p>
<ul class="task-items-list">
`;
task.task_items.forEach(item => {
html += `
<li>
<span class="line-ref">Line ${item.lineno}</span>
<span class="var-name">${escapeHtml(item.var)}</span>
<span class="gt-answer loading" id="gt-${item.lineno}-${item.var}">…</span>
</li>
`;
});
html += `
</ul>
</div>
`;
}
// Show output prediction task if exists
if (task.output_pred) {
html += `
<div class="task-section">
<h3>Output Completion Task</h3>
<p style="margin-bottom: 10px; color: #7f8c8d;">
The model needs to complete this test assertion:
</p>
<pre class="code-block">${escapeHtml(task.output_pred)}</pre>
</div>
`;
}
html += `</div>`;
document.getElementById('task-content').innerHTML = html;
// Fetch and apply ground truth (coverage + variable answers)
if (hasGroundTruth && task.task_items) {
loadAndApplyGroundTruth(problem.idx, task.input_idx, task.task_items);
}
}
function showLangTab(idx) {
// Only toggle tabs within the #lang-tabs container (not code-mode tabs)
const langTabsContainer = document.getElementById('lang-tabs');
if (langTabsContainer) {
langTabsContainer.querySelectorAll('.lang-tab').forEach((tab, i) => {
tab.classList.toggle('active', i === idx);
});
}
document.querySelectorAll('.lang-code-panel').forEach((panel, i) => {
panel.classList.toggle('active', i === idx);
});
// Re-apply the current code mode (canonical/buggy) to the newly visible panel
if (window._currentCodeMode) {
const problem = window.currentProblem;
if (problem && problem.lang_solutions) {
const sol = problem.lang_solutions[idx];
if (sol) {
const canonical = document.getElementById('lang-code-canonical-' + idx);
const buggy = document.getElementById('lang-code-buggy-' + idx);
if (canonical) canonical.style.display = window._currentCodeMode === 'canonical' ? '' : 'none';
if (buggy) buggy.style.display = window._currentCodeMode === 'buggy' ? '' : 'none';
// If buggy mode is selected but this language has no buggy code, fall back to canonical
if (window._currentCodeMode === 'buggy' && !buggy && canonical) {
canonical.style.display = '';
}
}
}
}
// Update test section
const problem = window.currentProblem;
if (problem && problem.lang_solutions) {
const sol = problem.lang_solutions[idx];
const testContainer = document.getElementById('lang-test-container');
if (testContainer && sol && sol.test) {
testContainer.innerHTML = `<h2>Test Suite</h2><pre class="code-block">${escapeHtml(sol.test)}</pre>`;
} else if (testContainer) {
testContainer.innerHTML = '';
}
}
}
function toggleCodeMode(mode) {
window._currentCodeMode = mode;
const problem = window.currentProblem;
if (!problem || !problem.lang_solutions) return;
// Update mode tabs
const modeTabs = document.querySelectorAll('#code-mode-tabs .lang-tab');
modeTabs.forEach(tab => {
tab.classList.toggle('active', tab.textContent.trim().toLowerCase() === mode);
});
// Toggle visibility of canonical/buggy code in all panels
problem.lang_solutions.forEach((sol, i) => {
const canonical = document.getElementById('lang-code-canonical-' + i);
const buggy = document.getElementById('lang-code-buggy-' + i);
if (canonical) canonical.style.display = mode === 'canonical' ? '' : 'none';
if (buggy) buggy.style.display = mode === 'buggy' ? '' : 'none';
});
}
function showFimTab(idx) {
const tabs = document.querySelectorAll('#fim-tabs .lang-tab');
tabs.forEach((tab, i) => tab.classList.toggle('active', i === idx));
for (let i = 0; i < 3; i++) {
const panel = document.getElementById('fim-panel-' + i);
if (panel) panel.classList.toggle('active', i === idx);
}
}
/**
* Split a unified diff into per-file sections and render each with a GitHub-style
* file header bar. Returns an HTML string with one card per file.
*/
function renderDiffFiles(diffText, title) {
if (!diffText) return '';
// Split into per-file chunks by "diff --git" boundaries
const files = [];
let current = null;
diffText.split('\n').forEach(line => {
if (line.startsWith('diff --git')) {
if (current) files.push(current);
// Extract file path from "diff --git a/path b/path"
const m = line.match(/^diff --git a\/(.+?) b\/(.+)/);
const filePath = m ? m[2] : line;
current = { path: filePath, lines: [line] };
} else if (current) {
current.lines.push(line);
} else {
// Lines before any diff header — create a default section
if (!current) current = { path: '', lines: [] };
current.lines.push(line);
}
});
if (current) files.push(current);
if (files.length === 0) return '';
let html = '';
if (files.length === 1 && !files[0].path) {
// Single unnamed diff — render as before
html += `<div class="card"><h2>${escapeHtml(title)}</h2><div class="diff-view">${renderDiff(diffText)}</div></div>`;
} else {
html += `<div class="card"><h2>${escapeHtml(title)}</h2>`;
files.forEach(file => {
const diffChunk = file.lines.join('\n');
// Count additions/deletions
let adds = 0, dels = 0;
file.lines.forEach(l => {
if (l.startsWith('+') && !l.startsWith('+++')) adds++;
if (l.startsWith('-') && !l.startsWith('---')) dels++;
});
const statsHtml = `<span class="diff-file-stats"><span class="diff-stat-add">+${adds}</span> <span class="diff-stat-del">-${dels}</span></span>`;
html += `
<div class="diff-file-section">
<div class="diff-file-header">
<span class="diff-file-path">${escapeHtml(file.path)}</span>
${statsHtml}
</div>
<div class="diff-view">${renderDiff(diffChunk)}</div>
</div>
`;
});
html += `</div>`;
}
return html;
}
/**
* Render a unified diff with line numbers and file headers (GitHub-style).
*/
function renderDiff(diffText) {
if (!diffText) return '';
const lines = diffText.split('\n');
let oldLine = 0, newLine = 0;
const rows = [];
lines.forEach(line => {
if (line.startsWith('diff ')) {
rows.push(`<tr class="diff-tr-header"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-header">${escapeHtml(line)}</td></tr>`);
return;
}
if (line.startsWith('---') || line.startsWith('+++')) {
rows.push(`<tr class="diff-tr-header"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-header">${escapeHtml(line)}</td></tr>`);
return;
}
if (line.startsWith('@@')) {
// Parse hunk header: @@ -oldStart,oldCount +newStart,newCount @@
const m = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
if (m) {
oldLine = parseInt(m[1]);
newLine = parseInt(m[2]);
}
rows.push(`<tr class="diff-tr-hunk"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-hunk">${escapeHtml(line)}</td></tr>`);
return;
}
if (line.startsWith('+')) {
rows.push(`<tr class="diff-tr-add"><td class="diff-ln"></td><td class="diff-ln">${newLine}</td><td class="diff-td-add">${escapeHtml(line.substring(1))}</td></tr>`);
newLine++;
} else if (line.startsWith('-')) {
rows.push(`<tr class="diff-tr-del"><td class="diff-ln">${oldLine}</td><td class="diff-ln"></td><td class="diff-td-del">${escapeHtml(line.substring(1))}</td></tr>`);
oldLine++;
} else if (line.startsWith(' ')) {
rows.push(`<tr class="diff-tr-ctx"><td class="diff-ln">${oldLine}</td><td class="diff-ln">${newLine}</td><td class="diff-td-ctx">${escapeHtml(line.substring(1))}</td></tr>`);
oldLine++;
newLine++;
} else if (line.trim() === '') {
// Empty trailing line
} else {
rows.push(`<tr class="diff-tr-ctx"><td class="diff-ln">${oldLine}</td><td class="diff-ln">${newLine}</td><td class="diff-td-ctx">${escapeHtml(line)}</td></tr>`);
oldLine++;
newLine++;
}
});
return `<table class="diff-table">${rows.join('')}</table>`;
}
/**
* Simple line-by-line diff (LCS-based) between two code strings.
* Returns an array of {type: 'context'|'add'|'del', line: string}.
*/
function computeUnifiedDiff(oldText, newText) {
const oldLines = (oldText || '').split('\n');
const newLines = (newText || '').split('\n');
// LCS for line sequences
const m = oldLines.length, n = newLines.length;
// For very large files, just show both in full instead of computing LCS
if (m * n > 500000) {
const result = [];
oldLines.forEach(l => result.push({type: 'del', line: l}));
newLines.forEach(l => result.push({type: 'add', line: l}));
return result;
}
const dp = Array.from({length: m + 1}, () => new Uint16Array(n + 1));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (oldLines[i - 1] === newLines[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// Backtrack to build diff
const result = [];
let i = m, j = n;
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
result.push({type: 'context', line: oldLines[i - 1]});
i--; j--;
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
result.push({type: 'add', line: newLines[j - 1]});
j--;
} else {
result.push({type: 'del', line: oldLines[i - 1]});
i--;
}
}
result.reverse();
// Compact: only show hunks with context (3 lines around changes)
const contextSize = 3;
const hasChange = result.map(r => r.type !== 'context');
const show = new Uint8Array(result.length);
for (let k = 0; k < result.length; k++) {
if (hasChange[k]) {
for (let c = Math.max(0, k - contextSize); c <= Math.min(result.length - 1, k + contextSize); c++) {
show[c] = 1;
}
}
}
const compacted = [];
let lastShown = -1;
for (let k = 0; k < result.length; k++) {
if (show[k]) {
if (lastShown >= 0 && k - lastShown > 1) {
compacted.push({type: 'separator', line: '...'});
}
compacted.push(result[k]);
lastShown = k;
}
}
return compacted.length > 0 ? compacted : result;
}
/**
* Render the output of computeUnifiedDiff into diff HTML with line numbers.
*/
function renderComputedDiff(diffEntries) {
let oldLine = 1, newLine = 1;
const rows = diffEntries.map(entry => {
if (entry.type === 'separator') {
return `<tr class="diff-tr-hunk"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-hunk">${escapeHtml(entry.line)}</td></tr>`;
}
if (entry.type === 'del') {
const row = `<tr class="diff-tr-del"><td class="diff-ln">${oldLine}</td><td class="diff-ln"></td><td class="diff-td-del">${escapeHtml(entry.line)}</td></tr>`;
oldLine++;
return row;
}
if (entry.type === 'add') {
const row = `<tr class="diff-tr-add"><td class="diff-ln"></td><td class="diff-ln">${newLine}</td><td class="diff-td-add">${escapeHtml(entry.line)}</td></tr>`;
newLine++;
return row;
}
// context
const row = `<tr class="diff-tr-ctx"><td class="diff-ln">${oldLine}</td><td class="diff-ln">${newLine}</td><td class="diff-td-ctx">${escapeHtml(entry.line)}</td></tr>`;
oldLine++;
newLine++;
return row;
});
return `<table class="diff-table">${rows.join('')}</table>`;
}
function escapeHtml(text) {
if (text === null || text === undefined) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
loadProblem();