const form = document.getElementById("search-form"); const queryInput = document.getElementById("query-input"); const budgetInput = document.getElementById("budget-input"); const searchBtn = document.getElementById("search-btn"); const pipelineSection = document.getElementById("pipeline"); // Matches the new pipeline-grid: each agent has a status
with these ids. const stageEls = [ document.getElementById("search-status"), document.getElementById("price-status"), document.getElementById("review-status"), document.getElementById("spec-status"), document.getElementById("rank-status"), ]; const stageDefaults = [ "Scanning listings...", "Computing value...", "Reading reviews...", "Extracting specifications...", "Preparing verdict...", ]; const stageDone = [ "Listings scanned ✓", "Value computed ✓", "Reviews synthesized ✓", "Specs extracted ✓", "Verdict filed ✓", ]; // The little status dot next to each agent name (span.dot inside .agent-top) const stageDots = document.querySelectorAll("#pipeline .agent .dot"); const resultsSection = document.getElementById("results"); const cardsWrap = document.getElementById("cards"); const banner = document.getElementById("banner"); const compareWrap = document.getElementById("compare-wrap"); const compareTable = document.getElementById("compare-table"); const toggleCompareBtn = document.getElementById("toggle-compare"); const emptyState = document.getElementById("empty-state"); const errorState = document.getElementById("error-state"); const errorText = document.getElementById("error-text"); // Optional elements -- safe to be absent, we just skip them if so. const resultsTitle = document.getElementById("results-title"); const dataSourceBadge = document.getElementById("data-source-badge"); let pipelineTimer = null; function setText(el, text) { if (el) el.textContent = text; } document.querySelectorAll(".chip").forEach((chip) => { chip.addEventListener("click", () => { queryInput.value = chip.dataset.q; form.requestSubmit(); }); }); if (toggleCompareBtn) { toggleCompareBtn.addEventListener("click", () => { compareWrap.classList.toggle("hidden"); setText( toggleCompareBtn, compareWrap.classList.contains("hidden") ? "Compare Products" : "Hide Comparison" ); }); } form.addEventListener("submit", async (e) => { e.preventDefault(); const query = queryInput.value.trim(); if (!query) return; const budget = budgetInput.value ? Number(budgetInput.value) : null; hide(emptyState); hide(errorState); hide(resultsSection); show(pipelineSection); animatePipeline(); searchBtn.disabled = true; const originalBtnHTML = searchBtn.innerHTML; searchBtn.innerHTML = ' Agents working…'; try { const res = await fetch("/api/recommend", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, budget_max: budget }), }); const data = await res.json(); if (!res.ok) { throw new Error(data.detail || "Something went wrong on the server."); } finishPipeline(); setTimeout(() => renderResults(query, data), 450); } catch (err) { finishPipeline(); hide(pipelineSection); showError(err.message); } finally { searchBtn.disabled = false; searchBtn.innerHTML = originalBtnHTML; } }); function animatePipeline() { clearInterval(pipelineTimer); stageEls.forEach((el, i) => setText(el, stageDefaults[i])); stageDots.forEach((dot) => { dot.style.background = ""; dot.style.boxShadow = ""; }); let i = 0; const activate = (idx) => { const dot = stageDots[idx]; if (dot) { dot.style.background = "#d98e2b"; dot.style.boxShadow = "0 0 0 4px rgba(217,142,43,0.18)"; } }; const complete = (idx) => { const dot = stageDots[idx]; if (dot) { dot.style.background = "#2f6b4f"; dot.style.boxShadow = "none"; } setText(stageEls[idx], stageDone[idx]); }; activate(0); pipelineTimer = setInterval(() => { complete(i); i++; if (i < stageEls.length) { activate(i); } else { clearInterval(pipelineTimer); } }, 900); } function finishPipeline() { clearInterval(pipelineTimer); stageEls.forEach((el, i) => setText(el, stageDone[i])); stageDots.forEach((dot) => { dot.style.background = "#2f6b4f"; dot.style.boxShadow = "none"; }); } function showError(message) { setText(errorText, message); show(errorState); } function renderResults(query, data) { hide(pipelineSection); show(resultsSection); if (resultsTitle) setText(resultsTitle, `Verdict for "${query}"`); if (dataSourceBadge) { if (data.used_live_data) { setText(dataSourceBadge, "LIVE DATA · SERPAPI"); dataSourceBadge.className = "badge live"; } else { setText(dataSourceBadge, "SAMPLE CATALOG"); dataSourceBadge.className = "badge sample"; } } if (data.errors && data.errors.length) { setText(banner, data.errors.join(" ")); show(banner); } else { hide(banner); } const results = data.results || []; if (results.length === 0) { cardsWrap.innerHTML = ""; showError("No products matched. Try a broader search term."); hide(resultsSection); return; } cardsWrap.innerHTML = results.map(cardHTML).join(""); compareTable.innerHTML = compareTableHTML(results); compareWrap.classList.add("hidden"); if (toggleCompareBtn) setText(toggleCompareBtn, "Compare Products"); } function fmtPrice(price, currency) { if (price === null || price === undefined) return "Price unavailable"; const symbol = currency === "INR" ? "₹" : currency ? currency + " " : ""; return `${symbol}${Number(price).toLocaleString("en-IN")}`; } function placeholderImage(title) { const short = (title || "Product").split(" ").slice(0, 3).join(" "); return `https://placehold.co/400x400/ece7db/14171f?font=roboto&text=${encodeURIComponent(short)}`; } function cardHTML(item) { const pros = item.review_analysis?.pros || []; const cons = item.review_analysis?.cons || []; const confidence = item.review_analysis?.confidence || ""; const features = item.spec_analysis?.key_features || []; const valueScore = item.price_analysis?.value_score; const valueVerdict = item.price_analysis?.verdict || ""; const realReviews = item.real_review_snippets || []; const imgSrc = item.thumbnail || placeholderImage(item.title); return `
${escapeHTML(item.reasoning)}
` : ""} ${valueScore !== undefined ? `"${escapeHTML(r.snippet)}"