| const form = document.getElementById("analyze-form"); |
| const statusBox = document.getElementById("status"); |
| const metaBox = document.getElementById("meta"); |
|
|
| const mHashtag = document.getElementById("m-hashtag"); |
| const mGemini = document.getElementById("m-gemini"); |
| const mFallback = document.getElementById("m-fallback"); |
| const mModels = document.getElementById("m-models"); |
|
|
| const pieDiv = document.getElementById("pie"); |
| const lineDiv = document.getElementById("line"); |
| const tableDiv = document.getElementById("table"); |
|
|
| |
| const cursor = document.getElementById("parallax-cursor"); |
| window.addEventListener("mousemove", (e) => { |
| const x = e.clientX, y = e.clientY; |
| cursor.style.opacity = ".9"; |
| cursor.style.left = x + "px"; |
| cursor.style.top = y + "px"; |
| }); |
|
|
| |
| function fmtPct(n){ return (Math.round(n * 100) / 100).toFixed(2); } |
|
|
| function renderMeta(meta) { |
| mHashtag.textContent = meta.hashtag; |
| mGemini.textContent = `Gemini: ${meta.generated_by.gemini}`; |
| mFallback.textContent = `Fallback: ${meta.generated_by.fallback}`; |
| mModels.textContent = `Gen: ${meta.model.generation} • Sentiment: ${meta.model.sentiment}`; |
|
|
| |
| metaBox.style.opacity = "0"; |
| metaBox.style.transform = "translateY(10px)"; |
| requestAnimationFrame(() => { |
| metaBox.style.transition = "all .6s ease"; |
| metaBox.style.opacity = "1"; |
| metaBox.style.transform = "translateY(0)"; |
| }); |
| } |
|
|
| function renderPie(percent) { |
| const data = [{ |
| values: [percent.positive, percent.neutral, percent.negative], |
| labels: ['Positive', 'Neutral', 'Negative'], |
| type: 'pie', |
| textinfo: 'label+percent', |
| hoverinfo: 'label+percent', |
| hole: .35 |
| }]; |
| const layout = { |
| paper_bgcolor: 'rgba(0,0,0,0)', |
| plot_bgcolor: 'rgba(0,0,0,0)', |
| font: {color: '#eaf2ff'}, |
| margin: {l: 4, r: 4, t: 0, b: 0}, |
| showlegend: false, |
| transition: {duration: 500, easing: "cubic-in-out"} |
| }; |
| Plotly.newPlot(pieDiv, data, layout, {displayModeBar:false, responsive:true}).then(() => { |
| pieDiv.style.opacity = "0"; |
| pieDiv.style.transform = "scale(0.9)"; |
| requestAnimationFrame(() => { |
| pieDiv.style.transition = "all .6s ease"; |
| pieDiv.style.opacity = "1"; |
| pieDiv.style.transform = "scale(1)"; |
| }); |
| }); |
| } |
|
|
| function renderLine(rolling) { |
| const data = [{ |
| x: [...Array(rolling.length).keys()].map(i => i+1), |
| y: rolling, |
| type: 'scatter', |
| mode: 'lines+markers', |
| line: {shape: 'spline', smoothing: 1.3} |
| }]; |
| const layout = { |
| paper_bgcolor: 'rgba(0,0,0,0)', |
| plot_bgcolor: 'rgba(0,0,0,0)', |
| font: {color: '#eaf2ff'}, |
| margin: {l: 30, r: 10, t: 0, b: 24}, |
| yaxis: {range:[0,1], tickformat: '.0%'}, |
| transition: {duration: 600, easing: "cubic-in-out"} |
| }; |
| Plotly.newPlot(lineDiv, data, layout, {displayModeBar:false, responsive:true}).then(() => { |
| lineDiv.style.opacity = "0"; |
| lineDiv.style.transform = "translateY(20px)"; |
| requestAnimationFrame(() => { |
| lineDiv.style.transition = "all .7s ease"; |
| lineDiv.style.opacity = "1"; |
| lineDiv.style.transform = "translateY(0)"; |
| }); |
| }); |
| } |
|
|
| function renderTable(rows) { |
| tableDiv.innerHTML = ""; |
| rows.forEach((r, i) => { |
| const row = document.createElement("div"); |
| row.className = "row"; |
|
|
| |
| const c1 = document.createElement("div"); |
| c1.className = "cell"; |
| c1.textContent = r.text; |
|
|
| |
| const c2 = document.createElement("div"); |
| c2.className = "cell"; |
| const chip = document.createElement("span"); |
| chip.className = "chip " + (r.source === "gemini" ? "chip-gemini" : "chip-fallback"); |
| chip.textContent = r.source === "gemini" ? "Gemini" : "Fallback"; |
| c2.appendChild(chip); |
|
|
| |
| const c3 = document.createElement("div"); |
| c3.className = "cell"; |
| const badge = document.createElement("span"); |
| const s = r.sentiment; |
| badge.className = "badge " + (s === "POSITIVE" ? "pos" : s === "NEGATIVE" ? "neg" : "neu"); |
| badge.textContent = s + " " + (r.score.toFixed(2)); |
| c3.appendChild(badge); |
|
|
| row.appendChild(c1); |
| row.appendChild(c2); |
| row.appendChild(c3); |
|
|
| |
| row.style.opacity = "0"; |
| row.style.transform = "translateX(-15px)"; |
| setTimeout(() => { |
| row.style.transition = "all .4s ease"; |
| row.style.opacity = "1"; |
| row.style.transform = "translateX(0)"; |
| }, 100 * i); |
|
|
| tableDiv.appendChild(row); |
| }); |
| } |
|
|
| form.addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| const hashtag = document.getElementById("hashtag").value.trim(); |
| const count = parseInt(document.getElementById("count").value || "20", 10); |
|
|
| if(!hashtag){ |
| alert("Please enter a hashtag (e.g., #gla)"); |
| return; |
| } |
|
|
| statusBox.classList.remove("hidden"); |
| metaBox.classList.add("hidden"); |
|
|
| try { |
| const resp = await fetch("/api/analyze", { |
| method: "POST", |
| headers: {"Content-Type": "application/json"}, |
| body: JSON.stringify({hashtag, count}) |
| }); |
| if(!resp.ok){ |
| const err = await resp.json().catch(()=>({})); |
| throw new Error(err.error || `HTTP ${resp.status}`); |
| } |
| const data = await resp.json(); |
|
|
| |
| renderMeta(data.meta); |
| metaBox.classList.remove("hidden"); |
|
|
| |
| renderPie(data.aggregate.percent); |
| renderLine(data.aggregate.rolling); |
|
|
| |
| renderTable(data.rows); |
|
|
| } catch (err) { |
| console.error(err); |
| alert("Failed: " + err.message); |
| } finally { |
| statusBox.classList.add("hidden"); |
| } |
| }); |
|
|