import { submitUrls, getJobs, getDownloadUrl } from './api'; // Elements const urlInput = document.getElementById('urlInput') as HTMLInputElement; const addUrlBtn = document.getElementById('addUrlBtn') as HTMLButtonElement; const submitBtn = document.getElementById('submitBtn') as HTMLButtonElement; const urlListEl = document.getElementById('urlList') as HTMLUListElement; const urlCountEl = document.getElementById('urlCount') as HTMLSpanElement; const jobsListEl = document.getElementById('jobsList') as HTMLTableSectionElement; const downloadBtn = document.getElementById('downloadBtn') as HTMLButtonElement; // State let urlsToScrape: string[] = []; // Events addUrlBtn.addEventListener('click', addUrl); urlInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') addUrl(); }); submitBtn.addEventListener('click', submitScrape); function addUrl() { let url = urlInput.value.trim(); if (!url) return; // checks if the url is empty if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; // if the url is not starting with http:// or https://, it will add https:// to the url } urlsToScrape.push(url); // adds the url to the urlsToScrape array urlInput.value = ''; renderUrlList(); } function removeUrl(index: number) { urlsToScrape.splice(index, 1); renderUrlList(); } // Need to attach to window so HTML onclick handler works (window as any).removeUrl = removeUrl; function renderUrlList() { urlCountEl.textContent = urlsToScrape.length.toString(); submitBtn.disabled = urlsToScrape.length === 0; if (urlsToScrape.length === 0) { urlListEl.innerHTML = '
  • No URLs added yet.
  • '; return; } urlListEl.innerHTML = ''; urlsToScrape.forEach((url, i) => { const li = document.createElement('li'); const span = document.createElement('span'); span.textContent = url; li.appendChild(span); const btn = document.createElement('button'); btn.innerHTML = '×'; btn.onclick = () => removeUrl(i); li.appendChild(btn); urlListEl.appendChild(li); }); } async function submitScrape() { if (urlsToScrape.length === 0) return; submitBtn.disabled = true; submitBtn.textContent = 'Submitting...'; try { await submitUrls(urlsToScrape); // Clear list on success urlsToScrape = []; renderUrlList(); fetchJobs(); // Update UI immediately } catch (error) { alert('Error submitting urls. Ensure your backend is running.'); console.error(error); } finally { submitBtn.textContent = 'Start Extraction'; } } // Polling and rendering jobs // All statuses that mean "something went wrong" — used to normalize CSS classes const ERROR_STATUSES = ['failed', 'Failed', 'Wrong URL', 'Bot Blocked', 'LLM limit exceeded']; function isErrorStatus(status: string): boolean { return ERROR_STATUSES.includes(status) || status.startsWith('Wrong URL') || status.startsWith('Bot Blocked'); } function getStatusHtml(status: string, displayText?: string) { const label = displayText || status; const cssStatus = isErrorStatus(status) ? 'failed' : status; let icon = ''; if (status === 'completed') { icon = ``; } else if (status === 'processing') { icon = ``; } else if (isErrorStatus(status)) { icon = ``; } else if (status === 'pending') { icon = ``; } return `${icon}${label}`; } async function fetchJobs() { try { const jobs = await getJobs(); renderJobs(jobs); } catch (e) { console.error('API connection failed:', e); } } function renderJobs(jobs: Record) { const entries = Object.entries(jobs); if (entries.length === 0) { jobsListEl.innerHTML = 'No URLs processing right now.'; downloadBtn.style.display = 'none'; return; } let allCompleted = true; let rowsHtml = ''; let counter = 1; let hasdata = false; for (const [, job] of entries) { if (job.status !== 'completed') { } // job.urls is a map of id -> {url, status} const urlTrackingList = Object.values(job.urls || {}) as {url: string, status: string, reason?: string}[]; for (const {url, status, reason} of urlTrackingList) { if (status === 'completed') hasdata = true; allCompleted = true; const displayStatus = (isErrorStatus(status) && reason) ? reason : status; const rowCssClass = isErrorStatus(status) ? 'failed' : status; rowsHtml += ` ${counter++} ${url} ${getStatusHtml(status, displayStatus)} `; } } jobsListEl.innerHTML = rowsHtml; if (allCompleted && hasdata) { downloadBtn.style.display = 'inline-block'; downloadBtn.onclick = () => { downloadBtn.disabled = true; downloadBtn.innerHTML = ` Downloading...`; window.location.href = getDownloadUrl(); setTimeout(() => { window.location.reload(); }, 2000); // Reload after 2s to allow download to fire }; } else { downloadBtn.style.display = 'none'; } } // Download logic handled inside renderJobs // Initial setup renderUrlList(); setInterval(fetchJobs, 3000); // Polling every 3s fetchJobs();