Spaces:
Paused
Paused
samarth70
Fix Groq completion token limit, replace decommissioned model, improve Gemini error logging, add gitignore
f07ec3c | // Ensure Chart.js uses the right defaults for dark mode | |
| Chart.defaults.color = '#94A3B8'; | |
| Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.1)'; | |
| // Global chart instances so we can destroy them before re-rendering | |
| let charts = {}; | |
| // API Configuration: Set this to your Hugging Face Space URL if deploying separately | |
| // For local development or combined deployment, leave it as an empty string | |
| const API_BASE_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' | |
| ? '' | |
| : (window.BACKEND_URL || ''); | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const form = document.getElementById('uploadForm'); | |
| const analyzeBtn = document.getElementById('analyzeBtn'); | |
| const btnText = analyzeBtn.querySelector('.btn-text'); | |
| const spinner = analyzeBtn.querySelector('.spinner'); | |
| const resultsArea = document.getElementById('resultsArea'); | |
| const inputSection = document.querySelector('.input-section'); | |
| // Tab switching logic | |
| const tabBtns = document.querySelectorAll('.tab-btn'); | |
| tabBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| tabBtns.forEach(b => b.classList.remove('active')); | |
| document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active')); | |
| btn.classList.add('active'); | |
| document.getElementById(btn.dataset.tab).classList.add('active'); | |
| }); | |
| }); | |
| // Example button logic | |
| const exampleBtns = document.querySelectorAll('.example-btn'); | |
| exampleBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const file = btn.dataset.file; | |
| const search = btn.dataset.search; | |
| // Populate search term | |
| document.getElementById('searchTerm').value = search; | |
| // Clear file input since we are using an example file | |
| document.getElementById('pdfFile').value = ''; | |
| // Submit form with example data | |
| submitAnalysis(null, search, file); | |
| }); | |
| }); | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const fileInput = document.getElementById('pdfFile'); | |
| const searchTerm = document.getElementById('searchTerm').value; | |
| if (!fileInput.files.length) { | |
| alert("Please upload a PDF file or choose an example."); | |
| return; | |
| } | |
| const file = fileInput.files[0]; | |
| submitAnalysis(file, searchTerm, null); | |
| }); | |
| async function submitAnalysis(file, searchTerm, exampleFile) { | |
| const formData = new FormData(); | |
| if (file) { | |
| formData.append('file', file); | |
| } else if (exampleFile) { | |
| formData.append('example_file', exampleFile); | |
| } | |
| formData.append('search_term', searchTerm); | |
| // UI Loading state | |
| analyzeBtn.disabled = true; | |
| btnText.textContent = 'Analyzing...'; | |
| spinner.classList.remove('hidden'); | |
| resultsArea.classList.add('hidden'); | |
| // Reset tabs to Summary | |
| tabBtns.forEach(b => b.classList.remove('active')); | |
| document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active')); | |
| document.querySelector('[data-tab="summary"]').classList.add('active'); | |
| document.getElementById('summary').classList.add('active'); | |
| console.log("Starting analysis for:", { file: file?.name, searchTerm, exampleFile }); | |
| try { | |
| const response = await fetch(`${API_BASE_URL}/analyze`, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const contentType = response.headers.get("content-type"); | |
| if (!response.ok) { | |
| if (contentType && contentType.includes("application/json")) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.detail || 'Analysis failed'); | |
| } else { | |
| const errorText = await response.text(); | |
| console.error("Backend Error (Non-JSON):", errorText); | |
| throw new Error(`Server Error (${response.status}). The backend might still be starting up or is misconfigured.`); | |
| } | |
| } | |
| if (!contentType || !contentType.includes("application/json")) { | |
| throw new Error("Invalid response from server. Expected JSON but received something else. Check if the Backend URL is correct."); | |
| } | |
| const data = await response.json(); | |
| console.log("Analysis data received:", data); | |
| try { | |
| renderResults(data); | |
| } catch (renderError) { | |
| console.error("Error in renderResults:", renderError); | |
| // Continue anyway to show the results area even if some charts fail | |
| } | |
| console.log("Transitioning UI: hiding input, showing results"); | |
| // Hide input section and show results | |
| inputSection.classList.add('hidden'); | |
| resultsArea.classList.remove('hidden'); | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| } catch (error) { | |
| console.error("Analysis error:", error); | |
| alert('Error: ' + error.message); | |
| } finally { | |
| analyzeBtn.disabled = false; | |
| btnText.textContent = 'Analyze Manifesto'; | |
| spinner.classList.add('hidden'); | |
| } | |
| } | |
| }); | |
| // Show input form again (back button) | |
| function showInputForm() { | |
| document.querySelector('.input-section').classList.remove('hidden'); | |
| document.getElementById('resultsArea').classList.add('hidden'); | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| } | |
| function renderResults(data) { | |
| // 1. Text Content (Markdown) | |
| document.getElementById('summaryContent').innerHTML = marked.parse(data.summary); | |
| document.getElementById('searchContent').innerHTML = marked.parse(data.search_result); | |
| // 2. Topics Grid | |
| const topicsContent = document.getElementById('topicsContent'); | |
| topicsContent.innerHTML = ''; | |
| // Sort topics by score | |
| const sortedTopics = Object.entries(data.topics) | |
| .filter(([key]) => key !== 'no_data' && key !== 'error' && key !== 'no_content' && key !== 'no_tokens') | |
| .sort((a, b) => b[1] - a[1]); | |
| sortedTopics.forEach(([topic, score]) => { | |
| const tag = document.createElement('div'); | |
| tag.className = 'topic-tag'; | |
| // Normalize score display | |
| const displayScore = (score * 100).toFixed(1); | |
| tag.innerHTML = `<span class="topic-name">${topic}</span><span class="topic-score">Relevance: ${displayScore}</span>`; | |
| topicsContent.appendChild(tag); | |
| }); | |
| // Destroy existing charts | |
| Object.values(charts).forEach(chart => { | |
| try { chart.destroy(); } catch(e) {} | |
| }); | |
| charts = {}; | |
| // 3. Sentiment & Subjectivity Charts | |
| try { | |
| renderBarChart('sentimentChart', 'Polarity', data.sentiment.polarity, -1, 1, | |
| data.sentiment.polarity > 0 ? '#4CAF50' : data.sentiment.polarity < 0 ? '#F44336' : '#9E9E9E'); | |
| } catch (e) { console.error("Sentiment chart failed:", e); } | |
| try { | |
| renderBarChart('subjectivityChart', 'Subjectivity', data.sentiment.subjectivity, 0, 1, | |
| data.sentiment.subjectivity > 0.5 ? '#B667F1' : '#42A5F5'); | |
| } catch (e) { console.error("Subjectivity chart failed:", e); } | |
| // 4. Word Cloud | |
| try { | |
| renderWordCloud('wordCloudChart', data.word_cloud_freq); | |
| } catch (e) { console.error("Word cloud failed:", e); } | |
| // 5. Frequency Chart | |
| try { | |
| renderFrequencyChart('frequencyChart', sortedTopics); | |
| } catch (e) { console.error("Frequency chart failed:", e); } | |
| // 6. Dispersion Plot | |
| try { | |
| renderDispersionPlot('dispersionChart', data.dispersion, data.total_tokens); | |
| } catch (e) { console.error("Dispersion plot failed:", e); } | |
| } | |
| function renderBarChart(canvasId, label, value, min, max, color) { | |
| const ctx = document.getElementById(canvasId).getContext('2d'); | |
| charts[canvasId] = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: [label], | |
| datasets: [{ | |
| label: 'Score', | |
| data: [value], | |
| backgroundColor: color, | |
| borderRadius: 5 | |
| }] | |
| }, | |
| options: { | |
| indexAxis: 'y', | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { display: false }, | |
| tooltip: { callbacks: { label: (ctx) => `Score: ${ctx.raw.toFixed(3)}` } } | |
| }, | |
| scales: { | |
| x: { min: min, max: max } | |
| } | |
| } | |
| }); | |
| } | |
| function renderWordCloud(canvasId, freqDict) { | |
| if (!freqDict || Object.keys(freqDict).length === 0) return; | |
| const ctx = document.getElementById(canvasId).getContext('2d'); | |
| // Format data for chartjs-wordcloud | |
| // Filter out any invalid entries and limit to top 50 for stability | |
| const filteredEntries = Object.entries(freqDict) | |
| .filter(([word, freq]) => word && freq > 0) | |
| .slice(0, 50); | |
| const words = filteredEntries.map(e => e[0]); | |
| const frequencies = filteredEntries.map(e => e[1]); | |
| if (words.length === 0) return; | |
| // Scale frequencies for better sizing | |
| const maxFreq = Math.max(...frequencies); | |
| const scaledFrequencies = frequencies.map(f => (f / maxFreq) * 40 + 10); // Min 10px, Max 50px | |
| charts[canvasId] = new Chart(ctx, { | |
| type: 'wordCloud', | |
| data: { | |
| labels: words, | |
| datasets: [{ | |
| label: 'Word Cloud', | |
| data: scaledFrequencies, | |
| color: () => `hsl(${Math.random() * 360}, 70%, 60%)` // Random vibrant colors | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { legend: { display: false } } | |
| } | |
| }); | |
| } | |
| function renderFrequencyChart(canvasId, sortedTopics) { | |
| const ctx = document.getElementById(canvasId).getContext('2d'); | |
| // sortedTopics is an array of [word, score] | |
| const words = sortedTopics.slice(0, 15).map(item => item[0]); | |
| const scores = sortedTopics.slice(0, 15).map(item => item[1]); | |
| charts[canvasId] = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: words, | |
| datasets: [{ | |
| label: 'Relevance Score', | |
| data: scores, | |
| backgroundColor: '#4F46E5', | |
| borderRadius: 4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { legend: { display: false } }, | |
| scales: { | |
| y: { beginAtZero: true } | |
| } | |
| } | |
| }); | |
| } | |
| function renderDispersionPlot(canvasId, dispersionData, totalTokens) { | |
| const ctx = document.getElementById(canvasId).getContext('2d'); | |
| const datasets = []; | |
| const words = Object.keys(dispersionData); | |
| const colors = ['#4F46E5', '#F59E0B', '#10B981', '#EC4899', '#8B5CF6']; | |
| words.forEach((word, index) => { | |
| // Create scatter points | |
| const points = dispersionData[word].map(offset => ({ | |
| x: offset, | |
| y: index + 1 // Offset Y by word index | |
| })); | |
| datasets.push({ | |
| label: word, | |
| data: points, | |
| backgroundColor: colors[index % colors.length], | |
| pointRadius: 3, | |
| pointHoverRadius: 5, | |
| pointStyle: 'rect' // Use small rectangles like a barcode | |
| }); | |
| }); | |
| charts[canvasId] = new Chart(ctx, { | |
| type: 'scatter', | |
| data: { datasets: datasets }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| tooltip: { | |
| callbacks: { | |
| label: (ctx) => `Word: ${ctx.dataset.label}, Position: ${ctx.raw.x}` | |
| } | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| title: { display: true, text: 'Position in Text' }, | |
| min: 0, | |
| max: totalTokens > 0 ? totalTokens : undefined | |
| }, | |
| y: { | |
| title: { display: false }, | |
| min: 0, | |
| max: words.length + 1, | |
| ticks: { | |
| stepSize: 1, | |
| callback: function(value) { | |
| if (value > 0 && value <= words.length) { | |
| return words[value - 1]; | |
| } | |
| return ''; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |