Pragthedon commited on
Commit
e6d7e29
·
1 Parent(s): a9233f4

First Repo

Browse files
__pycache__/api_wrapper.cpython-313.pyc ADDED
Binary file (5.89 kB). View file
 
__pycache__/model.cpython-313.pyc ADDED
Binary file (13.9 kB). View file
 
api_wrapper.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==========================================
2
+ # API WRAPPER FOR FLASK
3
+ # ==========================================
4
+ import sqlite3
5
+
6
+ def run_fact_check_api(claim):
7
+ """
8
+ API-friendly version that returns structured data instead of printing.
9
+ Returns dict with evidence, NLI results, and metadata.
10
+
11
+ Note: This is a simplified version for demo. For full functionality,
12
+ install all dependencies from requirements.txt
13
+ """
14
+ try:
15
+ # Try to import the model
16
+ from model import (
17
+ init_db, clear_db, embed_model, fetch_rss, fetch_gdelt, fetch_newsapi,
18
+ fetch_wikipedia, fetch_duckduckgo, build_faiss,
19
+ load_all_evidence, nli_model, FAISS_FILE
20
+ )
21
+ import faiss
22
+
23
+ # Full implementation
24
+ init_db()
25
+ clear_db()
26
+
27
+ claim_emb = embed_model.encode([claim], normalize_embeddings=True)
28
+
29
+ # Fetch evidence from all sources
30
+ fetch_rss(claim_emb)
31
+ gdelt_count = fetch_gdelt(claim, claim_emb)
32
+ newsapi_count = fetch_newsapi(claim, claim_emb)
33
+ fetch_wikipedia(claim)
34
+
35
+ # Count evidence
36
+ with sqlite3.connect("evidence.db") as conn:
37
+ cur = conn.cursor()
38
+ cur.execute("SELECT COUNT(*) FROM evidence")
39
+ total_count = cur.fetchone()[0]
40
+
41
+ activate_fallback = False
42
+
43
+ if (gdelt_count + newsapi_count) == 0 or total_count < 3:
44
+ activate_fallback = True
45
+
46
+ faiss_ready = build_faiss()
47
+
48
+ if faiss_ready:
49
+ index = faiss.read_index(FAISS_FILE)
50
+ D, _ = index.search(claim_emb, 1)
51
+ if len(D) > 0 and len(D[0]) > 0 and D[0][0] < 0.30:
52
+ activate_fallback = True
53
+
54
+ if activate_fallback:
55
+ fetch_duckduckgo(claim, claim_emb)
56
+ faiss_ready = build_faiss()
57
+
58
+ if not faiss_ready:
59
+ return {
60
+ "success": False,
61
+ "error": "No relevant evidence found.",
62
+ "evidence": [],
63
+ "nli_results": []
64
+ }
65
+
66
+ index = faiss.read_index(FAISS_FILE)
67
+ D, I = index.search(claim_emb, 5)
68
+
69
+ rows = load_all_evidence()
70
+
71
+ # Build evidence list
72
+ evidence_list = []
73
+ for i, idx in enumerate(I[0]):
74
+ with sqlite3.connect("evidence.db") as conn:
75
+ cur = conn.cursor()
76
+ cur.execute("SELECT text, source FROM evidence WHERE id = ?", (rows[idx][0],))
77
+ result = cur.fetchone()
78
+ if result:
79
+ evidence_list.append({
80
+ "text": result[0],
81
+ "source": result[1],
82
+ "similarity": float(D[0][i])
83
+ })
84
+
85
+ # Build NLI results
86
+ nli_results = []
87
+ for idx in I[0]:
88
+ evidence_text = rows[idx][1]
89
+ nli_input = f"{evidence_text} [SEP] {claim}"
90
+ result = nli_model(nli_input)
91
+
92
+ # Extract label and score
93
+ if result and len(result) > 0:
94
+ nli_results.append({
95
+ "evidence": evidence_text[:200],
96
+ "label": result[0].get("label", "unknown"),
97
+ "score": float(result[0].get("score", 0.0))
98
+ })
99
+
100
+ # Calculate Final Verdict
101
+ verdict = "Uncertain"
102
+ confidence = 0.0
103
+
104
+ if nli_results:
105
+ # Simple heuristic: averaged or max voting
106
+ entail_scores = [r['score'] for r in nli_results if r['label'] == 'entailment']
107
+ contra_scores = [r['score'] for r in nli_results if r['label'] == 'contradiction']
108
+
109
+ avg_entail = sum(entail_scores) / len(entail_scores) if entail_scores else 0
110
+ avg_contra = sum(contra_scores) / len(contra_scores) if contra_scores else 0
111
+
112
+ if avg_entail > avg_contra and avg_entail > 0.4:
113
+ verdict = "True"
114
+ confidence = avg_entail
115
+ elif avg_contra > avg_entail and avg_contra > 0.4:
116
+ verdict = "False"
117
+ confidence = avg_contra
118
+ else:
119
+ verdict = "Mixture/Uncertain"
120
+ confidence = max(avg_entail, avg_contra)
121
+
122
+ return {
123
+ "success": True,
124
+ "claim": claim,
125
+ "verdict": verdict,
126
+ "confidence": round(confidence, 2),
127
+ "evidence": evidence_list,
128
+ "nli_results": nli_results,
129
+ "total_evidence": len(evidence_list)
130
+ }
131
+
132
+ except ImportError as e:
133
+ print(f"DEBUG: ImportError in api_wrapper: {e}")
134
+ # Return demo data if dependencies are missing
135
+ return {
136
+ "success": True,
137
+ "claim": claim,
138
+ "evidence": [
139
+ {
140
+ "text": "This is demo evidence from RSS feed. Install dependencies from requirements.txt for real fact-checking.",
141
+ "source": "RSS",
142
+ "similarity": 0.85
143
+ },
144
+ {
145
+ "text": "This is demo evidence from GDELT. The full system searches multiple news sources and databases.",
146
+ "source": "GDELT",
147
+ "similarity": 0.78
148
+ },
149
+ {
150
+ "text": "This is demo evidence from Wikipedia. Install all dependencies to enable real-time fact verification.",
151
+ "source": "Wikipedia",
152
+ "similarity": 0.72
153
+ }
154
+ ],
155
+ "nli_results": [
156
+ {
157
+ "evidence": "Demo evidence showing entailment (supports the claim)",
158
+ "label": "entailment",
159
+ "score": 0.89
160
+ },
161
+ {
162
+ "evidence": "Demo evidence showing neutral stance",
163
+ "label": "neutral",
164
+ "score": 0.65
165
+ },
166
+ {
167
+ "evidence": "Demo evidence showing contradiction",
168
+ "label": "contradiction",
169
+ "score": 0.45
170
+ }
171
+ ],
172
+ "total_evidence": 3
173
+ }
174
+
175
+ except Exception as e:
176
+ print(f"DEBUG: General Exception in api_wrapper: {e}")
177
+ import traceback
178
+ traceback.print_exc()
179
+ return {
180
+ "success": False,
181
+ "error": str(e),
182
+ "evidence": [],
183
+ "nli_results": []
184
+ }
app.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, session
2
+ from api_wrapper import run_fact_check_api
3
+ import os
4
+
5
+ app = Flask(__name__)
6
+ app.secret_key = os.urandom(24) # For session management
7
+
8
+ # Configure logging
9
+ import sys
10
+ import logging
11
+ logging.basicConfig(filename='app.log', level=logging.DEBUG)
12
+ # Redirect stdout/stderr to logger
13
+ class StreamToLogger(object):
14
+ def __init__(self, logger, log_level=logging.INFO):
15
+ self.logger = logger
16
+ self.log_level = log_level
17
+ self.linebuf = ''
18
+ def write(self, buf):
19
+ for line in buf.rstrip().splitlines():
20
+ self.logger.log(self.log_level, line.rstrip())
21
+ def flush(self):
22
+ pass
23
+ sys.stdout = StreamToLogger(logging.getLogger('STDOUT'), logging.INFO)
24
+ sys.stderr = StreamToLogger(logging.getLogger('STDERR'), logging.ERROR)
25
+
26
+ @app.route('/')
27
+ def index():
28
+ """Main landing page with claim submission form"""
29
+ return render_template('index.html')
30
+
31
+ @app.route('/check', methods=['POST'])
32
+ def check_claim():
33
+ """Process fact-check request"""
34
+ claim = request.form.get('claim', '').strip()
35
+
36
+ if not claim:
37
+ return jsonify({
38
+ "success": False,
39
+ "error": "Claim cannot be empty"
40
+ }), 400
41
+
42
+ # Run fact check
43
+ result = run_fact_check_api(claim)
44
+
45
+ # Store result in session for results page
46
+ session['last_result'] = result
47
+
48
+ return jsonify(result)
49
+
50
+ @app.route('/results')
51
+ def results():
52
+ """Display fact-check results"""
53
+ result = session.get('last_result')
54
+
55
+ if not result:
56
+ return redirect(url_for('index'))
57
+
58
+ return render_template('results.html', result=result)
59
+
60
+ @app.errorhandler(404)
61
+ def not_found(error):
62
+ return render_template('index.html'), 404
63
+
64
+ @app.errorhandler(500)
65
+ def internal_error(error):
66
+ return jsonify({
67
+ "success": False,
68
+ "error": "Internal server error"
69
+ }), 500
70
+
71
+ if __name__ == '__main__':
72
+ app.run(debug=True, host='0.0.0.0', port=5000)
faiss.index ADDED
Binary file (9.26 kB). View file
 
