from __future__ import annotations import gradio as gr from dotenv import load_dotenv from components import subscriptions import utils import theme import os load_dotenv() # Version marker for cache busting __version__ = "3.2.0" # Debug: Check OAuth environment variables print("=== OAuth Debug Info (Gradio 6.0.1) ===") print(f"OAUTH_CLIENT_ID set: {bool(os.getenv('OAUTH_CLIENT_ID'))}") print(f"OAUTH_CLIENT_SECRET set: {bool(os.getenv('OAUTH_CLIENT_SECRET'))}") print(f"OAUTH_SCOPES: {os.getenv('OAUTH_SCOPES')}") print(f"SPACE_HOST: {os.getenv('SPACE_HOST', 'not set')}") print("========================================") def main(): # Custom CSS for dataset cards custom_css = theme.css + """ /* Hide any share buttons globally */ .share-button, [class*="share"], button[title="Share"], .icon-buttons, .image-button-group { display: none !important; } /* Hero section styling */ .hero-section { justify-content: center !important; padding: 1.5rem 1rem 0.5rem !important; background: transparent !important; } .hero-logo { max-width: 200px !important; background: transparent !important; border: none !important; box-shadow: none !important; margin: 0 auto !important; } .hero-logo img { object-fit: contain !important; } .hero-subtitle { font-size: 1rem !important; color: var(--text-secondary, #98989d) !important; margin: 0 0 1rem 0 !important; font-weight: 400 !important; text-align: center !important; } /* Force center the subtitle container */ .hero-subtitle-wrapper { width: 100% !important; display: flex !important; justify-content: center !important; } .hero-subtitle-wrapper + div, .hero-subtitle-wrapper ~ div { width: 100%; } /* Dataset card styling */ .dataset-card-group { background: var(--bg-card, #1c1c1e) !important; border: 1px solid rgba(255,255,255,0.06) !important; border-radius: 16px !important; padding: 1.25rem 1.5rem !important; margin-bottom: 0.75rem !important; transition: all 0.2s ease !important; } .dataset-card-group:hover { border-color: rgba(255,255,255,0.12) !important; background: rgba(255,255,255,0.02) !important; } /* Card header */ .card-header-row { margin-bottom: 0.5rem !important; gap: 1rem !important; align-items: center !important; } .card-title-col { min-width: 0 !important; } .card-price-col { flex-shrink: 0 !important; min-width: auto !important; } .dataset-title { font-size: 1.125rem !important; font-weight: 600 !important; margin: 0 !important; color: var(--text-primary, #f5f5f7) !important; line-height: 1.4 !important; } .dataset-id { font-size: 0.6875rem !important; color: var(--text-tertiary, #6e6e73) !important; margin: 0.25rem 0 0 0 !important; font-family: 'JetBrains Mono', monospace !important; background: rgba(255,255,255,0.04) !important; padding: 0.15rem 0.4rem !important; border-radius: 4px !important; display: inline-block !important; } .dataset-desc { font-size: 0.875rem !important; color: var(--text-secondary, #98989d) !important; line-height: 1.5 !important; margin: 0 0 1rem 0 !important; } /* Price badges */ .price-badge { display: inline-flex !important; align-items: center !important; justify-content: center !important; padding: 0.4rem 0.9rem !important; border-radius: 20px !important; font-size: 0.8125rem !important; font-weight: 600 !important; white-space: nowrap !important; } .price-badge.paid { background: rgba(255,255,255,0.08) !important; color: var(--text-primary, #f5f5f7) !important; } .price-badge.free { background: rgba(52, 199, 89, 0.15) !important; color: #32d74b !important; } /* Card footer */ .card-footer-row { padding-top: 0.875rem !important; margin-top: 0.25rem !important; border-top: 1px solid rgba(255,255,255,0.06) !important; gap: 0 !important; } .card-footer-row > div { flex: 1 !important; } /* Subscribe buttons */ .subscribe-btn, .subscribe-btn button { width: 100% !important; background: linear-gradient(180deg, #f7d052 0%, #e9b93a 100%) !important; color: #1a1a1a !important; border: none !important; border-radius: 10px !important; padding: 0.75rem 1.5rem !important; font-weight: 600 !important; font-size: 0.9375rem !important; transition: all 0.15s ease !important; cursor: pointer !important; box-shadow: 0 1px 2px rgba(0,0,0,0.1), inset 0 1px 0 rgba(255,255,255,0.2) !important; } .subscribe-btn:hover, .subscribe-btn:hover button { background: linear-gradient(180deg, #fad965 0%, #f0c243 100%) !important; box-shadow: 0 2px 8px rgba(247, 208, 82, 0.3), inset 0 1px 0 rgba(255,255,255,0.25) !important; } .subscribe-btn:active, .subscribe-btn:active button { background: linear-gradient(180deg, #e5b835 0%, #d4a52e 100%) !important; box-shadow: inset 0 1px 2px rgba(0,0,0,0.1) !important; } /* Login hint */ .login-hint { font-size: 0.8125rem !important; color: var(--text-tertiary, #6e6e73) !important; font-style: italic !important; margin: 0 !important; padding: 0.5rem 0 !important; text-align: center !important; } /* Tighten up Gradio's default spacing */ .gradio-container .gr-group { gap: 0 !important; } .gradio-container .gr-block { padding: 0 !important; } """ with gr.Blocks(title="DataPass", theme=theme.get_theme(), css=custom_css) as demo: # State to track logged-in username (triggers re-render when changed) logged_in_user = gr.State(value=None) # Hero Section with logo and subtitle with gr.Row(elem_classes="hero-section"): with gr.Column(): gr.Image( value="datapass_logo.png", show_label=False, show_download_button=False, show_fullscreen_button=False, show_share_button=False, container=False, height=120, elem_classes="hero-logo" ) gr.HTML('
Your pass to private datasets.
') # Simple auth row with Gradio 6 LoginButton with gr.Row(): user_status = gr.Markdown() gr.LoginButton(size="sm") # Status message for subscription actions subscribe_status = gr.Markdown() # Main Tabs with gr.Tabs() as tabs: # How It Works Tab with gr.Tab("How It Works", id="about"): gr.HTML("""❌ You lose control of your data
✅ Data stays private on HF
When a subscriber asks a question, here's what happens behind the scenes:
The user never sees the SQL, never touches DuckDB, never accesses HF directly.
{dataset_id}
''') with gr.Column(scale=1, min_width=100, elem_classes="card-price-col"): gr.HTML(f'{price_text}') # Description gr.HTML(f'{description}
') # Footer with action with gr.Row(elem_classes="card-footer-row"): if username: # User is logged in - show subscribe button btn = gr.Button( button_text, variant="primary", size="lg", key=f"subscribe-btn-{dataset_id}", elem_classes="subscribe-btn" ) # Define handler with frozen variables (critical for loops!) def make_subscribe_handler(ds_id, ds_name, ds_free): def handler(p: gr.OAuthProfile | None, t: gr.OAuthToken | None): if not p: return "Please sign in first to subscribe." hf_token = t.token if t else None if ds_free: result = utils.subscribe_free(ds_id, p.username, hf_token) if "error" in result: return f"Error: {result['error']}" return f"Your 24-hour DataPass for **{ds_name}** is active! Go to **My Subscriptions** to get your access token." else: result = utils.create_checkout_session(ds_id, p.username, hf_token) if "error" in result: return f"Error: {result['error']}" if "checkout_url" in result: return f"[Click here to complete payment]({result['checkout_url']})" return "Error creating checkout session." return handler btn.click( fn=make_subscribe_handler(dataset_id, display_name, is_free), outputs=[subscribe_status] ) else: # User not logged in - show hint gr.HTML('Sign in with Hugging Face to subscribe
') # Subscriptions Tab with gr.Tab("My Subscriptions", id="subscriptions"): subscriptions_container = gr.HTML() # Footer gr.HTML("""DataPass — Your pass to private datasets.
Powered by Hugging Face & MCP