extracter / static /app.js
deveos's picture
Upload 5 files
0a5a54d verified
Raw
History Blame Contribute Delete
5.07 kB
const form = document.querySelector("#uploadForm");
const pdfInput = document.querySelector("#pdfInput");
const fileName = document.querySelector("#fileName");
const startButton = document.querySelector("#startButton");
const statusText = document.querySelector("#statusText");
const batchText = document.querySelector("#batchText");
const progressBar = document.querySelector("#progressBar");
const jobIdText = document.querySelector("#jobIdText");
const pageCounter = document.querySelector("#pageCounter");
const tabCounter = document.querySelector("#tabCounter");
const messageText = document.querySelector("#messageText");
const downloadLink = document.querySelector("#downloadLink");
const downloadCsvLink = document.querySelector("#downloadCsvLink");
const previewBody = document.querySelector("#previewBody");
const rowCount = document.querySelector("#rowCount");
const browserViewStatus = document.querySelector("#browserViewStatus");
const browserViewImage = document.querySelector("#browserViewImage");
const browserViewEmpty = document.querySelector("#browserViewEmpty");
let pollTimer = null;
pdfInput.addEventListener("change", () => {
fileName.textContent = pdfInput.files[0]?.name || "Choose a bank statement PDF";
});
form.addEventListener("submit", async (event) => {
event.preventDefault();
clearInterval(pollTimer);
downloadLink.hidden = true;
downloadCsvLink.hidden = true;
startButton.disabled = true;
setStatus({ id: "", status: "uploading", message: "Uploading PDF", progress: 2, completed_batches: 0, total_batches: 0 });
const formData = new FormData(form);
try {
const response = await fetch("/api/jobs", { method: "POST", body: formData });
if (!response.ok) throw new Error(await readError(response));
const payload = await response.json();
pollJob(payload.job_id);
pollTimer = setInterval(() => pollJob(payload.job_id), 2500);
} catch (error) {
setStatus({ status: "error", message: error.message, progress: 100, completed_batches: 0, total_batches: 0 });
startButton.disabled = false;
}
});
async function pollJob(jobId) {
try {
const response = await fetch(`/api/jobs/${jobId}`);
if (!response.ok) throw new Error(await readError(response));
const job = await response.json();
setStatus(job);
updateBrowserView(job);
if (job.status === "done") {
clearInterval(pollTimer);
startButton.disabled = false;
downloadLink.hidden = false;
downloadLink.href = `/api/jobs/${jobId}/download`;
downloadCsvLink.hidden = false;
downloadCsvLink.href = `/api/jobs/${jobId}/download.csv`;
renderPreview(job.data_preview || []);
}
if (job.status === "error") {
clearInterval(pollTimer);
startButton.disabled = false;
}
} catch (error) {
clearInterval(pollTimer);
startButton.disabled = false;
setStatus({ status: "error", message: error.message, progress: 100, completed_batches: 0, total_batches: 0 });
}
}
function setStatus(job) {
statusText.textContent = titleCase(job.status || "ready");
jobIdText.textContent = job.id ? `Job ${job.id}` : "No job started";
pageCounter.textContent = `${job.completed_pages || 0} / ${job.total_pages || 0} pages`;
tabCounter.textContent = `${job.active_tabs || 0} active tabs`;
const pageText = job.current_pages ? ` Current pages: ${job.current_pages}.` : "";
messageText.textContent = `${job.error || job.message || ""}${pageText}`;
batchText.textContent = `${job.completed_batches || 0} / ${job.total_batches || 0} waves`;
progressBar.style.width = `${Math.max(0, Math.min(job.progress || 0, 100))}%`;
}
function updateBrowserView(job) {
if (!job.id || !job.latest_screenshot) {
browserViewStatus.textContent = "Waiting";
return;
}
browserViewStatus.textContent = "Latest screenshot";
browserViewImage.hidden = false;
browserViewEmpty.hidden = true;
browserViewImage.src = `/api/jobs/${job.id}/browser-view/latest?t=${Date.now()}`;
}
function renderPreview(rows) {
rowCount.textContent = rows.length ? `First ${rows.length} rows` : "No rows returned";
if (!rows.length) {
previewBody.innerHTML = '<tr><td colspan="5" class="empty">No preview rows were returned.</td></tr>';
return;
}
previewBody.innerHTML = rows.map((row) => `
<tr>
<td>${escapeHtml(row.date || "")}</td>
<td>${escapeHtml(row.description || "")}</td>
<td>${escapeHtml(row.voucher_type || "")}</td>
<td>${escapeHtml(row.amount || "")}</td>
<td>${escapeHtml(row.closing || "")}</td>
</tr>
`).join("");
}
async function readError(response) {
try {
const payload = await response.json();
return payload.detail || response.statusText;
} catch {
return response.statusText;
}
}
function titleCase(value) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}