timchen0618's picture
Show structure runs side by side
59868c7 verified
Raw
History Blame Contribute Delete
9.4 kB
(function () {
'use strict';
const state = {
index: null,
datasetId: null,
search: '',
filtered: [],
selected: 0,
cache: new Map(),
};
const dom = {
tabs: document.getElementById('datasetTabs'),
search: document.getElementById('search'),
list: document.getElementById('docList'),
main: document.getElementById('main'),
meta: document.getElementById('runMeta'),
prev: document.getElementById('prevBtn'),
next: document.getElementById('nextBtn'),
counter: document.getElementById('counter'),
};
const esc = (value) => String(value ?? '')
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
function dataset() {
return state.index.datasets.find((row) => row.id === state.datasetId);
}
function renderMarkdown(value) {
if (window.marked && window.marked.parse) {
try { return window.marked.parse(String(value ?? '')); } catch (_) {}
}
return `<pre>${esc(value)}</pre>`;
}
function parseCsv(text) {
const rows = [];
let row = [], field = '', quoted = false;
for (let i = 0; i < text.length; i++) {
const ch = text[i];
if (quoted) {
if (ch === '"' && text[i + 1] === '"') { field += '"'; i++; }
else if (ch === '"') quoted = false;
else field += ch;
} else if (ch === '"') quoted = true;
else if (ch === ',') { row.push(field); field = ''; }
else if (ch === '\n') { row.push(field.replace(/\r$/, '')); rows.push(row); row = []; field = ''; }
else field += ch;
}
if (field || row.length) { row.push(field); rows.push(row); }
return rows;
}
function renderArtifactContent(artifact) {
if (artifact.format === 'csv') {
const rows = parseCsv(artifact.content);
if (!rows.length) return '<pre></pre>';
return `<div class="table-wrap"><table><thead><tr>${rows[0].map((cell) => `<th>${esc(cell)}</th>`).join('')}</tr></thead><tbody>${rows.slice(1).map((row) => `<tr>${row.map((cell) => `<td>${esc(cell)}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`;
}
if (artifact.format === 'json' || artifact.format === 'jsonl') {
try {
const value = artifact.format === 'json'
? JSON.parse(artifact.content)
: artifact.content.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
return `<pre>${esc(JSON.stringify(value, null, 2))}</pre>`;
} catch (_) {}
}
if (artifact.format === 'md') return renderMarkdown(artifact.content);
return `<pre>${esc(artifact.content)}</pre>`;
}
function renderTabs() {
dom.tabs.innerHTML = state.index.datasets.map((row) =>
`<button class="dataset-tab ${row.id === state.datasetId ? 'active' : ''}" data-id="${esc(row.id)}">${esc(row.label)}</button>`
).join('');
dom.tabs.querySelectorAll('button').forEach((button) => {
button.addEventListener('click', () => setDataset(button.dataset.id));
});
}
function renderRuns() {
const ds = dataset();
dom.meta.innerHTML = ds.runs.map((item) => {
const qualityText = item.quality
? `${item.quality.final_scaffold_files ?? '—'} files · ${item.quality.total_failures ?? '—'} failures`
: (item.status === 'pending' ? 'Output intentionally blank while AML runs' : 'No schema-v3 audit report');
return `<div class="run-meta-item">
<strong>${esc(item.label)}</strong>
<span>${esc(item.job)}</span>
<span>schema: ${esc(item.schema)} · status: ${esc(item.status)}</span>
<span>${esc(qualityText)}</span>
</div>`;
}).join('');
}
function recompute() {
const query = state.search.trim().toLowerCase();
state.filtered = dataset().records.filter((row) => {
if (!query) return true;
return `${row.doc_id} ${row.source_preview}`.toLowerCase().includes(query);
});
if (state.selected >= state.filtered.length) state.selected = Math.max(0, state.filtered.length - 1);
}
function renderList() {
const runs = dataset().runs;
dom.list.innerHTML = state.filtered.map((row, index) => {
const counts = runs.map((item) => `${item.id === 'current' ? 'C' : 'L'} ${row.run_counts[item.id] ?? 0}`).join(' · ');
return `<li class="${index === state.selected ? 'active' : ''}" data-index="${index}">
<span class="count-pill">${counts}</span>
<div class="doc-id">${esc(row.doc_id)}</div>
<div class="doc-preview">${esc(row.source_preview)}</div>
</li>`;
}).join('');
dom.list.querySelectorAll('li').forEach((item) => {
item.addEventListener('click', () => { state.selected = Number(item.dataset.index); render(); });
});
dom.counter.textContent = state.filtered.length ? `${state.selected + 1} / ${state.filtered.length}` : '0 / 0';
dom.prev.disabled = state.selected <= 0;
dom.next.disabled = state.selected >= state.filtered.length - 1;
}
async function loadRecord(summary) {
if (!state.cache.has(summary.record)) {
state.cache.set(summary.record, fetch(summary.record).then((response) => {
if (!response.ok) throw new Error(`Failed to load ${summary.record}: ${response.status}`);
return response.json();
}));
}
return state.cache.get(summary.record);
}
function renderStructures(record, selectedRun) {
const value = record.runs[selectedRun.id];
if (selectedRun.status === 'pending') {
return `<div class="pending-panel"><div><strong>Current run pending</strong><p>${esc(selectedRun.job)}</p><p>This panel is intentionally blank. The source document and legacy extraction remain available.</p></div></div>`;
}
if (!value.artifacts.length) {
return '<div class="empty-panel"><div><span class="badge warn">No structure produced</span><p>The document was part of this run, but no applicable scaffold artifact was emitted.</p></div></div>';
}
const groups = new Map();
value.artifacts.forEach((artifact) => {
if (!groups.has(artifact.shape)) groups.set(artifact.shape, []);
groups.get(artifact.shape).push(artifact);
});
return [...groups.entries()].map(([shape, artifacts]) =>
`<section class="shape-group">
<div class="shape-title"><strong>${esc(artifacts[0].shape_label)}</strong><span class="badge good">${artifacts.length} artifact${artifacts.length === 1 ? '' : 's'}</span></div>
${artifacts.map((artifact) => `<article class="artifact">
<h4>${esc(artifact.filename)}</h4>
<div class="description">${esc(artifact.unit_description || artifact.raw_shape)} · raw shape: ${esc(artifact.raw_shape)}</div>
${renderArtifactContent(artifact)}
</article>`).join('')}
</section>`
).join('');
}
async function renderMain() {
if (!state.filtered.length) {
dom.main.innerHTML = '<div class="loading">No matching documents.</div>';
return;
}
const summary = state.filtered[state.selected];
dom.main.innerHTML = '<div class="loading">Loading document...</div>';
try {
const record = await loadRecord(summary);
const runs = dataset().runs;
const countBadges = runs.map((item) =>
`<span class="badge ${item.status === 'pending' ? 'pending' : 'good'}">${esc(item.label)}: ${record.runs[item.id].n_artifacts}</span>`
).join('');
dom.main.innerHTML = `<div class="record-head">
${countBadges}
<h2>${esc(record.doc_id)}</h2>
<div class="meta">${esc(record.dataset_label)} · source ${esc(record.source.source_path)} · SHA-256 ${esc(record.source.sha256)}</div>
</div>
<div class="split">
<section class="panel"><div class="panel-title">Source document</div><div class="panel-body source-body">${renderMarkdown(record.source.contents)}</div></section>
${runs.map((item) => `<section class="panel structure-panel">
<div class="panel-title">${esc(item.label)}</div>
<div class="structure-body">${renderStructures(record, item)}</div>
</section>`).join('')}
</div>`;
} catch (error) {
dom.main.innerHTML = `<div class="loading">${esc(error.message)}</div>`;
}
}
function render() {
renderTabs();
renderRuns();
recompute();
renderList();
renderMain();
}
function setDataset(id) {
state.datasetId = id;
state.selected = 0;
state.search = '';
dom.search.value = '';
render();
}
dom.search.addEventListener('input', () => { state.search = dom.search.value; state.selected = 0; render(); });
dom.prev.addEventListener('click', () => { if (state.selected > 0) { state.selected--; render(); } });
dom.next.addEventListener('click', () => { if (state.selected < state.filtered.length - 1) { state.selected++; render(); } });
document.addEventListener('keydown', (event) => {
if (event.target.matches('input, select')) return;
if (event.key === 'ArrowLeft') dom.prev.click();
if (event.key === 'ArrowRight') dom.next.click();
});
fetch('data/index.json')
.then((response) => response.json())
.then((index) => {
state.index = index;
state.datasetId = index.datasets[0].id;
render();
})
.catch((error) => {
dom.main.innerHTML = `<div class="loading">Failed to load viewer: ${esc(error.message)}</div>`;
});
})();