# app.py import logging import sys import os from constants import FONT_FAMILY_SHORT logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') logger = logging.getLogger(__name__) # Force rebuild to fetch latest data from GitHub repo logger.info("Starting OpenHands Index application") # Setup mock data before anything else try: from setup_data import setup_mock_data, start_background_refresh, CACHE_TTL_SECONDS setup_mock_data() logger.info("Data setup completed successfully") # Start background refresh scheduler (checks for new data every hour) start_background_refresh() logger.info(f"Background refresh scheduler started (interval: {CACHE_TTL_SECONDS}s)") except Exception as e: logger.error(f"Error during data setup: {e}", exc_info=True) logger.warning("Continuing with app startup despite error") import gradio as gr import urllib.parse from huggingface_hub import HfApi from config import LEADERBOARD_PATH, LOCAL_DEBUG from content import css from main_page import build_page as build_main_page from bug_fixing import build_page as build_bug_fixing_page from app_creation import build_page as build_app_creation_page from frontend_development import build_page as build_frontend_page from test_generation import build_page as build_test_generation_page from information_gathering import build_page as build_information_gathering_page from about import build_page as build_about_page logger.info(f"All modules imported (LOCAL_DEBUG={LOCAL_DEBUG})") api = HfApi() LOGO_PATH = "assets/logo.svg" # PostHog analytics (client-side) POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY", "phc_ERBPfEE0gwNgkOBsxbHr1wh9mBsYcsw4zSLtvdA9RFg") posthog_script = f""" """ # JavaScripts scroll_script = """ """ redirect_script = """ """ # JavaScript to fix navigation links to use relative paths (avoids domain mismatch when behind proxy) fix_nav_links_script = """ """ tooltip_script = """ """ # JavaScript to handle dark mode for Plotly charts and OpenHands logos dark_mode_script = """ """ # --- Theme Definition --- # Color scheme aligned with OpenHands brand (from openhands-ui/tokens.css) # Primary: Yellow (#FFE165), Neutral: Grey scale, Accents: Green (#BCFF8C), Red (#FF684E) theme = gr.themes.Base( # Primary hue - Yellow (OpenHands brand color) primary_hue=gr.themes.Color( c50="#FFFCF0", c100="#FFF3C0", c200="#FFEEAA", c300="#FFEA92", c400="#FFE57B", c500="#FFE165", c600="#DCC257", c700="#BBA54A", c800="#99873D", c900="#76682F", c950="#534921" ), # Secondary hue - Green accent (from OpenHands palette) secondary_hue=gr.themes.Color( c50="#F8FFF4", c100="#E4FFD0", c200="#DAFFBF", c300="#CFFFAD", c400="#C6FF9D", c500="#BCFF8C", c600="#A2DC79", c700="#8ABB67", c800="#719954", c900="#577641", c950="#3D532E" ), # Neutral hue - Grey scale (OpenHands dark mode colors) neutral_hue=gr.themes.Color( c50="#F7F8FB", c100="#EBEDF3", c200="#D4D8E7", c300="#B1B9D3", c400="#82889B", c500="#525662", c600="#3A3C45", c700="#2F3137", c800="#222328", c900="#18191C", c950="#0D0D0F" ), font=[FONT_FAMILY_SHORT, 'sans-serif'], font_mono=['monospace'], ).set( body_text_color='*neutral_950', body_text_color_subdued='*neutral_700', body_text_color_subdued_dark='*neutral_300', body_text_color_dark='*neutral_50', background_fill_primary='*neutral_50', background_fill_primary_dark='*neutral_900', background_fill_secondary='*neutral_100', background_fill_secondary_dark='*neutral_800', border_color_accent='*primary_500', border_color_accent_subdued='*neutral_300', border_color_accent_subdued_dark='*neutral_600', color_accent='*primary_500', color_accent_soft='*neutral_200', color_accent_soft_dark='*neutral_800', link_text_color='*neutral_700', link_text_color_dark='*neutral_300', link_text_color_active_dark='*primary_500', link_text_color_hover_dark='*primary_400', link_text_color_visited_dark='*neutral_400', table_even_background_fill='*neutral_100', table_even_background_fill_dark='*neutral_800', button_primary_background_fill='*primary_500', button_primary_background_fill_dark='*primary_500', button_primary_background_fill_hover='*primary_400', button_primary_background_fill_hover_dark='*primary_400', button_secondary_background_fill='*secondary_500', button_secondary_background_fill_dark='*secondary_600', button_secondary_text_color='*neutral_900', button_secondary_text_color_dark='*neutral_900', block_title_text_color='*neutral_900', button_primary_text_color='*neutral_900', block_title_text_color_dark='*neutral_50', button_primary_text_color_dark='*neutral_900', block_border_color='*neutral_300', block_border_color_dark='*neutral_700', block_background_fill_dark='*neutral_900', block_background_fill='*neutral_50', checkbox_label_text_color='*neutral_900', checkbox_label_background_fill='*neutral_200', checkbox_label_background_fill_dark='*neutral_700', checkbox_background_color_selected='*primary_500', checkbox_background_color_selected_dark='*primary_500', ) try: with open(LOGO_PATH, "r") as f: svg_content = f.read() encoded_svg = urllib.parse.quote(svg_content) home_icon_data_uri = f"data:image/svg+xml,{encoded_svg}" except FileNotFoundError: logger.warning(f"Home icon file not found at {LOGO_PATH}") home_icon_data_uri = "none" # Load dark mode logo (PNG) LOGO_DARK_PATH = "assets/logo-dark.png" try: import base64 with open(LOGO_DARK_PATH, "rb") as f: dark_logo_content = f.read() encoded_dark_logo = base64.b64encode(dark_logo_content).decode('utf-8') home_icon_dark_data_uri = f"data:image/png;base64,{encoded_dark_logo}" except FileNotFoundError: logger.warning(f"Dark mode logo file not found at {LOGO_DARK_PATH}") home_icon_dark_data_uri = home_icon_data_uri # Fallback to light logo # --- This is the final CSS --- final_css = css + f""" /* --- Find the "Home" button and replace its text with an icon --- */ .nav-holder nav a[href$="/"] {{ display: none !important; }} .nav-holder nav a[href*="/home"] {{ grid-row: 1 !important; grid-column: 1 !important; justify-self: start !important; display: flex !important; align-items: center !important; justify-content: center !important; /* 2. Hide the original "Home" text */ font-size: 0 !important; text-indent: -9999px; /* 3. Apply the icon as the background (light mode) */ background-image: url("{home_icon_data_uri}") !important; background-size: contain !important; background-repeat: no-repeat !important; background-position: center !important; width: 240px !important; height: 50px !important; padding: 0 !important; border: none !important; outline: none !important; }} /* Dark mode logo override */ .dark .nav-holder nav a[href*="/home"] {{ background-image: url("{home_icon_dark_data_uri}") !important; }} """ # --- Gradio App Definition --- logger.info("Creating Gradio application") demo = gr.Blocks( theme=theme, css=final_css, head=posthog_script + scroll_script + redirect_script + fix_nav_links_script + tooltip_script + dark_mode_script, title="OpenHands Index", ) with demo.route("Home", "/home"): build_main_page() with demo.route("Issue Resolution", "/issue-resolution"): build_bug_fixing_page() with demo.route("Greenfield", "/greenfield"): build_app_creation_page() with demo.route("Frontend", "/frontend"): build_frontend_page() with demo.route("Testing", "/testing"): build_test_generation_page() with demo.route("Information Gathering", "/information-gathering"): build_information_gathering_page() with demo.route("About", "/about"): build_about_page() logger.info("All routes configured") # Mount the REST API on /api from fastapi import FastAPI from api import api_app # Create a parent FastAPI app that will host both the API and Gradio root_app = FastAPI() root_app.mount("/api", api_app) # Mount Gradio app - root redirect is handled by the proxy app = gr.mount_gradio_app(root_app, demo, path="/") logger.info("REST API mounted at /api, Gradio app mounted at /") # Launch the app if __name__ == "__main__": import uvicorn # Respect platform port/host if provided (e.g., OpenHands runtime) port = int(os.environ.get("PORT", os.environ.get("GRADIO_SERVER_PORT", 7860))) host = os.environ.get("HOST", os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0")) logger.info(f"Launching app on {host}:{port}") uvicorn.run(app, host=host, port=port) logger.info("App launched successfully")