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 += `