CHRISDANIEL145 commited on
Commit ·
622a0b7
0
Parent(s):
Initial commit of TruthCheck with Cyber-Noir UI
Browse files- .gitignore +13 -0
- .vscode/launch.json +12 -0
- .vscode/settings.json +9 -0
- README.md +2 -0
- app.py +293 -0
- curl.exe +3 -0
- google_search.py +26 -0
- models/__init__.py +4 -0
- models/claim_extractor.py +34 -0
- models/evidence_retriever.py +206 -0
- models/keyword_extractor.py +40 -0
- models/nli_classifier.py +182 -0
- requirements.txt +25 -0
- run.py +17 -0
- static/css/style.css +72 -0
- static/js/main.js +140 -0
- templates/api.html +199 -0
- templates/dashboard.html +284 -0
- templates/how_it_works.html +216 -0
- templates/index.html +282 -0
- utils/__init__.py +3 -0
- utils/config.py +22 -0
- utils/similarity.py +43 -0
.gitignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
|
| 3 |
+
__pycache__/
|
| 4 |
+
|
| 5 |
+
*.pyc
|
| 6 |
+
|
| 7 |
+
.env
|
| 8 |
+
|
| 9 |
+
db.sqlite3
|
| 10 |
+
|
| 11 |
+
history.db
|
| 12 |
+
|
| 13 |
+
*.log
|
.vscode/launch.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "0.2.0",
|
| 3 |
+
"configurations": [
|
| 4 |
+
{
|
| 5 |
+
"name": "TruthCheck Gradio",
|
| 6 |
+
"type": "python",
|
| 7 |
+
"request": "launch",
|
| 8 |
+
"program": "run.py",
|
| 9 |
+
"console": "integratedTerminal"
|
| 10 |
+
}
|
| 11 |
+
]
|
| 12 |
+
}
|
.vscode/settings.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"python.defaultInterpreterPath": "./venv/bin/python",
|
| 3 |
+
"python.terminal.activateEnvironment": true,
|
| 4 |
+
"files.exclude": {
|
| 5 |
+
"**/__pycache__": true,
|
| 6 |
+
"**/*.pyc": true
|
| 7 |
+
},
|
| 8 |
+
"html.autoClosingTags": false
|
| 9 |
+
}
|
README.md
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TruthCheck-App
|
| 2 |
+
An intelligent fact-checking tool would not only help users verify the accuracy of digital content instantly but also foster greater transparency and accountability in online communication..
|
app.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import os
|
| 3 |
+
from flask import Flask, render_template, request, jsonify
|
| 4 |
+
from functools import lru_cache
|
| 5 |
+
import hashlib
|
| 6 |
+
import sqlite3
|
| 7 |
+
import datetime
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
from models.claim_extractor import ClaimExtractor
|
| 11 |
+
from models.keyword_extractor import KeywordExtractor
|
| 12 |
+
from models.evidence_retriever import EvidenceRetriever
|
| 13 |
+
from models.nli_classifier import NLIClassifier
|
| 14 |
+
from utils.similarity import calculate_similarity
|
| 15 |
+
from utils.config import Config
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# Initialize models globally
|
| 19 |
+
claim_extractor = ClaimExtractor()
|
| 20 |
+
keyword_extractor = KeywordExtractor()
|
| 21 |
+
evidence_retriever = EvidenceRetriever()
|
| 22 |
+
nli_classifier = NLIClassifier()
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class TruthCheckSystem:
|
| 26 |
+
def __init__(self):
|
| 27 |
+
self.claim_extractor = claim_extractor
|
| 28 |
+
self.keyword_extractor = keyword_extractor
|
| 29 |
+
self.evidence_retriever = evidence_retriever
|
| 30 |
+
self.nli_classifier = nli_classifier
|
| 31 |
+
self.cache = {}
|
| 32 |
+
|
| 33 |
+
def _get_cache_key(self, text):
|
| 34 |
+
"""Generate cache key for claim"""
|
| 35 |
+
return hashlib.md5(text.encode()).hexdigest()
|
| 36 |
+
|
| 37 |
+
def verify_claim(self, text):
|
| 38 |
+
"""
|
| 39 |
+
Enhanced fact verification with multi-evidence aggregation
|
| 40 |
+
and consensus mechanism (similar to FactCheck system)
|
| 41 |
+
"""
|
| 42 |
+
try:
|
| 43 |
+
# Check cache
|
| 44 |
+
cache_key = self._get_cache_key(text)
|
| 45 |
+
if cache_key in self.cache:
|
| 46 |
+
print("Returning cached result")
|
| 47 |
+
return self.cache[cache_key]
|
| 48 |
+
|
| 49 |
+
# Step 1: Extract claims
|
| 50 |
+
claims = self.claim_extractor.extract_claims(text)
|
| 51 |
+
if not claims:
|
| 52 |
+
result = ("Low Confidence", 0.3, "No valid claims found. Please provide a clear factual statement.")
|
| 53 |
+
self.cache[cache_key] = result
|
| 54 |
+
return result
|
| 55 |
+
|
| 56 |
+
claim = claims[0]
|
| 57 |
+
|
| 58 |
+
# Step 2: Extract keywords
|
| 59 |
+
keywords = self.keyword_extractor.extract_keywords(claim)
|
| 60 |
+
|
| 61 |
+
# Step 3: Retrieve evidence from multiple sources
|
| 62 |
+
evidence_items = self.evidence_retriever.get_evidence(keywords)
|
| 63 |
+
|
| 64 |
+
if not evidence_items:
|
| 65 |
+
result = ("Low Confidence", 0.3, "Not enough reliable evidence found.")
|
| 66 |
+
self.cache[cache_key] = result
|
| 67 |
+
return result
|
| 68 |
+
|
| 69 |
+
# Step 4: Filter by semantic similarity
|
| 70 |
+
relevant_evidence = []
|
| 71 |
+
for item in evidence_items:
|
| 72 |
+
similarity = calculate_similarity(claim, item['content'])
|
| 73 |
+
if similarity > Config.SIMILARITY_THRESHOLD:
|
| 74 |
+
item['similarity_score'] = similarity
|
| 75 |
+
relevant_evidence.append(item)
|
| 76 |
+
|
| 77 |
+
if not relevant_evidence:
|
| 78 |
+
result = ("Low Confidence", 0.4, "No semantically relevant evidence found.")
|
| 79 |
+
self.cache[cache_key] = result
|
| 80 |
+
return result
|
| 81 |
+
|
| 82 |
+
# Step 5: Sort by combined score (credibility + similarity)
|
| 83 |
+
for item in relevant_evidence:
|
| 84 |
+
item['combined_score'] = (
|
| 85 |
+
item.get('credibility_score', 0.5) * 0.6 +
|
| 86 |
+
item.get('similarity_score', 0.5) * 0.4
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
relevant_evidence.sort(key=lambda x: x['combined_score'], reverse=True)
|
| 90 |
+
|
| 91 |
+
# Step 6: Multi-Evidence NLI with Consensus Mechanism
|
| 92 |
+
# Use top 4 evidence sources (as per FactCheck research)
|
| 93 |
+
top_evidence = relevant_evidence[:4]
|
| 94 |
+
|
| 95 |
+
nli_results = []
|
| 96 |
+
for evidence_item in top_evidence:
|
| 97 |
+
nli_result = self.nli_classifier.classify(claim, evidence_item['content'])
|
| 98 |
+
nli_results.append({
|
| 99 |
+
'nli': nli_result,
|
| 100 |
+
'credibility': evidence_item.get('credibility_score', 0.5),
|
| 101 |
+
'similarity': evidence_item.get('similarity_score', 0.5),
|
| 102 |
+
'source': evidence_item.get('source', 'Unknown'),
|
| 103 |
+
'url': evidence_item.get('url', '')
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
# Step 7: Weighted Consensus Voting
|
| 107 |
+
entailment_score = 0
|
| 108 |
+
contradiction_score = 0
|
| 109 |
+
neutral_score = 0
|
| 110 |
+
|
| 111 |
+
total_weight = 0
|
| 112 |
+
for result in nli_results:
|
| 113 |
+
# Weight by credibility and confidence
|
| 114 |
+
weight = result['credibility'] * result['nli']['confidence']
|
| 115 |
+
total_weight += weight
|
| 116 |
+
|
| 117 |
+
if result['nli']['label'] == 'ENTAILMENT':
|
| 118 |
+
entailment_score += weight
|
| 119 |
+
elif result['nli']['label'] == 'CONTRADICTION':
|
| 120 |
+
contradiction_score += weight
|
| 121 |
+
else:
|
| 122 |
+
neutral_score += weight
|
| 123 |
+
|
| 124 |
+
# Normalize scores
|
| 125 |
+
if total_weight > 0:
|
| 126 |
+
entailment_score /= total_weight
|
| 127 |
+
contradiction_score /= total_weight
|
| 128 |
+
neutral_score /= total_weight
|
| 129 |
+
|
| 130 |
+
# Step 8: Determine final label with consensus threshold
|
| 131 |
+
consensus_threshold = 0.6 # Require 60% agreement
|
| 132 |
+
|
| 133 |
+
max_score = max(entailment_score, contradiction_score, neutral_score)
|
| 134 |
+
|
| 135 |
+
if max_score == entailment_score and entailment_score >= consensus_threshold:
|
| 136 |
+
label = "True"
|
| 137 |
+
final_confidence = entailment_score
|
| 138 |
+
elif max_score == contradiction_score and contradiction_score >= consensus_threshold:
|
| 139 |
+
label = "False"
|
| 140 |
+
final_confidence = contradiction_score
|
| 141 |
+
else:
|
| 142 |
+
label = "Low Confidence"
|
| 143 |
+
final_confidence = max(entailment_score, contradiction_score, neutral_score)
|
| 144 |
+
|
| 145 |
+
# Step 9: Prepare evidence summary
|
| 146 |
+
evidence_summary = self._format_evidence_summary(nli_results, top_evidence)
|
| 147 |
+
|
| 148 |
+
result = (label, final_confidence, evidence_summary)
|
| 149 |
+
|
| 150 |
+
# Cache result
|
| 151 |
+
self.cache[cache_key] = result
|
| 152 |
+
|
| 153 |
+
return result
|
| 154 |
+
|
| 155 |
+
except Exception as e:
|
| 156 |
+
print(f"Error during claim verification: {e}")
|
| 157 |
+
import traceback
|
| 158 |
+
traceback.print_exc()
|
| 159 |
+
return ("Error", 0.0, f"An internal error occurred: {str(e)}")
|
| 160 |
+
|
| 161 |
+
def _format_evidence_summary(self, nli_results, evidence_items):
|
| 162 |
+
"""Format evidence summary with sources and verdicts"""
|
| 163 |
+
summary_parts = []
|
| 164 |
+
|
| 165 |
+
summary_parts.append(f"**Analyzed {len(nli_results)} sources:**\n")
|
| 166 |
+
|
| 167 |
+
for i, (nli_res, evidence) in enumerate(zip(nli_results, evidence_items), 1):
|
| 168 |
+
source = nli_res['source']
|
| 169 |
+
verdict = nli_res['nli']['label']
|
| 170 |
+
confidence = nli_res['nli']['confidence']
|
| 171 |
+
credibility = nli_res['credibility']
|
| 172 |
+
url = nli_res['url']
|
| 173 |
+
|
| 174 |
+
# Get snippet
|
| 175 |
+
content = evidence.get('content', '')[:300]
|
| 176 |
+
|
| 177 |
+
summary_parts.append(
|
| 178 |
+
f"\n**Source {i}: {source}**\n"
|
| 179 |
+
f"Verdict: {verdict} (Confidence: {confidence:.2%})\n"
|
| 180 |
+
f"Credibility Score: {credibility:.2f}\n"
|
| 181 |
+
f"Excerpt: {content}...\n"
|
| 182 |
+
f"URL: {url}\n"
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
return "\n".join(summary_parts)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# Initialize system
|
| 189 |
+
truthcheck_system_instance = TruthCheckSystem()
|
| 190 |
+
|
| 191 |
+
def init_db():
|
| 192 |
+
"""Initialize SQLite database"""
|
| 193 |
+
conn = sqlite3.connect('history.db')
|
| 194 |
+
c = conn.cursor()
|
| 195 |
+
c.execute('''
|
| 196 |
+
CREATE TABLE IF NOT EXISTS verifications (
|
| 197 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 198 |
+
claim TEXT NOT NULL,
|
| 199 |
+
label TEXT NOT NULL,
|
| 200 |
+
confidence REAL,
|
| 201 |
+
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 202 |
+
)
|
| 203 |
+
''')
|
| 204 |
+
conn.commit()
|
| 205 |
+
conn.close()
|
| 206 |
+
|
| 207 |
+
init_db()
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def create_app():
|
| 211 |
+
app = Flask(__name__, static_folder='static', template_folder='templates')
|
| 212 |
+
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', Config.SECRET_KEY)
|
| 213 |
+
app.config['DEBUG'] = Config.DEBUG
|
| 214 |
+
|
| 215 |
+
@app.route('/')
|
| 216 |
+
def index():
|
| 217 |
+
return render_template('index.html')
|
| 218 |
+
|
| 219 |
+
@app.route('/how-it-works')
|
| 220 |
+
def how_it_works():
|
| 221 |
+
return render_template('how_it_works.html')
|
| 222 |
+
|
| 223 |
+
@app.route('/api-docs')
|
| 224 |
+
def api_docs():
|
| 225 |
+
return render_template('api.html')
|
| 226 |
+
|
| 227 |
+
@app.route('/dashboard')
|
| 228 |
+
def dashboard():
|
| 229 |
+
return render_template('dashboard.html')
|
| 230 |
+
|
| 231 |
+
@app.route('/api/history')
|
| 232 |
+
def get_history():
|
| 233 |
+
try:
|
| 234 |
+
conn = sqlite3.connect('history.db')
|
| 235 |
+
conn.row_factory = sqlite3.Row
|
| 236 |
+
c = conn.cursor()
|
| 237 |
+
c.execute('SELECT * FROM verifications ORDER BY date DESC LIMIT 50')
|
| 238 |
+
rows = c.fetchall()
|
| 239 |
+
conn.close()
|
| 240 |
+
|
| 241 |
+
history = []
|
| 242 |
+
for row in rows:
|
| 243 |
+
history.append({
|
| 244 |
+
'id': row['id'],
|
| 245 |
+
'claim': row['claim'],
|
| 246 |
+
'label': row['label'],
|
| 247 |
+
'confidence': row['confidence'],
|
| 248 |
+
'date': row['date']
|
| 249 |
+
})
|
| 250 |
+
return jsonify(history)
|
| 251 |
+
except Exception as e:
|
| 252 |
+
return jsonify({'error': str(e)}), 500
|
| 253 |
+
|
| 254 |
+
@app.route('/api/verify', methods=['POST'])
|
| 255 |
+
def verify_claim_api():
|
| 256 |
+
try:
|
| 257 |
+
data = request.get_json()
|
| 258 |
+
claim_text = data.get('claim', '')
|
| 259 |
+
|
| 260 |
+
if not claim_text:
|
| 261 |
+
return jsonify({'error': 'No claim provided'}), 400
|
| 262 |
+
|
| 263 |
+
label, confidence, evidence = truthcheck_system_instance.verify_claim(claim_text)
|
| 264 |
+
|
| 265 |
+
# Save to DB
|
| 266 |
+
try:
|
| 267 |
+
conn = sqlite3.connect('history.db')
|
| 268 |
+
c = conn.cursor()
|
| 269 |
+
c.execute('INSERT INTO verifications (claim, label, confidence) VALUES (?, ?, ?)',
|
| 270 |
+
(claim_text, label, float(confidence)))
|
| 271 |
+
conn.commit()
|
| 272 |
+
conn.close()
|
| 273 |
+
except Exception as e:
|
| 274 |
+
print(f"DB Error: {e}")
|
| 275 |
+
|
| 276 |
+
result = {
|
| 277 |
+
'label': label,
|
| 278 |
+
'confidence': round(confidence, 3),
|
| 279 |
+
'evidence': evidence,
|
| 280 |
+
'claim': claim_text
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
return jsonify(result)
|
| 284 |
+
|
| 285 |
+
except Exception as e:
|
| 286 |
+
print(f"API error: {e}")
|
| 287 |
+
return jsonify({'error': f'Server error: {str(e)}'}), 500
|
| 288 |
+
|
| 289 |
+
@app.route('/health')
|
| 290 |
+
def health_check():
|
| 291 |
+
return jsonify({'status': 'healthy', 'message': 'TruthCheck is running.'})
|
| 292 |
+
|
| 293 |
+
return app
|
curl.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
curl.exe -X POST http://127.0.0.1:5000/api/verify `
|
| 2 |
+
-H "Content-Type: application/json" `
|
| 3 |
+
-d "{\"claim\": \"The sun is a star.\"}"
|
google_search.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# google_search.py
|
| 2 |
+
|
| 3 |
+
class SearchResult:
|
| 4 |
+
def __init__(self, snippet, url, source_title=None):
|
| 5 |
+
self.snippet = snippet
|
| 6 |
+
self.url = url
|
| 7 |
+
self.source_title = source_title
|
| 8 |
+
|
| 9 |
+
class SearchResponse:
|
| 10 |
+
def __init__(self, results):
|
| 11 |
+
self.results = results
|
| 12 |
+
|
| 13 |
+
def search(queries, num_results=3):
|
| 14 |
+
"""Mock search function returning dummy data for testing."""
|
| 15 |
+
responses = []
|
| 16 |
+
for query in queries:
|
| 17 |
+
dummy_results = [
|
| 18 |
+
SearchResult(
|
| 19 |
+
snippet=f"This is a mock snippet for query '{query}' - result {i+1}.",
|
| 20 |
+
url=f"https://example.com/{query.replace(' ', '_')}/{i}",
|
| 21 |
+
source_title="Mock News Source"
|
| 22 |
+
)
|
| 23 |
+
for i in range(num_results)
|
| 24 |
+
]
|
| 25 |
+
responses.append(SearchResponse(results=dummy_results))
|
| 26 |
+
return responses
|
models/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
TruthCheck Models Package
|
| 3 |
+
Contains all the NLP and ML models for fact-checking
|
| 4 |
+
"""
|
models/claim_extractor.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/claim_extractor.py
|
| 2 |
+
import re
|
| 3 |
+
import spacy
|
| 4 |
+
|
| 5 |
+
class ClaimExtractor:
|
| 6 |
+
def __init__(self): # Corrected __init__
|
| 7 |
+
try:
|
| 8 |
+
self.nlp = spacy.load("en_core_web_sm")
|
| 9 |
+
except OSError:
|
| 10 |
+
print("Please install spaCy English model: python -m spacy download en_core_web_sm")
|
| 11 |
+
raise
|
| 12 |
+
|
| 13 |
+
def extract_claims(self, text):
|
| 14 |
+
"""Extract factual claims from text"""
|
| 15 |
+
if not text or len(text.strip()) < 10:
|
| 16 |
+
return []
|
| 17 |
+
|
| 18 |
+
# Use spaCy for sentence segmentation
|
| 19 |
+
doc = self.nlp(text)
|
| 20 |
+
claims = []
|
| 21 |
+
|
| 22 |
+
for sent in doc.sents:
|
| 23 |
+
sentence = sent.text.strip()
|
| 24 |
+
|
| 25 |
+
# Filter out questions, commands, and short sentences
|
| 26 |
+
if (len(sentence.split()) > 5 and
|
| 27 |
+
not sentence.endswith('?') and
|
| 28 |
+
not sentence.startswith(('How', 'What', 'When', 'Where', 'Why', 'Who')) and
|
| 29 |
+
not re.match(r'^(Please|Let|Can you)', sentence, re.IGNORECASE)):
|
| 30 |
+
|
| 31 |
+
claims.append(sentence)
|
| 32 |
+
|
| 33 |
+
return claims if claims else [text.strip()]
|
| 34 |
+
|
models/evidence_retriever.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/evidence_retriever.py
|
| 2 |
+
import wikipedia
|
| 3 |
+
import requests
|
| 4 |
+
from bs4 import BeautifulSoup
|
| 5 |
+
import time
|
| 6 |
+
from urllib.parse import quote_plus
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class EvidenceRetriever:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.wikipedia_timeout = 10
|
| 12 |
+
self.max_evidence_sources = 10
|
| 13 |
+
self.trusted_domains = [
|
| 14 |
+
'reuters.com', 'apnews.com', 'bbc.com', 'nature.com',
|
| 15 |
+
'science.org', 'who.int', 'cdc.gov', 'nasa.gov',
|
| 16 |
+
'wikipedia.org', '.gov', '.edu'
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
def get_evidence(self, keywords):
|
| 20 |
+
"""Retrieve evidence from multiple sources with credibility scoring"""
|
| 21 |
+
evidence = []
|
| 22 |
+
|
| 23 |
+
# Get evidence from Wikipedia (most reliable)
|
| 24 |
+
wiki_evidence = self._get_wikipedia_evidence(keywords)
|
| 25 |
+
evidence.extend(wiki_evidence)
|
| 26 |
+
|
| 27 |
+
# Get evidence from web search (using Google Custom Search as fallback)
|
| 28 |
+
web_evidence = self._get_web_search_evidence(keywords)
|
| 29 |
+
evidence.extend(web_evidence)
|
| 30 |
+
|
| 31 |
+
# Sort by credibility score and return top sources
|
| 32 |
+
evidence.sort(key=lambda x: x.get('credibility_score', 0.0), reverse=True)
|
| 33 |
+
|
| 34 |
+
return evidence[:10]
|
| 35 |
+
|
| 36 |
+
def _assign_credibility_score(self, source_type, url):
|
| 37 |
+
"""Assign credibility scores based on source type and domain"""
|
| 38 |
+
if 'wikipedia.org' in url:
|
| 39 |
+
return 0.95
|
| 40 |
+
elif any(url.endswith(gov) for gov in ['.gov', '.gov/']):
|
| 41 |
+
return 0.92
|
| 42 |
+
elif any(url.endswith(edu) for edu in ['.edu', '.edu/']):
|
| 43 |
+
return 0.88
|
| 44 |
+
elif any(trusted in url for trusted in ['reuters.com', 'apnews.com', 'bbc.com']):
|
| 45 |
+
return 0.85
|
| 46 |
+
elif any(sci in url for sci in ['nature.com', 'science.org', 'ncbi.nlm.nih.gov']):
|
| 47 |
+
return 0.90
|
| 48 |
+
elif source_type == 'wikipedia':
|
| 49 |
+
return 0.95
|
| 50 |
+
elif source_type == 'academic':
|
| 51 |
+
return 0.88
|
| 52 |
+
elif source_type == 'government':
|
| 53 |
+
return 0.92
|
| 54 |
+
elif source_type == 'news_trusted':
|
| 55 |
+
return 0.80
|
| 56 |
+
else:
|
| 57 |
+
return 0.60
|
| 58 |
+
|
| 59 |
+
def _get_wikipedia_evidence(self, keywords):
|
| 60 |
+
"""Retrieve evidence from Wikipedia"""
|
| 61 |
+
evidence = []
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
search_terms = ' '.join(keywords[:4])
|
| 65 |
+
search_results = wikipedia.search(search_terms, results=5)
|
| 66 |
+
|
| 67 |
+
for title in search_results:
|
| 68 |
+
try:
|
| 69 |
+
summary = wikipedia.summary(title, sentences=7, auto_suggest=False)
|
| 70 |
+
url = f'https://en.wikipedia.org/wiki/{title.replace(" ", "_")}'
|
| 71 |
+
|
| 72 |
+
evidence.append({
|
| 73 |
+
'content': summary,
|
| 74 |
+
'source': f'Wikipedia - {title}',
|
| 75 |
+
'url': url,
|
| 76 |
+
'credibility_score': self._assign_credibility_score('wikipedia', url),
|
| 77 |
+
'source_type': 'wikipedia'
|
| 78 |
+
})
|
| 79 |
+
|
| 80 |
+
if len(evidence) >= 3:
|
| 81 |
+
break
|
| 82 |
+
|
| 83 |
+
except (wikipedia.DisambiguationError, wikipedia.PageError):
|
| 84 |
+
continue
|
| 85 |
+
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f"Wikipedia search error: {e}")
|
| 88 |
+
|
| 89 |
+
return evidence
|
| 90 |
+
|
| 91 |
+
def _get_web_search_evidence(self, keywords):
|
| 92 |
+
"""Retrieve evidence from web search using SearXNG metasearch"""
|
| 93 |
+
evidence = []
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
query = ' '.join(keywords[:5])
|
| 97 |
+
|
| 98 |
+
# Method 1: Try DuckDuckGo Lite (more reliable)
|
| 99 |
+
ddg_results = self._search_duckduckgo_lite(query)
|
| 100 |
+
evidence.extend(ddg_results)
|
| 101 |
+
|
| 102 |
+
# Method 2: If DuckDuckGo fails, use direct scraping with Google
|
| 103 |
+
if len(evidence) < 3:
|
| 104 |
+
google_results = self._search_google_scrape(query)
|
| 105 |
+
evidence.extend(google_results)
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print(f"Web search error: {e}")
|
| 109 |
+
|
| 110 |
+
return evidence[:5]
|
| 111 |
+
|
| 112 |
+
def _search_duckduckgo_lite(self, query):
|
| 113 |
+
"""Search using DuckDuckGo Lite (HTML version, more stable)"""
|
| 114 |
+
results = []
|
| 115 |
+
|
| 116 |
+
try:
|
| 117 |
+
url = "https://lite.duckduckgo.com/lite/"
|
| 118 |
+
data = {"q": query}
|
| 119 |
+
headers = {
|
| 120 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
response = requests.post(url, data=data, headers=headers, timeout=10)
|
| 124 |
+
|
| 125 |
+
if response.status_code == 200:
|
| 126 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 127 |
+
|
| 128 |
+
# Find all result rows
|
| 129 |
+
result_table = soup.find_all('tr')
|
| 130 |
+
|
| 131 |
+
for row in result_table[:10]:
|
| 132 |
+
try:
|
| 133 |
+
link = row.find('a', class_='result-link')
|
| 134 |
+
snippet_td = row.find('td', class_='result-snippet')
|
| 135 |
+
|
| 136 |
+
if link and snippet_td:
|
| 137 |
+
result_url = link.get('href', '')
|
| 138 |
+
title = link.get_text(strip=True)
|
| 139 |
+
snippet = snippet_td.get_text(strip=True)
|
| 140 |
+
|
| 141 |
+
if result_url and snippet:
|
| 142 |
+
results.append({
|
| 143 |
+
'content': snippet,
|
| 144 |
+
'source': f'Web - {title}',
|
| 145 |
+
'url': result_url,
|
| 146 |
+
'credibility_score': self._assign_credibility_score('web', result_url),
|
| 147 |
+
'source_type': 'web'
|
| 148 |
+
})
|
| 149 |
+
|
| 150 |
+
if len(results) >= 3:
|
| 151 |
+
break
|
| 152 |
+
except:
|
| 153 |
+
continue
|
| 154 |
+
|
| 155 |
+
except Exception as e:
|
| 156 |
+
print(f"DuckDuckGo Lite error: {e}")
|
| 157 |
+
|
| 158 |
+
return results
|
| 159 |
+
|
| 160 |
+
def _search_google_scrape(self, query):
|
| 161 |
+
"""Search using Google scraping (fallback method)"""
|
| 162 |
+
results = []
|
| 163 |
+
|
| 164 |
+
try:
|
| 165 |
+
url = f"https://www.google.com/search?q={quote_plus(query)}"
|
| 166 |
+
headers = {
|
| 167 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
response = requests.get(url, headers=headers, timeout=10)
|
| 171 |
+
|
| 172 |
+
if response.status_code == 200:
|
| 173 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 174 |
+
|
| 175 |
+
# Find search results
|
| 176 |
+
search_results = soup.find_all('div', class_='g')
|
| 177 |
+
|
| 178 |
+
for result in search_results[:5]:
|
| 179 |
+
try:
|
| 180 |
+
link = result.find('a')
|
| 181 |
+
snippet_div = result.find('div', class_=['VwiC3b', 'lEBKkf'])
|
| 182 |
+
|
| 183 |
+
if link and snippet_div:
|
| 184 |
+
result_url = link.get('href', '')
|
| 185 |
+
title = result.find('h3')
|
| 186 |
+
title_text = title.get_text(strip=True) if title else 'Unknown'
|
| 187 |
+
snippet = snippet_div.get_text(strip=True)
|
| 188 |
+
|
| 189 |
+
if result_url.startswith('http') and snippet:
|
| 190 |
+
results.append({
|
| 191 |
+
'content': snippet,
|
| 192 |
+
'source': f'Web - {title_text}',
|
| 193 |
+
'url': result_url,
|
| 194 |
+
'credibility_score': self._assign_credibility_score('web', result_url),
|
| 195 |
+
'source_type': 'web'
|
| 196 |
+
})
|
| 197 |
+
|
| 198 |
+
if len(results) >= 3:
|
| 199 |
+
break
|
| 200 |
+
except:
|
| 201 |
+
continue
|
| 202 |
+
|
| 203 |
+
except Exception as e:
|
| 204 |
+
print(f"Google scrape error: {e}")
|
| 205 |
+
|
| 206 |
+
return results
|
models/keyword_extractor.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/keyword_extractor.py
|
| 2 |
+
import spacy
|
| 3 |
+
from collections import Counter
|
| 4 |
+
|
| 5 |
+
class KeywordExtractor:
|
| 6 |
+
def __init__(self): # Corrected __init__
|
| 7 |
+
try:
|
| 8 |
+
self.nlp = spacy.load("en_core_web_sm")
|
| 9 |
+
except OSError:
|
| 10 |
+
print("Please install spaCy English model: python -m spacy download en_core_web_sm")
|
| 11 |
+
raise
|
| 12 |
+
|
| 13 |
+
def extract_keywords(self, text):
|
| 14 |
+
"""Extract keywords and named entities from text"""
|
| 15 |
+
doc = self.nlp(text)
|
| 16 |
+
|
| 17 |
+
keywords = []
|
| 18 |
+
|
| 19 |
+
# Extract named entities
|
| 20 |
+
for ent in doc.ents:
|
| 21 |
+
if ent.label_ in ['PERSON', 'ORG', 'GPE', 'PRODUCT', 'EVENT', 'DATE']:
|
| 22 |
+
keywords.append(ent.text)
|
| 23 |
+
|
| 24 |
+
# Extract noun phrases and important words
|
| 25 |
+
for chunk in doc.noun_chunks:
|
| 26 |
+
if len(chunk.text.split()) <= 3: # Avoid very long phrases
|
| 27 |
+
keywords.append(chunk.text)
|
| 28 |
+
|
| 29 |
+
# Extract individual important words
|
| 30 |
+
for token in doc:
|
| 31 |
+
if (token.pos_ in ['NOUN', 'PROPN'] and
|
| 32 |
+
not token.is_stop and
|
| 33 |
+
not token.is_punct and
|
| 34 |
+
len(token.text) > 2):
|
| 35 |
+
keywords.append(token.text)
|
| 36 |
+
|
| 37 |
+
# Remove duplicates and return most common
|
| 38 |
+
keyword_counts = Counter(keywords)
|
| 39 |
+
return [word for word, count in keyword_counts.most_common(10)]
|
| 40 |
+
|
models/nli_classifier.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/nli_classifier.py
|
| 2 |
+
from transformers import pipeline
|
| 3 |
+
import torch
|
| 4 |
+
from collections import Counter
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class NLIClassifier:
|
| 8 |
+
_instance = None
|
| 9 |
+
_initialized = False
|
| 10 |
+
|
| 11 |
+
def __new__(cls):
|
| 12 |
+
if cls._instance is None:
|
| 13 |
+
cls._instance = super().__new__(cls)
|
| 14 |
+
return cls._instance
|
| 15 |
+
|
| 16 |
+
def __init__(self):
|
| 17 |
+
if self._initialized:
|
| 18 |
+
return
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
print("Loading NLI models (this may take a moment)...")
|
| 22 |
+
device = 0 if torch.cuda.is_available() else -1
|
| 23 |
+
|
| 24 |
+
self.models = []
|
| 25 |
+
|
| 26 |
+
# Model 1: RoBERTa-large-MNLI (most accurate)
|
| 27 |
+
try:
|
| 28 |
+
self.models.append({
|
| 29 |
+
'name': 'roberta-large-mnli',
|
| 30 |
+
'pipeline': pipeline(
|
| 31 |
+
"text-classification",
|
| 32 |
+
model="roberta-large-mnli",
|
| 33 |
+
device=device
|
| 34 |
+
),
|
| 35 |
+
'weight': 0.5
|
| 36 |
+
})
|
| 37 |
+
print("✓ Loaded RoBERTa-large-MNLI")
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"⚠ Failed to load RoBERTa-large-MNLI: {e}")
|
| 40 |
+
|
| 41 |
+
# Model 2: DeBERTa-v3-large MNLI fine-tuned (use pre-trained version)
|
| 42 |
+
try:
|
| 43 |
+
self.models.append({
|
| 44 |
+
'name': 'deberta-v3-large-mnli',
|
| 45 |
+
'pipeline': pipeline(
|
| 46 |
+
"text-classification",
|
| 47 |
+
model="MoritzLaurer/DeBERTa-v3-large-mnli-fever-anli-ling-wanli",
|
| 48 |
+
device=device
|
| 49 |
+
),
|
| 50 |
+
'weight': 0.5
|
| 51 |
+
})
|
| 52 |
+
print("✓ Loaded DeBERTa-v3-large-MNLI")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f"⚠ Failed to load DeBERTa-v3-large-MNLI: {e}")
|
| 55 |
+
|
| 56 |
+
if not self.models:
|
| 57 |
+
# Fallback to BART if both fail
|
| 58 |
+
try:
|
| 59 |
+
self.models.append({
|
| 60 |
+
'name': 'bart-large-mnli',
|
| 61 |
+
'pipeline': pipeline(
|
| 62 |
+
"zero-shot-classification",
|
| 63 |
+
model="facebook/bart-large-mnli",
|
| 64 |
+
device=device
|
| 65 |
+
),
|
| 66 |
+
'weight': 1.0
|
| 67 |
+
})
|
| 68 |
+
print("✓ Loaded BART-large-MNLI (fallback)")
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print(f"✗ Failed to load any NLI model: {e}")
|
| 71 |
+
raise Exception("No NLI models loaded successfully")
|
| 72 |
+
|
| 73 |
+
# Normalize weights
|
| 74 |
+
total_weight = sum(m['weight'] for m in self.models)
|
| 75 |
+
for model in self.models:
|
| 76 |
+
model['weight'] /= total_weight
|
| 77 |
+
|
| 78 |
+
self._initialized = True
|
| 79 |
+
print(f"✓ Successfully loaded {len(self.models)} NLI model(s)")
|
| 80 |
+
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"Error loading NLI models: {e}")
|
| 83 |
+
self.models = []
|
| 84 |
+
self._initialized = False
|
| 85 |
+
|
| 86 |
+
def classify(self, claim, evidence):
|
| 87 |
+
"""Classify relationship between claim and evidence using ensemble"""
|
| 88 |
+
if not self.models:
|
| 89 |
+
return {
|
| 90 |
+
'label': 'NEUTRAL',
|
| 91 |
+
'confidence': 0.5,
|
| 92 |
+
'model_votes': {}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
try:
|
| 96 |
+
results = []
|
| 97 |
+
model_votes = {}
|
| 98 |
+
|
| 99 |
+
for model_info in self.models:
|
| 100 |
+
try:
|
| 101 |
+
pipeline_obj = model_info['pipeline']
|
| 102 |
+
model_name = model_info['name']
|
| 103 |
+
|
| 104 |
+
# Handle different pipeline types
|
| 105 |
+
if 'bart' in model_name:
|
| 106 |
+
result = pipeline_obj(
|
| 107 |
+
evidence,
|
| 108 |
+
candidate_labels=["entailment", "contradiction", "neutral"],
|
| 109 |
+
hypothesis_template="This example is {}."
|
| 110 |
+
)
|
| 111 |
+
label = result['labels'][0]
|
| 112 |
+
confidence = result['scores'][0]
|
| 113 |
+
else:
|
| 114 |
+
# Standard NLI: premise [SEP] hypothesis
|
| 115 |
+
input_text = f"{evidence} [SEP] {claim}"
|
| 116 |
+
result = pipeline_obj(input_text)[0]
|
| 117 |
+
label = result['label']
|
| 118 |
+
confidence = result['score']
|
| 119 |
+
|
| 120 |
+
# Map labels
|
| 121 |
+
label_mapping = {
|
| 122 |
+
'ENTAILMENT': 'ENTAILMENT',
|
| 123 |
+
'CONTRADICTION': 'CONTRADICTION',
|
| 124 |
+
'NEUTRAL': 'NEUTRAL',
|
| 125 |
+
'entailment': 'ENTAILMENT',
|
| 126 |
+
'contradiction': 'CONTRADICTION',
|
| 127 |
+
'neutral': 'NEUTRAL',
|
| 128 |
+
'LABEL_0': 'CONTRADICTION',
|
| 129 |
+
'LABEL_1': 'NEUTRAL',
|
| 130 |
+
'LABEL_2': 'ENTAILMENT'
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
mapped_label = label_mapping.get(label, 'NEUTRAL')
|
| 134 |
+
|
| 135 |
+
results.append({
|
| 136 |
+
'label': mapped_label,
|
| 137 |
+
'confidence': confidence,
|
| 138 |
+
'weight': model_info['weight']
|
| 139 |
+
})
|
| 140 |
+
|
| 141 |
+
model_votes[model_name] = mapped_label
|
| 142 |
+
|
| 143 |
+
except Exception as e:
|
| 144 |
+
print(f"Error with model {model_info['name']}: {e}")
|
| 145 |
+
continue
|
| 146 |
+
|
| 147 |
+
if not results:
|
| 148 |
+
return {
|
| 149 |
+
'label': 'NEUTRAL',
|
| 150 |
+
'confidence': 0.5,
|
| 151 |
+
'model_votes': {}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
# Weighted voting
|
| 155 |
+
weighted_scores = {
|
| 156 |
+
'ENTAILMENT': 0.0,
|
| 157 |
+
'CONTRADICTION': 0.0,
|
| 158 |
+
'NEUTRAL': 0.0
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
for result in results:
|
| 162 |
+
weighted_scores[result['label']] += result['confidence'] * result['weight']
|
| 163 |
+
|
| 164 |
+
# Get final label and confidence
|
| 165 |
+
final_label = max(weighted_scores, key=weighted_scores.get)
|
| 166 |
+
total_score = sum(weighted_scores.values())
|
| 167 |
+
final_confidence = weighted_scores[final_label] / total_score if total_score > 0 else 0.5
|
| 168 |
+
|
| 169 |
+
return {
|
| 170 |
+
'label': final_label,
|
| 171 |
+
'confidence': final_confidence,
|
| 172 |
+
'model_votes': model_votes,
|
| 173 |
+
'weighted_scores': weighted_scores
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
except Exception as e:
|
| 177 |
+
print(f"NLI classification error: {e}")
|
| 178 |
+
return {
|
| 179 |
+
'label': 'NEUTRAL',
|
| 180 |
+
'confidence': 0.5,
|
| 181 |
+
'model_votes': {}
|
| 182 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# requirements.txt - FIXED
|
| 2 |
+
Flask==3.0.0
|
| 3 |
+
Werkzeug==3.0.1
|
| 4 |
+
|
| 5 |
+
# spaCy with model
|
| 6 |
+
spacy==3.7.5
|
| 7 |
+
https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl
|
| 8 |
+
|
| 9 |
+
# Wikipedia and web scraping
|
| 10 |
+
wikipedia==1.4.0
|
| 11 |
+
requests==2.31.0
|
| 12 |
+
beautifulsoup4==4.12.2
|
| 13 |
+
lxml==4.9.3
|
| 14 |
+
|
| 15 |
+
# NLP and transformers
|
| 16 |
+
sentence-transformers==2.7.0
|
| 17 |
+
transformers==4.36.0
|
| 18 |
+
torch==2.1.0
|
| 19 |
+
torchvision==0.16.0
|
| 20 |
+
tokenizers==0.15.2
|
| 21 |
+
huggingface-hub==0.23.0
|
| 22 |
+
|
| 23 |
+
# Scientific computing
|
| 24 |
+
scikit-learn==1.3.2
|
| 25 |
+
numpy==1.26.2
|
run.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# run.py - Entry point to start the application
|
| 2 |
+
|
| 3 |
+
# Import the create_app function from your app.py
|
| 4 |
+
from app import create_app
|
| 5 |
+
|
| 6 |
+
# Create the Flask application instance
|
| 7 |
+
app = create_app()
|
| 8 |
+
|
| 9 |
+
if __name__ == "__main__":
|
| 10 |
+
print("🚀 Starting TruthCheck System...")
|
| 11 |
+
print("📊 Flask Backend: http://127.0.0.1:5000") # Updated to 127.0.0.1 for local access
|
| 12 |
+
|
| 13 |
+
# Run the Flask application
|
| 14 |
+
# debug=True enables reloader and debugger, useful during development
|
| 15 |
+
# host='0.0.0.0' makes the server accessible from other devices on the network
|
| 16 |
+
# port=5000 is the default Flask port
|
| 17 |
+
app.run(debug=True, host='0.0.0.0', port=5000)
|
static/css/style.css
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Custom Scrollbar */
|
| 2 |
+
::-webkit-scrollbar {
|
| 3 |
+
width: 6px;
|
| 4 |
+
height: 6px;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
::-webkit-scrollbar-track {
|
| 8 |
+
background: #0f172a;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
::-webkit-scrollbar-thumb {
|
| 12 |
+
background: #1e293b;
|
| 13 |
+
border-radius: 3px;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
::-webkit-scrollbar-thumb:hover {
|
| 17 |
+
background: #0ea5e9;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
/* Base Styles */
|
| 21 |
+
body {
|
| 22 |
+
-webkit-font-smoothing: antialiased;
|
| 23 |
+
-moz-osx-font-smoothing: grayscale;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.perspective-1000 {
|
| 27 |
+
perspective: 1000px;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* Animations */
|
| 31 |
+
@keyframes fadeInUp {
|
| 32 |
+
from {
|
| 33 |
+
opacity: 0;
|
| 34 |
+
transform: translate3d(0, 20px, 0);
|
| 35 |
+
}
|
| 36 |
+
to {
|
| 37 |
+
opacity: 1;
|
| 38 |
+
transform: translate3d(0, 0, 0);
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.animate-fade-in-up {
|
| 43 |
+
animation: fadeInUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
/* Neon Text utilities */
|
| 47 |
+
.text-neon-cyan {
|
| 48 |
+
color: #00f3ff;
|
| 49 |
+
text-shadow: 0 0 5px rgba(0, 243, 255, 0.5), 0 0 10px rgba(0, 243, 255, 0.3);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.text-neon-purple {
|
| 53 |
+
color: #bc13fe;
|
| 54 |
+
text-shadow: 0 0 5px rgba(188, 19, 254, 0.5), 0 0 10px rgba(188, 19, 254, 0.3);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
/* Markdown Content Styling within Evidence */
|
| 58 |
+
#evidenceList strong {
|
| 59 |
+
color: #38bdf8;
|
| 60 |
+
font-weight: 600;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
#evidenceList a {
|
| 64 |
+
color: #0ea5e9;
|
| 65 |
+
text-decoration: underline;
|
| 66 |
+
text-decoration-thickness: 1px;
|
| 67 |
+
text-underline-offset: 2px;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#evidenceList a:hover {
|
| 71 |
+
color: #00f3ff;
|
| 72 |
+
}
|
static/js/main.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
+
const claimInput = document.getElementById('claimInput');
|
| 3 |
+
const verifyBtn = document.getElementById('verifyBtn');
|
| 4 |
+
const resultSection = document.getElementById('resultSection');
|
| 5 |
+
const loadingOverlay = document.getElementById('loadingOverlay');
|
| 6 |
+
const loadingText = document.getElementById('loadingText');
|
| 7 |
+
const charCount = document.getElementById('charCount');
|
| 8 |
+
|
| 9 |
+
// Stats Elements
|
| 10 |
+
const resultLabel = document.getElementById('resultLabel');
|
| 11 |
+
const confidenceScore = document.getElementById('confidenceScore');
|
| 12 |
+
const confidenceCircle = document.getElementById('confidenceCircle');
|
| 13 |
+
const evidenceList = document.getElementById('evidenceList');
|
| 14 |
+
const resultBar = document.getElementById('resultBar');
|
| 15 |
+
|
| 16 |
+
// Character Count
|
| 17 |
+
claimInput.addEventListener('input', () => {
|
| 18 |
+
const len = claimInput.value.length;
|
| 19 |
+
charCount.textContent = len;
|
| 20 |
+
if (len > 500) {
|
| 21 |
+
charCount.classList.add('text-red-500');
|
| 22 |
+
verifyBtn.disabled = true;
|
| 23 |
+
verifyBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
| 24 |
+
} else {
|
| 25 |
+
charCount.classList.remove('text-red-500');
|
| 26 |
+
verifyBtn.disabled = false;
|
| 27 |
+
verifyBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
| 28 |
+
}
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
// Verification Logic
|
| 32 |
+
verifyBtn.addEventListener('click', async () => {
|
| 33 |
+
const claim = claimInput.value.trim();
|
| 34 |
+
|
| 35 |
+
if (!claim) {
|
| 36 |
+
alert('Please enter a claim to verify.');
|
| 37 |
+
return;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// Show Loading
|
| 41 |
+
loadingOverlay.classList.remove('hidden');
|
| 42 |
+
resultSection.classList.add('hidden');
|
| 43 |
+
|
| 44 |
+
// Animated Loading Text
|
| 45 |
+
const steps = [
|
| 46 |
+
"Extracting Facutal Claims...",
|
| 47 |
+
"Scanning Knowledge Base...",
|
| 48 |
+
"Retrieving Global Evidence...",
|
| 49 |
+
"Running NLI Models...",
|
| 50 |
+
"Calculating Consensus..."
|
| 51 |
+
];
|
| 52 |
+
|
| 53 |
+
let stepIndex = 0;
|
| 54 |
+
const interval = setInterval(() => {
|
| 55 |
+
if(stepIndex < steps.length) {
|
| 56 |
+
loadingText.textContent = steps[stepIndex];
|
| 57 |
+
stepIndex++;
|
| 58 |
+
}
|
| 59 |
+
}, 800);
|
| 60 |
+
|
| 61 |
+
try {
|
| 62 |
+
const response = await fetch('/api/verify', {
|
| 63 |
+
method: 'POST',
|
| 64 |
+
headers: {
|
| 65 |
+
'Content-Type': 'application/json'
|
| 66 |
+
},
|
| 67 |
+
body: JSON.stringify({ claim: claim })
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
clearInterval(interval);
|
| 71 |
+
const data = await response.json();
|
| 72 |
+
|
| 73 |
+
if (data.error) {
|
| 74 |
+
throw new Error(data.error);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// Update UI with results
|
| 78 |
+
displayResults(data);
|
| 79 |
+
|
| 80 |
+
} catch (error) {
|
| 81 |
+
clearInterval(interval);
|
| 82 |
+
alert('Error: ' + error.message);
|
| 83 |
+
} finally {
|
| 84 |
+
loadingOverlay.classList.add('hidden');
|
| 85 |
+
}
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
function displayResults(data) {
|
| 89 |
+
resultSection.classList.remove('hidden');
|
| 90 |
+
|
| 91 |
+
// 1. Label
|
| 92 |
+
resultLabel.textContent = data.label;
|
| 93 |
+
|
| 94 |
+
// Color coding
|
| 95 |
+
let colorClass = 'text-gray-400';
|
| 96 |
+
let barColor = 'bg-gray-400';
|
| 97 |
+
let strokeColor = 'text-gray-400';
|
| 98 |
+
|
| 99 |
+
if (data.label.toLowerCase() === 'true') {
|
| 100 |
+
colorClass = 'text-neon-green';
|
| 101 |
+
barColor = 'bg-green-500';
|
| 102 |
+
strokeColor = 'text-green-500';
|
| 103 |
+
resultLabel.style.color = '#4ade80'; // Tailwind green-400
|
| 104 |
+
} else if (data.label.toLowerCase() === 'false') {
|
| 105 |
+
colorClass = 'text-neon-red';
|
| 106 |
+
barColor = 'bg-red-500';
|
| 107 |
+
strokeColor = 'text-red-500';
|
| 108 |
+
resultLabel.style.color = '#f87171'; // Tailwind red-400
|
| 109 |
+
} else {
|
| 110 |
+
resultLabel.style.color = '#fbbf24'; // Tailwind amber-400
|
| 111 |
+
barColor = 'bg-amber-400';
|
| 112 |
+
strokeColor = 'text-amber-400';
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
resultBar.className = `absolute top-0 left-0 w-1 h-full ${barColor}`;
|
| 116 |
+
confidenceCircle.setAttribute('class', strokeColor);
|
| 117 |
+
|
| 118 |
+
// 2. Confidence
|
| 119 |
+
const percentage = Math.round(data.confidence * 100);
|
| 120 |
+
confidenceScore.textContent = `${percentage}%`;
|
| 121 |
+
|
| 122 |
+
// Animate Circle
|
| 123 |
+
// C = 2 * pi * r = 2 * 3.14159 * 28 ≈ 175.9
|
| 124 |
+
const circumference = 175.9;
|
| 125 |
+
const offset = circumference - (data.confidence * circumference);
|
| 126 |
+
confidenceCircle.style.strokeDasharray = `${circumference} ${circumference}`;
|
| 127 |
+
confidenceCircle.style.strokeDashoffset = offset;
|
| 128 |
+
|
| 129 |
+
// 3. Evidence
|
| 130 |
+
// Format markdown-like evidence summary
|
| 131 |
+
const formattedEvidence = data.evidence
|
| 132 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| 133 |
+
.replace(/\n/g, '<br>');
|
| 134 |
+
|
| 135 |
+
evidenceList.innerHTML = formattedEvidence;
|
| 136 |
+
|
| 137 |
+
// Scroll to results
|
| 138 |
+
resultSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 139 |
+
}
|
| 140 |
+
});
|
templates/api.html
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="scroll-smooth">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>API Documentation | TruthCheck</title>
|
| 8 |
+
|
| 9 |
+
<!-- Fonts -->
|
| 10 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
+
<link
|
| 13 |
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;900&family=Fira+Code:wght@400;500&display=swap"
|
| 14 |
+
rel="stylesheet">
|
| 15 |
+
|
| 16 |
+
<!-- Tailwind CSS -->
|
| 17 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 18 |
+
<script>
|
| 19 |
+
tailwind.config = {
|
| 20 |
+
darkMode: 'class',
|
| 21 |
+
theme: {
|
| 22 |
+
extend: {
|
| 23 |
+
fontFamily: {
|
| 24 |
+
sans: ['Inter', 'sans-serif'],
|
| 25 |
+
display: ['Orbitron', 'sans-serif'],
|
| 26 |
+
mono: ['Fira Code', 'monospace'],
|
| 27 |
+
},
|
| 28 |
+
colors: {
|
| 29 |
+
brand: {
|
| 30 |
+
50: '#f0f9ff',
|
| 31 |
+
100: '#e0f2fe',
|
| 32 |
+
200: '#bae6fd',
|
| 33 |
+
300: '#7dd3fc',
|
| 34 |
+
400: '#38bdf8',
|
| 35 |
+
500: '#0ea5e9',
|
| 36 |
+
600: '#0284c7',
|
| 37 |
+
700: '#0369a1',
|
| 38 |
+
800: '#075985',
|
| 39 |
+
900: '#0c4a6e',
|
| 40 |
+
950: '#082f49',
|
| 41 |
+
},
|
| 42 |
+
neon: {
|
| 43 |
+
cyan: '#00f3ff',
|
| 44 |
+
purple: '#bc13fe',
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
animation: {
|
| 48 |
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 49 |
+
'fade-in-up': 'fadeInUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) forwards',
|
| 50 |
+
},
|
| 51 |
+
keyframes: {
|
| 52 |
+
fadeInUp: {
|
| 53 |
+
'from': { opacity: '0', transform: 'translate3d(0, 20px, 0)' },
|
| 54 |
+
'to': { opacity: '1', transform: 'translate3d(0, 0, 0)' }
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
</script>
|
| 61 |
+
|
| 62 |
+
<!-- Icons -->
|
| 63 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 64 |
+
|
| 65 |
+
<!-- Custom CSS -->
|
| 66 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 67 |
+
</head>
|
| 68 |
+
|
| 69 |
+
<body
|
| 70 |
+
class="bg-slate-950 text-white min-h-screen relative overflow-x-hidden selection:bg-brand-500 selection:text-white">
|
| 71 |
+
|
| 72 |
+
<!-- Background Grid Effect -->
|
| 73 |
+
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
|
| 74 |
+
<div
|
| 75 |
+
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%)]">
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<!-- Navigation -->
|
| 80 |
+
<nav class="relative z-50 border-b border-white/10 backdrop-blur-md bg-slate-950/50">
|
| 81 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 82 |
+
<div class="flex items-center justify-between h-20">
|
| 83 |
+
<a href="/" class="flex items-center gap-3 group">
|
| 84 |
+
<div
|
| 85 |
+
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">
|
| 86 |
+
<i class="fa-solid fa-shield-halved text-brand-400 text-xl"></i>
|
| 87 |
+
</div>
|
| 88 |
+
<span
|
| 89 |
+
class="font-display font-bold text-2xl tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-400">
|
| 90 |
+
TRUTH<span class="text-brand-400">CHECK</span>
|
| 91 |
+
</span>
|
| 92 |
+
</a>
|
| 93 |
+
<div class="hidden md:flex gap-8">
|
| 94 |
+
<a href="/"
|
| 95 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Analyzer</a>
|
| 96 |
+
<a href="/how-it-works"
|
| 97 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">How it
|
| 98 |
+
Works</a>
|
| 99 |
+
<a href="/api-docs" class="text-sm font-medium text-brand-400 border-b-2 border-brand-400">API</a>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</nav>
|
| 104 |
+
|
| 105 |
+
<!-- Main Content -->
|
| 106 |
+
<main class="relative z-10 container mx-auto px-4 py-16">
|
| 107 |
+
|
| 108 |
+
<div class="max-w-4xl mx-auto space-y-12 animate-fade-in-up">
|
| 109 |
+
|
| 110 |
+
<!-- Header -->
|
| 111 |
+
<div class="space-y-4">
|
| 112 |
+
<h1 class="font-display text-4xl md:text-5xl font-bold text-white">REST API Reference</h1>
|
| 113 |
+
<p class="text-slate-400 text-lg">Integrate TruthCheck verification directly into your applications.</p>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<!-- Endpoint Card -->
|
| 117 |
+
<div class="bg-slate-900/50 border border-white/5 rounded-2xl overflow-hidden">
|
| 118 |
+
<div class="p-6 border-b border-white/5 bg-slate-900 flex justify-between items-center">
|
| 119 |
+
<div class="flex items-center gap-4">
|
| 120 |
+
<span
|
| 121 |
+
class="px-3 py-1 bg-green-500/20 text-green-400 font-mono text-sm font-bold rounded">POST</span>
|
| 122 |
+
<code class="text-lg text-white font-mono">/api/verify</code>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
|
| 126 |
+
<div class="p-8 space-y-8">
|
| 127 |
+
<!-- Description -->
|
| 128 |
+
<div>
|
| 129 |
+
<h3 class="font-display text-lg text-white mb-2">Description</h3>
|
| 130 |
+
<p class="text-slate-400">Submit a text claim for verification. The system will process the
|
| 131 |
+
claim and return a verdict with supporting evidence.</p>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<!-- Request -->
|
| 135 |
+
<div>
|
| 136 |
+
<h3 class="font-display text-lg text-white mb-4">Request Body</h3>
|
| 137 |
+
<div class="bg-slate-950 p-6 rounded-xl border border-white/10 relative group">
|
| 138 |
+
<button class="absolute top-4 right-4 text-slate-500 hover:text-white transition-colors"><i
|
| 139 |
+
class="fa-regular fa-copy"></i></button>
|
| 140 |
+
<pre><code class="language-json text-sm font-mono text-brand-300">{
|
| 141 |
+
"claim": "The Eiffel Tower is located in London"
|
| 142 |
+
}</code></pre>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
<!-- Response -->
|
| 147 |
+
<div>
|
| 148 |
+
<h3 class="font-display text-lg text-white mb-4">Response</h3>
|
| 149 |
+
<div class="bg-slate-950 p-6 rounded-xl border border-white/10 relative group">
|
| 150 |
+
<button class="absolute top-4 right-4 text-slate-500 hover:text-white transition-colors"><i
|
| 151 |
+
class="fa-regular fa-copy"></i></button>
|
| 152 |
+
<pre><code class="language-json text-sm font-mono text-emerald-300">{
|
| 153 |
+
"label": "False",
|
| 154 |
+
"confidence": 0.98,
|
| 155 |
+
"evidence": "**Analyzed 4 sources**...",
|
| 156 |
+
"claim": "The Eiffel Tower is located in London"
|
| 157 |
+
}</code></pre>
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<!-- Status Codes -->
|
| 164 |
+
<div class="bg-slate-900/50 border border-white/5 rounded-2xl overflow-hidden p-8">
|
| 165 |
+
<h3 class="font-display text-lg text-white mb-6">Status Codes</h3>
|
| 166 |
+
<div class="space-y-4">
|
| 167 |
+
<div class="flex gap-4">
|
| 168 |
+
<code class="text-green-400 font-mono font-bold w-12">200</code>
|
| 169 |
+
<span class="text-slate-300">Successful verification.</span>
|
| 170 |
+
</div>
|
| 171 |
+
<div class="flex gap-4">
|
| 172 |
+
<code class="text-amber-400 font-mono font-bold w-12">400</code>
|
| 173 |
+
<span class="text-slate-300">Bad request (missing claim).</span>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="flex gap-4">
|
| 176 |
+
<code class="text-red-400 font-mono font-bold w-12">500</code>
|
| 177 |
+
<span class="text-slate-300">Internal server error.</span>
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
</div>
|
| 183 |
+
</main>
|
| 184 |
+
|
| 185 |
+
<!-- Footer -->
|
| 186 |
+
<footer class="relative z-10 border-t border-white/5 mt-20 bg-slate-950">
|
| 187 |
+
<div
|
| 188 |
+
class="max-w-7xl mx-auto px-4 py-8 flex flex-col md:flex-row items-center justify-between text-slate-500 text-sm">
|
| 189 |
+
<div>© 2025 TruthCheck AI. All Systems Nominal.</div>
|
| 190 |
+
<div class="flex gap-4 mt-4 md:mt-0">
|
| 191 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Privacy</a>
|
| 192 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Terms</a>
|
| 193 |
+
<a href="https://github.com/CHRISDANIEL145" class="hover:text-brand-400 transition-colors">Github</a>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
</footer>
|
| 197 |
+
</body>
|
| 198 |
+
|
| 199 |
+
</html>
|
templates/dashboard.html
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="scroll-smooth">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Command Center | TruthCheck</title>
|
| 8 |
+
|
| 9 |
+
<!-- Fonts -->
|
| 10 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
+
<link
|
| 13 |
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;900&display=swap"
|
| 14 |
+
rel="stylesheet">
|
| 15 |
+
|
| 16 |
+
<!-- Tailwind CSS -->
|
| 17 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 18 |
+
<script>
|
| 19 |
+
tailwind.config = {
|
| 20 |
+
darkMode: 'class',
|
| 21 |
+
theme: {
|
| 22 |
+
extend: {
|
| 23 |
+
fontFamily: {
|
| 24 |
+
sans: ['Inter', 'sans-serif'],
|
| 25 |
+
display: ['Orbitron', 'sans-serif'],
|
| 26 |
+
},
|
| 27 |
+
colors: {
|
| 28 |
+
brand: {
|
| 29 |
+
50: '#f0f9ff',
|
| 30 |
+
100: '#e0f2fe',
|
| 31 |
+
200: '#bae6fd',
|
| 32 |
+
300: '#7dd3fc',
|
| 33 |
+
400: '#38bdf8',
|
| 34 |
+
500: '#0ea5e9',
|
| 35 |
+
600: '#0284c7',
|
| 36 |
+
700: '#0369a1',
|
| 37 |
+
800: '#075985',
|
| 38 |
+
900: '#0c4a6e',
|
| 39 |
+
950: '#082f49',
|
| 40 |
+
},
|
| 41 |
+
neon: {
|
| 42 |
+
cyan: '#00f3ff',
|
| 43 |
+
purple: '#bc13fe',
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
animation: {
|
| 47 |
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 48 |
+
'fade-in': 'fadeIn 0.5s ease-out forwards',
|
| 49 |
+
},
|
| 50 |
+
keyframes: {
|
| 51 |
+
fadeIn: {
|
| 52 |
+
'from': { opacity: '0' },
|
| 53 |
+
'to': { opacity: '1' }
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
</script>
|
| 60 |
+
|
| 61 |
+
<!-- Icons -->
|
| 62 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 63 |
+
|
| 64 |
+
<!-- Custom CSS -->
|
| 65 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 66 |
+
|
| 67 |
+
<!-- Chart.js -->
|
| 68 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 69 |
+
</head>
|
| 70 |
+
|
| 71 |
+
<body
|
| 72 |
+
class="bg-slate-950 text-white min-h-screen relative overflow-x-hidden selection:bg-brand-500 selection:text-white">
|
| 73 |
+
|
| 74 |
+
<!-- Background Grid Effect -->
|
| 75 |
+
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
|
| 76 |
+
<div
|
| 77 |
+
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%)]">
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
<!-- Navigation -->
|
| 82 |
+
<nav class="relative z-50 border-b border-white/10 backdrop-blur-md bg-slate-950/50">
|
| 83 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 84 |
+
<div class="flex items-center justify-between h-20">
|
| 85 |
+
<a href="/" class="flex items-center gap-3 group">
|
| 86 |
+
<div
|
| 87 |
+
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">
|
| 88 |
+
<i class="fa-solid fa-shield-halved text-brand-400 text-xl"></i>
|
| 89 |
+
</div>
|
| 90 |
+
<span
|
| 91 |
+
class="font-display font-bold text-2xl tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-400">
|
| 92 |
+
TRUTH<span class="text-brand-400">CHECK</span>
|
| 93 |
+
</span>
|
| 94 |
+
</a>
|
| 95 |
+
<div class="hidden md:flex gap-8">
|
| 96 |
+
<a href="/"
|
| 97 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Analyzer</a>
|
| 98 |
+
<a href="#" class="text-sm font-medium text-brand-400 border-b-2 border-brand-400">Dashboard</a>
|
| 99 |
+
<a href="/how-it-works"
|
| 100 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">How it
|
| 101 |
+
Works</a>
|
| 102 |
+
<a href="/api-docs"
|
| 103 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">API</a>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
</nav>
|
| 108 |
+
|
| 109 |
+
<!-- Main Content -->
|
| 110 |
+
<main class="relative z-10 container mx-auto px-4 py-8">
|
| 111 |
+
|
| 112 |
+
<!-- Header -->
|
| 113 |
+
<div class="flex justify-between items-end mb-8 animate-fade-in">
|
| 114 |
+
<div>
|
| 115 |
+
<h1 class="font-display text-3xl md:text-4xl font-bold text-white mb-2">Command Center</h1>
|
| 116 |
+
<p class="text-slate-400">Global verification telemetry and history.</p>
|
| 117 |
+
</div>
|
| 118 |
+
<button onclick="loadHistory()"
|
| 119 |
+
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">
|
| 120 |
+
<i class="fa-solid fa-rotate-right mr-2"></i> Refresh
|
| 121 |
+
</button>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<!-- Stats Grid -->
|
| 125 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-12 animate-fade-in" style="animation-delay: 0.1s;">
|
| 126 |
+
<!-- Stat 1 -->
|
| 127 |
+
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
|
| 128 |
+
<div class="flex items-start justify-between">
|
| 129 |
+
<div>
|
| 130 |
+
<div class="text-slate-400 text-sm font-medium mb-1">Total Scans</div>
|
| 131 |
+
<div class="text-3xl font-display font-bold text-white" id="statTotal">0</div>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="bg-brand-500/10 p-3 rounded-lg text-brand-400">
|
| 134 |
+
<i class="fa-solid fa-chart-line"></i>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
<!-- Stat 2 -->
|
| 139 |
+
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
|
| 140 |
+
<div class="flex items-start justify-between">
|
| 141 |
+
<div>
|
| 142 |
+
<div class="text-slate-400 text-sm font-medium mb-1">Truth Rate</div>
|
| 143 |
+
<div class="text-3xl font-display font-bold text-white" id="statTrueRate">0%</div>
|
| 144 |
+
</div>
|
| 145 |
+
<div class="bg-green-500/10 p-3 rounded-lg text-green-400">
|
| 146 |
+
<i class="fa-solid fa-check"></i>
|
| 147 |
+
</div>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
<!-- Stat 3 -->
|
| 151 |
+
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
|
| 152 |
+
<div class="flex items-start justify-between">
|
| 153 |
+
<div>
|
| 154 |
+
<div class="text-slate-400 text-sm font-medium mb-1">Misinfo Detected</div>
|
| 155 |
+
<div class="text-3xl font-display font-bold text-white" id="statFalseRate">0%</div>
|
| 156 |
+
</div>
|
| 157 |
+
<div class="bg-red-500/10 p-3 rounded-lg text-red-400">
|
| 158 |
+
<i class="fa-solid fa-triangle-exclamation"></i>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
<!-- Stat 4 -->
|
| 163 |
+
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
|
| 164 |
+
<div class="flex items-start justify-between">
|
| 165 |
+
<div>
|
| 166 |
+
<div class="text-slate-400 text-sm font-medium mb-1">Avg Confidence</div>
|
| 167 |
+
<div class="text-3xl font-display font-bold text-white" id="statAvgConf">0%</div>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="bg-neon-purple/10 p-3 rounded-lg text-neon-purple">
|
| 170 |
+
<i class="fa-solid fa-bullseye"></i>
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<!-- History Table -->
|
| 177 |
+
<div class="bg-slate-900/50 border border-white/5 rounded-2xl overflow-hidden backdrop-blur-sm animate-fade-in"
|
| 178 |
+
style="animation-delay: 0.2s;">
|
| 179 |
+
<div class="p-6 border-b border-white/5">
|
| 180 |
+
<h3 class="font-display text-xl font-bold text-white">Recent Verifications</h3>
|
| 181 |
+
</div>
|
| 182 |
+
|
| 183 |
+
<div class="overflow-x-auto">
|
| 184 |
+
<table class="w-full text-left">
|
| 185 |
+
<thead class="bg-slate-950/50 text-slate-400 uppercase text-xs font-medium">
|
| 186 |
+
<tr>
|
| 187 |
+
<th class="px-6 py-4">Status</th>
|
| 188 |
+
<th class="px-6 py-4">Claim</th>
|
| 189 |
+
<th class="px-6 py-4">Confidence</th>
|
| 190 |
+
<th class="px-6 py-4">Timestamp</th>
|
| 191 |
+
</tr>
|
| 192 |
+
</thead>
|
| 193 |
+
<tbody class="divide-y divide-white/5 text-sm" id="historyTableBody">
|
| 194 |
+
<!-- Rows injected via JS -->
|
| 195 |
+
</tbody>
|
| 196 |
+
</table>
|
| 197 |
+
</div>
|
| 198 |
+
|
| 199 |
+
<div id="emptyState" class="hidden p-12 text-center text-slate-500">
|
| 200 |
+
<i class="fa-solid fa-inbox text-4xl mb-4 opacity-50"></i>
|
| 201 |
+
<p>No verification history found.</p>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
|
| 205 |
+
</main>
|
| 206 |
+
|
| 207 |
+
<script>
|
| 208 |
+
document.addEventListener('DOMContentLoaded', loadHistory);
|
| 209 |
+
|
| 210 |
+
async function loadHistory() {
|
| 211 |
+
try {
|
| 212 |
+
const response = await fetch('/api/history');
|
| 213 |
+
const data = await response.json();
|
| 214 |
+
|
| 215 |
+
updateStats(data);
|
| 216 |
+
renderTable(data);
|
| 217 |
+
} catch (error) {
|
| 218 |
+
console.error("Failed to load history:", error);
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
function updateStats(data) {
|
| 223 |
+
if (!data.length) return;
|
| 224 |
+
|
| 225 |
+
const total = data.length;
|
| 226 |
+
const trueCount = data.filter(i => i.label.toLowerCase() === 'true').length;
|
| 227 |
+
const falseCount = data.filter(i => i.label.toLowerCase() === 'false').length;
|
| 228 |
+
|
| 229 |
+
const totalConfidence = data.reduce((acc, curr) => acc + curr.confidence, 0);
|
| 230 |
+
const avgConf = total > 0 ? (totalConfidence / total) : 0;
|
| 231 |
+
|
| 232 |
+
document.getElementById('statTotal').textContent = total;
|
| 233 |
+
document.getElementById('statTrueRate').textContent = Math.round((trueCount / total) * 100) + '%';
|
| 234 |
+
document.getElementById('statFalseRate').textContent = Math.round((falseCount / total) * 100) + '%';
|
| 235 |
+
document.getElementById('statAvgConf').textContent = Math.round(avgConf * 100) + '%';
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
function renderTable(data) {
|
| 239 |
+
const tbody = document.getElementById('historyTableBody');
|
| 240 |
+
const emptyState = document.getElementById('emptyState');
|
| 241 |
+
tbody.innerHTML = '';
|
| 242 |
+
|
| 243 |
+
if (data.length === 0) {
|
| 244 |
+
emptyState.classList.remove('hidden');
|
| 245 |
+
return;
|
| 246 |
+
} else {
|
| 247 |
+
emptyState.classList.add('hidden');
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
data.forEach(row => {
|
| 251 |
+
const tr = document.createElement('tr');
|
| 252 |
+
tr.className = 'hover:bg-white/5 transition-colors';
|
| 253 |
+
|
| 254 |
+
// Status Badge
|
| 255 |
+
let statusBadge = '';
|
| 256 |
+
if (row.label.toLowerCase() === 'true') {
|
| 257 |
+
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>';
|
| 258 |
+
} else if (row.label.toLowerCase() === 'false') {
|
| 259 |
+
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>';
|
| 260 |
+
} else {
|
| 261 |
+
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>';
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
// Date
|
| 265 |
+
const date = new Date(row.date).toLocaleString();
|
| 266 |
+
|
| 267 |
+
tr.innerHTML = `
|
| 268 |
+
<td class="px-6 py-4 whitespace-nowrap">${statusBadge}</td>
|
| 269 |
+
<td class="px-6 py-4 text-slate-300 font-light truncate max-w-md" title="${row.claim}">${row.claim}</td>
|
| 270 |
+
<td class="px-6 py-4 text-slate-400 flex items-center gap-2">
|
| 271 |
+
<div class="w-16 h-1.5 bg-slate-800 rounded-full overflow-hidden">
|
| 272 |
+
<div class="h-full bg-brand-500" style="width: ${row.confidence * 100}%"></div>
|
| 273 |
+
</div>
|
| 274 |
+
${Math.round(row.confidence * 100)}%
|
| 275 |
+
</td>
|
| 276 |
+
<td class="px-6 py-4 text-slate-500 font-mono text-xs">${date}</td>
|
| 277 |
+
`;
|
| 278 |
+
tbody.appendChild(tr);
|
| 279 |
+
});
|
| 280 |
+
}
|
| 281 |
+
</script>
|
| 282 |
+
</body>
|
| 283 |
+
|
| 284 |
+
</html>
|
templates/how_it_works.html
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="scroll-smooth">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>How it Works | TruthCheck</title>
|
| 8 |
+
|
| 9 |
+
<!-- Fonts -->
|
| 10 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
+
<link
|
| 13 |
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;900&display=swap"
|
| 14 |
+
rel="stylesheet">
|
| 15 |
+
|
| 16 |
+
<!-- Tailwind CSS -->
|
| 17 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 18 |
+
<script>
|
| 19 |
+
tailwind.config = {
|
| 20 |
+
darkMode: 'class',
|
| 21 |
+
theme: {
|
| 22 |
+
extend: {
|
| 23 |
+
fontFamily: {
|
| 24 |
+
sans: ['Inter', 'sans-serif'],
|
| 25 |
+
display: ['Orbitron', 'sans-serif'],
|
| 26 |
+
},
|
| 27 |
+
colors: {
|
| 28 |
+
brand: {
|
| 29 |
+
50: '#f0f9ff',
|
| 30 |
+
100: '#e0f2fe',
|
| 31 |
+
200: '#bae6fd',
|
| 32 |
+
300: '#7dd3fc',
|
| 33 |
+
400: '#38bdf8',
|
| 34 |
+
500: '#0ea5e9',
|
| 35 |
+
600: '#0284c7',
|
| 36 |
+
700: '#0369a1',
|
| 37 |
+
800: '#075985',
|
| 38 |
+
900: '#0c4a6e',
|
| 39 |
+
950: '#082f49',
|
| 40 |
+
},
|
| 41 |
+
neon: {
|
| 42 |
+
cyan: '#00f3ff',
|
| 43 |
+
purple: '#bc13fe',
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
animation: {
|
| 47 |
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 48 |
+
'fade-in-up': 'fadeInUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) forwards',
|
| 49 |
+
},
|
| 50 |
+
keyframes: {
|
| 51 |
+
fadeInUp: {
|
| 52 |
+
'from': { opacity: '0', transform: 'translate3d(0, 20px, 0)' },
|
| 53 |
+
'to': { opacity: '1', transform: 'translate3d(0, 0, 0)' }
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
</script>
|
| 60 |
+
|
| 61 |
+
<!-- Icons -->
|
| 62 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 63 |
+
|
| 64 |
+
<!-- Custom CSS -->
|
| 65 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 66 |
+
</head>
|
| 67 |
+
|
| 68 |
+
<body
|
| 69 |
+
class="bg-slate-950 text-white min-h-screen relative overflow-x-hidden selection:bg-brand-500 selection:text-white">
|
| 70 |
+
|
| 71 |
+
<!-- Background Grid Effect -->
|
| 72 |
+
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
|
| 73 |
+
<div
|
| 74 |
+
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%)]">
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<!-- Navigation -->
|
| 79 |
+
<nav class="relative z-50 border-b border-white/10 backdrop-blur-md bg-slate-950/50">
|
| 80 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 81 |
+
<div class="flex items-center justify-between h-20">
|
| 82 |
+
<a href="/" class="flex items-center gap-3 group">
|
| 83 |
+
<div
|
| 84 |
+
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">
|
| 85 |
+
<i class="fa-solid fa-shield-halved text-brand-400 text-xl"></i>
|
| 86 |
+
</div>
|
| 87 |
+
<span
|
| 88 |
+
class="font-display font-bold text-2xl tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-400">
|
| 89 |
+
TRUTH<span class="text-brand-400">CHECK</span>
|
| 90 |
+
</span>
|
| 91 |
+
</a>
|
| 92 |
+
<div class="hidden md:flex gap-8">
|
| 93 |
+
<a href="/"
|
| 94 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Analyzer</a>
|
| 95 |
+
<a href="/dashboard"
|
| 96 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Dashboard</a>
|
| 97 |
+
<a href="/how-it-works" class="text-sm font-medium text-brand-400 border-b-2 border-brand-400">How
|
| 98 |
+
it Works</a>
|
| 99 |
+
<a href="/api-docs"
|
| 100 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">API</a>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
</nav>
|
| 105 |
+
|
| 106 |
+
<!-- Main Content -->
|
| 107 |
+
<main class="relative z-10 container mx-auto px-4 py-16">
|
| 108 |
+
|
| 109 |
+
<div class="max-w-4xl mx-auto space-y-16 animate-fade-in-up">
|
| 110 |
+
|
| 111 |
+
<!-- Header -->
|
| 112 |
+
<div class="text-center space-y-4">
|
| 113 |
+
<h1 class="font-display text-4xl md:text-5xl font-bold text-white">System Architecture</h1>
|
| 114 |
+
<p class="text-slate-400 text-lg">Declassified overview of the TruthCheck verification pipeline.</p>
|
| 115 |
+
</div>
|
| 116 |
+
|
| 117 |
+
<!-- Steps -->
|
| 118 |
+
<div class="space-y-12">
|
| 119 |
+
<!-- Step 1 -->
|
| 120 |
+
<div
|
| 121 |
+
class="flex flex-col md:flex-row gap-8 items-center bg-slate-900/50 border border-white/5 p-8 rounded-2xl hover:border-brand-500/30 transition-colors">
|
| 122 |
+
<div
|
| 123 |
+
class="flex-shrink-0 w-16 h-16 rounded-full bg-brand-500/10 border border-brand-500/30 flex items-center justify-center text-2xl font-display font-bold text-brand-400">
|
| 124 |
+
01
|
| 125 |
+
</div>
|
| 126 |
+
<div>
|
| 127 |
+
<h3 class="font-display text-2xl font-bold text-white mb-2">Claim Extraction</h3>
|
| 128 |
+
<p class="text-slate-400 leading-relaxed">
|
| 129 |
+
The system first analyzes your input text using <strong>spaCy</strong> to identify factual
|
| 130 |
+
claims. It filters out questions, opinions, and personal statements, isolating only
|
| 131 |
+
verifiable assertions about the real world.
|
| 132 |
+
</p>
|
| 133 |
+
</div>
|
| 134 |
+
</div>
|
| 135 |
+
|
| 136 |
+
<!-- Step 2 -->
|
| 137 |
+
<div
|
| 138 |
+
class="flex flex-col md:flex-row gap-8 items-center bg-slate-900/50 border border-white/5 p-8 rounded-2xl hover:border-neon-cyan/30 transition-colors">
|
| 139 |
+
<div
|
| 140 |
+
class="flex-shrink-0 w-16 h-16 rounded-full bg-neon-cyan/10 border border-neon-cyan/30 flex items-center justify-center text-2xl font-display font-bold text-neon-cyan">
|
| 141 |
+
02
|
| 142 |
+
</div>
|
| 143 |
+
<div>
|
| 144 |
+
<h3 class="font-display text-2xl font-bold text-white mb-2">Evidence Retrieval</h3>
|
| 145 |
+
<p class="text-slate-400 leading-relaxed">
|
| 146 |
+
Using extracted keywords, TruthCheck scrapes trusted sources (Wikipedia, Government domains,
|
| 147 |
+
Scientific Journals) via standard search protocols. It specifically prioritizes
|
| 148 |
+
high-credibility domains like <code>.gov</code>, <code>.edu</code>, and
|
| 149 |
+
<code>reuters.com</code>.
|
| 150 |
+
</p>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
<!-- Step 3 -->
|
| 155 |
+
<div
|
| 156 |
+
class="flex flex-col md:flex-row gap-8 items-center bg-slate-900/50 border border-white/5 p-8 rounded-2xl hover:border-neon-purple/30 transition-colors">
|
| 157 |
+
<div
|
| 158 |
+
class="flex-shrink-0 w-16 h-16 rounded-full bg-neon-purple/10 border border-neon-purple/30 flex items-center justify-center text-2xl font-display font-bold text-neon-purple">
|
| 159 |
+
03
|
| 160 |
+
</div>
|
| 161 |
+
<div>
|
| 162 |
+
<h3 class="font-display text-2xl font-bold text-white mb-2">NLI Classification</h3>
|
| 163 |
+
<p class="text-slate-400 leading-relaxed">
|
| 164 |
+
The core "brain" uses Large Language Models (RoBERTa & DeBERTa) fine-tuned for
|
| 165 |
+
<strong>Natural Language Inference (NLI)</strong>. It compares the claim against each piece
|
| 166 |
+
of evidence to determine if the evidence <em>Entails</em> (supports), <em>Contradicts</em>
|
| 167 |
+
(refutes), or is <em>Neutral</em> towards the claim.
|
| 168 |
+
</p>
|
| 169 |
+
</div>
|
| 170 |
+
</div>
|
| 171 |
+
|
| 172 |
+
<!-- Step 4 -->
|
| 173 |
+
<div
|
| 174 |
+
class="flex flex-col md:flex-row gap-8 items-center bg-slate-900/50 border border-white/5 p-8 rounded-2xl hover:border-green-500/30 transition-colors">
|
| 175 |
+
<div
|
| 176 |
+
class="flex-shrink-0 w-16 h-16 rounded-full bg-green-500/10 border border-green-500/30 flex items-center justify-center text-2xl font-display font-bold text-green-500">
|
| 177 |
+
04
|
| 178 |
+
</div>
|
| 179 |
+
<div>
|
| 180 |
+
<h3 class="font-display text-2xl font-bold text-white mb-2">Consensus Voting</h3>
|
| 181 |
+
<p class="text-slate-400 leading-relaxed">
|
| 182 |
+
Finally, all model judgments are aggregated using a weighted voting mechanism. Sources with
|
| 183 |
+
higher domain authority carry more weight. The system calculates a final confidence score
|
| 184 |
+
and issues a verdict: <strong>TRUE</strong>, <strong>FALSE</strong>, or <strong>LOW
|
| 185 |
+
CONFIDENCE</strong>.
|
| 186 |
+
</p>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<!-- CTA -->
|
| 192 |
+
<div class="text-center pt-8">
|
| 193 |
+
<a href="/"
|
| 194 |
+
class="inline-flex items-center gap-2 px-8 py-3 bg-brand-600 hover:bg-brand-500 text-white rounded-full font-bold font-display transition-all shadow-[0_0_20px_rgba(14,165,233,0.3)] hover:shadow-[0_0_30px_rgba(14,165,233,0.5)]">
|
| 195 |
+
<i class="fa-solid fa-play"></i> Try the Analyzer
|
| 196 |
+
</a>
|
| 197 |
+
</div>
|
| 198 |
+
|
| 199 |
+
</div>
|
| 200 |
+
</main>
|
| 201 |
+
|
| 202 |
+
<!-- Footer -->
|
| 203 |
+
<footer class="relative z-10 border-t border-white/5 mt-20 bg-slate-950">
|
| 204 |
+
<div
|
| 205 |
+
class="max-w-7xl mx-auto px-4 py-8 flex flex-col md:flex-row items-center justify-between text-slate-500 text-sm">
|
| 206 |
+
<div>© 2025 TruthCheck AI. All Systems Nominal.</div>
|
| 207 |
+
<div class="flex gap-4 mt-4 md:mt-0">
|
| 208 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Privacy</a>
|
| 209 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Terms</a>
|
| 210 |
+
<a href="https://github.com/CHRISDANIEL145" class="hover:text-brand-400 transition-colors">Github</a>
|
| 211 |
+
</div>
|
| 212 |
+
</div>
|
| 213 |
+
</footer>
|
| 214 |
+
</body>
|
| 215 |
+
|
| 216 |
+
</html>
|
templates/index.html
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="scroll-smooth">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>TruthCheck | AI-Powered Fact Verification</title>
|
| 8 |
+
|
| 9 |
+
<!-- Fonts -->
|
| 10 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
+
<link
|
| 13 |
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;900&display=swap"
|
| 14 |
+
rel="stylesheet">
|
| 15 |
+
|
| 16 |
+
<!-- Tailwind CSS -->
|
| 17 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 18 |
+
<script>
|
| 19 |
+
tailwind.config = {
|
| 20 |
+
darkMode: 'class',
|
| 21 |
+
theme: {
|
| 22 |
+
extend: {
|
| 23 |
+
fontFamily: {
|
| 24 |
+
sans: ['Inter', 'sans-serif'],
|
| 25 |
+
display: ['Orbitron', 'sans-serif'],
|
| 26 |
+
},
|
| 27 |
+
colors: {
|
| 28 |
+
brand: {
|
| 29 |
+
50: '#f0f9ff',
|
| 30 |
+
100: '#e0f2fe',
|
| 31 |
+
200: '#bae6fd',
|
| 32 |
+
300: '#7dd3fc',
|
| 33 |
+
400: '#38bdf8',
|
| 34 |
+
500: '#0ea5e9', // Sky Blue
|
| 35 |
+
600: '#0284c7',
|
| 36 |
+
700: '#0369a1',
|
| 37 |
+
800: '#075985',
|
| 38 |
+
900: '#0c4a6e',
|
| 39 |
+
950: '#082f49',
|
| 40 |
+
},
|
| 41 |
+
neon: {
|
| 42 |
+
cyan: '#00f3ff',
|
| 43 |
+
purple: '#bc13fe',
|
| 44 |
+
green: '#0aff0a',
|
| 45 |
+
red: '#ff0a0a'
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
animation: {
|
| 49 |
+
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
| 50 |
+
'glow': 'glow 2s ease-in-out infinite alternate',
|
| 51 |
+
'scan': 'scan 2s linear infinite',
|
| 52 |
+
},
|
| 53 |
+
keyframes: {
|
| 54 |
+
glow: {
|
| 55 |
+
'0%': { boxShadow: '0 0 5px #00f3ff, 0 0 10px #00f3ff' },
|
| 56 |
+
'100%': { boxShadow: '0 0 20px #00f3ff, 0 0 40px #00f3ff' }
|
| 57 |
+
},
|
| 58 |
+
scan: {
|
| 59 |
+
'0%': { transform: 'translateY(-100%)' },
|
| 60 |
+
'100%': { transform: 'translateY(100%)' }
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
</script>
|
| 67 |
+
|
| 68 |
+
<!-- Icons -->
|
| 69 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 70 |
+
|
| 71 |
+
<!-- Custom CSS -->
|
| 72 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 73 |
+
</head>
|
| 74 |
+
|
| 75 |
+
<body
|
| 76 |
+
class="bg-slate-950 text-white min-h-screen relative overflow-x-hidden selection:bg-brand-500 selection:text-white">
|
| 77 |
+
|
| 78 |
+
<!-- Background Grid Effect -->
|
| 79 |
+
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
|
| 80 |
+
<div
|
| 81 |
+
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%)]">
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<!-- Glowing Orbs -->
|
| 86 |
+
<div
|
| 87 |
+
class="fixed top-0 left-1/4 w-96 h-96 bg-brand-500/20 rounded-full blur-[128px] pointer-events-none animate-pulse-slow">
|
| 88 |
+
</div>
|
| 89 |
+
<div class="fixed bottom-0 right-1/4 w-96 h-96 bg-neon-purple/20 rounded-full blur-[128px] pointer-events-none animate-pulse-slow"
|
| 90 |
+
style="animation-delay: 1.5s;"></div>
|
| 91 |
+
|
| 92 |
+
<!-- Navigation -->
|
| 93 |
+
<nav class="relative z-50 border-b border-white/10 backdrop-blur-md bg-slate-950/50">
|
| 94 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 95 |
+
<div class="flex items-center justify-between h-20">
|
| 96 |
+
<div class="flex items-center gap-3">
|
| 97 |
+
<div
|
| 98 |
+
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)]">
|
| 99 |
+
<i class="fa-solid fa-shield-halved text-brand-400 text-xl"></i>
|
| 100 |
+
</div>
|
| 101 |
+
<span
|
| 102 |
+
class="font-display font-bold text-2xl tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-400">
|
| 103 |
+
TRUTH<span class="text-brand-400">CHECK</span>
|
| 104 |
+
</span>
|
| 105 |
+
</div>
|
| 106 |
+
<div class="hidden md:flex gap-8">
|
| 107 |
+
<a href="/" class="text-sm font-medium text-brand-400 border-b-2 border-brand-400">Analyzer</a>
|
| 108 |
+
<a href="/dashboard"
|
| 109 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Dashboard</a>
|
| 110 |
+
<a href="/how-it-works"
|
| 111 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">How it
|
| 112 |
+
Works</a>
|
| 113 |
+
<a href="/api-docs"
|
| 114 |
+
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">API</a>
|
| 115 |
+
</div>
|
| 116 |
+
<button
|
| 117 |
+
class="bg-brand-600 hover:bg-brand-500 text-white px-6 py-2 rounded-full font-medium text-sm transition-all shadow-[0_0_20px_rgba(14,165,233,0.3)] hover:shadow-[0_0_30px_rgba(14,165,233,0.5)] border border-brand-400/50">
|
| 118 |
+
Connect
|
| 119 |
+
</button>
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
</nav>
|
| 123 |
+
|
| 124 |
+
<!-- Main Content -->
|
| 125 |
+
<main class="relative z-10 container mx-auto px-4 py-16 flex flex-col items-center">
|
| 126 |
+
|
| 127 |
+
<!-- Hero Section -->
|
| 128 |
+
<div class="text-center max-w-4xl mx-auto mb-16 space-y-6 animate-fade-in-up">
|
| 129 |
+
<div
|
| 130 |
+
class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-brand-500/10 border border-brand-500/20 text-brand-300 text-sm font-medium mb-4">
|
| 131 |
+
<span class="relative flex h-2 w-2">
|
| 132 |
+
<span
|
| 133 |
+
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-brand-400 opacity-75"></span>
|
| 134 |
+
<span class="relative inline-flex rounded-full h-2 w-2 bg-brand-500"></span>
|
| 135 |
+
</span>
|
| 136 |
+
AI-Powered Verification Engine V2.0
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<h1 class="font-display text-5xl md:text-7xl font-bold leading-tight">
|
| 140 |
+
Verify Reality in <br>
|
| 141 |
+
<span
|
| 142 |
+
class="text-transparent bg-clip-text bg-gradient-to-r from-brand-400 via-neon-cyan to-brand-500 drop-shadow-[0_0_15px_rgba(14,165,233,0.5)]">
|
| 143 |
+
Real-Time
|
| 144 |
+
</span>
|
| 145 |
+
</h1>
|
| 146 |
+
|
| 147 |
+
<p class="text-slate-400 text-lg md:text-xl max-w-2xl mx-auto font-light">
|
| 148 |
+
Analyze any text statement using our advanced neural network ensemble.
|
| 149 |
+
Detect misinformation with military-grade precision.
|
| 150 |
+
</p>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
<!-- Verification Interface -->
|
| 154 |
+
<div class="w-full max-w-3xl relative group perspective-1000">
|
| 155 |
+
<!-- Glass Card -->
|
| 156 |
+
<div
|
| 157 |
+
class="relative bg-slate-900/60 backdrop-blur-xl border border-white/10 rounded-2xl p-1 shadow-2xl transition-all duration-300 hover:border-brand-500/30 hover:shadow-[0_0_50px_rgba(14,165,233,0.1)]">
|
| 158 |
+
|
| 159 |
+
<div class="p-6 md:p-8 space-y-6">
|
| 160 |
+
<!-- Input Area -->
|
| 161 |
+
<div class="relative">
|
| 162 |
+
<textarea id="claimInput" rows="4"
|
| 163 |
+
class="w-full bg-slate-950/80 border border-slate-700/50 rounded-xl p-5 text-lg text-slate-200 placeholder-slate-600 focus:outline-none focus:border-brand-500/50 focus:ring-1 focus:ring-brand-500/50 transition-all resize-none font-light tracking-wide"
|
| 164 |
+
placeholder="Enter a statement to verify... (e.g., 'The Eiffel Tower is located in London')"></textarea>
|
| 165 |
+
|
| 166 |
+
<div class="absolute bottom-4 right-4 text-xs text-slate-500 font-mono">
|
| 167 |
+
<span id="charCount">0</span>/500
|
| 168 |
+
</div>
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
<!-- Action Bar -->
|
| 172 |
+
<div class="flex items-center justify-between">
|
| 173 |
+
<div class="flex gap-4 text-sm text-slate-400">
|
| 174 |
+
<label
|
| 175 |
+
class="flex items-center gap-2 cursor-pointer hover:text-brand-300 transition-colors">
|
| 176 |
+
<input type="checkbox" checked
|
| 177 |
+
class="accent-brand-500 bg-slate-800 border-slate-600 rounded">
|
| 178 |
+
<span>Deep Search</span>
|
| 179 |
+
</label>
|
| 180 |
+
<label
|
| 181 |
+
class="flex items-center gap-2 cursor-pointer hover:text-brand-300 transition-colors">
|
| 182 |
+
<input type="checkbox" checked
|
| 183 |
+
class="accent-brand-500 bg-slate-800 border-slate-600 rounded">
|
| 184 |
+
<span>Multi-Model Consensus</span>
|
| 185 |
+
</label>
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
+
<button id="verifyBtn"
|
| 189 |
+
class="group relative px-8 py-3 bg-white text-slate-950 rounded-xl font-bold font-display hover:bg-brand-50 text-base transition-all overflow-hidden">
|
| 190 |
+
<span class="relative z-10 flex items-center gap-2">
|
| 191 |
+
INIT_SCAN <i
|
| 192 |
+
class="fa-solid fa-arrow-right group-hover:translate-x-1 transition-transform"></i>
|
| 193 |
+
</span>
|
| 194 |
+
<div
|
| 195 |
+
class="absolute inset-0 bg-gradient-to-r from-brand-400 to-neon-cyan opacity-0 group-hover:opacity-20 transition-opacity">
|
| 196 |
+
</div>
|
| 197 |
+
</button>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<!-- Loading Overlay -->
|
| 202 |
+
<div id="loadingOverlay"
|
| 203 |
+
class="absolute inset-0 bg-slate-950/90 backdrop-blur-sm rounded-2xl flex flex-col items-center justify-center z-20 hidden">
|
| 204 |
+
<div class="relative w-24 h-24 mb-6">
|
| 205 |
+
<div class="absolute inset-0 border-4 border-slate-800 rounded-full"></div>
|
| 206 |
+
<div class="absolute inset-0 border-t-4 border-brand-500 rounded-full animate-spin"></div>
|
| 207 |
+
<div class="absolute inset-4 bg-brand-500/20 rounded-full blur-md animate-pulse"></div>
|
| 208 |
+
<i
|
| 209 |
+
class="fa-solid fa-fingerprint absolute inset-0 flex items-center justify-center text-brand-400 text-3xl opacity-80"></i>
|
| 210 |
+
</div>
|
| 211 |
+
<div class="font-display text-xl font-bold text-white tracking-widest animate-pulse">ANALYZING</div>
|
| 212 |
+
<div class="text-brand-400 font-mono text-sm mt-2" id="loadingText">Connecting to neural core...
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
|
| 217 |
+
<!-- Result Section -->
|
| 218 |
+
<div id="resultSection" class="mt-8 hidden space-y-6">
|
| 219 |
+
|
| 220 |
+
<!-- Main Label Card -->
|
| 221 |
+
<div
|
| 222 |
+
class="bg-slate-900/80 border border-white/10 rounded-2xl p-8 backdrop-blur-md relative overflow-hidden">
|
| 223 |
+
<div class="absolute top-0 left-0 w-1 h-full bg-gray-600" id="resultBar"></div>
|
| 224 |
+
|
| 225 |
+
<div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
|
| 226 |
+
<div>
|
| 227 |
+
<div class="text-slate-400 text-sm font-mono mb-1 uppercase tracking-wider">Verdict Analysis
|
| 228 |
+
</div>
|
| 229 |
+
<h2 id="resultLabel" class="font-display text-4xl font-bold text-white">--</h2>
|
| 230 |
+
</div>
|
| 231 |
+
|
| 232 |
+
<div class="flex items-center gap-6">
|
| 233 |
+
<div class="text-right">
|
| 234 |
+
<div class="text-3xl font-bold font-display text-white" id="confidenceScore">0%</div>
|
| 235 |
+
<div class="text-slate-400 text-xs uppercase">Confidence</div>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="w-16 h-16 relative flex items-center justify-center">
|
| 238 |
+
<svg class="w-full h-full transform -rotate-90">
|
| 239 |
+
<circle cx="32" cy="32" r="28" stroke="currentColor" stroke-width="4"
|
| 240 |
+
fill="transparent" class="text-slate-800" />
|
| 241 |
+
<circle id="confidenceCircle" cx="32" cy="32" r="28" stroke="currentColor"
|
| 242 |
+
stroke-width="4" fill="transparent" class="text-brand-500"
|
| 243 |
+
stroke-dasharray="175.9" stroke-dashoffset="175.9" />
|
| 244 |
+
</svg>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
</div>
|
| 248 |
+
</div>
|
| 249 |
+
|
| 250 |
+
<!-- Evidence Section -->
|
| 251 |
+
<div class="bg-slate-900/50 border border-white/5 rounded-2xl p-6 backdrop-blur-md">
|
| 252 |
+
<h3 class="font-display text-lg font-bold text-white mb-4 flex items-center gap-2">
|
| 253 |
+
<i class="fa-solid fa-list-check text-brand-400"></i> Evidence Stream
|
| 254 |
+
</h3>
|
| 255 |
+
<div id="evidenceList"
|
| 256 |
+
class="space-y-4 max-h-[500px] overflow-y-auto pr-2 custom-scrollbar text-slate-300 font-light leading-relaxed">
|
| 257 |
+
<!-- Evidence items injected here -->
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
|
| 264 |
+
</main>
|
| 265 |
+
|
| 266 |
+
<!-- Footer -->
|
| 267 |
+
<footer class="relative z-10 border-t border-white/5 mt-20 bg-slate-950">
|
| 268 |
+
<div
|
| 269 |
+
class="max-w-7xl mx-auto px-4 py-8 flex flex-col md:flex-row items-center justify-between text-slate-500 text-sm">
|
| 270 |
+
<div>© 2025 TruthCheck AI. All Systems Nominal.</div>
|
| 271 |
+
<div class="flex gap-4 mt-4 md:mt-0">
|
| 272 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Privacy</a>
|
| 273 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Terms</a>
|
| 274 |
+
<a href="#" class="hover:text-brand-400 transition-colors">Github</a>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
</footer>
|
| 278 |
+
|
| 279 |
+
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
| 280 |
+
</body>
|
| 281 |
+
|
| 282 |
+
</html>
|
utils/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
TruthCheck Utilities Package
|
| 3 |
+
"""
|
utils/config.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/config.py
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
class Config:
|
| 5 |
+
"""Enhanced configuration for TruthCheck"""
|
| 6 |
+
|
| 7 |
+
# Model settings
|
| 8 |
+
SIMILARITY_THRESHOLD = 0.45 # Lowered slightly for better recall
|
| 9 |
+
CONFIDENCE_THRESHOLD = 0.6 # Consensus threshold
|
| 10 |
+
CONSENSUS_THRESHOLD = 0.6 # For multi-evidence voting
|
| 11 |
+
|
| 12 |
+
# Evidence settings
|
| 13 |
+
MAX_EVIDENCE_SOURCES = 10
|
| 14 |
+
TOP_EVIDENCE_FOR_NLI = 4 # Use top 4 for consensus
|
| 15 |
+
|
| 16 |
+
# Flask settings
|
| 17 |
+
SECRET_KEY = os.environ.get('SECRET_KEY', 'truthcheck-production-key-2025')
|
| 18 |
+
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
|
| 19 |
+
|
| 20 |
+
# Model paths
|
| 21 |
+
SPACY_MODEL = "en_core_web_sm"
|
| 22 |
+
SBERT_MODEL = "all-MiniLM-L6-v2"
|
utils/similarity.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils/similarity.py
|
| 2 |
+
from sentence_transformers import SentenceTransformer
|
| 3 |
+
import numpy as np
|
| 4 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 5 |
+
|
| 6 |
+
class SimilarityCalculator:
|
| 7 |
+
def __init__(self): # Corrected __init__
|
| 8 |
+
"""Initialize sentence transformer model"""
|
| 9 |
+
try:
|
| 10 |
+
self.model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 11 |
+
except Exception as e:
|
| 12 |
+
print(f"Error loading similarity model: {e}")
|
| 13 |
+
self.model = None
|
| 14 |
+
|
| 15 |
+
def calculate_similarity(self, text1, text2):
|
| 16 |
+
"""Calculate semantic similarity between two texts"""
|
| 17 |
+
if not self.model:
|
| 18 |
+
print("Similarity model not loaded. Returning fallback similarity.")
|
| 19 |
+
return 0.5 # Fallback similarity
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
# Encode texts to embeddings
|
| 23 |
+
embeddings = self.model.encode([text1, text2])
|
| 24 |
+
|
| 25 |
+
# Calculate cosine similarity
|
| 26 |
+
similarity = cosine_similarity(
|
| 27 |
+
embeddings[0].reshape(1, -1),
|
| 28 |
+
embeddings[1].reshape(1, -1)
|
| 29 |
+
)[0][0]
|
| 30 |
+
|
| 31 |
+
return float(similarity)
|
| 32 |
+
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"Similarity calculation error: {e}")
|
| 35 |
+
return 0.5
|
| 36 |
+
|
| 37 |
+
# Global similarity calculator instance
|
| 38 |
+
_similarity_calculator = SimilarityCalculator()
|
| 39 |
+
|
| 40 |
+
def calculate_similarity(text1, text2):
|
| 41 |
+
"""Global function to calculate similarity"""
|
| 42 |
+
return _similarity_calculator.calculate_similarity(text1, text2)
|
| 43 |
+
|