import gradio as gr import requests import os from datetime import datetime API_KEY = os.getenv("OPENWEATHER_API_KEYY") BASE_URL = "http://api.openweathermap.org/data/2.5/weather" def get_weather(city): try: if not API_KEY: raise ValueError("API key not configured") params = { "q": city, "appid": API_KEY, "units": "metric" } res = requests.get(BASE_URL, params=params, timeout=10) res.raise_for_status() data = res.json() temp = data["main"]["temp"] condition = data["weather"][0]["description"].capitalize() humidity = data["main"]["humidity"] wind_speed = data.get("wind", {}).get("speed", 0) # Weather icon mapping weather_id = data["weather"][0]["icon"] icon_map = { "01d": "☀️", "01n": "🌙", "02d": "⛅", "02n": "⛅", "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️", "09d": "🌧️", "09n": "🌧️", "10d": "🌦️", "10n": "🌦️", "11d": "⛈️", "11n": "⛈️", "13d": "🌨️", "13n": "🌨️", "50d": "🌫️", "50n": "🌫️" } icon = icon_map.get(weather_id, "🌤️") # Generate hourly forecast (text only) hourly = [ f"{datetime.now().strftime('%H:%M')} → {round(temp + (i - 2))}°C" for i in range(6) ] return temp, condition, humidity, wind_speed, icon, hourly, city except Exception as e: return f"Error: {str(e)}", "", "", "", "❌", [], city def create_weather_display(temp, condition, humidity, wind_speed, icon, hourly, city): """Create beautiful weather display HTML with added classes for responsiveness""" if isinstance(temp, str) and "Error" in temp: # Error case return f"""

{temp}

Please check your city name and API key

""" # Format temperature temp_display = f"{round(temp)}" if isinstance(temp, (int, float)) else temp # Create main weather display with classes main_display = f"""

🌦 Weather Wizard

📍 {city.title()}

{temp_display}°C

{condition}

💨 {wind_speed} m/s
💧 {humidity}%
{icon}
""" # Create hourly forecast with classes hourly_html = "" if hourly: hourly_items = "" for forecast in hourly: # Extract temp and time from the format "HH:MM → XXX°C" parts = forecast.split(' → ') time_part = parts[0] if len(parts) > 0 else "" temp_part = parts[1].replace('°C', '°') if len(parts) > 1 else "" hourly_items += f"""
{temp_part}
{time_part}
""" hourly_html = f"""

📊 Hourly Forecast

{hourly_items}
""" return main_display + hourly_html # Custom CSS with media queries for responsiveness custom_css = """ body, .gradio-container { background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%) !important; color: white !important; font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif !important; } /* Modern search bar styling */ .search-container input { background: linear-gradient(135deg, rgba(203,77,11,0.1) 0%, rgba(255,140,60,0.05) 100%) !important; color: white !important; border: 2px solid rgba(203,77,11,0.3) !important; border-radius: 16px !important; padding: 16px 24px !important; font-size: 18px !important; font-weight: 500 !important; backdrop-filter: blur(10px) !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 8px 32px rgba(203,77,11,0.1) !important; } .search-container input:focus { border-color: rgb(203,77,11) !important; box-shadow: 0 0 0 4px rgba(203,77,11,0.2), 0 12px 40px rgba(203,77,11,0.15) !important; background: linear-gradient(135deg, rgba(203,77,11,0.15) 0%, rgba(255,140,60,0.08) 100%) !important; transform: translateY(-1px) !important; } .search-container input::placeholder { color: rgba(255,255,255,0.6) !important; font-weight: 400 !important; } /* 2025 Modern Button */ .modern-button { background: linear-gradient(135deg, rgb(203,77,11) 0%, #ff6b35 100%) !important; border: none !important; padding: 16px 32px !important; border-radius: 16px !important; font-weight: 600 !important; font-size: 16px !important; letter-spacing: 0.5px !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 8px 32px rgba(203,77,11,0.25) !important; position: relative !important; overflow: hidden !important; color: white !important; } .modern-button:hover { transform: translateY(-3px) !important; box-shadow: 0 16px 40px rgba(203,77,11,0.4) !important; background: linear-gradient(135deg, #e8550d 0%, #ff7c47 100%) !important; } .modern-button:active { transform: translateY(-1px) !important; } .gradio-html { background: transparent !important; } /* Enhanced container styling */ .gradio-container { background: radial-gradient(ellipse at top, rgba(203,77,11,0.1) 0%, transparent 70%) !important; } /* Media queries for mobile responsiveness */ @media (max-width: 768px) { .header { padding: 16px !important; } .header h1 { font-size: 28px !important; } .header p { font-size: 16px !important; } .search-container input { padding: 12px 16px !important; font-size: 16px !important; } .modern-button { padding: 12px 24px !important; font-size: 14px !important; } .weather-main { padding: 24px !important; } .weather-flex { flex-direction: column !important; } .weather-flex > div { width: 100% !important; text-align: center !important; } .weather-main h1 { font-size: 48px !important; } .weather-main p { font-size: 20px !important; } .weather-icon { font-size: 100px !important; margin: 20px 0 !important; } .hourly-forecast { padding: 24px !important; } .hourly-items { overflow-x: auto !important; flex-wrap: nowrap !important; } .hourly-items > div { min-width: 100px !important; margin: 0 4px !important; } } """ # Create Gradio interface with gr.Blocks(css=custom_css, title="Weather Wizard", theme=gr.themes.Glass()) as app: gr.HTML("""

🌦️ Weather Wizard

AI-powered weather forecasts for the modern world

""") with gr.Column(elem_classes=["search-section"]): city = gr.Textbox( label="", placeholder="Search any city worldwide... (e.g. New York, Tokyo, London)", container=False, elem_classes=["search-container"], show_label=False ) btn = gr.Button( "✨ Discover Weather", variant="primary", size="lg", elem_classes=["modern-button"] ) # Weather Display weather_display = gr.HTML() def update_weather_display(city): if not city.strip(): city = "London" # Default city temp, condition, humidity, wind_speed, icon, hourly, city_name = get_weather(city.strip()) weather_html = create_weather_display(temp, condition, humidity, wind_speed, icon, hourly, city_name) return weather_html # Event handlers btn.click( fn=update_weather_display, inputs=city, outputs=weather_display ) city.submit( fn=update_weather_display, inputs=city, outputs=weather_display ) # Load default weather on startup app.load( fn=lambda: update_weather_display("London"), outputs=weather_display ) if __name__ == "__main__": app.launch()