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}%
"""
# 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"""
"""
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("""
""")
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()