| (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, '&').replace(/</g, '<').replace(/>/g, '>') |
| .replace(/"/g, '"').replace(/'/g, '''); |
|
|
| 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>`; |
| }); |
| })(); |
|
|