weather-wizard / app.py
uumerrr684's picture
Update app.py
50ee269 verified
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"""
<div class="weather-main" style="
background: linear-gradient(135deg, #2b2b2b 0%, #3a3a3a 100%);
padding: 40px;
border-radius: 24px;
box-shadow: 0 0 40px rgba(203,77,11,0.2);
color: white;
font-family: 'Inter', 'SF Pro Display', sans-serif;
text-align: center;
border: 1px solid rgba(203,77,11,0.2);
">
<div style="font-size: 80px; margin-bottom: 20px;">❌</div>
<h2 style="color: rgb(203,77,11); margin: 0;">{temp}</h2>
<p style="color: #ccc; margin-top: 10px;">Please check your city name and API key</p>
</div>
"""
# Format temperature
temp_display = f"{round(temp)}" if isinstance(temp, (int, float)) else temp
# Create main weather display with classes
main_display = f"""
<div class="weather-main" style="
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
padding: 48px;
border-radius: 24px;
box-shadow: 0 0 50px rgba(203,77,11,0.15), 0 20px 40px rgba(0,0,0,0.3);
color: white;
font-family: 'Inter', 'SF Pro Display', sans-serif;
margin: 24px 0;
border: 1px solid rgba(203,77,11,0.2);
backdrop-filter: blur(20px);
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 36px;">
<h2 style="color: rgb(203,77,11); font-size: 32px; margin: 0; font-weight: 700; letter-spacing: -0.5px;">
🌦 Weather Wizard
</h2>
<div style="
color: white;
font-size: 28px;
font-weight: 600;
background: linear-gradient(135deg, rgba(203,77,11,0.2) 0%, rgba(255,140,60,0.1) 100%);
padding: 12px 24px;
border-radius: 16px;
border: 1px solid rgba(203,77,11,0.3);
">
πŸ“ {city.title()}
</div>
</div>
<div class="weather-flex" style="display: flex; justify-content: space-between; align-items: center;">
<div style="flex: 1;">
<h1 style="
font-size: 84px;
margin: 0;
font-weight: 200;
background: linear-gradient(135deg, white 0%, rgba(203,77,11,0.8) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -2px;
">{temp_display}Β°C</h1>
<p style="font-size: 28px; margin: 12px 0 0 0; color: #e0e0e0; font-weight: 500;">{condition}</p>
<div style="margin-top: 32px; display: flex; gap: 48px; font-size: 20px;">
<div style="
display: flex;
align-items: center;
gap: 12px;
background: rgba(203,77,11,0.1);
padding: 12px 20px;
border-radius: 12px;
border: 1px solid rgba(203,77,11,0.2);
">
<span style="font-size: 24px;">πŸ’¨</span>
<span style="font-weight: 600;">{wind_speed} m/s</span>
</div>
<div style="
display: flex;
align-items: center;
gap: 12px;
background: rgba(203,77,11,0.1);
padding: 12px 20px;
border-radius: 12px;
border: 1px solid rgba(203,77,11,0.2);
">
<span style="font-size: 24px;">πŸ’§</span>
<span style="font-weight: 600;">{humidity}%</span>
</div>
</div>
</div>
<div style="flex: 1; text-align: center;">
<div class="weather-icon" style="
font-size: 140px;
margin: 20px 0;
filter: drop-shadow(0 0 20px rgba(203,77,11,0.3));
">{icon}</div>
</div>
</div>
</div>
"""
# 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"""
<div style="
text-align: center;
flex: 1;
padding: 20px 10px;
background: linear-gradient(135deg, rgba(203,77,11,0.1) 0%, rgba(255,140,60,0.05) 100%);
border-radius: 16px;
margin: 0 4px;
border: 1px solid rgba(203,77,11,0.2);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
">
<div style="
font-size: 22px;
font-weight: 700;
margin-bottom: 12px;
color: white;
background: linear-gradient(135deg, white 0%, rgb(203,77,11) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
">
{temp_part}
</div>
<div style="font-size: 16px; color: #ccc; font-weight: 500;">
{time_part}
</div>
</div>
"""
hourly_html = f"""
<div class="hourly-forecast" style="
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
padding: 36px;
border-radius: 24px;
box-shadow: 0 0 50px rgba(203,77,11,0.15), 0 20px 40px rgba(0,0,0,0.3);
color: white;
font-family: 'Inter', 'SF Pro Display', sans-serif;
margin-top: 24px;
border: 1px solid rgba(203,77,11,0.2);
backdrop-filter: blur(20px);
">
<h3 style="
margin: 0 0 24px 0;
color: rgb(203,77,11);
font-size: 24px;
font-weight: 700;
letter-spacing: -0.5px;
">πŸ“Š Hourly Forecast</h3>
<div class="hourly-items" style="display: flex; gap: 8px; justify-content: space-between;">
{hourly_items}
</div>
</div>
"""
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("""
<div class="header" style="
text-align: center;
padding: 32px;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
border-bottom: 1px solid rgba(203,77,11,0.2);
">
<h1 style="
color: rgb(203,77,11);
font-size: 42px;
margin: 0;
font-family: 'Inter', 'SF Pro Display', sans-serif;
font-weight: 800;
letter-spacing: -1px;
background: linear-gradient(135deg, rgb(203,77,11) 0%, #ff8c3c 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
">
🌦️ Weather Wizard
</h1>
<p style="
color: rgba(255,255,255,0.7);
font-size: 20px;
margin: 12px 0 0 0;
font-weight: 400;
letter-spacing: 0.5px;
">
AI-powered weather forecasts for the modern world
</p>
</div>
""")
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()