// ============================================================ // TruthLens — main.js // Content Reliability Scoring System // ============================================================ // ── Tab switching ───────────────────────────────────────── function switchTab(name, btn) { document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.getElementById('panel-' + name).classList.add('active'); btn.classList.add('active'); } // ── Upload & preview ────────────────────────────────────── const zone = document.getElementById('upload-zone'); zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('drag-over'); }); zone.addEventListener('dragleave', () => zone.classList.remove('drag-over')); zone.addEventListener('drop', e => { e.preventDefault(); zone.classList.remove('drag-over'); const f = e.dataTransfer.files[0]; if (f) { document.getElementById('image-input').files = e.dataTransfer.files; showPreview(f); } }); function previewImage(input) { if (input.files?.[0]) showPreview(input.files[0]); } function showPreview(file) { const r = new FileReader(); r.onload = e => { document.getElementById('preview-img').src = e.target.result; document.getElementById('image-preview').style.display = 'block'; }; r.readAsDataURL(file); } // ── Reliability scoring ─────────────────────────────────── function reliabilityScore(aiScore, confidence, modelsAgree) { let base = (1 - aiScore) * 100; if (confidence < 0.70) base *= 0.85; if (!modelsAgree) base *= 0.90; return Math.round(Math.max(0, Math.min(100, base))); } function scoreTier(score) { if (score >= 75) return { cls: 'high', label: 'High Reliability', color: '#027a48' }; if (score >= 50) return { cls: 'medium', label: 'Moderate Reliability', color: '#b54708' }; if (score >= 25) return { cls: 'low', label: 'Low Reliability', color: '#c4320a' }; return { cls: 'vlow', label: 'Very Low Reliability', color: '#d92d20' }; } function interpretation(score, aiScore, modelsAgree, detailsObj) { const pct = Math.round(aiScore * 100); let text = ''; if (score >= 75) { text = `Content shows strong indicators of human authorship. AI probability is low at ${pct}%.`; } else if (score >= 50) { text = `Content shows mixed signals. AI probability is ${pct}% — could be human-written with AI assistance, or lightly edited AI output.`; } else if (score >= 25) { text = `Content shows significant AI indicators at ${pct}% probability. Likely AI-generated or heavily AI-assisted.`; } else { text = `Content is highly likely to be AI-generated (${pct}% AI probability). Strong signals detected across multiple analysis methods.`; } if (!modelsAgree) { text += ' Note: classical ML models and transformer-based model disagree — the transformer model (GLYPH) is weighted higher as it covers modern AI systems.'; } if (detailsObj?.glyph && detailsObj?.lr) { const glyphSaysAI = detailsObj.glyph.ai_prob >= 0.5; const lrSaysAI = detailsObj.lr.ai_prob >= 0.5; if (glyphSaysAI !== lrSaysAI) { text += ` GLYPH (semantic analysis) says ${glyphSaysAI ? 'AI' : 'Human'} while LR (word patterns) says ${lrSaysAI ? 'AI' : 'Human'} — this divergence often indicates AI text that has been edited or paraphrased.`; } } return text; } function scoreRing(score, tier) { const r = 32; const circ = 2 * Math.PI * r; const offset = circ - (score / 100) * circ; return `
${score}
`; } function warnIcon() { return ``; } // ── PDF download button ─────────────────────────────────── function pdfButton(reportData) { const encoded = encodeURIComponent(JSON.stringify(reportData)); return `
`; } async function downloadPDF(data) { const btn = event.target.closest('.btn-pdf'); const orig = btn.innerHTML; btn.innerHTML = ` Generating...`; btn.disabled = true; try { const resp = await fetch('/download-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!resp.ok) throw new Error('PDF generation failed'); const blob = await resp.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'truthlens_report.pdf'; a.click(); URL.revokeObjectURL(url); } catch (e) { alert('PDF generation failed: ' + e.message); } finally { btn.innerHTML = orig; btn.disabled = false; } } // ── Render full reliability result ──────────────────────── function renderReliability(aiScore, confidence, modelsAgree, signals, warning, detailsObj, inputText, type) { const score = reliabilityScore(aiScore, confidence, modelsAgree); const tier = scoreTier(score); const interp = interpretation(score, aiScore, modelsAgree, detailsObj); const pct = v => Math.round((v || 0) * 100); // Score header let html = `
${scoreRing(score, tier)}
${tier.label}
Reliability Score: ${score}/100  ·  AI Probability: ${pct(aiScore)}%
`; // Probability bar html += `
More HumanMore AI
`; // Signal breakdown if (signals && Object.keys(signals).length) { const items = Object.entries(signals) .filter(([, v]) => v !== null && v !== undefined) .map(([key, val]) => { const sc = val.ai_prob ?? val.ai_score ?? 0.5; const isAI = sc >= 0.5; const col = isAI ? '#d92d20' : '#027a48'; return `
${key.replace(/_/g,' ')}
${pct(sc)}%
${isAI ? 'AI signal' : 'Human signal'}
`; }).join(''); if (items) html += `
Contributing Signals
${items}
`; } // Interpretation html += `
Interpretation
${interp}
`; // Warning if (warning) html += `
${warnIcon()}${warning}
`; // ── PDF report data ─────────────────────────────────── const reportData = { type: type || 'Content', input_preview: inputText ? inputText.substring(0, 200) + '...' : '', reliability_score: score, tier: tier.label, ai_probability: pct(aiScore) + '%', confidence: pct(confidence) + '%', models_agree: modelsAgree ? 'Yes' : 'No', interpretation: interp, signals: signals ? Object.fromEntries( Object.entries(signals) .filter(([,v]) => v) .map(([k, v]) => [k, pct(v.ai_prob ?? v.ai_score ?? 0.5) + '%']) ) : {}, warning: warning || 'None' }; html += pdfButton(reportData); return html; } // ── TEXT ────────────────────────────────────────────────── async function analyzeText() { const text = document.getElementById('text-input').value.trim(); const err = document.getElementById('text-error'); const load = document.getElementById('text-loader'); const res = document.getElementById('text-result'); err.className = 'error-box'; res.className = 'result card'; if (text.length < 50) { err.textContent = 'Please enter at least 50 characters for reliable scoring.'; err.className = 'error-box active'; return; } load.className = 'loader active'; try { const r = await fetch('/predict-text', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) }); const d = await r.json(); if (d.error) throw new Error(d.error); const details = d.details || {}; const aiScore = details.final?.ai_score ?? (d.label === 'AI-generated' ? d.confidence : 1 - d.confidence); const confidence = d.confidence || 0.5; const signals = {}; if (details.lr) signals['LR (TF-IDF)'] = details.lr; if (details.sgd) signals['SGD (TF-IDF)'] = details.sgd; if (details.glyph) signals['GLYPH (DeBERTa)'] = details.glyph; const labels = Object.values(signals).map(s => s.ai_prob >= 0.5 ? 'ai' : 'human'); const modelsAgree = new Set(labels).size <= 1; res.innerHTML = renderReliability(aiScore, confidence, modelsAgree, signals, d.warning, details, text, 'Text'); res.className = 'result card active'; } catch (e) { err.textContent = e.message || 'Analysis failed.'; err.className = 'error-box active'; } finally { load.className = 'loader'; } } // ── IMAGE ───────────────────────────────────────────────── async function analyzeImage() { const input = document.getElementById('image-input'); const err = document.getElementById('image-error'); const load = document.getElementById('image-loader'); const res = document.getElementById('image-result'); err.className = 'error-box'; res.className = 'result card'; if (!input.files?.[0]) { err.textContent = 'Please upload an image first.'; err.className = 'error-box active'; return; } load.className = 'loader active'; const fd = new FormData(); fd.append('image', input.files[0]); try { const r = await fetch('/predict-image', { method: 'POST', body: fd }); const d = await r.json(); if (d.error) throw new Error(d.error); res.innerHTML = renderReliability( d.ai_score, d.confidence, d.models_agree, d.breakdown, d.warning, null, `Image: ${input.files[0].name}`, 'Image' ); res.className = 'result card active'; } catch (e) { err.textContent = e.message || 'Analysis failed.'; err.className = 'error-box active'; } finally { load.className = 'loader'; } } // ── URL ─────────────────────────────────────────────────── async function analyzeURL() { const url = document.getElementById('url-input').value.trim(); const err = document.getElementById('url-error'); const load = document.getElementById('url-loader'); const res = document.getElementById('url-result'); err.className = 'error-box'; res.className = 'result card'; if (!url) { err.textContent = 'Please enter a URL.'; err.className = 'error-box active'; return; } load.className = 'loader active'; try { const r = await fetch('/predict-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const d = await r.json(); if (d.error) throw new Error(d.error); let html = ''; if (d.title || d.text_preview) { html += `
`; if (d.title) html += `
${d.title}
`; if (d.text_preview) html += `
${d.text_preview}
`; html += `
`; } html += `
`; if (d.text_result) { const ts = d.text_result.confidence ?? 0.5; const tAI = d.text_result.label === 'AI-generated' ? ts : 1 - ts; const tScore = reliabilityScore(tAI, ts, true); const tTier = scoreTier(tScore); html += `
Text Reliability
${tScore}/100
${tTier.label}
`; } if (d.image_result) { const is_ = d.image_result.confidence ?? 0.5; const iAI = d.image_result.label === 'AI-generated' ? is_ : 1 - is_; const iScore = reliabilityScore(iAI, is_, true); const iTier = scoreTier(iScore); html += `
Image Reliability (${d.images_checked} checked)
${iScore}/100
${iTier.label}
`; } html += `
`; html += `
Overall Reliability Score
`; html += renderReliability( d.combined_score, d.combined_score, true, null, null, null, url, 'URL' ); res.innerHTML = html; res.className = 'result card active'; } catch (e) { err.textContent = e.message || 'Analysis failed.'; err.className = 'error-box active'; } finally { load.className = 'loader'; } } document.getElementById('url-input').addEventListener('keydown', e => { if (e.key === 'Enter') analyzeURL(); });