Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- api/__init__.py +3 -0
- api/client.py +86 -0
- app.py +913 -0
- components/__init__.py +3 -0
- components/verification_result.py +347 -0
- config/__init__.py +3 -0
- config/settings.py +70 -0
- requirements.txt +8 -0
- spaces_README.md +114 -0
- utils/__init__.py +3 -0
- utils/markdown_utils.py +63 -0
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)
|