# 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")