/** * LinkScout - Combined Popup Script * Smart Analysis. Simple Answers. * Combines features from both mis and mis_2 extensions */ const SERVER_URL = 'http://localhost:5000'; const API_ENDPOINT = `${SERVER_URL}/api/v1/analyze-chunks`; // DOM Elements const searchInput = document.getElementById('searchInput'); const clearBtn = document.getElementById('clearBtn'); const analyzeBtn = document.getElementById('analyzeBtn'); const scanPageBtn = document.getElementById('scanPageBtn'); const highlightBtn = document.getElementById('highlightBtn'); const clearHighlightBtn = document.getElementById('clearHighlightBtn'); const resultsSection = document.getElementById('resultsSection'); let isAnalyzing = false; let lastAnalysis = null; // ============= INITIALIZATION ============= document.addEventListener('DOMContentLoaded', () => { setupEventListeners(); checkServerConnection(); }); function setupEventListeners() { // Input handlers searchInput.addEventListener('input', (e) => { const hasValue = e.target.value.trim().length > 0; clearBtn.style.display = hasValue ? 'block' : 'none'; analyzeBtn.disabled = !hasValue; }); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && searchInput.value.trim()) { handleAnalyze(); } }); // Button handlers clearBtn.addEventListener('click', () => { searchInput.value = ''; clearBtn.style.display = 'none'; analyzeBtn.disabled = true; }); analyzeBtn.addEventListener('click', handleAnalyze); scanPageBtn.addEventListener('click', handleScanPage); highlightBtn.addEventListener('click', handleHighlight); clearHighlightBtn.addEventListener('click', handleClearHighlights); } async function checkServerConnection() { try { const response = await fetch(`${SERVER_URL}/health`, { method: 'GET', signal: AbortSignal.timeout(5000) }); if (response.ok) { const data = await response.json(); console.log('✅ Server connected:', data.name); } else { console.warn('⚠️ Server responded with error:', response.status); } } catch (error) { console.error('❌ Server connection failed:', error); showStatus('Server offline. Please start the server.', 'warning'); } } // ============= ANALYZE TEXT/URL ============= async function handleAnalyze() { const input = searchInput.value.trim(); if (!input || isAnalyzing) return; showLoading('Analyzing with AI...'); try { let result; if (isURL(input)) { result = await analyzeURL(input); } else { result = await analyzeText(input); } if (result) { lastAnalysis = result; displayResults(result); chrome.storage.local.set({ lastAnalysis: result }); } } catch (error) { console.error('Analysis error:', error); showError('Analysis Failed', error.message); } finally { isAnalyzing = false; } } function isURL(str) { try { new URL(str); return true; } catch { return str.startsWith('http://') || str.startsWith('https://'); } } async function analyzeText(text) { const response = await fetch(`${API_ENDPOINT}`, { // ✅ FIX: Use API_ENDPOINT constant method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: text, paragraphs: [{ index: 0, text: text, type: 'p' }] }), signal: AbortSignal.timeout(60000) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } return await response.json(); } async function analyzeURL(url) { // ✅ FIX: First fetch URL content, then analyze showLoading('Fetching URL content...'); // Try to fetch content from URL try { const fetchResponse = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(10000) }); if (!fetchResponse.ok) { throw new Error('Could not fetch URL'); } const html = await fetchResponse.text(); // Basic content extraction const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const paragraphs = Array.from(doc.querySelectorAll('p, article, .content')) .map((el, index) => ({ index, text: el.textContent.trim(), type: 'p' })) .filter(p => p.text.length > 50); showLoading('Analyzing content...'); // Send to analysis with URL context const response = await fetch(`${API_ENDPOINT}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: url, paragraphs: paragraphs.length > 0 ? paragraphs : [{ index: 0, text: html, type: 'p' }], content: paragraphs.map(p => p.text).join('\n\n') || html }), signal: AbortSignal.timeout(60000) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } return await response.json(); } catch (error) { console.warn('Direct URL fetch failed, trying server-side:', error); // Fallback: Let server handle URL fetching const response = await fetch(`${API_ENDPOINT}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: url, paragraphs: [{ index: 0, text: url, type: 'url' }] }), signal: AbortSignal.timeout(60000) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } return await response.json(); } } // ============= SCAN CURRENT PAGE ============= async function handleScanPage() { if (isAnalyzing) return; showLoading('Scanning page...'); isAnalyzing = true; try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); // Check if content script is loaded try { await chrome.tabs.sendMessage(tab.id, { action: 'ping' }); } catch (e) { console.log('Injecting content script...'); await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['utils/contentExtractor_v2.js', 'utils/cache.js', 'content.js'] }); await new Promise(resolve => setTimeout(resolve, 500)); } // Request analysis const response = await chrome.tabs.sendMessage(tab.id, { action: 'analyzeCurrentPage' }); if (response && response.success) { lastAnalysis = response.result; displayResults(response.result); showStatus('Analysis complete! Check the page for highlights.', 'success'); } else { throw new Error(response.error || 'Analysis failed'); } } catch (error) { console.error('Scan error:', error); showError('Scan Failed', error.message); } finally { isAnalyzing = false; } } // ============= HIGHLIGHT FUNCTIONS ============= async function handleHighlight() { try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const response = await chrome.tabs.sendMessage(tab.id, { action: 'highlightSuspicious' }); if (response && response.success) { showStatus('✅ Suspicious content highlighted!', 'success'); } else { showError('Highlight Failed', 'Please scan the page first'); } } catch (error) { console.error('Highlight error:', error); showError('Highlight Failed', error.message); } } async function handleClearHighlights() { try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); await chrome.tabs.sendMessage(tab.id, { action: 'clearHighlights' }); showStatus('✅ Highlights cleared', 'success'); } catch (error) { console.error('Clear error:', error); } } // ============= DISPLAY RESULTS ============= function displayResults(data) { console.log('📊 Displaying results:', data); if (!data) { showError('No Data', 'No analysis data received'); return; } const percentage = data.fake_percentage || data.misinformation_percentage || 0; const verdict = data.verdict || 'unknown'; const title = data.title || 'Analysis Result'; // ✅ FIX: Round percentage to 1 decimal place for clean display const displayPercentage = Math.round(percentage * 10) / 10; // Determine percentage class let percentageClass = 'low'; if (percentage > 60) percentageClass = 'high'; else if (percentage > 30) percentageClass = 'medium'; // Build HTML let html = `
${displayPercentage}%
${verdict.toUpperCase()}
`; // Tabs html += `
`; // Tab: Overview html += `
`; // Categories/Labels if (data.pretrained_models && data.pretrained_models.categories && data.pretrained_models.categories.length > 0) { html += `
🏷️ Article Category
${data.pretrained_models.categories.map(cat => `${cat}`).join('')}
`; } // Entities - BEAUTIFIED with badges if (data.pretrained_models && data.pretrained_models.named_entities && data.pretrained_models.named_entities.length > 0) { html += `
👥 Key Entities
${data.pretrained_models.named_entities.slice(0, 10).map(entity => `${entity}`).join(' ')}${data.pretrained_models.named_entities.length > 10 ? '...' : ''}
`; } // What's Correct (formatted properly) - ONLY if not default if (data.what_is_right && data.what_is_right !== 'See conclusion' && data.what_is_right !== 'See full conclusion') { // ✅ FIX: Remove ALL asterisks and duplicate headers let correctText = data.what_is_right .replace(/\*\*/g, '') // Remove all ** .replace(/WHAT IS CORRECT:/gi, '') // Remove header .replace(/What is correct:/gi, '') .trim(); // Remove leading * if present correctText = correctText.replace(/^\*+\s*/gm, ''); html += `
✅ What's Correct
${correctText}
`; } else if (data.what_is_right) { // Show placeholder when Groq API failed html += `
✅ What's Correct
⚠️ AI analysis unavailable (API rate limit). Analysis based on 8 ML models + Revolutionary Detection.
`; } // Hide default "See conclusion" sections - ONLY show real data // What's Wrong if (data.what_is_wrong && data.what_is_wrong !== 'See conclusion' && data.what_is_wrong !== 'See full conclusion') { // ✅ FIX: Remove ALL asterisks and duplicate headers let wrongText = data.what_is_wrong .replace(/\*\*/g, '') // Remove all ** .replace(/WHAT IS WRONG:/gi, '') // Remove header .replace(/What is wrong:/gi, '') .trim(); // Remove leading * if present wrongText = wrongText.replace(/^\*+\s*/gm, ''); html += `
❌ What's Wrong
${wrongText}
`; } // What Internet Says if (data.internet_says && data.internet_says !== 'See conclusion' && data.internet_says !== 'See full conclusion') { // ✅ FIX: Remove ALL asterisks and duplicate headers let internetText = data.internet_says .replace(/\*\*/g, '') .replace(/WHAT THE INTERNET SAYS:/gi, '') .replace(/What the internet says:/gi, '') .trim(); internetText = internetText.replace(/^\*+\s*/gm, ''); html += `
🌐 What the Internet Says
${internetText}
`; } // Recommendation - ONLY show if meaningful if (data.recommendation && data.recommendation !== 'Verify with credible sources' && data.recommendation.length > 30) { // ✅ FIX: Remove ALL asterisks and duplicate headers let recommendationText = data.recommendation .replace(/\*\*/g, '') .replace(/MY RECOMMENDATION:/gi, '') .replace(/Recommendation:/gi, '') .trim(); recommendationText = recommendationText.replace(/^\*+\s*/gm, ''); html += `
💡 Recommendation
${recommendationText}
`; } // Why It Matters - ONLY show if meaningful if (data.why_matters && data.why_matters !== 'Critical thinking is essential' && data.why_matters.length > 30) { // ✅ FIX: Remove ALL asterisks and duplicate headers let whyMattersText = data.why_matters .replace(/\*\*/g, '') .replace(/WHY THIS MATTERS:/gi, '') .replace(/Why this matters:/gi, '') .trim(); whyMattersText = whyMattersText.replace(/^\*+\s*/gm, ''); html += `
⚠️ Why This Matters
${whyMattersText}
`; } // Show summary stats if Groq failed if ((!data.what_is_right || data.what_is_right === 'See conclusion') && (!data.research || !data.research.research_findings)) { html += `
📊 Analysis Summary
Misinformation Score: ${percentage.toFixed(1)}%
Verdict: ${verdict.toUpperCase()}
Analyzed Paragraphs: ${data.overall?.total_paragraphs || 0}
Suspicious Paragraphs: ${data.overall?.suspicious_paragraphs || 0}
Models Used: 8 ML Models + Revolutionary Detection
`; } html += `
`; // Tab: Details html += `
`; // Groq AI Research Results if (data.research && data.research.research_findings) { html += `
🤖 Groq AI Research
${data.research.research_findings.replace(/\n/g, '
')}
`; } if (data.research && data.research.detailed_analysis) { html += `
🔬 Detailed Analysis
${data.research.detailed_analysis.replace(/\n/g, '
')}
`; } if (data.research && data.research.final_conclusion) { html += `
✅ Conclusion
${data.research.final_conclusion.replace(/\n/g, '
')}
`; } // ML Models section removed - entities shown in Overview tab only // ======================================== // REVOLUTIONARY DETECTION - 8 PHASES // ======================================== html += `
⚡ Revolutionary Detection System (8 Phases)
`; // ✅ NEW: Combined Credibility Summary (replaces 8 individual phases) if (data.combined_analysis) { const combined = data.combined_analysis; const score = combined.overall_score || 0; const verdict = combined.verdict || 'UNKNOWN'; const color = combined.verdict_color || '#607D8B'; // Meter visualization const meterWidth = Math.min(score, 100); let meterColor = '#10b981'; // Green (credible) if (score > 50) meterColor = '#ef4444'; // Red (not credible) else if (score > 35) meterColor = '#f59e0b'; // Orange (questionable) else if (score > 20) meterColor = '#3b82f6'; // Blue (mostly credible) html += `
🎯 Overall Credibility Analysis
Risk Score ${score.toFixed(0)}/100
${verdict}
0 - Highly Credible 100 - Not Credible
${data.overall?.fake_paragraphs || 0}
High Risk Paragraphs
${data.overall?.suspicious_paragraphs || 0}
Medium Risk Paragraphs
📊 Analysis Based On:
8 detection systems including linguistic patterns, claim verification, source credibility, entity verification, propaganda detection, network analysis, contradiction detection, and AI propagation analysis.
`; } // What's Correct/Wrong sections removed - shown in Overview tab only if (data.suspicious_items || data.chunks) { // ✅ STRICTER: Only show truly suspicious chunks (score >= 60) const allItems = data.suspicious_items || data.chunks || []; const items = allItems.filter(item => { const score = item.suspicious_score || item.score || 0; return score >= 60; // Changed from 40 to 60 }); if (items.length > 0) { html += `
⚠️ Suspicious Items (${items.length})
`; items.slice(0, 10).forEach((item, idx) => { const severity = item.severity || 'medium'; const chunkIndex = item.index !== undefined ? item.index : (item.chunk_index !== undefined ? item.chunk_index : idx); const score = item.suspicious_score || item.score || 0; html += `
📍 Paragraph ${chunkIndex + 1} ${score}/100
"${item.text_preview || item.text || 'Suspicious content'}"
${item.why_flagged || item.reason || 'Flagged as suspicious'}
👆 Click to jump to this paragraph
`; }); html += `
`; } } html += `
`; // Tab: Sources html += `
`; // Google Search Results if (data.research && data.research.google_results && data.research.google_results.length > 0) { html += `
🔗 Google Search Results (${data.research.google_results.length})
`; data.research.google_results.forEach((result, idx) => { html += ` ${idx + 1}. ${result.title}
${result.snippet || ''}
`; }); html += `
`; } if (data.research_sources || data.sources_found) { const sources = data.research_sources || data.sources_found || []; if (sources.length > 0) { html += `
🔗 Research Sources (${sources.length})
`; sources.forEach((source, idx) => { const url = typeof source === 'string' ? source : source.url; const name = typeof source === 'object' ? source.name || source.title : `Source ${idx + 1}`; html += ` ${name || url} `; }); html += `
`; } } html += `
`; // Tab: Images (NEW!) html += `
`; if (data.image_analysis && data.image_analysis.analyzed_images > 0) { const img_data = data.image_analysis; // Summary html += `
🖼️ Image Analysis Summary
${img_data.summary || 'No summary available'}
`; // Statistics html += `
📊 Statistics
${img_data.analyzed_images}
Analyzed
${img_data.ai_generated_count}
AI-Generated
${img_data.real_images_count}
Real Photos
`; // Suspicious Images if (img_data.suspicious_images && img_data.suspicious_images.length > 0) { html += `
⚠️ Suspicious AI-Generated Images
`; img_data.suspicious_images.forEach((img, idx) => { const detection = img.ai_detection || {}; html += `
Image ${img.index}: ${detection.verdict || 'AI-Generated'}
🎯 Confidence: ${(detection.confidence || 0).toFixed(1)}%
📐 Size: ${img.width}x${img.height}px
🔗 View Image ${img.reverse_search ? `
🔍 Verify with Reverse Search: Google TinEye Yandex
` : ''}
`; }); html += `
`; } // All Images List if (img_data.all_results && img_data.all_results.length > 0) { html += `
📋 All Images (${img_data.all_results.length})
`; img_data.all_results.forEach((img, idx) => { const detection = img.ai_detection || {}; const color = detection.is_ai_generated ? '#ef4444' : '#10b981'; html += `
${img.index}. ${detection.verdict || 'Unknown'} (${(detection.confidence || 0).toFixed(1)}%) ${img.width}x${img.height}px
`; }); html += `
`; } } else { html += `
${data.image_analysis && data.image_analysis.total_images === 0 ? '📷 No images found on this page' : '🖼️ Image analysis requires HTML content. Try scanning the page again.'}
`; } html += `
`; // Display resultsSection.innerHTML = html; // Show feedback section after analysis showFeedbackSection(); // Setup clickable suspicious paragraphs document.querySelectorAll('.clickable-paragraph').forEach(item => { item.addEventListener('click', () => { const chunkIndex = parseInt(item.dataset.chunkIndex); console.log('Clicked suspicious paragraph:', chunkIndex); // Send message to content script to scroll to paragraph chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { if (tabs[0]) { chrome.tabs.sendMessage(tabs[0].id, { action: 'scrollToParagraph', chunkIndex: chunkIndex }); // Visual feedback item.style.transform = 'scale(0.95)'; setTimeout(() => { item.style.transform = 'scale(1)'; }, 200); } }); }); // Hover effect item.addEventListener('mouseenter', () => { item.style.transform = 'scale(1.02)'; }); item.addEventListener('mouseleave', () => { item.style.transform = 'scale(1)'; }); }); // Setup tab switching document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { const targetTab = btn.dataset.tab; // Update buttons document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); // Update content document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); document.getElementById(targetTab).classList.add('active'); }); }); } // ============= UI HELPERS ============= function showLoading(message) { resultsSection.innerHTML = `
${message}
`; } function showError(title, message) { resultsSection.innerHTML = `

❌ ${title}

${message}

`; } function showStatus(message, type = 'info') { const color = type === 'success' ? '#10b981' : type === 'warning' ? '#f59e0b' : '#3b82f6'; const icon = type === 'success' ? '✅' : type === 'warning' ? '⚠️' : 'ℹ️'; // Show brief status notification const statusDiv = document.createElement('div'); statusDiv.style.cssText = ` position: fixed; top: 10px; left: 50%; transform: translateX(-50%); background: ${color}; color: white; padding: 10px 20px; border-radius: 6px; font-size: 12px; font-weight: 600; z-index: 10000; animation: slideDown 0.3s ease; `; statusDiv.textContent = `${icon} ${message}`; document.body.appendChild(statusDiv); setTimeout(() => { statusDiv.style.animation = 'slideUp 0.3s ease'; setTimeout(() => statusDiv.remove(), 300); }, 3000); } // ============= REINFORCEMENT LEARNING FEEDBACK ============= function setupFeedbackListeners() { const feedbackSection = document.getElementById('feedbackSection'); const feedbackCorrect = document.getElementById('feedbackCorrect'); const feedbackIncorrect = document.getElementById('feedbackIncorrect'); const feedbackAggressive = document.getElementById('feedbackAggressive'); const feedbackLenient = document.getElementById('feedbackLenient'); if (!feedbackCorrect || !feedbackIncorrect || !feedbackAggressive || !feedbackLenient) return; feedbackCorrect.addEventListener('click', () => sendFeedback('correct')); feedbackIncorrect.addEventListener('click', () => sendFeedback('incorrect')); feedbackAggressive.addEventListener('click', () => sendFeedback('too_aggressive')); feedbackLenient.addEventListener('click', () => sendFeedback('too_lenient')); } async function sendFeedback(feedbackType) { if (!lastAnalysis) { showStatus('No analysis to provide feedback for', 'warning'); return; } try { console.log(`📝 Sending ${feedbackType} feedback...`); const feedbackData = { analysis_data: { misinformation_percentage: lastAnalysis.misinformation_percentage || 0, suspicious_items: lastAnalysis.suspicious_items || [], content_preview: lastAnalysis.title || '', url: lastAnalysis.url || '', verdict: lastAnalysis.verdict || 'UNKNOWN' }, feedback: { feedback_type: feedbackType, timestamp: new Date().toISOString() } }; const response = await fetch(`${SERVER_URL}/feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(feedbackData), signal: AbortSignal.timeout(10000) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } const result = await response.json(); console.log('✅ Feedback processed:', result); // Show success message const feedbackSuccess = document.getElementById('feedbackSuccess'); if (feedbackSuccess) { feedbackSuccess.style.display = 'block'; setTimeout(() => { feedbackSuccess.style.display = 'none'; }, 3000); } // Update RL stats display if (result.rl_statistics) { updateRLStatsDisplay(result.rl_statistics); } showStatus('Feedback recorded - Thank you!', 'success'); } catch (error) { console.error('❌ Feedback error:', error); showStatus('Failed to send feedback', 'warning'); } } async function fetchRLStats() { try { const response = await fetch(`${SERVER_URL}/rl-stats`, { method: 'GET', signal: AbortSignal.timeout(5000) }); if (!response.ok) { throw new Error(`Server error: ${response.status}`); } const result = await response.json(); if (result.success && result.statistics) { updateRLStatsDisplay(result.statistics); } } catch (error) { console.error('❌ Failed to fetch RL stats:', error); } } function updateRLStatsDisplay(stats) { const rlStatsDisplay = document.getElementById('rlStatsDisplay'); const rlEpisodes = document.getElementById('rlEpisodes'); const rlAccuracy = document.getElementById('rlAccuracy'); const rlEpsilon = document.getElementById('rlEpsilon'); if (!rlStatsDisplay || !rlEpisodes || !rlAccuracy || !rlEpsilon) return; // Show the stats display rlStatsDisplay.style.display = 'block'; // Update values rlEpisodes.textContent = stats.total_episodes || 0; if (stats.average_accuracy !== undefined && stats.average_accuracy > 0) { rlAccuracy.textContent = `${(stats.average_accuracy * 100).toFixed(1)}%`; } else { rlAccuracy.textContent = 'Learning...'; } if (stats.epsilon !== undefined) { rlEpsilon.textContent = `${(stats.epsilon * 100).toFixed(1)}%`; } else { rlEpsilon.textContent = '--'; } } function showFeedbackSection() { const feedbackSection = document.getElementById('feedbackSection'); if (feedbackSection) { feedbackSection.style.display = 'block'; } } function hideFeedbackSection() { const feedbackSection = document.getElementById('feedbackSection'); if (feedbackSection) { feedbackSection.style.display = 'none'; } } // Initialize feedback listeners when page loads document.addEventListener('DOMContentLoaded', () => { setupFeedbackListeners(); fetchRLStats(); // Load initial RL stats }); console.log('✅ LinkScout popup loaded - Smart Analysis. Simple Answers.');