| |
|
|
| const emptyState = document.getElementById("emptyState"); |
| const reportContainer = document.getElementById("reportContainer"); |
| const severityBanner = document.getElementById("severityBanner"); |
| const severityPill = document.getElementById("severityPill"); |
| const severityFilename = document.getElementById("severityFilename"); |
| const statDefects = document.getElementById("statDefects"); |
| const statTypes = document.getElementById("statTypes"); |
| const statScore = document.getElementById("statScore"); |
| const findingsEl = document.getElementById("findings"); |
| const outputImage = document.getElementById("outputImage"); |
| const imageMetaEl = document.getElementById("imageMeta"); |
| const detectionsListEl = document.getElementById("detectionsList"); |
| const resultsTableBody = document.querySelector("#resultsTable tbody"); |
| const downloadCsvBtn = document.getElementById("downloadCsv"); |
| const downloadTxtBtn = document.getElementById("downloadTxt"); |
| const scanAnotherBtn = document.getElementById("scanAnotherBtn"); |
|
|
| const SEVERITY_CLASS = { |
| "No Damage Detected": "none", |
| "Minor": "minor", |
| "Moderate": "moderate", |
| "Severe": "severe", |
| }; |
|
|
| function formatBytes(bytes) { |
| if (bytes < 1024) return `${bytes} B`; |
| if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`; |
| return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; |
| } |
|
|
| function downloadText(filename, text) { |
| const blob = new Blob([text], { type: "text/plain" }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement("a"); |
| a.href = url; |
| a.download = filename; |
| a.click(); |
| URL.revokeObjectURL(url); |
| } |
|
|
| function renderSummary(summary) { |
| severityPill.textContent = summary.severity_label; |
| severityPill.className = `severity-pill ${SEVERITY_CLASS[summary.severity_label] || "none"}`; |
| severityBanner.className = `severity-banner glass-card ${SEVERITY_CLASS[summary.severity_label] || "none"}`; |
| statDefects.textContent = summary.total_defects; |
| statTypes.textContent = summary.by_class.length; |
| statScore.textContent = summary.severity_score; |
|
|
| if (summary.by_class.length === 0) { |
| findingsEl.innerHTML = `<p class="empty-note">No damage detected above the current confidence thresholds.</p>`; |
| return; |
| } |
|
|
| findingsEl.innerHTML = summary.by_class.map((c) => ` |
| <div class="finding-chip"> |
| <span class="swatch" style="background: rgb(${c.color.join(",")})"></span> |
| <span class="count">${c.count}</span> |
| <span>${c.class_name}</span> |
| </div> |
| `).join(""); |
| } |
|
|
| function renderDetections(detections, classColorById) { |
| const issues = detections |
| .filter((d) => d.class_id !== 0) |
| .sort((a, b) => b.confidence - a.confidence); |
|
|
| if (issues.length === 0) { |
| detectionsListEl.innerHTML = `<p class="empty-note">No damage detected above the current confidence thresholds.</p>`; |
| return; |
| } |
|
|
| detectionsListEl.innerHTML = issues.map((d) => { |
| const color = classColorById[d.class_id] || [102, 112, 133]; |
| const pct = Math.round(d.confidence * 100); |
| return ` |
| <div class="detection-item"> |
| <span class="swatch" style="background: rgb(${color.join(",")})"></span> |
| <span class="detection-name">${d.class_name}</span> |
| <div class="confidence-track"> |
| <div class="confidence-fill" style="width: ${pct}%; background: rgb(${color.join(",")})"></div> |
| </div> |
| <span class="confidence-value mono">${pct}%</span> |
| </div> |
| `; |
| }).join(""); |
| } |
|
|
| async function render() { |
| const raw = sessionStorage.getItem("cardentiq_result"); |
| if (!raw) { |
| emptyState.hidden = false; |
| return; |
| } |
|
|
| let data; |
| try { |
| data = JSON.parse(raw); |
| } catch { |
| emptyState.hidden = false; |
| return; |
| } |
|
|
| reportContainer.hidden = false; |
|
|
| let classColorById = {}; |
| try { |
| const res = await fetch("/api/classes"); |
| const classData = await res.json(); |
| classColorById = Object.fromEntries(classData.classes.map((c) => [c.id, c.color])); |
| } catch { |
| |
| } |
|
|
| severityFilename.textContent = data.image_info?.filename || ""; |
| outputImage.src = data.annotated_image; |
| imageMetaEl.textContent = data.image_info |
| ? `${data.image_info.width} × ${data.image_info.height} px · ${formatBytes(data.image_info.size_bytes)}` |
| : ""; |
|
|
| renderSummary(data.summary); |
| renderDetections(data.detections, classColorById); |
|
|
| resultsTableBody.innerHTML = data.detections.map((d) => ` |
| <tr> |
| <td>${d.class_name}</td> |
| <td>${d.confidence}</td> |
| <td>[${d.box_yolo.join(", ")}]</td> |
| </tr> |
| `).join(""); |
|
|
| downloadCsvBtn.disabled = data.detections.length === 0; |
| downloadTxtBtn.disabled = data.detections.length === 0; |
|
|
| downloadCsvBtn.addEventListener("click", () => downloadText("detections.csv", data.csv)); |
| downloadTxtBtn.addEventListener("click", () => downloadText("labels.txt", data.yolo_txt)); |
| } |
|
|
| scanAnotherBtn.addEventListener("click", () => sessionStorage.removeItem("cardentiq_result")); |
|
|
| render(); |
|
|