| <!DOCTYPE html> |
| <html lang="en" class="scroll-smooth"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Command Center | TruthCheck</title> |
|
|
| |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link |
| href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;900&display=swap" |
| rel="stylesheet"> |
|
|
| |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script> |
| tailwind.config = { |
| darkMode: 'class', |
| theme: { |
| extend: { |
| fontFamily: { |
| sans: ['Inter', 'sans-serif'], |
| display: ['Orbitron', 'sans-serif'], |
| }, |
| colors: { |
| brand: { |
| 50: '#f0f9ff', |
| 100: '#e0f2fe', |
| 200: '#bae6fd', |
| 300: '#7dd3fc', |
| 400: '#38bdf8', |
| 500: '#0ea5e9', |
| 600: '#0284c7', |
| 700: '#0369a1', |
| 800: '#075985', |
| 900: '#0c4a6e', |
| 950: '#082f49', |
| }, |
| neon: { |
| cyan: '#00f3ff', |
| purple: '#bc13fe', |
| } |
| }, |
| animation: { |
| 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', |
| 'fade-in': 'fadeIn 0.5s ease-out forwards', |
| }, |
| keyframes: { |
| fadeIn: { |
| 'from': { opacity: '0' }, |
| 'to': { opacity: '1' } |
| } |
| } |
| } |
| } |
| } |
| </script> |
|
|
| |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
| |
| <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| </head> |
|
|
| <body |
| class="bg-slate-950 text-white min-h-screen relative overflow-x-hidden selection:bg-brand-500 selection:text-white"> |
|
|
| |
| <div class="fixed inset-0 z-0 opacity-20 pointer-events-none"> |
| <div |
| class="absolute inset-0 bg-[linear-gradient(to_right,#4f4f4f2e_1px,transparent_1px),linear-gradient(to_bottom,#4f4f4f2e_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]"> |
| </div> |
| </div> |
|
|
| |
| <nav class="relative z-50 border-b border-white/10 backdrop-blur-md bg-slate-950/50"> |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
| <div class="flex items-center justify-between h-20"> |
| <a href="/" class="flex items-center gap-3 group"> |
| <div |
| class="relative w-10 h-10 flex items-center justify-center bg-brand-500/10 rounded-lg border border-brand-500/50 shadow-[0_0_15px_rgba(14,165,233,0.3)] group-hover:shadow-[0_0_25px_rgba(14,165,233,0.5)] transition-shadow"> |
| <i class="fa-solid fa-shield-halved text-brand-400 text-xl"></i> |
| </div> |
| <span |
| class="font-display font-bold text-2xl tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-400"> |
| TRUTH<span class="text-brand-400">CHECK</span> |
| </span> |
| </a> |
| <div class="hidden md:flex gap-8"> |
| <a href="/" |
| class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Analyzer</a> |
| <a href="#" class="text-sm font-medium text-brand-400 border-b-2 border-brand-400">Dashboard</a> |
| <a href="/how-it-works" |
| class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">How it |
| Works</a> |
| <a href="/api-docs" |
| class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">API</a> |
| </div> |
| </div> |
| </div> |
| </nav> |
|
|
| |
| <main class="relative z-10 container mx-auto px-4 py-8"> |
|
|
| |
| <div class="flex justify-between items-end mb-8 animate-fade-in"> |
| <div> |
| <h1 class="font-display text-3xl md:text-4xl font-bold text-white mb-2">Command Center</h1> |
| <p class="text-slate-400">Global verification telemetry and history.</p> |
| </div> |
| <button onclick="loadHistory()" |
| class="bg-slate-800 hover:bg-slate-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors border border-white/5"> |
| <i class="fa-solid fa-rotate-right mr-2"></i> Refresh |
| </button> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-12 animate-fade-in" style="animation-delay: 0.1s;"> |
| |
| <div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm"> |
| <div class="flex items-start justify-between"> |
| <div> |
| <div class="text-slate-400 text-sm font-medium mb-1">Total Scans</div> |
| <div class="text-3xl font-display font-bold text-white" id="statTotal">0</div> |
| </div> |
| <div class="bg-brand-500/10 p-3 rounded-lg text-brand-400"> |
| <i class="fa-solid fa-chart-line"></i> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm"> |
| <div class="flex items-start justify-between"> |
| <div> |
| <div class="text-slate-400 text-sm font-medium mb-1">Truth Rate</div> |
| <div class="text-3xl font-display font-bold text-white" id="statTrueRate">0%</div> |
| </div> |
| <div class="bg-green-500/10 p-3 rounded-lg text-green-400"> |
| <i class="fa-solid fa-check"></i> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm"> |
| <div class="flex items-start justify-between"> |
| <div> |
| <div class="text-slate-400 text-sm font-medium mb-1">Misinfo Detected</div> |
| <div class="text-3xl font-display font-bold text-white" id="statFalseRate">0%</div> |
| </div> |
| <div class="bg-red-500/10 p-3 rounded-lg text-red-400"> |
| <i class="fa-solid fa-triangle-exclamation"></i> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm"> |
| <div class="flex items-start justify-between"> |
| <div> |
| <div class="text-slate-400 text-sm font-medium mb-1">Avg Confidence</div> |
| <div class="text-3xl font-display font-bold text-white" id="statAvgConf">0%</div> |
| </div> |
| <div class="bg-neon-purple/10 p-3 rounded-lg text-neon-purple"> |
| <i class="fa-solid fa-bullseye"></i> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-slate-900/50 border border-white/5 rounded-2xl overflow-hidden backdrop-blur-sm animate-fade-in" |
| style="animation-delay: 0.2s;"> |
| <div class="p-6 border-b border-white/5"> |
| <h3 class="font-display text-xl font-bold text-white">Recent Verifications</h3> |
| </div> |
|
|
| <div class="overflow-x-auto"> |
| <table class="w-full text-left"> |
| <thead class="bg-slate-950/50 text-slate-400 uppercase text-xs font-medium"> |
| <tr> |
| <th class="px-6 py-4">Status</th> |
| <th class="px-6 py-4">Claim</th> |
| <th class="px-6 py-4">Confidence</th> |
| <th class="px-6 py-4">Timestamp</th> |
| </tr> |
| </thead> |
| <tbody class="divide-y divide-white/5 text-sm" id="historyTableBody"> |
| |
| </tbody> |
| </table> |
| </div> |
|
|
| <div id="emptyState" class="hidden p-12 text-center text-slate-500"> |
| <i class="fa-solid fa-inbox text-4xl mb-4 opacity-50"></i> |
| <p>No verification history found.</p> |
| </div> |
| </div> |
|
|
| </main> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', loadHistory); |
| |
| async function loadHistory() { |
| const btn = document.querySelector('button[onclick="loadHistory()"]'); |
| const originalContent = btn.innerHTML; |
| |
| try { |
| |
| btn.disabled = true; |
| btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin mr-2"></i> Loading...'; |
| |
| const response = await fetch('/api/history'); |
| const data = await response.json(); |
| |
| if (data.error) { |
| throw new Error(data.error); |
| } |
| |
| updateStats(data); |
| renderTable(data); |
| |
| |
| btn.innerHTML = '<i class="fa-solid fa-check mr-2"></i> Updated'; |
| setTimeout(() => { |
| btn.innerHTML = originalContent; |
| btn.disabled = false; |
| }, 1000); |
| |
| } catch (error) { |
| console.error("Failed to load history:", error); |
| btn.innerHTML = '<i class="fa-solid fa-triangle-exclamation mr-2"></i> Error'; |
| alert("Failed to refresh history: " + error.message); |
| |
| setTimeout(() => { |
| btn.innerHTML = originalContent; |
| btn.disabled = false; |
| }, 2000); |
| } |
| } |
| |
| function updateStats(data) { |
| if (!data.length) return; |
| |
| const total = data.length; |
| const trueCount = data.filter(i => i.label.toLowerCase() === 'true').length; |
| const falseCount = data.filter(i => i.label.toLowerCase() === 'false').length; |
| |
| const totalConfidence = data.reduce((acc, curr) => acc + curr.confidence, 0); |
| const avgConf = total > 0 ? (totalConfidence / total) : 0; |
| |
| document.getElementById('statTotal').textContent = total; |
| document.getElementById('statTrueRate').textContent = Math.round((trueCount / total) * 100) + '%'; |
| document.getElementById('statFalseRate').textContent = Math.round((falseCount / total) * 100) + '%'; |
| document.getElementById('statAvgConf').textContent = Math.round(avgConf * 100) + '%'; |
| } |
| |
| function renderTable(data) { |
| const tbody = document.getElementById('historyTableBody'); |
| const emptyState = document.getElementById('emptyState'); |
| tbody.innerHTML = ''; |
| |
| if (data.length === 0) { |
| emptyState.classList.remove('hidden'); |
| return; |
| } else { |
| emptyState.classList.add('hidden'); |
| } |
| |
| data.forEach(row => { |
| const tr = document.createElement('tr'); |
| tr.className = 'hover:bg-white/5 transition-colors'; |
| |
| |
| let statusBadge = ''; |
| if (row.label.toLowerCase() === 'true') { |
| statusBadge = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20"><i class="fa-solid fa-check"></i> TRUE</span>'; |
| } else if (row.label.toLowerCase() === 'false') { |
| statusBadge = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-red-500/10 text-red-400 border border-red-500/20"><i class="fa-solid fa-xmark"></i> FALSE</span>'; |
| } else { |
| statusBadge = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-amber-500/10 text-amber-400 border border-amber-500/20"><i class="fa-solid fa-question"></i> LOW CONF</span>'; |
| } |
| |
| |
| const date = new Date(row.date).toLocaleString(); |
| |
| tr.innerHTML = ` |
| <td class="px-6 py-4 whitespace-nowrap">${statusBadge}</td> |
| <td class="px-6 py-4 text-slate-300 font-light truncate max-w-md" title="${row.claim}">${row.claim}</td> |
| <td class="px-6 py-4 text-slate-400 flex items-center gap-2"> |
| <div class="w-16 h-1.5 bg-slate-800 rounded-full overflow-hidden"> |
| <div class="h-full bg-brand-500" style="width: ${row.confidence * 100}%"></div> |
| </div> |
| ${Math.round(row.confidence * 100)}% |
| </td> |
| <td class="px-6 py-4 text-slate-500 font-mono text-xs">${date}</td> |
| `; |
| tbody.appendChild(tr); |
| }); |
| } |
| </script> |
| </body> |
|
|
| </html> |