CHRISDANIEL145 commited on
Commit
622a0b7
·
0 Parent(s):

Initial commit of TruthCheck with Cyber-Noir UI

Browse files
.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>&copy; 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>&copy; 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>&copy; 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
+