/**
* 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 += `
Overview
Details
Sources
🖼️ Images
`;
// 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
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 += `
`;
}
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 ? `
` : ''}
`;
});
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 = `
`;
}
function showError(title, message) {
resultsSection.innerHTML = `
`;
}
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.');