lightmate commited on
Commit
dd8c8ac
·
verified ·
1 Parent(s): 6c9ec6b

Upload 12 files

Browse files
api/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ API client module for DGaze
3
+ """
api/client.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API client for communicating with the DGaze backend.
3
+ Similar to the client.ts in the React frontend.
4
+ """
5
+
6
+ import requests
7
+ from typing import Dict, Any, Optional
8
+ from config.settings import settings
9
+
10
+
11
+ class DGazeAPIClient:
12
+ """Client for interacting with the DGaze verification API."""
13
+
14
+ def __init__(self):
15
+ self.base_url = settings.API_BASE_URL
16
+ self.verify_endpoint = settings.API_ENDPOINT
17
+ self.health_endpoint = settings.HEALTH_ENDPOINT
18
+
19
+ def verify_news(self, input_text: str, access_token: Optional[str] = None) -> Dict[str, Any]:
20
+ """
21
+ Send news text to backend for verification.
22
+
23
+ Args:
24
+ input_text: The news text or URL to verify
25
+ access_token: Optional JWT token for authenticated requests
26
+
27
+ Returns:
28
+ Dict containing the verification results
29
+
30
+ Raises:
31
+ requests.exceptions.RequestException: For API errors
32
+ """
33
+ if not input_text.strip():
34
+ raise ValueError("Input text cannot be empty")
35
+
36
+ # Prepare the request payload
37
+ payload = {
38
+ "input": input_text,
39
+ "input_type": "text"
40
+ }
41
+
42
+ # Prepare headers
43
+ headers = {"Content-Type": "application/json"}
44
+ if access_token:
45
+ headers["Authorization"] = f"Bearer {access_token}"
46
+
47
+ # Make API request to backend
48
+ response = requests.post(
49
+ self.verify_endpoint,
50
+ json=payload,
51
+ timeout=60,
52
+ headers=headers
53
+ )
54
+
55
+ if response.status_code == 401:
56
+ raise ValueError("Authentication required. Please log in to continue.")
57
+ elif response.status_code == 403:
58
+ raise ValueError("Access denied. You don't have permission to perform this action.")
59
+ elif response.status_code != 200:
60
+ raise requests.exceptions.HTTPError(
61
+ f"API Error: Server returned status {response.status_code}"
62
+ )
63
+
64
+ data = response.json()
65
+
66
+ if data.get("status") != "success":
67
+ raise ValueError(f"Verification Failed: {data.get('message', 'Unknown error')}")
68
+
69
+ return data
70
+
71
+ def check_health(self) -> bool:
72
+ """
73
+ Check if the backend service is healthy.
74
+
75
+ Returns:
76
+ True if backend is accessible, False otherwise
77
+ """
78
+ try:
79
+ response = requests.get(self.health_endpoint, timeout=5)
80
+ return response.status_code == 200
81
+ except requests.exceptions.RequestException:
82
+ return False
83
+
84
+
85
+ # Create a default client instance
86
+ api_client = DGazeAPIClient()
app.py ADDED
@@ -0,0 +1,913 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DGaze - Simplified Auth0 + Gradio Implementation
3
+ Based on proven patterns from successful implementations.
4
+ """
5
+
6
+ import os
7
+ import gradio as gr
8
+ import uvicorn
9
+ import time
10
+ import uuid
11
+ from fastapi import FastAPI, Request, Depends
12
+ from fastapi.responses import RedirectResponse, FileResponse
13
+ from starlette.middleware.sessions import SessionMiddleware
14
+ from urllib.parse import quote_plus, urlencode
15
+ from starlette.config import Config
16
+ from authlib.integrations.starlette_client import OAuth, OAuthError
17
+
18
+ # Import our modular components
19
+ from config.settings import settings
20
+ from api.client import api_client
21
+ from components.verification_result import format_verification_results
22
+
23
+ # Create FastAPI app
24
+ app = FastAPI(title="DGaze - News Verification")
25
+
26
+ # Add CORS middleware for better API access
27
+ from fastapi.middleware.cors import CORSMiddleware
28
+ app.add_middleware(
29
+ CORSMiddleware,
30
+ allow_origins=["*"], # In production, this should be more restrictive
31
+ allow_credentials=True,
32
+ allow_methods=["*"],
33
+ allow_headers=["*"],
34
+ )
35
+
36
+ # Static files handler to fix 404 errors with Gradio assets
37
+ @app.get("/assets/{path:path}")
38
+ async def get_assets(path: str):
39
+ """Handle asset requests that might be missing when Gradio is mounted."""
40
+ # Try to serve from demo assets first
41
+ demo_asset_path = f"/demo/assets/{path}"
42
+ return RedirectResponse(url=demo_asset_path)
43
+
44
+ # Configure OAuth
45
+ config_data = {
46
+ 'AUTH0_CLIENT_ID': settings.AUTH0_CLIENT_ID,
47
+ 'AUTH0_CLIENT_SECRET': settings.AUTH0_CLIENT_SECRET
48
+ }
49
+ starlette_config = Config(environ=config_data)
50
+ oauth = OAuth(starlette_config)
51
+
52
+ oauth.register(
53
+ "auth0",
54
+ client_id=settings.AUTH0_CLIENT_ID,
55
+ client_secret=settings.AUTH0_CLIENT_SECRET,
56
+ client_kwargs={
57
+ "scope": "openid profile email",
58
+ },
59
+ server_metadata_url=f'https://{settings.AUTH0_DOMAIN}/.well-known/openid-configuration',
60
+ )
61
+
62
+ # Add session middleware
63
+ app.add_middleware(SessionMiddleware, secret_key=settings.SECRET_KEY)
64
+
65
+ # Simple user dependency
66
+ def get_user(request: Request):
67
+ """Get current user from session."""
68
+ user = request.session.get('user')
69
+ if user:
70
+ return user.get('name') or user.get('email', 'Unknown User')
71
+ return None
72
+
73
+ # Free trial management functions
74
+ # In-memory trial tracking (since session data doesn't persist properly with mounted Gradio apps)
75
+ trial_tracker = {}
76
+ session_timestamps = {}
77
+
78
+ def get_or_create_session_id(request: Request) -> str:
79
+ """Get existing session ID or create a new one."""
80
+ session_id = request.session.get('session_id')
81
+ if not session_id:
82
+ session_id = f"trial_{uuid.uuid4().hex[:12]}"
83
+ request.session['session_id'] = session_id
84
+ print(f"DEBUG: Created new session ID: {session_id}")
85
+ return session_id
86
+
87
+ def cleanup_old_sessions():
88
+ """Clean up sessions older than 24 hours."""
89
+ current_time = time.time()
90
+ old_sessions = []
91
+ for session_id, timestamp in session_timestamps.items():
92
+ if current_time - timestamp > 86400: # 24 hours
93
+ old_sessions.append(session_id)
94
+
95
+ for session_id in old_sessions:
96
+ trial_tracker.pop(session_id, None)
97
+ session_timestamps.pop(session_id, None)
98
+
99
+ if old_sessions:
100
+ print(f"DEBUG: Cleaned up {len(old_sessions)} old sessions")
101
+
102
+ def get_trial_count(request: Request) -> int:
103
+ """Get current trial verification count from in-memory tracker."""
104
+ session_id = get_or_create_session_id(request)
105
+ cleanup_old_sessions() # Periodic cleanup
106
+ count = trial_tracker.get(session_id, 0)
107
+ print(f"DEBUG: get_trial_count() for session {session_id}: {count}")
108
+ return count
109
+
110
+ def increment_trial_count(request: Request) -> int:
111
+ """Increment and return trial verification count."""
112
+ session_id = get_or_create_session_id(request)
113
+ current_count = trial_tracker.get(session_id, 0)
114
+ new_count = current_count + 1
115
+ trial_tracker[session_id] = new_count
116
+ session_timestamps[session_id] = time.time() # Update timestamp
117
+ print(f"DEBUG: increment_trial_count() for session {session_id}: {current_count} -> {new_count}")
118
+ print(f"DEBUG: Trial tracker state: {dict(list(trial_tracker.items())[-3:])}") # Show last 3 for brevity
119
+ return new_count
120
+
121
+ def has_remaining_trials(request: Request) -> bool:
122
+ """Check if user has remaining free trials."""
123
+ remaining = get_trial_count(request) < 2
124
+ print(f"DEBUG: has_remaining_trials() returning: {remaining}")
125
+ return remaining
126
+
127
+ # Authentication routes
128
+ @app.get('/')
129
+ def public(user: str = Depends(get_user), request: Request = None):
130
+ # Check if this is an Auth0 callback (has code parameter)
131
+ if request:
132
+ query_params = dict(request.query_params)
133
+ if 'code' in query_params and 'state' in query_params:
134
+ # This is an Auth0 callback, redirect to auth handler
135
+ return RedirectResponse(url=f'/auth?{request.url.query}')
136
+
137
+ if user:
138
+ # Authenticated user - go to full demo
139
+ return RedirectResponse(url='/demo')
140
+ else:
141
+ # Unauthenticated user - go to free trial
142
+ return RedirectResponse(url='/trial')
143
+
144
+ @app.route('/login')
145
+ async def login(request: Request):
146
+ # Use root URL as redirect_uri to match React app pattern
147
+ redirect_uri = f"http://localhost:{settings.GRADIO_SERVER_PORT}/"
148
+ return await oauth.auth0.authorize_redirect(request, redirect_uri)
149
+
150
+ @app.route('/logout')
151
+ async def logout(request: Request):
152
+ # Clean up trial data
153
+ session_id = request.session.get('session_id')
154
+ if session_id:
155
+ trial_tracker.pop(session_id, None)
156
+ session_timestamps.pop(session_id, None)
157
+ print(f"DEBUG: Cleaned up trial data for session {session_id}")
158
+
159
+ request.session.pop('user', None)
160
+ request.session.pop('session_id', None)
161
+ return RedirectResponse(url=
162
+ "https://"
163
+ + settings.AUTH0_DOMAIN
164
+ + "/v2/logout?"
165
+ + urlencode(
166
+ {
167
+ "returnTo": f"http://localhost:{settings.GRADIO_SERVER_PORT}/",
168
+ "client_id": settings.AUTH0_CLIENT_ID,
169
+ },
170
+ quote_via=quote_plus,
171
+ )
172
+ )
173
+
174
+ @app.route('/auth') # callback
175
+ async def auth(request: Request):
176
+ try:
177
+ access_token = await oauth.auth0.authorize_access_token(request)
178
+ userinfo = dict(access_token)["userinfo"]
179
+ print(f"DEBUG: Auth successful, userinfo: {userinfo}") # Debug log
180
+ request.session['user'] = userinfo
181
+ print(f"DEBUG: Session after setting user: {dict(request.session)}") # Debug log
182
+ return RedirectResponse(url='/')
183
+ except OAuthError as e:
184
+ print(f"DEBUG: Auth error: {e}") # Debug log
185
+ return RedirectResponse(url='/')
186
+
187
+ @app.get('/api/user')
188
+ async def get_user_info(request: Request):
189
+ """API endpoint to get current user info for JavaScript."""
190
+ print(f"DEBUG: /api/user endpoint called!")
191
+ print(f"DEBUG: Request headers: {dict(request.headers)}")
192
+ print(f"DEBUG: Request cookies: {request.cookies}")
193
+ user = request.session.get('user')
194
+ print(f"DEBUG: Session user data: {user}") # Debug log
195
+ print(f"DEBUG: All session data: {dict(request.session)}") # Debug log
196
+ if user:
197
+ user_response = {
198
+ "authenticated": True,
199
+ "user": {
200
+ "name": user.get('name'),
201
+ "email": user.get('email'),
202
+ "picture": user.get('picture')
203
+ }
204
+ }
205
+ print(f"DEBUG: Returning user response: {user_response}")
206
+ return user_response
207
+ else:
208
+ print("DEBUG: No user found in session")
209
+ return {"authenticated": False}
210
+
211
+ # Enhanced verification function with trial tracking
212
+ def verify_news_with_trial_check(input_text: str, request: gr.Request) -> str:
213
+ """Verification function with free trial tracking."""
214
+ if not input_text.strip():
215
+ return "Please enter some text or URL to verify"
216
+
217
+ try:
218
+ # Debug logging for session tracking
219
+ print(f"DEBUG: Request type: {type(request)}")
220
+ print(f"DEBUG: Request attributes: {dir(request)}")
221
+
222
+ # Get the FastAPI request object from Gradio request
223
+ fastapi_request = None
224
+ if hasattr(request, 'request'):
225
+ fastapi_request = request.request
226
+ elif hasattr(request, 'fastapi_request'):
227
+ fastapi_request = request.fastapi_request
228
+ else:
229
+ print(f"DEBUG: Cannot find FastAPI request in Gradio request object")
230
+ # Fallback - proceed without trial tracking for now
231
+ data = api_client.verify_news(input_text)
232
+ result = format_verification_results(data)
233
+ return f"""
234
+ <div style="background: #fff3cd; border: 1px solid #ffeaa7; padding: 1rem; margin: 1rem 0; border-radius: 4px;">
235
+ <p style="margin: 0; color: #856404; font-size: 0.9rem;">
236
+ ⚠️ Session tracking unavailable. Please sign in for full functionality.
237
+ <a href="/login" style="color: #856404; text-decoration: underline;">Sign in here</a>
238
+ </p>
239
+ </div>
240
+ """ + result
241
+
242
+ print(f"DEBUG: FastAPI request found: {fastapi_request is not None}")
243
+
244
+ if fastapi_request:
245
+ # Check current trial count
246
+ current_count = get_trial_count(fastapi_request)
247
+ print(f"DEBUG: Current trial count: {current_count}")
248
+ print(f"DEBUG: Session data: {dict(fastapi_request.session)}")
249
+
250
+ # Check if user is authenticated
251
+ user = get_user(fastapi_request)
252
+ print(f"DEBUG: User authenticated: {user is not None}")
253
+
254
+ if not user:
255
+ # For non-authenticated users, check trial limits
256
+ if current_count >= 2: # Changed from has_remaining_trials to direct check
257
+ print(f"DEBUG: Trial limit reached, showing sign-in prompt")
258
+ # Redirect to sign up page
259
+ return """
260
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
261
+ padding: 2rem; border-radius: 12px; color: white; text-align: center;
262
+ box-shadow: 0 10px 25px rgba(0,0,0,0.1);">
263
+ <h2 style="margin: 0 0 1rem 0; font-size: 1.5rem; color: white !important;">🔐 Please Sign In for More Searches</h2>
264
+ <p style="margin: 0 0 1.5rem 0; opacity: 0.9; color: white !important;">
265
+ You've used your 2 free verifications! Sign in to continue using DGaze with unlimited access.
266
+ </p>
267
+ <a href="/login" style="
268
+ display: inline-block;
269
+ background-color: #4f46e5;
270
+ color: white !important;
271
+ padding: 12px 30px;
272
+ text-decoration: none;
273
+ border-radius: 8px;
274
+ font-weight: 600;
275
+ transition: all 0.3s ease;
276
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
277
+ " onmouseover="this.style.backgroundColor='#4338ca'; this.style.transform='translateY(-2px)'"
278
+ onmouseout="this.style.backgroundColor='#4f46e5'; this.style.transform='translateY(0)'">
279
+ 🚀 Sign In Now
280
+ </a>
281
+ <p style="margin: 1.5rem 0 0 0; font-size: 0.9rem; opacity: 0.8; color: white !important;">
282
+ Free • Secure • No spam
283
+ </p>
284
+ </div>
285
+ """
286
+
287
+ # Increment trial count for non-authenticated users
288
+ new_count = increment_trial_count(fastapi_request)
289
+ remaining = 2 - new_count
290
+ print(f"DEBUG: Incremented trial count to: {new_count}, remaining: {remaining}")
291
+
292
+ # Proceed with verification
293
+ data = api_client.verify_news(input_text)
294
+ result = format_verification_results(data)
295
+
296
+ # Add trial info for non-authenticated users
297
+ trial_info = f"""
298
+ <div style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 1rem; margin: 1rem 0; border-radius: 4px;">
299
+ <p style="margin: 0; color: #1565c0; font-size: 0.9rem;">
300
+ ⚡ Free Trial: <strong>{remaining} verification{'s' if remaining != 1 else ''} remaining</strong>
301
+ {' • <a href="/login" style="color: #1565c0; text-decoration: underline;">Sign in for unlimited access</a>' if remaining > 0 else ''}
302
+ </p>
303
+ </div>
304
+ """
305
+ result = trial_info + result
306
+ return result
307
+ else:
308
+ # Authenticated user - proceed normally
309
+ data = api_client.verify_news(input_text)
310
+ return format_verification_results(data)
311
+ else:
312
+ # Fallback if we can't access session
313
+ data = api_client.verify_news(input_text)
314
+ return format_verification_results(data)
315
+
316
+ except Exception as e:
317
+ print(f"DEBUG: Error in verify_news_with_trial_check: {e}")
318
+ return f"Error: {str(e)}"
319
+
320
+ # Regular verification function for authenticated users
321
+ def verify_news(input_text: str) -> str:
322
+ """Main verification function for authenticated users."""
323
+ if not input_text.strip():
324
+ return "Please enter some text or URL to verify"
325
+
326
+ try:
327
+ data = api_client.verify_news(input_text)
328
+ return format_verification_results(data)
329
+ except Exception as e:
330
+ return f"Error: {str(e)}"
331
+
332
+ # Gradio interfaces
333
+ def create_trial_interface():
334
+ """Create free trial interface for non-authenticated users."""
335
+ with gr.Blocks(
336
+ title="DGaze - Free Trial",
337
+ css="""
338
+ /* White theme - full width design */
339
+ html, body {
340
+ background-color: white !important;
341
+ margin: 0 !important;
342
+ padding: 0 !important;
343
+ }
344
+
345
+ .gradio-container {
346
+ background-color: white !important;
347
+ max-width: none !important;
348
+ margin: 0 !important;
349
+ padding: 20px !important;
350
+ min-height: 100vh !important;
351
+ }
352
+
353
+ .main {
354
+ background-color: white !important;
355
+ max-width: 1200px !important;
356
+ margin: 0 auto !important;
357
+ padding: 0 20px !important;
358
+ }
359
+
360
+ /* Block elements */
361
+ .block {
362
+ background-color: white !important;
363
+ }
364
+
365
+ .block.padded {
366
+ background-color: white !important;
367
+ }
368
+
369
+ /* Trial banner styling */
370
+ .trial-banner {
371
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
372
+ color: white !important;
373
+ padding: 1.5rem !important;
374
+ border-radius: 12px !important;
375
+ margin-bottom: 2rem !important;
376
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
377
+ text-align: center !important;
378
+ }
379
+
380
+ /* Ensure all text in trial banner is white */
381
+ .trial-banner h1,
382
+ .trial-banner h2,
383
+ .trial-banner h3,
384
+ .trial-banner h4,
385
+ .trial-banner h5,
386
+ .trial-banner h6,
387
+ .trial-banner p,
388
+ .trial-banner span,
389
+ .trial-banner div {
390
+ color: white !important;
391
+ }
392
+
393
+ /* Input styling - white background, dark text */
394
+ .gr-textbox, .gr-text-input, textarea {
395
+ background-color: white !important;
396
+ border: 1px solid #e0e0e0 !important;
397
+ color: #212529 !important;
398
+ border-radius: 8px !important;
399
+ }
400
+
401
+ .gr-textbox input, .gr-textbox textarea {
402
+ background-color: white !important;
403
+ color: #212529 !important;
404
+ border: 1px solid #e0e0e0 !important;
405
+ }
406
+
407
+ /* Focus states */
408
+ .gr-textbox:focus-within, textarea:focus, input:focus {
409
+ border-color: #4263eb !important;
410
+ outline: none !important;
411
+ box-shadow: 0 0 0 2px rgba(66, 99, 235, 0.1) !important;
412
+ }
413
+
414
+ /* Button styling */
415
+ .gr-button {
416
+ background-color: #4263eb !important;
417
+ color: white !important;
418
+ border: none !important;
419
+ border-radius: 8px !important;
420
+ }
421
+
422
+ .gr-button:hover {
423
+ background-color: #364fc7 !important;
424
+ }
425
+
426
+ /* Form containers */
427
+ .gr-form {
428
+ background-color: #f8f9fa !important;
429
+ border: 1px solid #e9ecef !important;
430
+ border-radius: 8px !important;
431
+ padding: 16px !important;
432
+ }
433
+
434
+ /* Examples styling */
435
+ .gr-examples {
436
+ background-color: #f8f9fa !important;
437
+ border: 1px solid #e9ecef !important;
438
+ border-radius: 8px !important;
439
+ padding: 16px !important;
440
+ }
441
+
442
+ /* Labels and text */
443
+ label, .gr-form label {
444
+ color: #212529 !important;
445
+ font-weight: 500 !important;
446
+ }
447
+
448
+ /* Ensure proper text contrast everywhere */
449
+ p, span, div, h1, h2, h3, h4, h5, h6 {
450
+ color: #212529 !important;
451
+ }
452
+
453
+ /* Output HTML container */
454
+ .gr-html {
455
+ background-color: white !important;
456
+ }
457
+
458
+ /* Main title styling */
459
+ .main-title {
460
+ color: #212529 !important;
461
+ text-align: center !important;
462
+ font-size: 3rem !important;
463
+ font-weight: 800 !important;
464
+ margin: 2rem 0 0.5rem 0 !important;
465
+ }
466
+
467
+ /* Subtitle styling */
468
+ .subtitle {
469
+ text-align: center !important;
470
+ color: #495057 !important;
471
+ font-size: 1.2rem !important;
472
+ margin-top: 0 !important;
473
+ margin-bottom: 2rem !important;
474
+ }
475
+ """
476
+ ) as demo:
477
+ # Trial banner
478
+ gr.HTML("""
479
+ <div class="trial-banner">
480
+ <h2 style="margin: 0 0 0.5rem 0; font-size: 1.8rem; font-weight: bold; color: white !important;">🚀 Free Trial - DGaze</h2>
481
+ <p style="margin: 0; opacity: 0.9; font-size: 1.1rem; color: white !important;">Try our news verification system with 2 free searches!</p>
482
+ <p style="margin: 0.5rem 0 0 0; font-size: 0.9rem; opacity: 0.8; color: white !important;">No signup required • Sign in for unlimited access</p>
483
+ </div>
484
+ """)
485
+
486
+ # Main title
487
+ gr.HTML('<h1 class="main-title">DGaze</h1>')
488
+ gr.HTML('<p class="subtitle">Advanced News Verification System</p>')
489
+
490
+ # Auth options
491
+ with gr.Row():
492
+ with gr.Column():
493
+ gr.HTML("""
494
+ <div style="text-align: center; margin: 1rem 0;">
495
+ <a href="/login" style="
496
+ display: inline-block;
497
+ background-color: #4263eb;
498
+ color: white;
499
+ padding: 12px 24px;
500
+ text-decoration: none;
501
+ border-radius: 8px;
502
+ font-weight: 600;
503
+ margin-right: 1rem;
504
+ transition: all 0.3s ease;
505
+ " onmouseover="this.style.backgroundColor='#364fc7'"
506
+ onmouseout="this.style.backgroundColor='#4263eb'">
507
+ 🔐 Sign In for Unlimited Access
508
+ </a>
509
+ <span style="color: #6c757d; font-size: 0.9rem;">or continue with free trial below</span>
510
+ </div>
511
+ """)
512
+
513
+ # Main interface
514
+ with gr.Column():
515
+ input_text = gr.Textbox(
516
+ label="Enter news text or URL to verify",
517
+ placeholder="Paste news text to verify...",
518
+ lines=5,
519
+ max_lines=10
520
+ )
521
+
522
+ submit_btn = gr.Button("Check Truthfulness (Free Trial)", variant="primary")
523
+
524
+ with gr.Column():
525
+ output_html = gr.HTML(label="Verification Results", visible=True)
526
+
527
+ # Handle submission with trial tracking
528
+ submit_btn.click(fn=verify_news_with_trial_check, inputs=input_text, outputs=output_html)
529
+ input_text.submit(fn=verify_news_with_trial_check, inputs=input_text, outputs=output_html)
530
+
531
+ # Examples
532
+ gr.Examples(
533
+ examples=[
534
+ ["""BREAKING: Revolutionary AI breakthrough! 🚀 Scientists at MIT have developed a new quantum AI system that can predict earthquakes with 99.7% accuracy up to 6 months in advance. The system, called "QuakeNet AI", uses quantum computing combined with machine learning to analyze seismic patterns invisible to current technology. Dr. Sarah Chen, lead researcher, claims this could save millions of lives and prevent billions in damages. The technology will be commercially available by 2026 according to insider sources. This comes just weeks after similar breakthroughs in cancer detection AI. What do you think about this amazing discovery? #AI #Earthquake #MIT #Science"""],
535
+ ["""SpaceX conducted another major test for their Starship program yesterday, with Elon Musk claiming on social media that Flight 10 is "ready to revolutionize space travel forever." The company fired up all 33 Raptor engines on their Super Heavy booster at the Starbase facility in Texas. According to various reports and this detailed article (https://www.space.com/space-exploration/launches-spacecraft/spacex-fires-up-super-heavy-booster-ahead-of-starships-10th-test-flight-video), the test was part of preparations for the upcoming 10th test flight. However, some critics argue that SpaceX is moving too fast without proper safety protocols, especially after Flight 9 experienced issues. The FAA is still investigating the previous mission where both the booster and ship were lost. Industry experts remain divided on whether this aggressive testing schedule is beneficial or dangerous for the future of commercial spaceflight. 🚀 #SpaceX #Starship #Space"""]
536
+ ],
537
+ inputs=[input_text]
538
+ )
539
+
540
+ return demo
541
+
542
+ def create_login_interface():
543
+ """Create login page interface."""
544
+ with gr.Blocks(
545
+ title="DGaze - Login",
546
+ css="""
547
+ /* White theme - full width design */
548
+ html, body {
549
+ background-color: white !important;
550
+ margin: 0 !important;
551
+ padding: 0 !important;
552
+ }
553
+
554
+ .gradio-container {
555
+ max-width: none !important;
556
+ width: 100% !important;
557
+ margin: 0 !important;
558
+ padding: 0 !important;
559
+ background-color: white !important;
560
+ min-height: 100vh !important;
561
+ display: flex !important;
562
+ align-items: center !important;
563
+ justify-content: center !important;
564
+ }
565
+
566
+ .login-card {
567
+ background: #f8f9fa !important;
568
+ border-radius: 12px !important;
569
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1) !important;
570
+ padding: 3rem !important;
571
+ text-align: center !important;
572
+ border: 1px solid #e9ecef !important;
573
+ width: 100% !important;
574
+ max-width: 500px !important;
575
+ margin: 2rem !important;
576
+ }
577
+
578
+ /* Ensure all text is visible */
579
+ h1, h2, h3, h4, h5, h6, p, span, div, label {
580
+ color: #212529 !important;
581
+ }
582
+ """
583
+ ) as demo:
584
+ gr.HTML("""
585
+ <div class="login-card">
586
+ <div style="margin-bottom: 2rem;">
587
+ <h1 style="color: #212529; margin-bottom: 1rem; font-size: 2.5rem; font-weight: bold;">🔐 DGaze</h1>
588
+ <p style="color: #495057; font-size: 1.2rem; margin-bottom: 0.5rem;">News Verification System</p>
589
+ <p style="color: #6c757d;">Sign in for unlimited verifications</p>
590
+ </div>
591
+
592
+ <div style="margin: 2rem 0;">
593
+ <a href="/login" style="
594
+ display: inline-block;
595
+ background-color: #4263eb;
596
+ color: white;
597
+ padding: 15px 40px;
598
+ text-decoration: none;
599
+ border-radius: 8px;
600
+ font-weight: 600;
601
+ font-size: 1.1rem;
602
+ transition: all 0.3s ease;
603
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
604
+ " onmouseover="this.style.backgroundColor='#364fc7'; this.style.transform='translateY(-1px)'"
605
+ onmouseout="this.style.backgroundColor='#4263eb'; this.style.transform='translateY(0)'">
606
+ 🚀 Login with Auth0
607
+ </a>
608
+ </div>
609
+
610
+ <div style="margin: 1.5rem 0;">
611
+ <p style="color: #6c757d; font-size: 0.9rem;">Or try our free version:</p>
612
+ <a href="/trial" style="
613
+ color: #4263eb;
614
+ text-decoration: underline;
615
+ font-weight: 500;
616
+ ">
617
+ Continue with 2 Free Verifications
618
+ </a>
619
+ </div>
620
+
621
+ <p style="color: #6c757d; font-size: 0.9rem; margin-top: 1rem;">
622
+ Powered by Auth0 • Secure & Private
623
+ </p>
624
+ </div>
625
+ """)
626
+ return demo
627
+
628
+ def create_main_interface():
629
+ """Create main authenticated interface."""
630
+ with gr.Blocks(
631
+ title="DGaze - News Verification",
632
+ css="""
633
+ /* White theme - keeping original format */
634
+ html, body {
635
+ background-color: white !important;
636
+ margin: 0 !important;
637
+ padding: 0 !important;
638
+ }
639
+
640
+ .gradio-container {
641
+ background-color: white !important;
642
+ max-width: none !important;
643
+ margin: 0 !important;
644
+ padding: 20px !important;
645
+ min-height: 100vh !important;
646
+ }
647
+
648
+ .main {
649
+ background-color: white !important;
650
+ max-width: 1200px !important;
651
+ margin: 0 auto !important;
652
+ padding: 0 20px !important;
653
+ }
654
+
655
+ /* Block elements */
656
+ .block {
657
+ background-color: white !important;
658
+ }
659
+
660
+ .block.padded {
661
+ background-color: white !important;
662
+ }
663
+
664
+ /* User info section - keeping original format but white */
665
+ .user-info {
666
+ background: #f8f9fa !important;
667
+ color: #212529 !important;
668
+ padding: 1.5rem !important;
669
+ border-radius: 12px !important;
670
+ margin-bottom: 2rem !important;
671
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
672
+ border: 1px solid #e9ecef !important;
673
+ }
674
+
675
+ /* Input styling - white background, dark text */
676
+ .gr-textbox, .gr-text-input, textarea {
677
+ background-color: white !important;
678
+ border: 1px solid #e0e0e0 !important;
679
+ color: #212529 !important;
680
+ border-radius: 8px !important;
681
+ }
682
+
683
+ .gr-textbox input, .gr-textbox textarea {
684
+ background-color: white !important;
685
+ color: #212529 !important;
686
+ border: 1px solid #e0e0e0 !important;
687
+ }
688
+
689
+ /* Ensure textarea content is visible */
690
+ textarea, input[type="text"] {
691
+ background-color: white !important;
692
+ color: #212529 !important;
693
+ border: 1px solid #e0e0e0 !important;
694
+ }
695
+
696
+ /* Focus states */
697
+ .gr-textbox:focus-within, textarea:focus, input:focus {
698
+ border-color: #4263eb !important;
699
+ outline: none !important;
700
+ box-shadow: 0 0 0 2px rgba(66, 99, 235, 0.1) !important;
701
+ }
702
+
703
+ /* Button styling */
704
+ .gr-button {
705
+ background-color: #4263eb !important;
706
+ color: white !important;
707
+ border: none !important;
708
+ border-radius: 8px !important;
709
+ }
710
+
711
+ .gr-button:hover {
712
+ background-color: #364fc7 !important;
713
+ }
714
+
715
+ .gr-button[variant="secondary"] {
716
+ background-color: #6b7280 !important;
717
+ color: white !important;
718
+ }
719
+
720
+ .gr-button[variant="secondary"]:hover {
721
+ background-color: #4b5563 !important;
722
+ }
723
+
724
+ /* Form containers */
725
+ .gr-form {
726
+ background-color: #f8f9fa !important;
727
+ border: 1px solid #e9ecef !important;
728
+ border-radius: 8px !important;
729
+ padding: 16px !important;
730
+ }
731
+
732
+ /* Examples styling */
733
+ .gr-examples {
734
+ background-color: #f8f9fa !important;
735
+ border: 1px solid #e9ecef !important;
736
+ border-radius: 8px !important;
737
+ padding: 16px !important;
738
+ }
739
+
740
+ /* Labels and text */
741
+ label, .gr-form label {
742
+ color: #212529 !important;
743
+ font-weight: 500 !important;
744
+ }
745
+
746
+ /* Ensure proper text contrast everywhere */
747
+ p, span, div, h1, h2, h3, h4, h5, h6 {
748
+ color: #212529 !important;
749
+ }
750
+
751
+ /* Output HTML container */
752
+ .gr-html {
753
+ background-color: white !important;
754
+ }
755
+
756
+ /* Main title styling - keeping original format */
757
+ .main-title {
758
+ color: #212529 !important;
759
+ text-align: center !important;
760
+ font-size: 3rem !important;
761
+ font-weight: 800 !important;
762
+ margin: 2rem 0 0.5rem 0 !important;
763
+ }
764
+
765
+ /* Subtitle styling */
766
+ .subtitle {
767
+ text-align: center !important;
768
+ color: #495057 !important;
769
+ font-size: 1.2rem !important;
770
+ margin-top: 0 !important;
771
+ margin-bottom: 2rem !important;
772
+ }
773
+ """
774
+ ) as demo:
775
+ # User info section - load user data server-side using our existing get_user function
776
+ def format_user_info(request: gr.Request):
777
+ """Format user info HTML based on current user session"""
778
+ try:
779
+ # Use our existing user detection logic
780
+ user_name = get_user(request.request) if hasattr(request, 'request') else None
781
+ print(f"DEBUG: Gradio user detection result: {user_name}")
782
+
783
+ if user_name:
784
+ return f"""
785
+ <div class="user-info" id="user-section">
786
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;">
787
+ <div style="flex: 1;">
788
+ <h3 style="margin: 0; font-size: 1.3rem; color: #212529;">👋 Welcome!</h3>
789
+ <p style="margin: 5px 0 0 0; color: #495057; font-size: 1rem;">Hello, {user_name}! Unlimited verifications available 🎉</p>
790
+ </div>
791
+ <div style="margin-top: 10px;">
792
+ <a href="/logout" style="
793
+ background-color: #dc3545;
794
+ color: white;
795
+ padding: 10px 20px;
796
+ text-decoration: none;
797
+ border-radius: 8px;
798
+ font-size: 0.95rem;
799
+ font-weight: 500;
800
+ transition: background-color 0.3s ease;
801
+ " onmouseover="this.style.backgroundColor='#c82333'"
802
+ onmouseout="this.style.backgroundColor='#dc3545'">
803
+ Logout
804
+ </a>
805
+ </div>
806
+ </div>
807
+ </div>
808
+ """
809
+ else:
810
+ return """
811
+ <div class="user-info" id="user-section">
812
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;">
813
+ <div style="flex: 1;">
814
+ <h3 style="margin: 0; font-size: 1.3rem; color: #212529;">👋 Welcome!</h3>
815
+ <p style="margin: 5px 0 0 0; color: #495057; font-size: 1rem;">Please log in to continue</p>
816
+ </div>
817
+ <div style="margin-top: 10px;">
818
+ <a href="/logout" style="
819
+ background-color: #dc3545;
820
+ color: white;
821
+ padding: 10px 20px;
822
+ text-decoration: none;
823
+ border-radius: 8px;
824
+ font-size: 0.95rem;
825
+ font-weight: 500;
826
+ transition: background-color 0.3s ease;
827
+ ">
828
+ Logout
829
+ </a>
830
+ </div>
831
+ </div>
832
+ </div>
833
+ """
834
+ except Exception as e:
835
+ print(f"DEBUG: Error formatting user info: {e}")
836
+ return """
837
+ <div class="user-info" id="user-section">
838
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;">
839
+ <div style="flex: 1;">
840
+ <h3 style="margin: 0; font-size: 1.3rem; color: #212529;">👋 Welcome!</h3>
841
+ <p style="margin: 5px 0 0 0; color: #495057; font-size: 1rem;">Loading user info...</p>
842
+ </div>
843
+ <div style="margin-top: 10px;">
844
+ <a href="/logout" style="
845
+ background-color: #dc3545;
846
+ color: white;
847
+ padding: 10px 20px;
848
+ text-decoration: none;
849
+ border-radius: 8px;
850
+ font-size: 0.95rem;
851
+ font-weight: 500;
852
+ ">
853
+ Logout
854
+ </a>
855
+ </div>
856
+ </div>
857
+ </div>
858
+ """
859
+
860
+ user_info_html = gr.HTML()
861
+ demo.load(format_user_info, None, user_info_html)
862
+
863
+ # Main title - keeping original format
864
+ gr.HTML('<h1 class="main-title">DGaze</h1>')
865
+ gr.HTML('<p class="subtitle">Advanced News Verification System - Unlimited Access</p>')
866
+
867
+ # Main interface - keeping original format
868
+ with gr.Column():
869
+ input_text = gr.Textbox(
870
+ label="Enter news text or URL to verify",
871
+ placeholder="Paste news text to verify...",
872
+ lines=5,
873
+ max_lines=10
874
+ )
875
+
876
+ submit_btn = gr.Button("Check Truthfulness", variant="primary")
877
+
878
+ with gr.Column():
879
+ output_html = gr.HTML(label="Verification Results", visible=True)
880
+
881
+ # Handle submission
882
+ submit_btn.click(fn=verify_news, inputs=input_text, outputs=output_html)
883
+ input_text.submit(fn=verify_news, inputs=input_text, outputs=output_html)
884
+
885
+ # Examples - keeping original format
886
+ gr.Examples(
887
+ examples=[
888
+ ["""BREAKING: Revolutionary AI breakthrough! 🚀 Scientists at MIT have developed a new quantum AI system that can predict earthquakes with 99.7% accuracy up to 6 months in advance. The system, called "QuakeNet AI", uses quantum computing combined with machine learning to analyze seismic patterns invisible to current technology. Dr. Sarah Chen, lead researcher, claims this could save millions of lives and prevent billions in damages. The technology will be commercially available by 2026 according to insider sources. This comes just weeks after similar breakthroughs in cancer detection AI. What do you think about this amazing discovery? #AI #Earthquake #MIT #Science"""],
889
+ ["""SpaceX conducted another major test for their Starship program yesterday, with Elon Musk claiming on social media that Flight 10 is "ready to revolutionize space travel forever." The company fired up all 33 Raptor engines on their Super Heavy booster at the Starbase facility in Texas. According to various reports and this detailed article (https://www.space.com/space-exploration/launches-spacecraft/spacex-fires-up-super-heavy-booster-ahead-of-starships-10th-test-flight-video), the test was part of preparations for the upcoming 10th test flight. However, some critics argue that SpaceX is moving too fast without proper safety protocols, especially after Flight 9 experienced issues. The FAA is still investigating the previous mission where both the booster and ship were lost. Industry experts remain divided on whether this aggressive testing schedule is beneficial or dangerous for the future of commercial spaceflight. 🚀 #SpaceX #Starship #Space"""]
890
+ ],
891
+ inputs=[input_text]
892
+ )
893
+
894
+ return demo
895
+
896
+ if __name__ == "__main__":
897
+ # Create interfaces
898
+ trial_demo = create_trial_interface()
899
+ login_demo = create_login_interface()
900
+ main_demo = create_main_interface()
901
+
902
+ # Mount Gradio apps
903
+ gr.mount_gradio_app(app, trial_demo, path="/trial")
904
+ gr.mount_gradio_app(app, login_demo, path="/login-page")
905
+ gr.mount_gradio_app(app, main_demo, path="/demo")
906
+
907
+ # Run the application
908
+ uvicorn.run(
909
+ app,
910
+ host=settings.GRADIO_SERVER_NAME,
911
+ port=settings.GRADIO_SERVER_PORT,
912
+ reload=False
913
+ )
components/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ Components module for DGaze - News Verification System
3
+ """
components/verification_result.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Verification result formatting components.
3
+ Similar to MultiClaimVerificationResult.tsx in the React frontend.
4
+ """
5
+
6
+ import html
7
+ from typing import Dict, Any, List
8
+ from utils.markdown_utils import convert_markdown_to_html
9
+
10
+
11
+ def format_verification_results(data: Dict[str, Any]) -> str:
12
+ """Format verification results in HTML to match the React frontend structure."""
13
+
14
+ verification_data = data.get("data", {})
15
+ verified_claims = verification_data.get("verified_claims", [])
16
+
17
+ if not verified_claims:
18
+ return """
19
+ <div style="text-align: center; padding: 2rem; background: white; border-radius: 8px; border: 1px solid #dee2e6;">
20
+ <h3>No Claims Found</h3>
21
+ <p>No specific claims found to verify in the provided text.</p>
22
+ </div>
23
+ """
24
+
25
+ # Get summary data
26
+ summary = verification_data.get("verification_summary", {})
27
+
28
+ html_parts = []
29
+
30
+ # Header Section
31
+ html_parts.append(_format_header(summary))
32
+
33
+ # Summary Stats Grid
34
+ html_parts.append(_format_stats_grid(summary))
35
+
36
+ # Truthfulness Distribution
37
+ html_parts.append(_format_truthfulness_distribution(summary))
38
+
39
+ # Individual Claims Section
40
+ html_parts.append(_format_claims_header())
41
+
42
+ # Individual Claim Cards
43
+ for i, claim in enumerate(verified_claims, 1):
44
+ html_parts.append(_format_claim_card(claim, i))
45
+
46
+ # Processing Time Breakdown
47
+ html_parts.append(_format_processing_time(summary))
48
+
49
+ return "".join(html_parts)
50
+
51
+
52
+ def _format_header(summary: Dict[str, Any]) -> str:
53
+ """Format the header section."""
54
+ return f"""
55
+ <div style="text-align: center; margin-bottom: 2rem;">
56
+ <h1 style="font-size: 2rem; font-weight: bold; color: #212529; margin-bottom: 0.5rem;">Verification Results</h1>
57
+ <p style="color: #495057;">
58
+ Analyzed {summary.get('total_claims_found', 'N/A')} claims in {summary.get('processing_time', {}).get('total_seconds', 0):.1f} seconds
59
+ </p>
60
+ </div>
61
+ """
62
+
63
+
64
+ def _format_stats_grid(summary: Dict[str, Any]) -> str:
65
+ """Format the statistics grid."""
66
+ avg_confidence = summary.get('average_confidence', 0) * 100
67
+ verification_rate = summary.get('verification_rate', '0%')
68
+ if isinstance(verification_rate, (int, float)):
69
+ verification_rate = f"{verification_rate:.1f}%"
70
+
71
+ return f"""
72
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem;">
73
+ <div style="background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e9ecef; text-align: center;">
74
+ <div style="font-size: 2rem; font-weight: bold; color: #4263eb; margin-bottom: 0.5rem;">{summary.get('total_claims_found', 'N/A')}</div>
75
+ <div style="font-size: 0.9rem; color: #495057; font-weight: 500;">Total Claims</div>
76
+ </div>
77
+ <div style="background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e9ecef; text-align: center;">
78
+ <div style="font-size: 2rem; font-weight: bold; color: #15aabf; margin-bottom: 0.5rem;">{summary.get('successful_verifications', 'N/A')}</div>
79
+ <div style="font-size: 0.9rem; color: #495057; font-weight: 500;">Verified</div>
80
+ </div>
81
+ <div style="background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e9ecef; text-align: center;">
82
+ <div style="font-size: 2rem; font-weight: bold; color: #4263eb; margin-bottom: 0.5rem;">{avg_confidence:.0f}%</div>
83
+ <div style="font-size: 0.9rem; color: #495057; font-weight: 500;">Avg Confidence</div>
84
+ </div>
85
+ <div style="background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e9ecef; text-align: center;">
86
+ <div style="font-size: 2rem; font-weight: bold; color: #fab005; margin-bottom: 0.5rem;">{verification_rate}</div>
87
+ <div style="font-size: 0.9rem; color: #495057; font-weight: 500;">Success Rate</div>
88
+ </div>
89
+ </div>
90
+ """
91
+
92
+
93
+ def _format_truthfulness_distribution(summary: Dict[str, Any]) -> str:
94
+ """Format the truthfulness distribution section."""
95
+ truthfulness_dist = summary.get('truthfulness_distribution', {})
96
+ if not truthfulness_dist:
97
+ return ""
98
+
99
+ dist_items = []
100
+ color_map = {
101
+ 'TRUE': '#e8f5e9',
102
+ 'MOSTLY TRUE': '#e3f2fd',
103
+ 'NEUTRAL': '#fff8e1',
104
+ 'MOSTLY FALSE': '#ffebee',
105
+ 'FALSE': '#ffebee'
106
+ }
107
+
108
+ for category, count in truthfulness_dist.items():
109
+ bg_color = color_map.get(category.upper(), '#f5f5f5')
110
+ dist_items.append(f"""
111
+ <div style="text-align: center; padding: 1rem; border-radius: 8px; background: {bg_color};">
112
+ <div style="font-size: 0.9rem; font-weight: 500; color: #212529; margin-bottom: 0.5rem;">{category}</div>
113
+ <div style="font-size: 1.2rem; font-weight: bold; color: #495057;">{count}</div>
114
+ </div>
115
+ """)
116
+
117
+ return f"""
118
+ <div style="background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e9ecef; margin-bottom: 2rem;">
119
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #212529; margin-bottom: 1rem;">Truthfulness Distribution</h3>
120
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
121
+ {"".join(dist_items)}
122
+ </div>
123
+ </div>
124
+ """
125
+
126
+
127
+ def _format_claims_header() -> str:
128
+ """Format the claims section header."""
129
+ return """
130
+ <h2 style="font-size: 1.5rem; font-weight: bold; color: #212529; margin: 2rem 0 1rem 0;">Verified Claims</h2>
131
+ """
132
+
133
+
134
+ def _format_claim_card(claim: Dict[str, Any], index: int) -> str:
135
+ """Format an individual claim card."""
136
+ truthfulness = claim.get("truthfulness", "UNKNOWN").upper()
137
+ confidence = claim.get("confidence", 0)
138
+ claim_text = claim.get("claim", "").strip()
139
+ evidence = claim.get("evidence", "").strip()
140
+ explanation = claim.get("explanation", "").strip()
141
+ sources = claim.get("sources", [])
142
+
143
+ # Status color
144
+ status_colors = {
145
+ 'TRUE': 'background: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9;',
146
+ 'MOSTLY TRUE': 'background: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb;',
147
+ 'NEUTRAL': 'background: #fff8e1; color: #ff8f00; border: 1px solid #ffecb3;',
148
+ 'MOSTLY FALSE': 'background: #ffebee; color: #c62828; border: 1px solid #ffcdd2;',
149
+ 'FALSE': 'background: #ffebee; color: #c62828; border: 1px solid #ffcdd2;'
150
+ }
151
+ status_style = status_colors.get(truthfulness, 'background: #f5f5f5; color: #424242; border: 1px solid #e0e0e0;')
152
+
153
+ # Confidence color
154
+ conf_color = '#1565c0' if confidence >= 0.8 else '#ff8f00' if confidence >= 0.6 else '#c62828'
155
+
156
+ # Check if we have valid evidence separate from explanation
157
+ has_valid_evidence = (evidence and
158
+ evidence.strip() and
159
+ evidence.lower() != 'evidence' and
160
+ evidence != '[object Object]' and
161
+ '[object Object]' not in evidence and
162
+ evidence.strip() not in ['# EVIDENCE', 'EVIDENCE', 'Evidence'] and
163
+ len(evidence.strip()) > 20 and
164
+ 'Evidence analysis is included in the explanation section' not in evidence)
165
+
166
+ # Format sources
167
+ sources_html = _format_sources(sources)
168
+
169
+ # Convert markdown to HTML for explanation and evidence
170
+ explanation_html = convert_markdown_to_html(str(explanation)) if explanation else "No analysis available for this claim."
171
+ evidence_html = convert_markdown_to_html(str(evidence)) if has_valid_evidence else ""
172
+
173
+ # Only first claim is expanded by default
174
+ is_expanded = (index == 1)
175
+
176
+ return f"""
177
+ <details {"open" if is_expanded else ""} style="background: white; border: 1px solid #dee2e6; border-radius: 8px; margin-bottom: 1.5rem; overflow: hidden; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);">
178
+ <summary style="padding: 1.5rem; cursor: pointer; background: white; color: #212529; border-bottom: 1px solid #e9ecef; list-style: none;">
179
+ <div style="display: flex; align-items: center; margin-bottom: 1rem;">
180
+ <span style="display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; border-radius: 50%; background: #4263eb; color: white !important; font-size: 0.9rem; font-weight: 500; margin-right: 0.75rem; text-align: center; line-height: 1;">
181
+ <span style="color: white !important;">{index}</span>
182
+ </span>
183
+ <h3 style="font-size: 1.1rem; font-weight: 600; color: #212529; margin: 0;">
184
+ Claim {index}
185
+ </h3>
186
+ </div>
187
+
188
+ <div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;">
189
+ <span style="padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; font-weight: 500; {status_style}">
190
+ {truthfulness}
191
+ </span>
192
+ <div style="display: flex; align-items: center; gap: 0.25rem;">
193
+ <span style="font-weight: 600; font-size: 0.875rem; color: {conf_color};">
194
+ {confidence * 100:.0f}%
195
+ </span>
196
+ <span style="font-size: 0.875rem; color: #495057;">confidence</span>
197
+ </div>
198
+ </div>
199
+
200
+ <p style="color: #212529; font-size: 1rem; line-height: 1.6; margin: 0 0 1rem 0;">{claim_text}</p>
201
+
202
+ <div style="display: flex; align-items: center; color: #4263eb; font-weight: 500; font-size: 0.9rem;">
203
+ <span style="margin-right: 0.5rem;">{'Hide Evidence' if is_expanded else 'Show Evidence'}</span>
204
+ <svg style="width: 1rem; height: 1rem; fill: none; stroke: #4263eb; transition: transform 0.2s; transform: {'rotate(180deg)' if is_expanded else 'rotate(0deg)'};" viewBox="0 0 24 24" stroke-width="2">
205
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
206
+ </svg>
207
+ </div>
208
+ </summary>
209
+
210
+ <!-- Evidence and Analysis Section -->
211
+ <div class="claim-content" style="padding: 1.5rem; background: #f8f9fa;">
212
+ <!-- Add CSS to ensure all text is visible -->
213
+ <style>
214
+ .claim-content * {{
215
+ color: #212529 !important;
216
+ }}
217
+ .claim-content strong {{
218
+ color: #212529 !important;
219
+ font-weight: 600 !important;
220
+ }}
221
+ .claim-content em {{
222
+ color: #495057 !important;
223
+ }}
224
+ .claim-content h1, .claim-content h2, .claim-content h3, .claim-content h4, .claim-content h5, .claim-content h6 {{
225
+ color: #212529 !important;
226
+ }}
227
+ .claim-content p {{
228
+ color: #495057 !important;
229
+ }}
230
+ /* Fix Confidence Notes: text color specifically */
231
+ .claim-content p:contains("Confidence Notes:") {{
232
+ color: #212529 !important;
233
+ }}
234
+ .claim-content p strong:contains("Confidence Notes:") {{
235
+ color: #212529 !important;
236
+ }}
237
+ </style>
238
+
239
+ <!-- Analysis Section -->
240
+ {f'''
241
+ <div style="margin-bottom: 1.5rem;">
242
+ <h4 style="font-size: 1rem; font-weight: 600; color: #212529; margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.5rem;">
243
+ <svg style="width: 1.25rem; height: 1.25rem; fill: none; stroke: #4263eb;" viewBox="0 0 24 24" stroke-width="2">
244
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
245
+ </svg>
246
+ Analysis
247
+ </h4>
248
+ <div style="background: white; padding: 1rem; border-radius: 6px; border: 1px solid #dee2e6;">
249
+ <div style="color: #495057; line-height: 1.6;">{explanation_html}</div>
250
+ </div>
251
+ </div>
252
+ ''' if explanation else ''}
253
+
254
+ <!-- Evidence Section -->
255
+ <div style="margin-bottom: 1.5rem;">
256
+ <h4 style="font-size: 1rem; font-weight: 600; color: #212529; margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.5rem;">
257
+ <svg style="width: 1.25rem; height: 1.25rem; fill: none; stroke: #15aabf;" viewBox="0 0 24 24" stroke-width="2">
258
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
259
+ </svg>
260
+ Evidence
261
+ </h4>
262
+ <div style="background: white; padding: 1rem; border-radius: 6px; border: 1px solid #dee2e6;">
263
+ {f'<div style="color: #495057; line-height: 1.6;">{evidence_html}</div>' if has_valid_evidence else '''
264
+ <div style="text-align: center; padding: 1rem; color: #868e96; font-style: italic;">
265
+ <svg style="width: 2rem; height: 2rem; margin: 0 auto 0.5rem; fill: none; stroke: #adb5bd;" viewBox="0 0 24 24" stroke-width="2">
266
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
267
+ </svg>
268
+ <p style="color: #868e96; margin: 0.5rem 0;">Detailed evidence analysis is included in the explanation above.</p>
269
+ <p style="font-size: 0.75rem; margin-top: 0.25rem; color: #868e96;">This claim's verification is based on the comprehensive analysis provided.</p>
270
+ </div>
271
+ '''}
272
+ </div>
273
+ </div>
274
+
275
+ {sources_html}
276
+ </div>
277
+ </details>
278
+ """
279
+
280
+
281
+ def _format_sources(sources: List) -> str:
282
+ """Format the sources section."""
283
+ if not sources:
284
+ return ""
285
+
286
+ source_links = []
287
+ for j, source in enumerate(sources[:3], 1):
288
+ if source:
289
+ # Handle both string URLs and source objects
290
+ if isinstance(source, str):
291
+ safe_url = html.escape(source)
292
+ # Extract domain name for title
293
+ try:
294
+ domain = source.split('/')[2] if '/' in source and len(source.split('/')) > 2 else source[:30]
295
+ safe_title = html.escape(domain)
296
+ except:
297
+ safe_title = html.escape(source[:30])
298
+ else:
299
+ safe_url = html.escape(source.get('url', ''))
300
+ safe_title = html.escape(source.get('title', 'Source'))
301
+
302
+ source_links.append(f"""
303
+ <a href="{safe_url}" target="_blank" style="display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; text-decoration: none; color: #4263eb; font-size: 0.875rem; font-weight: 500; transition: all 0.2s; margin-bottom: 0.5rem;" onmouseover="this.style.background='#e7f1ff'; this.style.borderColor='#4263eb';" onmouseout="this.style.background='#f8f9fa'; this.style.borderColor='#dee2e6';">
304
+ <svg style="width: 1rem; height: 1rem; fill: none; stroke: #4263eb;" viewBox="0 0 24 24" stroke-width="2">
305
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
306
+ </svg>
307
+ {safe_title}
308
+ </a>
309
+ """)
310
+
311
+ return f"""
312
+ <div style="margin-top: 1.5rem;">
313
+ <h4 style="font-size: 1rem; font-weight: 600; color: #212529; margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.5rem;">
314
+ <svg style="width: 1.25rem; height: 1.25rem; fill: none; stroke: #4263eb;" viewBox="0 0 24 24" stroke-width="2">
315
+ <path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
316
+ </svg>
317
+ Sources ({len(sources)})
318
+ </h4>
319
+ <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">{"".join(source_links)}</div>
320
+ </div>
321
+ """
322
+
323
+
324
+ def _format_processing_time(summary: Dict[str, Any]) -> str:
325
+ """Format the processing time breakdown section."""
326
+ step_breakdown = summary.get('processing_time', {}).get('step_breakdown', {})
327
+ if not step_breakdown:
328
+ return ""
329
+
330
+ breakdown_items = []
331
+ for step, time in step_breakdown.items():
332
+ step_name = step.replace('_', ' ').title()
333
+ breakdown_items.append(f"""
334
+ <div style="text-align: center; padding: 1rem; border-radius: 8px; background: #f8f9fa;">
335
+ <div style="font-size: 1.1rem; font-weight: bold; color: #4263eb; margin-bottom: 0.25rem;">{time:.1f}s</div>
336
+ <div style="font-size: 0.875rem; color: #495057;">{step_name}</div>
337
+ </div>
338
+ """)
339
+
340
+ return f"""
341
+ <div style="background: white; padding: 1.5rem; border-radius: 8px; border: 1px solid #e9ecef; margin-top: 2rem;">
342
+ <h3 style="font-size: 1.2rem; font-weight: 600; color: #212529; margin-bottom: 1rem;">Processing Time Breakdown</h3>
343
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
344
+ {"".join(breakdown_items)}
345
+ </div>
346
+ </div>
347
+ """
config/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ Configuration module for DGaze
3
+ """
config/settings.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for DGaze Gradio application.
3
+ Loads settings from environment variables and .env file.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ from dotenv import load_dotenv
9
+
10
+ # Load environment variables from .env file
11
+ load_dotenv()
12
+
13
+ class Settings:
14
+ """Application settings loaded from environment variables."""
15
+
16
+ def __init__(self):
17
+ # Backend API Configuration - MANDATORY from .env
18
+ self.API_BASE_URL = self._get_required_env("API_BASE_URL")
19
+ self.API_ENDPOINT = f"{self.API_BASE_URL}/api/verify"
20
+ self.HEALTH_ENDPOINT = f"{self.API_BASE_URL}/api/health"
21
+
22
+ # Auth0 Configuration - MANDATORY for authentication
23
+ self.AUTH0_DOMAIN = self._get_required_env("AUTH0_DOMAIN")
24
+ self.AUTH0_CLIENT_ID = self._get_required_env("AUTH0_CLIENT_ID")
25
+ self.AUTH0_CLIENT_SECRET = self._get_required_env("AUTH0_CLIENT_SECRET")
26
+ self.AUTH0_AUDIENCE = self._get_required_env("AUTH0_AUDIENCE")
27
+
28
+ # Gradio Application Configuration
29
+ self.GRADIO_SERVER_NAME = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")
30
+ self.GRADIO_SERVER_PORT = int(os.getenv("GRADIO_SERVER_PORT", "3000"))
31
+ self.GRADIO_SHARE = os.getenv("GRADIO_SHARE", "false").lower() == "true"
32
+
33
+ # Health Check Configuration
34
+ self.HEALTH_CHECK_ENABLED = os.getenv("HEALTH_CHECK_ENABLED", "true").lower() == "true"
35
+ self.HEALTH_CHECK_INTERVAL = int(os.getenv("HEALTH_CHECK_INTERVAL", "30"))
36
+
37
+ # Security
38
+ self.SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-for-sessions")
39
+
40
+ # Logging Configuration
41
+ self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
42
+ self.DEBUG = os.getenv("DEBUG", "false").lower() == "true"
43
+
44
+ # Rate Limiting
45
+ self.RATE_LIMIT_ENABLED = os.getenv("RATE_LIMIT_ENABLED", "true").lower() == "true"
46
+ self.RATE_LIMIT_REQUESTS = int(os.getenv("RATE_LIMIT_REQUESTS", "100"))
47
+ self.RATE_LIMIT_WINDOW = int(os.getenv("RATE_LIMIT_WINDOW", "3600"))
48
+
49
+ # Cache Configuration
50
+ self.CACHE_ENABLED = os.getenv("CACHE_ENABLED", "true").lower() == "true"
51
+ self.CACHE_TTL = int(os.getenv("CACHE_TTL", "3600"))
52
+
53
+ # OAuth Configuration - Use from .env if provided, otherwise build dynamically
54
+ self.OAUTH_REDIRECT_URI = os.getenv("AUTH0_REDIRECT_URI")
55
+ if not self.OAUTH_REDIRECT_URI:
56
+ self.OAUTH_REDIRECT_URI = f"http://localhost:{self.GRADIO_SERVER_PORT}/auth"
57
+ if self.GRADIO_SERVER_NAME != "localhost" and self.GRADIO_SERVER_NAME != "127.0.0.1":
58
+ self.OAUTH_REDIRECT_URI = f"http://{self.GRADIO_SERVER_NAME}:{self.GRADIO_SERVER_PORT}/auth"
59
+
60
+ def _get_required_env(self, var_name: str) -> str:
61
+ """Get a required environment variable or exit with error."""
62
+ value = os.getenv(var_name)
63
+ if not value:
64
+ print(f"ERROR: Required environment variable '{var_name}' not found in .env file!")
65
+ print(f"Please add '{var_name}=<your-value>' to your .env file")
66
+ sys.exit(1)
67
+ return value
68
+
69
+ # Create a settings instance
70
+ settings = Settings()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ requests>=2.25.0
3
+ python-dotenv
4
+ fastapi>=0.100.0
5
+ uvicorn[standard]>=0.23.0
6
+ authlib>=1.2.0
7
+ itsdangerous>=2.1.0
8
+ python-multipart>=0.0.6
spaces_README.md ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: DGaze News Verification
3
+ emoji: 🔍
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 4.0.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ short_description: Advanced news verification system using AI with Auth0 authentication
12
+ ---
13
+
14
+ # DGaze - Advanced News Verification System
15
+
16
+ An advanced AI-powered news verification system with authentication that helps identify potentially misleading or false information.
17
+
18
+ ## Features
19
+
20
+ - **🔐 Authentication**: Secure Auth0 integration with trial and full access modes
21
+ - **🆓 Free Trial**: 2 free verifications for unauthenticated users
22
+ - **🔍 Real-time Analysis**: Instant verification of news articles and URLs
23
+ - **📊 Credibility Scoring**: Detailed credibility scores for news content
24
+ - **🌐 Source Verification**: Check the reliability of news sources
25
+ - **📱 Multiple Interfaces**: Trial, login, and authenticated user interfaces
26
+ - **🎨 Beautiful UI**: Professional gradient design with responsive layout
27
+
28
+ ## How to Use
29
+
30
+ ### Free Trial (No Login Required)
31
+ 1. Visit the **trial interface** at `/trial`
32
+ 2. **Paste news text** or **URL** in the input box
33
+ 3. **Click "Verify News"** to analyze the content
34
+ 4. **Get detailed results** including credibility score and analysis
35
+ 5. Enjoy **2 free verifications** per session
36
+
37
+ ### Full Access (With Login)
38
+ 1. **Click "Sign In"** to authenticate via Auth0
39
+ 2. Get **unlimited verifications**
40
+ 3. Access to **premium features**
41
+ 4. **Session persistence** across visits
42
+
43
+ ## Configuration
44
+
45
+ This Space requires the following environment variables to be set in the Space settings:
46
+
47
+ ### Required Environment Variables
48
+
49
+ ```bash
50
+ # Backend API Configuration (REQUIRED)
51
+ API_BASE_URL=your-backend-api-url
52
+
53
+ # Auth0 Configuration (REQUIRED for authentication)
54
+ AUTH0_DOMAIN=your-auth0-domain
55
+ AUTH0_CLIENT_ID=your-auth0-client-id
56
+ AUTH0_CLIENT_SECRET=your-auth0-client-secret
57
+ AUTH0_AUDIENCE=your-auth0-audience
58
+
59
+ # Security
60
+ SECRET_KEY=your-session-secret-key
61
+
62
+ # Optional: App Configuration
63
+ GRADIO_SERVER_NAME=0.0.0.0
64
+ GRADIO_SERVER_PORT=7860
65
+ DEBUG=false
66
+ ```
67
+
68
+ ### Setting Environment Variables
69
+ 1. Go to your **Space Settings**
70
+ 2. Find **"Repository secrets"** or **"Variables and secrets"**
71
+ 3. Add each variable with **"Private"** visibility for sensitive data
72
+ 4. **Restart the Space** after adding variables
73
+
74
+ ## Try the Examples
75
+
76
+ We've included sample news articles to demonstrate the verification capabilities:
77
+ - Suspicious breakthrough claims with questionable sources
78
+ - Real news with proper sourcing and verification
79
+
80
+ ## Architecture
81
+
82
+ This application uses:
83
+ - **FastAPI**: Backend API framework with Auth0 integration
84
+ - **Gradio**: Interactive web interface with multiple mounted apps
85
+ - **Auth0**: Secure authentication and user management
86
+ - **Session Management**: Trial tracking and user state management
87
+ - **Modular Design**: Clean separation of concerns with api/, config/, and components/ modules
88
+
89
+ ## API Integration
90
+
91
+ The app connects to a backend API for news verification. Make sure your backend API supports:
92
+ - `POST /api/verify` - Main verification endpoint
93
+ - `GET /api/health` - Health check endpoint
94
+
95
+ ## Local Development
96
+
97
+ To run locally:
98
+ 1. Clone this repository
99
+ 2. Install dependencies: `pip install -r requirements.txt`
100
+ 3. Set up your `.env` file with the required variables
101
+ 4. Run: `python app.py`
102
+ 5. Access at `http://localhost:3000`
103
+
104
+ ## Built With
105
+
106
+ - **Gradio**: Interactive web interface
107
+ - **FastAPI**: Modern, fast web framework
108
+ - **Auth0**: Authentication platform
109
+ - **Python**: Backend processing
110
+ - **Hugging Face Spaces**: Hosting platform
111
+
112
+ ---
113
+
114
+ *Built with ❤️ for fighting misinformation and promoting media literacy*
utils/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ Utils package for DGaze Gradio application.
3
+ """
utils/markdown_utils.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Markdown utilities for converting markdown to HTML.
3
+ """
4
+
5
+ import re
6
+ import html
7
+
8
+
9
+ def convert_markdown_to_html(markdown_text: str) -> str:
10
+ """
11
+ Convert markdown text to HTML with basic formatting.
12
+
13
+ Args:
14
+ markdown_text: The markdown text to convert
15
+
16
+ Returns:
17
+ HTML formatted string
18
+ """
19
+ if not markdown_text or not isinstance(markdown_text, str):
20
+ return ""
21
+
22
+ # Escape HTML special characters first
23
+ text = html.escape(markdown_text.strip())
24
+
25
+ # Convert markdown to HTML
26
+ # Headers
27
+ text = re.sub(r'^### (.*?)$', r'<h3>\1</h3>', text, flags=re.MULTILINE)
28
+ text = re.sub(r'^## (.*?)$', r'<h2>\1</h2>', text, flags=re.MULTILINE)
29
+ text = re.sub(r'^# (.*?)$', r'<h1>\1</h1>', text, flags=re.MULTILINE)
30
+
31
+ # Bold text
32
+ text = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', text)
33
+ text = re.sub(r'__(.*?)__', r'<strong>\1</strong>', text)
34
+
35
+ # Special handling for "Confidence Notes:" to ensure proper styling
36
+ text = re.sub(r'<strong>Confidence Notes:</strong>', r'<strong style="color: #212529 !important;">Confidence Notes:</strong>', text)
37
+ text = re.sub(r'\*\*Confidence Notes:\*\*', r'<strong style="color: #212529 !important;">Confidence Notes:</strong>', text)
38
+
39
+ # Italic text
40
+ text = re.sub(r'\*(.*?)\*', r'<em>\1</em>', text)
41
+ text = re.sub(r'_(.*?)_', r'<em>\1</em>', text)
42
+
43
+ # Code blocks (triple backticks)
44
+ text = re.sub(r'```(.*?)```', r'<pre><code>\1</code></pre>', text, flags=re.DOTALL)
45
+
46
+ # Inline code
47
+ text = re.sub(r'`(.*?)`', r'<code>\1</code>', text)
48
+
49
+ # Convert line breaks to paragraphs
50
+ paragraphs = text.split('\n\n')
51
+ html_paragraphs = []
52
+
53
+ for paragraph in paragraphs:
54
+ paragraph = paragraph.strip()
55
+ if paragraph:
56
+ # Check if it's already wrapped in HTML tags
57
+ if not (paragraph.startswith('<') and paragraph.endswith('>')):
58
+ # Replace single line breaks with <br>
59
+ paragraph = paragraph.replace('\n', '<br>')
60
+ paragraph = f'<p>{paragraph}</p>'
61
+ html_paragraphs.append(paragraph)
62
+
63
+ return '\n'.join(html_paragraphs)