Spaces:
Sleeping
Sleeping
Delete app.py
Browse files
app.py
DELETED
@@ -1,913 +0,0 @@
|
|
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 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|