const resultsEl = document.getElementById('results'); const statusEl = document.getElementById('status'); const graphFrame = document.getElementById('graphFrame'); const viewerTitle = document.getElementById('viewerTitle'); const reportJsonLink = document.getElementById('reportJsonLink'); const diagEl = document.getElementById('diag'); const schemaRows = document.getElementById('schemaRows'); const nodesEl = document.getElementById('nodes'); const moduleSearchEl = document.getElementById('moduleSearch'); const moduleDetailEl = document.getElementById('moduleDetail'); const rawCodeEl = document.getElementById('rawCode'); const rawJsonBtn = document.getElementById('rawJsonBtn'); const moduleRawJsonEl = document.getElementById('moduleRawJson'); const runAnalysisBtn = document.getElementById('runAnalysisBtn'); const analysisTimeoutEl = document.getElementById('analysisTimeout'); const analysisOutputEl = document.getElementById('analysisOutput'); const bootstrapTrainingBtn = document.getElementById('bootstrapTrainingBtn'); const runTrainingBtn = document.getElementById('runTrainingBtn'); const refreshTrainingRunsBtn = document.getElementById('refreshTrainingRunsBtn'); const trainingRunsEl = document.getElementById('trainingRuns'); const trainingOutputEl = document.getElementById('trainingOutput'); const analysisRunIdEl = document.getElementById('analysisRunId'); const fetchRunAnalysisBtn = document.getElementById('fetchRunAnalysisBtn'); const runAnalysisOutputEl = document.getElementById('runAnalysisOutput'); let currentNodes = []; let selectedModuleId = ''; function findNodeByModuleId(moduleId) { const target = String(moduleId || '').trim(); if (!target) { return null; } const exact = currentNodes.find((node) => String(node.module_id) === target); if (exact) { return exact; } const lowered = target.toLowerCase(); const ci = currentNodes.find((node) => String(node.module_id).toLowerCase() === lowered); if (ci) { return ci; } return currentNodes.find((node) => String(node.module_id).endsWith(`.${target}`)) || null; } function selectModuleFromGraph(moduleId) { const node = findNodeByModuleId(moduleId); if (!node) { return; } showModule(node); } function bindGraphNodeClick(frameWin) { if (!frameWin || !frameWin.network || frameWin.__graphReviewNodeClickBound) { return; } frameWin.__graphReviewNodeClickBound = true; frameWin.network.on('click', (params) => { if (!params?.nodes?.length) { return; } const moduleId = params.nodes[0]; selectModuleFromGraph(moduleId); }); } window.addEventListener('message', (event) => { const payload = event.data; if (!payload || payload.type !== 'graphreview-node-select') { return; } selectModuleFromGraph(payload.moduleId); }); function normalizeTooltipText(raw) { const text = String(raw || ''); return text .replace(//gi, '\n') .replace(/<\/?b>/gi, '') .replace(/<[^>]+>/g, '') .replace(/[ \t]+\n/g, '\n') .trim(); } function normalizeGraphTooltips(frameDoc) { const tooltips = frameDoc.querySelectorAll('.vis-tooltip, .vis-network-tooltip'); tooltips.forEach((tooltip) => { const normalized = normalizeTooltipText(tooltip.textContent); if (normalized && normalized !== tooltip.textContent) { tooltip.textContent = normalized; } }); } function applyGraphTooltipStyles() { const frameDoc = graphFrame?.contentDocument; const frameWin = graphFrame?.contentWindow; if (!frameDoc) { return; } if (frameDoc.getElementById('graphreview-tooltip-style')) { return; } const style = frameDoc.createElement('style'); style.id = 'graphreview-tooltip-style'; style.textContent = ` .vis-tooltip, .vis-network-tooltip { max-width: 420px !important; white-space: pre-wrap !important; overflow-wrap: anywhere !important; word-break: break-word !important; line-height: 1.35 !important; text-align: left !important; } `; frameDoc.head?.appendChild(style); const observer = new MutationObserver(() => normalizeGraphTooltips(frameDoc)); observer.observe(frameDoc.body, { subtree: true, childList: true, characterData: true }); normalizeGraphTooltips(frameDoc); if (frameWin) { bindGraphNodeClick(frameWin); let retries = 0; const interval = setInterval(() => { retries += 1; bindGraphNodeClick(frameWin); if (frameWin.__graphReviewNodeClickBound || retries > 20) { clearInterval(interval); } }, 250); } } graphFrame?.addEventListener('load', applyGraphTooltipStyles); function fmtPct(value) { return `${(Number(value || 0) * 100).toFixed(1)}%`; } function fmtDate(seconds) { return new Date(seconds * 1000).toLocaleString(); } function setActiveTab(tabId) { document.querySelectorAll('.tab-btn').forEach((btn) => { btn.classList.toggle('active', btn.dataset.tab === tabId); }); document.querySelectorAll('.tab-panel').forEach((panel) => { panel.classList.toggle('active', panel.id === tabId); }); } function renderResultList(results) { resultsEl.innerHTML = ''; if (!results.length) { resultsEl.innerHTML = '
No reports found in outputs. Generate one first via CLI or /reports/generate.
'; return; } results.forEach((result, idx) => { const item = document.createElement('button'); item.className = 'result-item'; item.type = 'button'; item.innerHTML = `
${result.report_title}
${result.report_path}
nodes ${result.node_count ?? '-'} edges ${result.edge_count ?? '-'} confidence ${result.confidence_score == null ? '-' : Number(result.confidence_score).toFixed(3)}
${fmtDate(result.generated_at)}
`; item.addEventListener('click', () => selectResult(result, item)); resultsEl.appendChild(item); if (idx === 0) { item.click(); } }); } function renderSchema(columns) { schemaRows.innerHTML = ''; Object.entries(columns || {}).forEach(([tableName, cols]) => { const tr = document.createElement('tr'); tr.innerHTML = `${tableName}${(cols || []).join(', ')}`; schemaRows.appendChild(tr); }); } function renderDiagnostics(connectivity, metrics, report) { const reportNodeCount = Array.isArray(report?.nodes) ? report.nodes.length : 0; const reportEdgeCount = Array.isArray(report?.edges) ? report.edges.length : 0; const nodeCount = Number(connectivity?.node_count || 0) || reportNodeCount; const edgeCount = Number(connectivity?.edge_count || 0) || reportEdgeCount; const connectedComponents = Number(connectivity?.connected_components || 0); const largestComponentSize = Number(connectivity?.largest_component_size || 0); const isolatedNodes = Number(connectivity?.isolated_nodes || 0); const isolationRatio = Number(connectivity?.isolation_ratio || 0); const isolationClass = isolationRatio > 0.35 ? 'danger' : ''; diagEl.innerHTML = `
Nodes${nodeCount}
Edges${edgeCount}
Connected Components${connectedComponents}
Largest Component${largestComponentSize}
Isolated Nodes${isolatedNodes} (${fmtPct(isolationRatio)})
Precision / Recall / F1${Number(metrics.precision || 0).toFixed(3)} / ${Number(metrics.recall || 0).toFixed(3)} / ${Number(metrics.f1 || 0).toFixed(3)}
Security Coverage${Number(metrics.security_coverage || 0).toFixed(3)}
Stage Coverage${Number(metrics.stage_coverage || 0).toFixed(3)}
`; } function formatModuleDetail(node) { const findings = node.linter_findings || []; const reviews = node.reviews || []; const security = node.security_findings || []; const lastReview = reviews.length ? reviews[reviews.length - 1] : null; const findingList = findings.slice(0, 8).map((f) => ( `
  • [${String(f.severity || '').toUpperCase()}] ${f.code} at line ${f.line}: ${f.message}
  • ` )).join(''); const reviewList = reviews.slice(-6).reverse().map((r) => ( `
  • step ${r.step_number} | ${r.action_type} | reward ${Number(r.reward_given || 0).toFixed(2)}
  • ` )).join(''); return `

    ${node.module_id}

    Status${node.status}
    Summary${node.summary || '-'}
    Findings${findings.length}
    Security Findings${security.length}
    Reviews${reviews.length}
    Latest Review${lastReview ? `${lastReview.action_type} (step ${lastReview.step_number})` : '-'}

    Top Findings

    Recent Reviews

    `; } function showModule(node) { if (!node) { return; } selectedModuleId = node.module_id; moduleDetailEl.classList.remove('muted'); moduleDetailEl.innerHTML = formatModuleDetail(node); rawCodeEl.textContent = node.raw_code || '# No raw code available'; if (window.hljs) { window.hljs.highlightElement(rawCodeEl); } rawJsonBtn.style.display = 'inline-block'; moduleRawJsonEl.style.display = 'none'; moduleRawJsonEl.textContent = JSON.stringify(node, null, 2); document.querySelectorAll('.node-row').forEach((el) => { el.classList.toggle('active', el.dataset.moduleId === selectedModuleId); }); } function renderNodes(report) { currentNodes = report.nodes || []; nodesEl.innerHTML = ''; const query = (moduleSearchEl.value || '').trim().toLowerCase(); const filtered = currentNodes.filter((node) => !query || node.module_id.toLowerCase().includes(query)); filtered.forEach((node) => { const row = document.createElement('div'); row.className = 'node-row'; row.dataset.moduleId = node.module_id; const findings = (node.linter_findings || []).length; const reviews = (node.reviews || []).length; row.innerHTML = `
    ${node.module_id}
    status:${node.status} findings:${findings} reviews:${reviews}
    `; row.addEventListener('click', () => { showModule(node); }); nodesEl.appendChild(row); }); if (!filtered.length) { nodesEl.innerHTML = '
    No modules match search.
    '; } } async function selectResult(result, itemEl) { document.querySelectorAll('.result-item').forEach((el) => el.classList.remove('active')); itemEl.classList.add('active'); statusEl.textContent = `Loading ${result.report_path}...`; viewerTitle.textContent = result.report_title; graphFrame.src = result.graph_html_url || ''; reportJsonLink.href = result.report_json_url; const res = await fetch(`/ui/result?report_path=${encodeURIComponent(result.report_path)}`); if (!res.ok) { statusEl.textContent = `Failed to load report detail: ${res.status}`; return; } const detail = await res.json(); renderDiagnostics(detail.connectivity, detail.report.metrics || {}, detail.report || {}); renderSchema(detail.db_columns || {}); renderNodes(detail.report || {}); moduleDetailEl.classList.add('muted'); moduleDetailEl.textContent = 'Select a module to inspect report fields, findings, and reviews.'; rawCodeEl.textContent = 'Select a module to view raw code.'; rawJsonBtn.style.display = 'none'; moduleRawJsonEl.style.display = 'none'; moduleRawJsonEl.textContent = ''; selectedModuleId = ''; statusEl.textContent = `Loaded ${result.report_path}`; } async function runAnalysis() { const timeout = Number(analysisTimeoutEl.value || 45); analysisOutputEl.textContent = 'Running analyzers...'; const res = await fetch('/analysis/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ timeout_seconds: timeout }), }); const payload = await res.json(); analysisOutputEl.textContent = JSON.stringify(payload, null, 2); } async function bootstrapTraining() { trainingOutputEl.textContent = 'Running training bootstrap...'; const res = await fetch('/training/bootstrap', { method: 'POST' }); const payload = await res.json(); trainingOutputEl.textContent = JSON.stringify(payload, null, 2); } function renderTrainingRuns(rows) { if (!rows || !rows.length) { trainingRunsEl.classList.add('muted'); trainingRunsEl.innerHTML = 'No runs yet.'; return; } trainingRunsEl.classList.remove('muted'); trainingRunsEl.innerHTML = rows.map((row) => `
    ${row.run_id}
    ${row.model_name}
    precision ${Number(row.precision || 0).toFixed(3)} recall ${Number(row.recall || 0).toFixed(3)} tp ${row.true_positives} fp ${row.false_positives} fn ${row.false_negatives}
    ${new Date(row.created_at).toLocaleString()}
    `).join(''); trainingRunsEl.querySelectorAll('.run-analysis-btn').forEach((btn) => { btn.addEventListener('click', () => { const runId = btn.dataset.runId || ''; if (analysisRunIdEl) { analysisRunIdEl.value = runId; } fetchRunAnalysis().catch((error) => { if (runAnalysisOutputEl) { runAnalysisOutputEl.textContent = `Error: ${String(error)}`; } }); }); }); } async function refreshTrainingRuns() { const res = await fetch('/training/runs?limit=25'); if (!res.ok) { trainingRunsEl.classList.add('muted'); trainingRunsEl.textContent = `Failed to load runs: ${res.status}`; return; } const payload = await res.json(); renderTrainingRuns(payload); } async function runTrainingEpisode() { trainingOutputEl.textContent = 'Running training episode...'; const res = await fetch('/training/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force_seed: false, regression_tolerance: 0.01 }), }); const payload = await res.json(); trainingOutputEl.textContent = JSON.stringify(payload, null, 2); await refreshTrainingRuns(); } async function fetchRunAnalysis() { const runId = (analysisRunIdEl?.value || '').trim(); if (!runId) { runAnalysisOutputEl.textContent = 'Please select or enter a training run id first.'; return; } runAnalysisOutputEl.textContent = `Generating critical analysis for ${runId}...`; const res = await fetch(`/training/runs/${encodeURIComponent(runId)}/analysis`); const payload = await res.json(); if (!res.ok) { runAnalysisOutputEl.textContent = JSON.stringify(payload, null, 2); return; } runAnalysisOutputEl.textContent = payload.analysis || 'No analysis returned.'; } rawJsonBtn?.addEventListener('click', () => { const isHidden = moduleRawJsonEl.style.display === 'none'; moduleRawJsonEl.style.display = isHidden ? 'block' : 'none'; rawJsonBtn.textContent = isHidden ? 'Hide Raw Module Report JSON' : 'View Raw Module Report JSON'; }); moduleSearchEl?.addEventListener('input', () => { renderNodes({ nodes: currentNodes }); }); runAnalysisBtn?.addEventListener('click', () => { runAnalysis().catch((error) => { analysisOutputEl.textContent = `Error: ${String(error)}`; }); }); bootstrapTrainingBtn?.addEventListener('click', () => { bootstrapTraining().catch((error) => { trainingOutputEl.textContent = `Error: ${String(error)}`; }); }); runTrainingBtn?.addEventListener('click', () => { runTrainingEpisode().catch((error) => { trainingOutputEl.textContent = `Error: ${String(error)}`; }); }); refreshTrainingRunsBtn?.addEventListener('click', () => { refreshTrainingRuns().catch((error) => { trainingRunsEl.classList.add('muted'); trainingRunsEl.textContent = `Error: ${String(error)}`; }); }); fetchRunAnalysisBtn?.addEventListener('click', () => { fetchRunAnalysis().catch((error) => { if (runAnalysisOutputEl) { runAnalysisOutputEl.textContent = `Error: ${String(error)}`; } }); }); document.querySelectorAll('.tab-btn').forEach((btn) => { btn.addEventListener('click', () => setActiveTab(btn.dataset.tab)); }); async function init() { try { const res = await fetch('/ui/results'); if (!res.ok) { statusEl.textContent = `Failed to load results: ${res.status}`; return; } const results = await res.json(); renderResultList(results); await refreshTrainingRuns(); statusEl.textContent = `Found ${results.length} report set(s)`; } catch (err) { statusEl.textContent = `Failed to load UI: ${String(err)}`; } } init();