model.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==========================================
2
+ # IMPORTS
3
+ # ==========================================
4
+ import os
5
+ import requests
6
+ import sqlite3
7
+ import faiss
8
+ import numpy as np
9
+ import urllib.parse
10
+ from bs4 import BeautifulSoup
11
+ import feedparser
12
+ from sentence_transformers import SentenceTransformer
13
+ from transformers import pipeline
14
+
15
+ # ==========================================
16
+ # CONFIG
17
+ # ==========================================
18
+ DB_FILE = "evidence.db"
19
+ FAISS_FILE = "faiss.index"
20
+
21
+ embed_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
22
+ nli_model = pipeline(
23
+ "text-classification",
24
+ model="facebook/bart-large-mnli"
25
+ )
26
+
27
+ # ==========================================
28
+ # DATABASE
29
+ # ==========================================
30
+ def init_db():
31
+ with sqlite3.connect(DB_FILE) as conn:
32
+ cur = conn.cursor()
33
+ cur.execute("""
34
+ CREATE TABLE IF NOT EXISTS evidence (
35
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
36
+ text TEXT,
37
+ source TEXT
38
+ )
39
+ """)
40
+ conn.commit()
41
+
42
+ def clear_db():
43
+ with sqlite3.connect(DB_FILE) as conn:
44
+ conn.execute("DELETE FROM evidence")
45
+ conn.commit()
46
+
47
+ def save_evidence(text, source):
48
+ with sqlite3.connect(DB_FILE) as conn:
49
+ conn.execute(
50
+ "INSERT INTO evidence (text, source) VALUES (?, ?)",
51
+ (text, source)
52
+ )
53
+ conn.commit()
54
+
55
+ def load_all_evidence():
56
+ with sqlite3.connect(DB_FILE) as conn:
57
+ rows = conn.execute("SELECT id, text FROM evidence").fetchall()
58
+ return rows
59
+
60
+ # ==========================================
61
+ # RELEVANCE CHECK
62
+ # ==========================================
63
+ def is_relevant(claim_emb, text, threshold=0.15):
64
+ emb = embed_model.encode([text], normalize_embeddings=True)
65
+ sim = float(np.dot(claim_emb, emb[0]))
66
+ print(f"[DEBUG] Checking relevance for: '{text[:50]}...' Score: {sim:.4f}")
67
+ return sim >= threshold
68
+
69
+ # ==========================================
70
+ # RSS FETCH
71
+ # ==========================================
72
+ def fetch_rss(claim_emb):
73
+ print("[RSS] Fetching...")
74
+ feeds = [
75
+ "http://feeds.bbci.co.uk/news/rss.xml",
76
+ "http://rss.cnn.com/rss/edition.rss",
77
+ "https://timesofindia.indiatimes.com/rss.cms",
78
+ "https://www.hindustantimes.com/feeds/rss/topstories.rss",
79
+ "https://cfo.economictimes.indiatimes.com/rss",
80
+ "https://www.business-standard.com/rss/",
81
+ ]
82
+ count = 0
83
+ for url in feeds:
84
+ try:
85
+ feed = feedparser.parse(url)
86
+ print(f"[RSS] Parsed {url}, found {len(feed.entries)} entries")
87
+ for entry in feed.entries[:5]:
88
+ title = entry.title
89
+ if len(title) > 50 and is_relevant(claim_emb, title):
90
+ save_evidence(title, "RSS")
91
+ count += 1
92
+ except Exception as e:
93
+ print(f"[RSS] Error parsing {url}: {e}")
94
+ print(f"[RSS] Saved {count} items.")
95
+
96
+ # ==========================================
97
+ # GDELT FETCH
98
+ # ==========================================
99
+ def fetch_gdelt(claim, claim_emb):
100
+ print("[GDELT] Fetching...")
101
+ url = "https://api.gdeltproject.org/api/v2/doc/doc"
102
+ params = {
103
+ "query": claim,
104
+ "mode": "ArtList",
105
+ "format": "json",
106
+ "maxrecords": 5
107
+ }
108
+
109
+ added = 0
110
+ try:
111
+ r = requests.get(url, params=params, timeout=10)
112
+ r.raise_for_status()
113
+ data = r.json()
114
+ articles = data.get("articles", [])
115
+ print(f"[GDELT] Found {len(articles)} articles")
116
+
117
+ for art in articles:
118
+ title = art.get("title", "")
119
+ if len(title) > 80 and is_relevant(claim_emb, title):
120
+ save_evidence(title, "GDELT")
121
+ added += 1
122
+ except Exception as e:
123
+ print("[WARNING] GDELT failed:", e)
124
+
125
+ print(f"[GDELT] Saved {added} items.")
126
+ return added
127
+
128
+ # ==========================================
129
+ # NEWS API FETCH
130
+ # ==========================================
131
+ def fetch_newsapi(claim, claim_emb):
132
+ print("[NewsAPI] Fetching...")
133
+ # Using provided key
134
+ api_key = "126afa219039487aa89acedca060c8fd"
135
+ url = "https://newsapi.org/v2/everything"
136
+ params = {
137
+ "q": claim,
138
+ "apiKey": api_key,
139
+ "language": "en",
140
+ "sortBy": "relevancy",
141
+ "pageSize": 5
142
+ }
143
+
144
+ added = 0
145
+ try:
146
+ r = requests.get(url, params=params, timeout=10)
147
+ data = r.json()
148
+
149
+ if r.status_code != 200:
150
+ print(f"[WARNING] NewsAPI Error: {data.get('message', 'Unknown error')}")
151
+ return 0
152
+
153
+ articles = data.get("articles", [])
154
+ print(f"[NewsAPI] Found {len(articles)} articles")
155
+
156
+ for art in articles:
157
+ title = art.get("title", "")
158
+ description = art.get("description", "") or ""
159
+ content = f"{title}. {description}"
160
+
161
+ # Using low threshold as confirmed previously
162
+ if len(content) > 50 and is_relevant(claim_emb, content, threshold=0.05):
163
+ save_evidence(content, f"NewsAPI: {art.get('source', {}).get('name', 'Unknown')}")
164
+ added += 1
165
+ except Exception as e:
166
+ print("[WARNING] NewsAPI failed:", e)
167
+
168
+ print(f"[NewsAPI] Saved {added} items.")
169
+ return added
170
+
171
+ # ==========================================
172
+ # WIKIPEDIA (REST API)
173
+ # ==========================================
174
+ def fetch_wikipedia(claim):
175
+ print("[Wikipedia] Fetching...")
176
+ try:
177
+ query = urllib.parse.quote(claim)
178
+ url = f"https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch={query}&format=json"
179
+ # Add User-Agent to avoid 403 Forbidden
180
+ headers = {
181
+ "User-Agent": "FactCheckBot/1.0 (https://github.com/yourusername/factcheckbot; your-email@example.com)"
182
+ }
183
+ r = requests.get(url, headers=headers, timeout=10)
184
+ data = r.json()
185
+
186
+ results = data.get("query", {}).get("search", [])
187
+ print(f"[Wikipedia] Found {len(results)} search results")
188
+
189
+ saved = 0
190
+ for result in results[:3]:
191
+ title = result["title"]
192
+ page_url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{urllib.parse.quote(title)}"
193
+ r2 = requests.get(page_url, headers=headers, timeout=5)
194
+ if r2.status_code == 200:
195
+ extract = r2.json().get("extract", "")
196
+ # Further relaxed length check and threshold
197
+ if len(extract) > 20:
198
+ # Using a very low threshold to ensure WE GET SOMETHING
199
+ if is_relevant(embed_model.encode([claim], normalize_embeddings=True), extract, threshold=0.05):
200
+ save_evidence(extract, f"Wikipedia: {title}")
201
+ saved += 1
202
+ print(f"[Wikipedia] Saved {saved} items.")
203
+
204
+ except Exception as e:
205
+ print("[WARNING] Wikipedia failed:", e)
206
+
207
+ # ==========================================
208
+ # DUCKDUCKGO FALLBACK
209
+ # ==========================================
210
+ def fetch_duckduckgo(claim, claim_emb):
211
+ print("[Fallback] DuckDuckGo activated...")
212
+ try:
213
+ query = urllib.parse.quote(claim)
214
+ url = f"https://duckduckgo.com/html/?q={query}"
215
+ headers = {
216
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
217
+ }
218
+ r = requests.get(url, headers=headers, timeout=10)
219
+ soup = BeautifulSoup(r.text, "html.parser")
220
+
221
+ results = soup.find_all("a", class_="result__a", limit=5)
222
+ print(f"[DuckDuckGo] Found {len(results)} results")
223
+
224
+ saved = 0
225
+ for res in results:
226
+ text = res.get_text()
227
+ # drastically lower threshold for fallback
228
+ if len(text) > 30 and is_relevant(claim_emb, text, 0.05):
229
+ save_evidence(text, "DuckDuckGo")
230
+ saved += 1
231
+ print(f"[DuckDuckGo] Saved {saved} items")
232
+ except Exception as e:
233
+ print("[WARNING] DuckDuckGo failed:", e)
234
+
235
+ # ==========================================
236
+ # BUILD FAISS
237
+ # ==========================================
238
+ def build_faiss():
239
+ rows = load_all_evidence()
240
+ if not rows:
241
+ return False
242
+
243
+ texts = [row[1] for row in rows]
244
+ embeddings = embed_model.encode(texts, normalize_embeddings=True)
245
+
246
+ index = faiss.IndexFlatIP(embeddings.shape[1])
247
+ index.add(np.array(embeddings))
248
+
249
+ faiss.write_index(index, FAISS_FILE)
250
+ return True
251
+
252
+ # ==========================================
253
+ # MAIN PIPELINE
254
+ # ==========================================
255
+ def run_fact_check(claim):
256
+ print("\n[FACT CHECK]", claim)
257
+
258
+ init_db()
259
+ clear_db()
260
+
261
+ claim_emb = embed_model.encode([claim], normalize_embeddings=True)
262
+
263
+ fetch_rss(claim_emb)
264
+ gdelt_count = fetch_gdelt(claim, claim_emb)
265
+ fetch_wikipedia(claim)
266
+
267
+ with sqlite3.connect(DB_FILE) as conn:
268
+ total_count = conn.execute("SELECT COUNT(*) FROM evidence").fetchone()[0]
269
+
270
+ activate_fallback = (gdelt_count == 0 or total_count < 3)
271
+
272
+ if build_faiss():
273
+ if os.path.exists(FAISS_FILE):
274
+ index = faiss.read_index(FAISS_FILE)
275
+ D, _ = index.search(claim_emb, 1)
276
+ if len(D) > 0 and len(D[0]) > 0:
277
+ similarity = D[0][0]
278
+ if similarity < 0.30:
279
+ activate_fallback = True
280
+
281
+ if activate_fallback:
282
+ fetch_duckduckgo(claim, claim_emb)
283
+ build_faiss()
284
+
285
+ if not os.path.exists(FAISS_FILE):
286
+ print("[ERROR] No evidence found.")
287
+ return
288
+
289
+ index = faiss.read_index(FAISS_FILE)
290
+ D, I = index.search(claim_emb, 5)
291
+ rows = load_all_evidence()
292
+
293
+ print("\n[EVIDENCE]")
294
+ for idx in I[0]:
295
+ if idx < len(rows):
296
+ print("-", rows[idx][1][:200])
297
+
298
+ print("\n[NLI RESULTS]")
299
+ for idx in I[0]:
300
+ if idx < len(rows):
301
+ evidence_text = rows[idx][1]
302
+ result = nli_model({
303
+ "premise": evidence_text,
304
+ "hypothesis": claim
305
+ })
306
+ print(result)
307
+
308
+ # ==========================================
309
+ # RUN
310
+ # ==========================================
311
+ if __name__ == "__main__":
312
+ claim = input("Enter claim: ").strip()
313
+ if claim:
314
+ run_fact_check(claim)
315
+ else:
316
+ print("Claim cannot be empty.")
reproduce_issue.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import time
4
+
5
+ url = "http://127.0.0.1:5000/check"
6
+ data = {"claim": "The Earth is flat"}
7
+
8
+ print(f"Sending request to {url}...")
9
+ start_time = time.time()
10
+ try:
11
+ response = requests.post(url, data=data)
12
+ end_time = time.time()
13
+ print(f"Response status: {response.status_code}")
14
+ print(f"Time taken: {end_time - start_time:.2f} seconds")
15
+
16
+ try:
17
+ json_resp = response.json()
18
+ print(json.dumps(json_resp, indent=2))
19
+
20
+ # Check if it's demo data
21
+ evidence = json_resp.get("evidence", [])
22
+ if evidence and "demo evidence" in evidence[0].get("text", "").lower():
23
+ print("\n[!] DETECTED DEMO DATA: The backend failed to load the real model.")
24
+ else:
25
+ print("\n[+] Real model execution (or empty result).")
26
+
27
+ except Exception as e:
28
+ print(f"Failed to parse JSON: {e}")
29
+ print(response.text)
30
+
31
+ except Exception as e:
32
+ print(f"Request failed: {e}")
reproduce_issue_v2.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import time
4
+
5
+ url = "http://127.0.0.1:5000/check"
6
+ # Test cases
7
+ claims = [
8
+ "The Earth revolves around the Sun",
9
+ "The Earth is flat"
10
+ ]
11
+
12
+ for claim in claims:
13
+ print(f"\nTesting Claim: '{claim}'")
14
+ data = {"claim": claim}
15
+
16
+ try:
17
+ response = requests.post(url, data=data)
18
+ json_resp = response.json()
19
+
20
+ print(f"Verdict: {json_resp.get('verdict')}")
21
+ print(f"Confidence: {json_resp.get('confidence')}")
22
+
23
+ evidence = json_resp.get('evidence', [])
24
+ print(f"Evidence Found: {len(evidence)}")
25
+
26
+ # Print sources
27
+ sources = set([e.get('source', 'Unknown') for e in evidence])
28
+ print(f"Sources: {sources}")
29
+
30
+ except Exception as e:
31
+ print(f"Error: {e}")
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ flask>=3.0.0
2
+ requests>=2.31.0
3
+ beautifulsoup4>=4.12.2
4
+ feedparser>=6.0.10
5
+ sentence-transformers>=2.2.2
6
+ transformers>=4.35.0
7
+ faiss-cpu>=1.9.0
8
+ numpy>=1.24.3
9
+ torch>=2.1.0
static/style.css ADDED
@@ -0,0 +1,859 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ==========================================
2
+ RESET & BASE STYLES
3
+ ========================================== */
4
+ * {
5
+ margin: 0;
6
+ padding: 0;
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ :root {
11
+ /* Color Palette */
12
+ --primary: #6366f1;
13
+ --primary-dark: #4f46e5;
14
+ --primary-light: #818cf8;
15
+ --accent: #f59e0b;
16
+ --success: #10b981;
17
+ --error: #ef4444;
18
+ --warning: #f59e0b;
19
+ --neutral: #6b7280;
20
+
21
+ /* Gradients */
22
+ --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
23
+ --gradient-accent: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
24
+ --gradient-success: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
25
+ --gradient-bg: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
26
+
27
+ /* Backgrounds */
28
+ --bg-primary: #0f172a;
29
+ --bg-secondary: #1e293b;
30
+ --bg-card: rgba(255, 255, 255, 0.05);
31
+ --bg-glass: rgba(255, 255, 255, 0.1);
32
+
33
+ /* Text */
34
+ --text-primary: #f8fafc;
35
+ --text-secondary: #cbd5e1;
36
+ --text-muted: #94a3b8;
37
+
38
+ /* Shadows */
39
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1);
40
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.2);
41
+ --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.3);
42
+ --shadow-glow: 0 0 40px rgba(102, 126, 234, 0.4);
43
+
44
+ /* Spacing */
45
+ --spacing-xs: 0.5rem;
46
+ --spacing-sm: 1rem;
47
+ --spacing-md: 1.5rem;
48
+ --spacing-lg: 2rem;
49
+ --spacing-xl: 3rem;
50
+
51
+ /* Border Radius */
52
+ --radius-sm: 8px;
53
+ --radius-md: 12px;
54
+ --radius-lg: 16px;
55
+ --radius-xl: 24px;
56
+
57
+ /* Transitions */
58
+ --transition-fast: 0.2s ease;
59
+ --transition-normal: 0.3s ease;
60
+ --transition-slow: 0.5s ease;
61
+ }
62
+
63
+ body {
64
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
65
+ background: var(--bg-primary);
66
+ color: var(--text-primary);
67
+ line-height: 1.6;
68
+ min-height: 100vh;
69
+ overflow-x: hidden;
70
+ position: relative;
71
+ }
72
+
73
+ /* ==========================================
74
+ GRADIENT BACKGROUND
75
+ ========================================== */
76
+ .gradient-bg {
77
+ position: fixed;
78
+ top: 0;
79
+ left: 0;
80
+ width: 100%;
81
+ height: 100%;
82
+ background: var(--gradient-bg);
83
+ opacity: 0.15;
84
+ z-index: -1;
85
+ animation: gradientShift 15s ease infinite;
86
+ }
87
+
88
+ @keyframes gradientShift {
89
+
90
+ 0%,
91
+ 100% {
92
+ transform: scale(1) rotate(0deg);
93
+ }
94
+
95
+ 50% {
96
+ transform: scale(1.1) rotate(5deg);
97
+ }
98
+ }
99
+
100
+ /* ==========================================
101
+ CONTAINER & LAYOUT
102
+ ========================================== */
103
+ .container {
104
+ max-width: 1200px;
105
+ margin: 0 auto;
106
+ padding: var(--spacing-lg);
107
+ position: relative;
108
+ z-index: 1;
109
+ }
110
+
111
+ /* ==========================================
112
+ HEADER & HERO
113
+ ========================================== */
114
+ .hero {
115
+ text-align: center;
116
+ padding: var(--spacing-xl) 0;
117
+ margin-bottom: var(--spacing-lg);
118
+ }
119
+
120
+ .logo {
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ gap: var(--spacing-sm);
125
+ margin-bottom: var(--spacing-md);
126
+ }
127
+
128
+ .logo-icon {
129
+ font-size: 3rem;
130
+ animation: float 3s ease-in-out infinite;
131
+ }
132
+
133
+ @keyframes float {
134
+
135
+ 0%,
136
+ 100% {
137
+ transform: translateY(0);
138
+ }
139
+
140
+ 50% {
141
+ transform: translateY(-10px);
142
+ }
143
+ }
144
+
145
+ .logo h1 {
146
+ font-size: 3.5rem;
147
+ font-weight: 800;
148
+ background: var(--gradient-primary);
149
+ -webkit-background-clip: text;
150
+ -webkit-text-fill-color: transparent;
151
+ background-clip: text;
152
+ letter-spacing: -0.02em;
153
+ }
154
+
155
+ .accent {
156
+ background: var(--gradient-accent);
157
+ -webkit-background-clip: text;
158
+ -webkit-text-fill-color: transparent;
159
+ background-clip: text;
160
+ }
161
+
162
+ .tagline {
163
+ font-size: 1.25rem;
164
+ color: var(--text-secondary);
165
+ font-weight: 300;
166
+ }
167
+
168
+ /* ==========================================
169
+ CARDS
170
+ ========================================== */
171
+ .card {
172
+ background: var(--bg-card);
173
+ border-radius: var(--radius-lg);
174
+ padding: var(--spacing-lg);
175
+ margin-bottom: var(--spacing-lg);
176
+ border: 1px solid rgba(255, 255, 255, 0.1);
177
+ transition: all var(--transition-normal);
178
+ }
179
+
180
+ .glass-card {
181
+ background: var(--bg-glass);
182
+ backdrop-filter: blur(20px);
183
+ -webkit-backdrop-filter: blur(20px);
184
+ box-shadow: var(--shadow-lg);
185
+ }
186
+
187
+ .card:hover {
188
+ transform: translateY(-2px);
189
+ box-shadow: var(--shadow-glow);
190
+ border-color: rgba(102, 126, 234, 0.3);
191
+ }
192
+
193
+ .card-title {
194
+ font-size: 2rem;
195
+ font-weight: 700;
196
+ margin-bottom: var(--spacing-sm);
197
+ background: var(--gradient-primary);
198
+ -webkit-background-clip: text;
199
+ -webkit-text-fill-color: transparent;
200
+ background-clip: text;
201
+ }
202
+
203
+ .card-subtitle {
204
+ color: var(--text-secondary);
205
+ margin-bottom: var(--spacing-lg);
206
+ font-size: 1.05rem;
207
+ }
208
+
209
+ /* ==========================================
210
+ FORM ELEMENTS
211
+ ========================================== */
212
+ .claim-form {
213
+ margin-bottom: var(--spacing-lg);
214
+ }
215
+
216
+ .input-group {
217
+ position: relative;
218
+ margin-bottom: var(--spacing-md);
219
+ }
220
+
221
+ textarea {
222
+ width: 100%;
223
+ padding: var(--spacing-md);
224
+ background: rgba(255, 255, 255, 0.05);
225
+ border: 2px solid rgba(255, 255, 255, 0.1);
226
+ border-radius: var(--radius-md);
227
+ color: var(--text-primary);
228
+ font-family: inherit;
229
+ font-size: 1.05rem;
230
+ resize: vertical;
231
+ transition: all var(--transition-normal);
232
+ }
233
+
234
+ textarea:focus {
235
+ outline: none;
236
+ border-color: var(--primary);
237
+ background: rgba(255, 255, 255, 0.08);
238
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
239
+ }
240
+
241
+ textarea::placeholder {
242
+ color: var(--text-muted);
243
+ }
244
+
245
+ .char-counter {
246
+ position: absolute;
247
+ bottom: var(--spacing-xs);
248
+ right: var(--spacing-sm);
249
+ color: var(--text-muted);
250
+ font-size: 0.875rem;
251
+ }
252
+
253
+ /* ==========================================
254
+ BUTTONS
255
+ ========================================== */
256
+ .btn {
257
+ display: inline-flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ gap: var(--spacing-sm);
261
+ padding: var(--spacing-md) var(--spacing-xl);
262
+ font-size: 1.1rem;
263
+ font-weight: 600;
264
+ border: none;
265
+ border-radius: var(--radius-md);
266
+ cursor: pointer;
267
+ transition: all var(--transition-normal);
268
+ text-decoration: none;
269
+ font-family: inherit;
270
+ }
271
+
272
+ .btn-primary {
273
+ background: var(--gradient-primary);
274
+ color: white;
275
+ box-shadow: var(--shadow-md);
276
+ }
277
+
278
+ .btn-primary:hover {
279
+ transform: translateY(-2px);
280
+ box-shadow: var(--shadow-glow);
281
+ }
282
+
283
+ .btn-primary:active {
284
+ transform: translateY(0);
285
+ }
286
+
287
+ .btn-icon {
288
+ font-size: 1.5rem;
289
+ transition: transform var(--transition-fast);
290
+ }
291
+
292
+ .btn:hover .btn-icon {
293
+ transform: translateX(4px);
294
+ }
295
+
296
+ /* ==========================================
297
+ LOADING STATE
298
+ ========================================== */
299
+ .loading-state {
300
+ text-align: center;
301
+ padding: var(--spacing-xl);
302
+ }
303
+
304
+ .spinner {
305
+ width: 60px;
306
+ height: 60px;
307
+ margin: 0 auto var(--spacing-md);
308
+ border: 4px solid rgba(255, 255, 255, 0.1);
309
+ border-top-color: var(--primary);
310
+ border-radius: 50%;
311
+ animation: spin 1s linear infinite;
312
+ }
313
+
314
+ @keyframes spin {
315
+ to {
316
+ transform: rotate(360deg);
317
+ }
318
+ }
319
+
320
+ .loading-text {
321
+ font-size: 1.2rem;
322
+ color: var(--text-secondary);
323
+ margin-bottom: var(--spacing-lg);
324
+ }
325
+
326
+ .loading-steps {
327
+ display: flex;
328
+ flex-direction: column;
329
+ gap: var(--spacing-sm);
330
+ max-width: 400px;
331
+ margin: 0 auto;
332
+ }
333
+
334
+ .step {
335
+ padding: var(--spacing-sm) var(--spacing-md);
336
+ background: rgba(255, 255, 255, 0.03);
337
+ border-radius: var(--radius-sm);
338
+ color: var(--text-muted);
339
+ transition: all var(--transition-normal);
340
+ opacity: 0.5;
341
+ }
342
+
343
+ .step.active {
344
+ background: rgba(99, 102, 241, 0.2);
345
+ color: var(--primary-light);
346
+ opacity: 1;
347
+ transform: translateX(8px);
348
+ }
349
+
350
+ /* ==========================================
351
+ ERROR STATE
352
+ ========================================== */
353
+ .error-state {
354
+ text-align: center;
355
+ padding: var(--spacing-lg);
356
+ background: rgba(239, 68, 68, 0.1);
357
+ border: 1px solid rgba(239, 68, 68, 0.3);
358
+ border-radius: var(--radius-md);
359
+ }
360
+
361
+ .error-icon {
362
+ font-size: 2rem;
363
+ display: block;
364
+ margin-bottom: var(--spacing-sm);
365
+ }
366
+
367
+ .error-text {
368
+ color: #fca5a5;
369
+ font-size: 1.05rem;
370
+ }
371
+
372
+ /* ==========================================
373
+ FEATURES SECTION
374
+ ========================================== */
375
+ .features {
376
+ display: grid;
377
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
378
+ gap: var(--spacing-lg);
379
+ margin-top: var(--spacing-xl);
380
+ }
381
+
382
+ .feature {
383
+ text-align: center;
384
+ padding: var(--spacing-lg);
385
+ background: rgba(255, 255, 255, 0.03);
386
+ border-radius: var(--radius-md);
387
+ border: 1px solid rgba(255, 255, 255, 0.05);
388
+ transition: all var(--transition-normal);
389
+ }
390
+
391
+ .feature:hover {
392
+ background: rgba(255, 255, 255, 0.05);
393
+ transform: translateY(-4px);
394
+ }
395
+
396
+ .feature-icon {
397
+ font-size: 3rem;
398
+ display: block;
399
+ margin-bottom: var(--spacing-sm);
400
+ }
401
+
402
+ .feature h3 {
403
+ font-size: 1.3rem;
404
+ margin-bottom: var(--spacing-xs);
405
+ color: var(--text-primary);
406
+ }
407
+
408
+ .feature p {
409
+ color: var(--text-secondary);
410
+ font-size: 0.95rem;
411
+ }
412
+
413
+ /* ==========================================
414
+ RESULTS PAGE
415
+ ========================================== */
416
+ .header-small {
417
+ display: flex;
418
+ justify-content: space-between;
419
+ align-items: center;
420
+ padding: var(--spacing-md) 0;
421
+ margin-bottom: var(--spacing-lg);
422
+ }
423
+
424
+ .back-link {
425
+ color: var(--text-secondary);
426
+ text-decoration: none;
427
+ font-weight: 500;
428
+ transition: color var(--transition-fast);
429
+ }
430
+
431
+ .back-link:hover {
432
+ color: var(--primary-light);
433
+ }
434
+
435
+ .logo-small {
436
+ display: flex;
437
+ align-items: center;
438
+ gap: var(--spacing-xs);
439
+ }
440
+
441
+ .logo-small h1 {
442
+ font-size: 1.5rem;
443
+ font-weight: 700;
444
+ background: var(--gradient-primary);
445
+ -webkit-background-clip: text;
446
+ -webkit-text-fill-color: transparent;
447
+ background-clip: text;
448
+ }
449
+
450
+ .logo-small .logo-icon {
451
+ font-size: 1.5rem;
452
+ }
453
+
454
+ .claim-card {
455
+ margin-bottom: var(--spacing-xl);
456
+ }
457
+
458
+ .card-header {
459
+ display: flex;
460
+ justify-content: space-between;
461
+ align-items: center;
462
+ margin-bottom: var(--spacing-md);
463
+ }
464
+
465
+ .card-header h2 {
466
+ font-size: 1.5rem;
467
+ color: var(--text-primary);
468
+ }
469
+
470
+ .badge {
471
+ padding: var(--spacing-xs) var(--spacing-md);
472
+ border-radius: var(--radius-sm);
473
+ font-size: 0.875rem;
474
+ font-weight: 600;
475
+ }
476
+
477
+ .badge-success {
478
+ background: rgba(16, 185, 129, 0.2);
479
+ color: #6ee7b7;
480
+ }
481
+
482
+ .claim-text {
483
+ font-size: 1.3rem;
484
+ line-height: 1.8;
485
+ color: var(--text-primary);
486
+ margin-bottom: var(--spacing-md);
487
+ font-weight: 500;
488
+ }
489
+
490
+ .meta-info {
491
+ display: flex;
492
+ gap: var(--spacing-lg);
493
+ color: var(--text-secondary);
494
+ font-size: 0.95rem;
495
+ }
496
+
497
+ .meta-item strong {
498
+ color: var(--primary-light);
499
+ }
500
+
501
+ /* ==========================================
502
+ SECTIONS
503
+ ========================================== */
504
+ .section {
505
+ margin-bottom: var(--spacing-xl);
506
+ }
507
+
508
+ .section-title {
509
+ display: flex;
510
+ align-items: center;
511
+ gap: var(--spacing-sm);
512
+ font-size: 1.8rem;
513
+ margin-bottom: var(--spacing-sm);
514
+ color: var(--text-primary);
515
+ }
516
+
517
+ .section-icon {
518
+ font-size: 2rem;
519
+ }
520
+
521
+ .section-subtitle {
522
+ color: var(--text-secondary);
523
+ margin-bottom: var(--spacing-lg);
524
+ }
525
+
526
+ /* ==========================================
527
+ EVIDENCE CARDS
528
+ ========================================== */
529
+ .evidence-grid {
530
+ display: grid;
531
+ gap: var(--spacing-md);
532
+ }
533
+
534
+ .evidence-card {
535
+ animation: slideIn 0.5s ease forwards;
536
+ opacity: 0;
537
+ }
538
+
539
+ @keyframes slideIn {
540
+ from {
541
+ opacity: 0;
542
+ transform: translateY(20px);
543
+ }
544
+
545
+ to {
546
+ opacity: 1;
547
+ transform: translateY(0);
548
+ }
549
+ }
550
+
551
+ .evidence-header {
552
+ display: flex;
553
+ justify-content: space-between;
554
+ align-items: center;
555
+ margin-bottom: var(--spacing-sm);
556
+ }
557
+
558
+ .source-badge {
559
+ padding: 4px 12px;
560
+ border-radius: var(--radius-sm);
561
+ font-size: 0.75rem;
562
+ font-weight: 700;
563
+ text-transform: uppercase;
564
+ letter-spacing: 0.5px;
565
+ }
566
+
567
+ .source-rss {
568
+ background: rgba(245, 158, 11, 0.2);
569
+ color: #fbbf24;
570
+ }
571
+
572
+ .source-gdelt {
573
+ background: rgba(59, 130, 246, 0.2);
574
+ color: #93c5fd;
575
+ }
576
+
577
+ .source-wikipedia {
578
+ background: rgba(139, 92, 246, 0.2);
579
+ color: #c4b5fd;
580
+ }
581
+
582
+ .source-duckduckgo {
583
+ background: rgba(236, 72, 153, 0.2);
584
+ color: #f9a8d4;
585
+ }
586
+
587
+ .similarity-score {
588
+ color: var(--text-muted);
589
+ font-size: 0.875rem;
590
+ font-weight: 600;
591
+ }
592
+
593
+ .evidence-text {
594
+ color: var(--text-secondary);
595
+ line-height: 1.7;
596
+ }
597
+
598
+ /* ==========================================
599
+ NLI CARDS
600
+ ========================================== */
601
+ .nli-grid {
602
+ display: grid;
603
+ gap: var(--spacing-md);
604
+ }
605
+
606
+ .nli-card {
607
+ animation: slideIn 0.5s ease forwards;
608
+ opacity: 0;
609
+ border-left: 4px solid;
610
+ }
611
+
612
+ .nli-card.nli-entailment {
613
+ border-left-color: var(--success);
614
+ background: rgba(16, 185, 129, 0.05);
615
+ }
616
+
617
+ .nli-card.nli-contradiction {
618
+ border-left-color: var(--error);
619
+ background: rgba(239, 68, 68, 0.05);
620
+ }
621
+
622
+ .nli-card.nli-neutral {
623
+ border-left-color: var(--neutral);
624
+ background: rgba(107, 114, 128, 0.05);
625
+ }
626
+
627
+ .nli-header {
628
+ display: flex;
629
+ justify-content: space-between;
630
+ align-items: center;
631
+ margin-bottom: var(--spacing-sm);
632
+ }
633
+
634
+ .nli-label {
635
+ font-weight: 700;
636
+ font-size: 1.05rem;
637
+ }
638
+
639
+ .nli-entailment .nli-label {
640
+ color: #6ee7b7;
641
+ }
642
+
643
+ .nli-contradiction .nli-label {
644
+ color: #fca5a5;
645
+ }
646
+
647
+ .nli-neutral .nli-label {
648
+ color: #9ca3af;
649
+ }
650
+
651
+ .nli-confidence {
652
+ color: var(--text-muted);
653
+ font-size: 0.875rem;
654
+ font-weight: 600;
655
+ }
656
+
657
+ .nli-evidence {
658
+ color: var(--text-secondary);
659
+ line-height: 1.7;
660
+ margin-bottom: var(--spacing-sm);
661
+ }
662
+
663
+ .confidence-bar {
664
+ height: 6px;
665
+ background: rgba(255, 255, 255, 0.1);
666
+ border-radius: 3px;
667
+ overflow: hidden;
668
+ }
669
+
670
+ .confidence-fill {
671
+ height: 100%;
672
+ background: var(--gradient-primary);
673
+ border-radius: 3px;
674
+ transition: width var(--transition-slow);
675
+ }
676
+
677
+ /* ==========================================
678
+ SUMMARY CARD
679
+ ========================================== */
680
+ .summary-card {
681
+ margin-top: var(--spacing-xl);
682
+ }
683
+
684
+ .summary-card h3 {
685
+ font-size: 1.5rem;
686
+ margin-bottom: var(--spacing-md);
687
+ color: var(--text-primary);
688
+ }
689
+
690
+ .summary-stats {
691
+ display: grid;
692
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
693
+ gap: var(--spacing-md);
694
+ }
695
+
696
+ .stat {
697
+ text-align: center;
698
+ padding: var(--spacing-md);
699
+ background: rgba(255, 255, 255, 0.03);
700
+ border-radius: var(--radius-md);
701
+ }
702
+
703
+ .stat-value {
704
+ font-size: 2.5rem;
705
+ font-weight: 800;
706
+ background: var(--gradient-primary);
707
+ -webkit-background-clip: text;
708
+ -webkit-text-fill-color: transparent;
709
+ background-clip: text;
710
+ margin-bottom: var(--spacing-xs);
711
+ }
712
+
713
+ .stat-label {
714
+ color: var(--text-secondary);
715
+ font-size: 0.875rem;
716
+ text-transform: uppercase;
717
+ letter-spacing: 0.5px;
718
+ }
719
+
720
+ /* ==========================================
721
+ ACTIONS
722
+ ========================================== */
723
+ .actions {
724
+ text-align: center;
725
+ margin-top: var(--spacing-xl);
726
+ }
727
+
728
+ /* ==========================================
729
+ UTILITY CLASSES
730
+ ========================================== */
731
+ .hidden {
732
+ display: none !important;
733
+ }
734
+
735
+ .empty-state {
736
+ text-align: center;
737
+ padding: var(--spacing-xl);
738
+ color: var(--text-muted);
739
+ }
740
+
741
+ /* ==========================================
742
+ RESPONSIVE
743
+ ========================================== */
744
+ @media (max-width: 768px) {
745
+ .logo h1 {
746
+ font-size: 2.5rem;
747
+ }
748
+
749
+ .card-title {
750
+ font-size: 1.5rem;
751
+ }
752
+
753
+ .features {
754
+ grid-template-columns: 1fr;
755
+ }
756
+
757
+ .summary-stats {
758
+ grid-template-columns: repeat(2, 1fr);
759
+ }
760
+
761
+ .container {
762
+ padding: var(--spacing-md);
763
+ }
764
+ }
765
+
766
+ /* ==========================================
767
+ VERDICT CARD
768
+ ========================================== */
769
+ .verdict-card {
770
+ text-align: center;
771
+ border: 2px solid rgba(255, 255, 255, 0.1);
772
+ background: rgba(255, 255, 255, 0.05);
773
+ margin-bottom: var(--spacing-xl);
774
+ }
775
+
776
+ .verdict-card.true {
777
+ border-color: var(--success);
778
+ background: rgba(16, 185, 129, 0.1);
779
+ }
780
+
781
+ .verdict-card.false {
782
+ border-color: var(--error);
783
+ background: rgba(239, 68, 68, 0.1);
784
+ }
785
+
786
+ .verdict-card.uncertain {
787
+ border-color: var(--warning);
788
+ background: rgba(245, 158, 11, 0.1);
789
+ }
790
+
791
+ .verdict-header {
792
+ margin-bottom: var(--spacing-md);
793
+ }
794
+
795
+ .verdict-badge {
796
+ display: inline-block;
797
+ padding: var(--spacing-xs) var(--spacing-lg);
798
+ border-radius: var(--radius-xl);
799
+ font-size: 1.5rem;
800
+ font-weight: 800;
801
+ text-transform: uppercase;
802
+ letter-spacing: 2px;
803
+ margin-top: var(--spacing-xs);
804
+ }
805
+
806
+ .verdict-badge.true {
807
+ background: var(--success);
808
+ color: white;
809
+ box-shadow: 0 0 20px rgba(16, 185, 129, 0.4);
810
+ }
811
+
812
+ .verdict-badge.false {
813
+ background: var(--error);
814
+ color: white;
815
+ box-shadow: 0 0 20px rgba(239, 68, 68, 0.4);
816
+ }
817
+
818
+ .verdict-badge.uncertain {
819
+ background: var(--warning);
820
+ color: white;
821
+ box-shadow: 0 0 20px rgba(245, 158, 11, 0.4);
822
+ }
823
+
824
+ .verdict-text {
825
+ font-size: 1.25rem;
826
+ color: var(--text-primary);
827
+ margin-bottom: var(--spacing-lg);
828
+ }
829
+
830
+ .verdict-text strong {
831
+ color: white;
832
+ }
833
+
834
+ .confidence-bar-large {
835
+ height: 12px;
836
+ background: rgba(255, 255, 255, 0.1);
837
+ border-radius: 6px;
838
+ overflow: hidden;
839
+ max-width: 600px;
840
+ margin: 0 auto;
841
+ }
842
+
843
+ .confidence-fill-large {
844
+ height: 100%;
845
+ border-radius: 6px;
846
+ transition: width 1s ease-out;
847
+ }
848
+
849
+ .confidence-fill-large.true {
850
+ background: var(--success);
851
+ }
852
+
853
+ .confidence-fill-large.false {
854
+ background: var(--error);
855
+ }
856
+
857
+ .confidence-fill-large.uncertain {
858
+ background: var(--warning);
859
+ }
templates/index.html ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>FactCheck AI - Verify Claims with Evidence</title>
7
+ <meta name="description" content="Advanced AI-powered fact-checking system that verifies claims using multiple sources and natural language inference.">
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
12
+ </head>
13
+ <body>
14
+ <div class="gradient-bg"></div>
15
+
16
+ <div class="container">
17
+ <header class="hero">
18
+ <div class="logo">
19
+ <span class="logo-icon">🔍</span>
20
+ <h1>FactCheck<span class="accent">AI</span></h1>
21
+ </div>
22
+ <p class="tagline">Verify claims with AI-powered evidence analysis</p>
23
+ </header>
24
+
25
+ <main class="main-content">
26
+ <div class="card glass-card">
27
+ <h2 class="card-title">Enter a Claim to Verify</h2>
28
+ <p class="card-subtitle">Our AI will search multiple sources and analyze the evidence using natural language inference</p>
29
+
30
+ <form id="claimForm" class="claim-form">
31
+ <div class="input-group">
32
+ <textarea
33
+ id="claimInput"
34
+ name="claim"
35
+ placeholder="e.g., Climate change is caused by human activities"
36
+ rows="4"
37
+ required
38
+ maxlength="500"
39
+ ></textarea>
40
+ <div class="char-counter">
41
+ <span id="charCount">0</span>/500
42
+ </div>
43
+ </div>
44
+
45
+ <button type="submit" class="btn btn-primary" id="submitBtn">
46
+ <span class="btn-text">Verify Claim</span>
47
+ <span class="btn-icon">→</span>
48
+ </button>
49
+ </form>
50
+
51
+ <div id="loadingState" class="loading-state hidden">
52
+ <div class="spinner"></div>
53
+ <p class="loading-text">Analyzing claim and gathering evidence...</p>
54
+ <div class="loading-steps">
55
+ <div class="step active">📡 Fetching from news sources</div>
56
+ <div class="step">🌍 Searching GDELT database</div>
57
+ <div class="step">📚 Checking Wikipedia</div>
58
+ <div class="step">🧪 Running NLI analysis</div>
59
+ </div>
60
+ </div>
61
+
62
+ <div id="errorState" class="error-state hidden">
63
+ <span class="error-icon">⚠️</span>
64
+ <p class="error-text"></p>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="features">
69
+ <div class="feature">
70
+ <span class="feature-icon">🌐</span>
71
+ <h3>Multi-Source</h3>
72
+ <p>Aggregates evidence from RSS feeds, GDELT, Wikipedia, and more</p>
73
+ </div>
74
+ <div class="feature">
75
+ <span class="feature-icon">🤖</span>
76
+ <h3>AI-Powered</h3>
77
+ <p>Uses advanced NLI models to determine claim validity</p>
78
+ </div>
79
+ <div class="feature">
80
+ <span class="feature-icon">⚡</span>
81
+ <h3>Real-Time</h3>
82
+ <p>Get instant results with comprehensive evidence analysis</p>
83
+ </div>
84
+ </div>
85
+ </main>
86
+ </div>
87
+
88
+ <script>
89
+ const form = document.getElementById('claimForm');
90
+ const input = document.getElementById('claimInput');
91
+ const charCount = document.getElementById('charCount');
92
+ const submitBtn = document.getElementById('submitBtn');
93
+ const loadingState = document.getElementById('loadingState');
94
+ const errorState = document.getElementById('errorState');
95
+
96
+ // Character counter
97
+ input.addEventListener('input', () => {
98
+ charCount.textContent = input.value.length;
99
+ });
100
+
101
+ // Form submission
102
+ form.addEventListener('submit', async (e) => {
103
+ e.preventDefault();
104
+
105
+ const claim = input.value.trim();
106
+ if (!claim) {
107
+ showError('Please enter a claim to verify');
108
+ return;
109
+ }
110
+
111
+ // Show loading state
112
+ form.classList.add('hidden');
113
+ errorState.classList.add('hidden');
114
+ loadingState.classList.remove('hidden');
115
+
116
+ // Animate loading steps
117
+ animateLoadingSteps();
118
+
119
+ try {
120
+ const formData = new FormData();
121
+ formData.append('claim', claim);
122
+
123
+ const response = await fetch('/check', {
124
+ method: 'POST',
125
+ body: formData
126
+ });
127
+
128
+ const data = await response.json();
129
+
130
+ if (data.success) {
131
+ // Redirect to results page
132
+ window.location.href = '/results';
133
+ } else {
134
+ showError(data.error || 'Failed to verify claim');
135
+ }
136
+ } catch (error) {
137
+ showError('Network error. Please try again.');
138
+ }
139
+ });
140
+
141
+ function showError(message) {
142
+ loadingState.classList.add('hidden');
143
+ form.classList.remove('hidden');
144
+ errorState.classList.remove('hidden');
145
+ errorState.querySelector('.error-text').textContent = message;
146
+ }
147
+
148
+ function animateLoadingSteps() {
149
+ const steps = document.querySelectorAll('.loading-steps .step');
150
+ let currentStep = 0;
151
+
152
+ const interval = setInterval(() => {
153
+ if (currentStep < steps.length) {
154
+ steps[currentStep].classList.add('active');
155
+ currentStep++;
156
+ } else {
157
+ clearInterval(interval);
158
+ }
159
+ }, 1500);
160
+ }
161
+ </script>
162
+ </body>
163
+ </html>
templates/results.html ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Results - FactCheck AI</title>
8
+ <meta name="description" content="Fact-checking results with evidence and NLI analysis">
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
13
+ rel="stylesheet">
14
+ </head>
15
+
16
+ <body>
17
+ <div class="gradient-bg"></div>
18
+
19
+ <div class="container">
20
+ <header class="header-small">
21
+ <a href="/" class="back-link">← Back to Home</a>
22
+ <div class="logo-small">
23
+ <span class="logo-icon">🔍</span>
24
+ <h1>FactCheck<span class="accent">AI</span></h1>
25
+ </div>
26
+ </header>
27
+
28
+ <main class="results-content">
29
+ {% if result.success %}
30
+ <!-- Claim Card -->
31
+ <div class="card glass-card claim-card">
32
+ <div class="card-header">
33
+ <h2>Analyzed Claim</h2>
34
+ <span class="badge badge-success">✓ Analysis Complete</span>
35
+ </div>
36
+ <p class="claim-text">{{ result.claim }}</p>
37
+ <div class="meta-info">
38
+ <span class="meta-item">
39
+ <strong>{{ result.total_evidence }}</strong> evidence sources found
40
+ </span>
41
+ <span class="meta-item">
42
+ <strong>{{ result.nli_results|length }}</strong> NLI analyses performed
43
+ </span>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Verdict Section -->
48
+ {% if result.verdict %}
49
+ <div class="card glass-card verdict-card {{ result.verdict|lower }}">
50
+ <div class="verdict-header">
51
+ <h2>Final Verdict</h2>
52
+ <br>
53
+ <span class="verdict-badge {{ result.verdict|lower }}">{{ result.verdict }}</span>
54
+ </div>
55
+ <p class="verdict-text">
56
+ Based on the evidence, this claim is likely <strong>{{ result.verdict }}</strong> with <strong>{{
57
+ (result.confidence * 100)|round(0) }}%</strong> confidence.
58
+ </p>
59
+ <div class="confidence-bar-large">
60
+ <div class="confidence-fill-large {{ result.verdict|lower }}"
61
+ style="width: {{ (result.confidence * 100)|round(0) }}%"></div>
62
+ </div>
63
+ </div>
64
+ {% endif %}
65
+
66
+ <!-- Evidence Section -->
67
+ <div class="section">
68
+ <h2 class="section-title">
69
+ <span class="section-icon">🔎</span>
70
+ Evidence Sources
71
+ </h2>
72
+
73
+ {% if result.evidence %}
74
+ <div class="evidence-grid">
75
+ {% for evidence in result.evidence %}
76
+ <div class="card evidence-card" style="animation-delay: {{ loop.index0 * 0.1 }}s">
77
+ <div class="evidence-header">
78
+ <span class="source-badge source-{{ evidence.source|lower }}">
79
+ {{ evidence.source }}
80
+ </span>
81
+ <span class="similarity-score">
82
+ {{ (evidence.similarity * 100)|round(1) }}% match
83
+ </span>
84
+ </div>
85
+ <p class="evidence-text">{{ evidence.text }}</p>
86
+ </div>
87
+ {% endfor %}
88
+ </div>
89
+ {% else %}
90
+ <div class="card empty-state">
91
+ <p>No evidence found</p>
92
+ </div>
93
+ {% endif %}
94
+ </div>
95
+
96
+ <!-- NLI Results Section -->
97
+ <div class="section">
98
+ <h2 class="section-title">
99
+ <span class="section-icon">🧪</span>
100
+ Natural Language Inference Results
101
+ </h2>
102
+ <p class="section-subtitle">AI analysis of how the evidence relates to your claim</p>
103
+
104
+ {% if result.nli_results %}
105
+ <div class="nli-grid">
106
+ {% for nli in result.nli_results %}
107
+ <div class="card nli-card nli-{{ nli.label|lower }}"
108
+ style="animation-delay: {{ loop.index0 * 0.1 }}s">
109
+ <div class="nli-header">
110
+ <span class="nli-label">
111
+ {% if 'entail' in nli.label|lower %}
112
+ ✓ Supports
113
+ {% elif 'contradict' in nli.label|lower %}
114
+ ✗ Contradicts
115
+ {% else %}
116
+ ◐ Neutral
117
+ {% endif %}
118
+ </span>
119
+ <span class="nli-confidence">
120
+ {{ (nli.score * 100)|round(1) }}% confidence
121
+ </span>
122
+ </div>
123
+ <p class="nli-evidence">{{ nli.evidence }}</p>
124
+ <div class="confidence-bar">
125
+ <div class="confidence-fill" style="width: {{ (nli.score * 100)|round(1) }}%"></div>
126
+ </div>
127
+ </div>
128
+ {% endfor %}
129
+ </div>
130
+ {% else %}
131
+ <div class="card empty-state">
132
+ <p>No NLI results available</p>
133
+ </div>
134
+ {% endif %}
135
+ </div>
136
+
137
+ <!-- Summary Section -->
138
+ <div class="card glass-card summary-card">
139
+ <h3>Analysis Summary</h3>
140
+ <div class="summary-stats">
141
+ <div class="stat">
142
+ <div class="stat-value">{{ result.evidence|length }}</div>
143
+ <div class="stat-label">Evidence Pieces</div>
144
+ </div>
145
+ <div class="stat">
146
+ <div class="stat-value">
147
+ {% set supporting = namespace(count=0) %}
148
+ {% for nli in result.nli_results %}
149
+ {% if 'entail' in nli.label|lower %}
150
+ {% set supporting.count = supporting.count + 1 %}
151
+ {% endif %}
152
+ {% endfor %}
153
+ {{ supporting.count }}
154
+ </div>
155
+ <div class="stat-label">Supporting</div>
156
+ </div>
157
+ <div class="stat">
158
+ <div class="stat-value">
159
+ {% set contradicting = namespace(count=0) %}
160
+ {% for nli in result.nli_results %}
161
+ {% if 'contradict' in nli.label|lower %}
162
+ {% set contradicting.count = contradicting.count + 1 %}
163
+ {% endif %}
164
+ {% endfor %}
165
+ {{ contradicting.count }}
166
+ </div>
167
+ <div class="stat-label">Contradicting</div>
168
+ </div>
169
+ <div class="stat">
170
+ <div class="stat-value">
171
+ {% set neutral = namespace(count=0) %}
172
+ {% for nli in result.nli_results %}
173
+ {% if 'entail' not in nli.label|lower and 'contradict' not in nli.label|lower %}
174
+ {% set neutral.count = neutral.count + 1 %}
175
+ {% endif %}
176
+ {% endfor %}
177
+ {{ neutral.count }}
178
+ </div>
179
+ <div class="stat-label">Neutral</div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <div class="actions">
185
+ <a href="/" class="btn btn-primary">Check Another Claim</a>
186
+ </div>
187
+
188
+ {% else %}
189
+ <!-- Error State -->
190
+ <div class="card glass-card error-card">
191
+ <span class="error-icon-large">⚠️</span>
192
+ <h2>Analysis Failed</h2>
193
+ <p class="error-message">{{ result.error }}</p>
194
+ <a href="/" class="btn btn-primary">Try Again</a>
195
+ </div>
196
+ {% endif %}
197
+ </main>
198
+ </div>
199
+ </body>
200
+
201
+ </html>