RMMM_Frontend / app.js
Satyam-Singh's picture
Update app.js
ddd3e78 verified
const state = {
selectedSample: null,
uploadedFile: null,
lastResponse: null,
busy: false,
};
const dropZone = document.getElementById("drop-zone");
const fileInput = document.getElementById("file-input");
const browseBtn = document.getElementById("browse-btn");
const sampleGrid = document.getElementById("sample-grid");
const analyzeBtn = document.getElementById("analyze-btn");
const loader = document.getElementById("loader");
const resultPanel = document.getElementById("result-panel");
const previewWrapper = document.getElementById("preview-wrapper");
const previewImage = document.getElementById("preview-image");
const annotatedImage = document.getElementById("annotated-image");
const reportText = document.getElementById("report-text");
const findingsGrid = document.getElementById("findings-grid");
const insightsList = document.getElementById("insights-list");
const metricsGrid = document.getElementById("metrics-grid");
const metricsCard = document.getElementById("metrics-card");
const timeline = document.getElementById("timeline");
const statusChip = document.getElementById("status-chip");
const errorBanner = document.getElementById("error-banner");
const downloadBtn = document.getElementById("download-btn");
const viewJsonBtn = document.getElementById("view-json-btn");
const refreshSamples = document.getElementById("refresh-samples");
const jsonDialog = document.getElementById("json-dialog");
browseBtn.addEventListener("click", () => fileInput.click());
dropZone.addEventListener("dragover", (event) => {
event.preventDefault();
dropZone.classList.add("toggle-on");
});
dropZone.addEventListener("dragleave", () => dropZone.classList.remove("toggle-on"));
dropZone.addEventListener("drop", (event) => {
event.preventDefault();
dropZone.classList.remove("toggle-on");
const [file] = event.dataTransfer.files || [];
if (file) {
handleFileSelection(file);
}
});
fileInput.addEventListener("change", (event) => {
const [file] = event.target.files || [];
if (file) {
handleFileSelection(file);
}
});
refreshSamples.addEventListener("click", loadSamples);
viewJsonBtn.addEventListener("click", () => {
if (!state.lastResponse) return;
jsonDialog.innerHTML = `
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold">Raw Response</h3>
<button class="rounded-lg bg-slate-800 px-3 py-1 text-xs text-slate-300" id="close-json">Close</button>
</div>
<pre class="mt-4 max-h-[60vh] overflow-y-auto rounded-xl bg-slate-950/80 p-4 text-xs text-slate-200">${JSON.stringify(state.lastResponse, null, 2)}</pre>
`;
jsonDialog.showModal();
document.getElementById("close-json")?.addEventListener("click", () => jsonDialog.close());
});
downloadBtn.addEventListener("click", () => {
if (!state.lastResponse) return;
const blob = new Blob([state.lastResponse.report_text], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
const name = state.lastResponse.source_id || "xray-report";
link.download = `${name}-report.txt`;
link.click();
URL.revokeObjectURL(url);
});
analyzeBtn.addEventListener("click", async () => {
if (state.busy) return;
if (!state.uploadedFile && !state.selectedSample) {
showError("Provide an image via upload or sample gallery.");
return;
}
state.busy = true;
errorBanner.classList.add("hidden");
loader.classList.remove("hidden");
statusChip.textContent = "Processing";
statusChip.classList.add("border-sky-400", "text-sky-200");
try {
const payload = await buildPayload();
const response = await fetch("https://satyam-singh-rmmm-backend.hf.space/api/v1/report", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Server responded with ${response.status}`);
}
const data = await response.json();
state.lastResponse = data;
renderResults(data);
statusChip.textContent = "Ready";
} catch (error) {
console.error(error);
showError("Unable to generate report. Please retry.");
statusChip.textContent = "Error";
} finally {
loader.classList.add("hidden");
state.busy = false;
}
});
function showError(message) {
errorBanner.textContent = message;
errorBanner.classList.remove("hidden");
setTimeout(() => errorBanner.classList.add("hidden"), 6000);
}
async function buildPayload() {
if (state.uploadedFile) {
const base64 = await fileToDataUrl(state.uploadedFile);
return {
image_base64: base64,
filename: state.uploadedFile.name,
};
}
return {
image_base64: state.selectedSample.image_base64,
source_id: state.selectedSample.id,
filename: `${state.selectedSample.id}.png`,
};
}
function handleFileSelection(file) {
state.uploadedFile = file;
state.selectedSample = null;
previewFile(file);
renderSampleSelection();
}
async function previewFile(file) {
const dataUrl = await fileToDataUrl(file);
previewWrapper.classList.remove("hidden");
previewImage.src = dataUrl;
previewImage.classList.add("opacity-0");
requestAnimationFrame(() => previewImage.classList.remove("opacity-0"));
}
function renderSampleSelection() {
const cards = sampleGrid.querySelectorAll("[data-sample-id]");
cards.forEach((card) => card.classList.remove("ring-2", "ring-sky-400"));
if (state.selectedSample) {
const activeCard = sampleGrid.querySelector(`[data-sample-id="${state.selectedSample.id}"]`);
activeCard?.classList.add("ring-2", "ring-sky-400");
}
}
function renderResults(data) {
const now = new Date(data.created_at).toLocaleString();
resultPanel.classList.remove("hidden");
reportText.textContent = data.report_text;
annotatedImage.src = data.annotated_image;
insightsList.innerHTML = "";
data.insights.forEach((insight, index) => {
const li = document.createElement("li");
li.innerHTML = `<span class="text-sky-300">${index + 1}.</span> ${insight}`;
insightsList.appendChild(li);
});
findingsGrid.innerHTML = "";
Object.entries(data.findings).forEach(([term, info]) => {
const card = document.createElement("div");
card.className = "rounded-xl border border-slate-700/70 bg-slate-900/60 p-4 transition hover:border-sky-500/70";
card.innerHTML = `
<div class="flex items-center justify-between">
<p class="text-sm font-semibold text-slate-200">${term.replaceAll("_", " ")}</p>
<span class="rounded-full border border-sky-400/50 bg-sky-400/10 px-2 py-0.5 text-[11px] uppercase tracking-wide text-sky-200">${info.severity}</span>
</div>
<p class="mt-2 text-xs text-slate-400">${info.definition}</p>
`;
findingsGrid.appendChild(card);
});
if (data.metrics) {
metricsCard.classList.remove("hidden");
metricsGrid.innerHTML = "";
Object.entries(data.metrics).forEach(([key, value]) => {
const term = key.toUpperCase().replaceAll("_", " ");
metricsGrid.insertAdjacentHTML(
"beforeend",
`<div class="rounded-xl border border-slate-700/70 bg-slate-950/60 p-3"><dt class="text-xs text-slate-400">${term}</dt><dd class="text-lg font-semibold text-slate-100">${value}</dd></div>`
);
});
} else {
metricsCard.classList.add("hidden");
}
timeline.innerHTML = "";
data.status_chain.forEach((step, index) => {
const item = document.createElement("li");
item.className = "rounded-xl border border-slate-800/80 bg-slate-950/40 p-3";
item.innerHTML = `
<p class="text-xs uppercase tracking-wide text-slate-400">Step ${index + 1}</p>
<p class="text-sm font-semibold text-slate-200">${step.title}</p>
<p class="text-xs text-slate-400">${step.detail}</p>
`;
timeline.appendChild(item);
});
statusChip.textContent = `Updated ${now}`;
}
async function fileToDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
async function loadSamples() {
sampleGrid.innerHTML = '<div class="col-span-full flex items-center justify-center py-8 text-sm text-slate-500">Loading samples...</div>';
try {
const response = await fetch("https://satyam-singh-rmmm-backend.hf.space/api/v1/samples");
if (!response.ok) throw new Error("Failed to load samples");
const data = await response.json();
sampleGrid.innerHTML = "";
data.items.forEach((item) => {
const card = document.createElement("button");
card.type = "button";
card.dataset.sampleId = item.id;
card.className = "group overflow-hidden rounded-2xl border border-slate-800/70 bg-slate-900/40 text-left transition hover:border-sky-500/70 hover:bg-slate-900/70";
card.innerHTML = `
<div class="aspect-square overflow-hidden">
<img src="${item.image_base64}" alt="${item.title}" class="h-full w-full object-cover transition duration-500 group-hover:scale-105" />
</div>
<div class="p-3">
<p class="text-xs uppercase tracking-wide text-slate-400">Sample</p>
<p class="mt-1 text-sm font-semibold text-slate-100">${item.title}</p>
</div>
`;
card.addEventListener("click", () => {
state.selectedSample = item;
state.uploadedFile = null;
previewWrapper.classList.remove("hidden");
previewImage.src = item.image_base64;
renderSampleSelection();
});
sampleGrid.appendChild(card);
});
renderSampleSelection();
} catch (error) {
console.error(error);
sampleGrid.innerHTML = '<div class="col-span-full rounded-xl border border-rose-500/40 bg-rose-500/10 p-4 text-sm text-rose-200">Unable to load samples.</div>';
}
}
loadSamples();