uumerrr684 commited on
Commit
50ee269
·
verified ·
1 Parent(s): c83153b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +379 -68
app.py CHANGED
@@ -1,33 +1,33 @@
1
- # weather_wizard_app.py
2
- # A fully responsive Gradio Weather Wizard app using OpenWeatherMap API
3
-
4
  import gradio as gr
5
  import requests
6
  import os
7
- from datetime import datetime, timedelta
8
-
9
- # Environment variables
10
- API_KEY = os.getenv("OPENWEATHER_API_KEYY") # ensure your key name matches exactly
11
- BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
12
 
13
- # Function to fetch weather data
 
14
 
15
  def get_weather(city):
16
  try:
17
  if not API_KEY:
18
  raise ValueError("API key not configured")
19
- params = {"q": city, "appid": API_KEY, "units": "metric"}
 
 
 
 
 
 
20
  res = requests.get(BASE_URL, params=params, timeout=10)
21
  res.raise_for_status()
22
  data = res.json()
23
-
24
  temp = data["main"]["temp"]
25
  condition = data["weather"][0]["description"].capitalize()
26
  humidity = data["main"]["humidity"]
27
  wind_speed = data.get("wind", {}).get("speed", 0)
28
- icon_code = data["weather"][0]["icon"]
29
-
30
- # Map OpenWeatherMap icon codes to emojis
31
  icon_map = {
32
  "01d": "☀️", "01n": "🌙", "02d": "⛅", "02n": "⛅",
33
  "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️",
@@ -35,81 +35,392 @@ def get_weather(city):
35
  "11d": "⛈️", "11n": "⛈️", "13d": "🌨️", "13n": "🌨️",
36
  "50d": "🌫️", "50n": "🌫️"
37
  }
38
- icon = icon_map.get(icon_code, "🌤️")
39
-
40
- # Generate 6-hour forecast with exact hour steps
41
  hourly = [
42
- f"{(datetime.now().replace(minute=0, second=0) + timedelta(hours=i)).strftime('%H:%M')} → {round(temp + (i - 2))}°C"
43
  for i in range(6)
44
  ]
45
-
46
  return temp, condition, humidity, wind_speed, icon, hourly, city
 
47
  except Exception as e:
48
- return f"Error: {e}", "", "", "", "❌", [], city
49
-
50
-
51
- # Function to create HTML display
52
 
53
  def create_weather_display(temp, condition, humidity, wind_speed, icon, hourly, city):
54
- # Error case
55
- if isinstance(temp, str) and temp.startswith("Error"):
56
- return f"<div style='text-align:center;padding:32px;background:#2b2b2b;border-radius:24px;color:#f66;font-family:Inter;'>❌ {temp}</div>"
57
-
58
- temp_disp = f"{round(temp)}°C" if isinstance(temp, (int, float)) else temp
59
-
60
- # Main weather card
61
- html = f"""
62
- <div style="background:linear-gradient(135deg,#1a1a1a 0%,#2a2a2a 100%);padding:24px;border-radius:24px;box-shadow:0 0 50px rgba(203,77,11,0.15);color:white;font-family:'Inter',sans-serif;">
63
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;flex-wrap:wrap;">
64
- <h2 style="color:rgb(203,77,11);margin:0;font-size:24px;">🌦️ Weather Wizard</h2>
65
- <div style="background:rgba(203,77,11,0.2);padding:8px 16px;border-radius:16px;">📍 {city.title()}</div>
66
- </div>
67
- <div style="display:flex;align-items:center;gap:32px;flex-wrap:wrap;">
68
- <div><div style="font-size:64px;font-weight:200;background:linear-gradient(135deg,white,rgba(203,77,11,0.8));-webkit-background-clip:text;-webkit-text-fill-color:transparent;">{temp_disp}</div><p style="margin:8px 0;color:#ccc;">{condition}</p></div>
69
- <div style="font-size:96px;filter:drop-shadow(0 0 20px rgba(203,77,11,0.3));">{icon}</div>
70
- </div>
71
- <div style="display:flex;gap:24px;margin-top:24px;">
72
- <div style="display:flex;align-items:center;gap:8px;">💨 {wind_speed} m/s</div>
73
- <div style="display:flex;align-items:center;gap:8px;">💧 {humidity}%</div>
74
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
  """
77
- # Hourly forecast
 
 
78
  if hourly:
79
  hourly_items = ""
80
  for forecast in hourly:
81
- time_part, temp_part = forecast.split('')
82
- hourly_items += f"<div style='flex:1;min-width:60px;padding:12px;background:rgba(255,140,60,0.05);border-radius:16px;text-align:center;'><div style='font-weight:700;'>{temp_part}</div><div style='font-size:12px;color:#ccc;'>{time_part}</div></div>"
83
- html += f"<div style='display:flex;gap:8px;margin-top:16px;overflow-x:auto;'>{hourly_items}</div>"
84
- return html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- # Custom CSS overrides
87
  custom_css = """
88
- body, .gradio-container { background:linear-gradient(135deg,#0a0a0a,#1a1a1a) !important; color:white !important; }
89
- .search-container input { background:rgba(203,77,11,0.1) !important; border:2px solid rgba(203,77,11,0.3) !important; border-radius:16px !important; padding:16px !important; }
90
- .modern-button { background:linear-gradient(135deg, rgb(203,77,11), #ff6b35) !important; border:none !important; padding:16px 32px !important; border-radius:16px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  """
92
 
93
- # Build Gradio interface
94
  with gr.Blocks(css=custom_css, title="Weather Wizard", theme=gr.themes.Glass()) as app:
95
  gr.HTML("""
96
- <div style='text-align:center;padding:32px;background:linear-gradient(135deg,#0a0a0a,#1a1a1a);'>
97
- <h1 style='color:rgb(203,77,11);font-size:42px;margin:0;background:linear-gradient(135deg,rgb(203,77,11),#ff8c3c);-webkit-background-clip:text;-webkit-text-fill-color:transparent;'>🌦️ Weather Wizard</h1>
98
- <p style='color:rgba(255,255,255,0.7);margin-top:8px;'>AI-powered weather forecasts for the modern world</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  </div>
100
  """)
 
101
  with gr.Column(elem_classes=["search-section"]):
102
- city = gr.Textbox(label="", placeholder="Search any city worldwide...", show_label=False, elem_classes=["search-container"])
103
- btn = gr.Button("✨ Discover Weather", variant="primary", size="lg", elem_classes=["modern-button"])
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  weather_display = gr.HTML()
105
-
106
- def update(city):
107
- temp, condition, humidity, wind_speed, icon, hourly, name = get_weather(city.strip() or "London")
108
- return create_weather_display(temp, condition, humidity, wind_speed, icon, hourly, name)
109
-
110
- btn.click(fn=update, inputs=city, outputs=weather_display)
111
- city.submit(fn=update, inputs=city, outputs=weather_display)
112
- app.load(fn=lambda: update("London"), outputs=weather_display)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  if __name__ == "__main__":
115
- app.launch()
 
 
 
 
1
  import gradio as gr
2
  import requests
3
  import os
4
+ from datetime import datetime
 
 
 
 
5
 
6
+ API_KEY = os.getenv("OPENWEATHER_API_KEYY")
7
+ BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
8
 
9
  def get_weather(city):
10
  try:
11
  if not API_KEY:
12
  raise ValueError("API key not configured")
13
+
14
+ params = {
15
+ "q": city,
16
+ "appid": API_KEY,
17
+ "units": "metric"
18
+ }
19
+
20
  res = requests.get(BASE_URL, params=params, timeout=10)
21
  res.raise_for_status()
22
  data = res.json()
23
+
24
  temp = data["main"]["temp"]
25
  condition = data["weather"][0]["description"].capitalize()
26
  humidity = data["main"]["humidity"]
27
  wind_speed = data.get("wind", {}).get("speed", 0)
28
+
29
+ # Weather icon mapping
30
+ weather_id = data["weather"][0]["icon"]
31
  icon_map = {
32
  "01d": "☀️", "01n": "🌙", "02d": "⛅", "02n": "⛅",
33
  "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️",
 
35
  "11d": "⛈️", "11n": "⛈️", "13d": "🌨️", "13n": "🌨️",
36
  "50d": "🌫️", "50n": "🌫️"
37
  }
38
+ icon = icon_map.get(weather_id, "🌤️")
39
+
40
+ # Generate hourly forecast (text only)
41
  hourly = [
42
+ f"{datetime.now().strftime('%H:%M')} → {round(temp + (i - 2))}°C"
43
  for i in range(6)
44
  ]
45
+
46
  return temp, condition, humidity, wind_speed, icon, hourly, city
47
+
48
  except Exception as e:
49
+ return f"Error: {str(e)}", "", "", "", "❌", [], city
 
 
 
50
 
51
  def create_weather_display(temp, condition, humidity, wind_speed, icon, hourly, city):
52
+ """Create beautiful weather display HTML with added classes for responsiveness"""
53
+ if isinstance(temp, str) and "Error" in temp:
54
+ # Error case
55
+ return f"""
56
+ <div class="weather-main" style="
57
+ background: linear-gradient(135deg, #2b2b2b 0%, #3a3a3a 100%);
58
+ padding: 40px;
59
+ border-radius: 24px;
60
+ box-shadow: 0 0 40px rgba(203,77,11,0.2);
61
+ color: white;
62
+ font-family: 'Inter', 'SF Pro Display', sans-serif;
63
+ text-align: center;
64
+ border: 1px solid rgba(203,77,11,0.2);
65
+ ">
66
+ <div style="font-size: 80px; margin-bottom: 20px;">❌</div>
67
+ <h2 style="color: rgb(203,77,11); margin: 0;">{temp}</h2>
68
+ <p style="color: #ccc; margin-top: 10px;">Please check your city name and API key</p>
69
+ </div>
70
+ """
71
+
72
+ # Format temperature
73
+ temp_display = f"{round(temp)}" if isinstance(temp, (int, float)) else temp
74
+
75
+ # Create main weather display with classes
76
+ main_display = f"""
77
+ <div class="weather-main" style="
78
+ background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
79
+ padding: 48px;
80
+ border-radius: 24px;
81
+ box-shadow: 0 0 50px rgba(203,77,11,0.15), 0 20px 40px rgba(0,0,0,0.3);
82
+ color: white;
83
+ font-family: 'Inter', 'SF Pro Display', sans-serif;
84
+ margin: 24px 0;
85
+ border: 1px solid rgba(203,77,11,0.2);
86
+ backdrop-filter: blur(20px);
87
+ ">
88
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 36px;">
89
+ <h2 style="color: rgb(203,77,11); font-size: 32px; margin: 0; font-weight: 700; letter-spacing: -0.5px;">
90
+ 🌦 Weather Wizard
91
+ </h2>
92
+ <div style="
93
+ color: white;
94
+ font-size: 28px;
95
+ font-weight: 600;
96
+ background: linear-gradient(135deg, rgba(203,77,11,0.2) 0%, rgba(255,140,60,0.1) 100%);
97
+ padding: 12px 24px;
98
+ border-radius: 16px;
99
+ border: 1px solid rgba(203,77,11,0.3);
100
+ ">
101
+ 📍 {city.title()}
102
+ </div>
103
+ </div>
104
+
105
+ <div class="weather-flex" style="display: flex; justify-content: space-between; align-items: center;">
106
+ <div style="flex: 1;">
107
+ <h1 style="
108
+ font-size: 84px;
109
+ margin: 0;
110
+ font-weight: 200;
111
+ background: linear-gradient(135deg, white 0%, rgba(203,77,11,0.8) 100%);
112
+ -webkit-background-clip: text;
113
+ -webkit-text-fill-color: transparent;
114
+ background-clip: text;
115
+ letter-spacing: -2px;
116
+ ">{temp_display}°C</h1>
117
+ <p style="font-size: 28px; margin: 12px 0 0 0; color: #e0e0e0; font-weight: 500;">{condition}</p>
118
+ <div style="margin-top: 32px; display: flex; gap: 48px; font-size: 20px;">
119
+ <div style="
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 12px;
123
+ background: rgba(203,77,11,0.1);
124
+ padding: 12px 20px;
125
+ border-radius: 12px;
126
+ border: 1px solid rgba(203,77,11,0.2);
127
+ ">
128
+ <span style="font-size: 24px;">💨</span>
129
+ <span style="font-weight: 600;">{wind_speed} m/s</span>
130
+ </div>
131
+ <div style="
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 12px;
135
+ background: rgba(203,77,11,0.1);
136
+ padding: 12px 20px;
137
+ border-radius: 12px;
138
+ border: 1px solid rgba(203,77,11,0.2);
139
+ ">
140
+ <span style="font-size: 24px;">💧</span>
141
+ <span style="font-weight: 600;">{humidity}%</span>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ <div style="flex: 1; text-align: center;">
146
+ <div class="weather-icon" style="
147
+ font-size: 140px;
148
+ margin: 20px 0;
149
+ filter: drop-shadow(0 0 20px rgba(203,77,11,0.3));
150
+ ">{icon}</div>
151
+ </div>
152
+ </div>
153
  </div>
154
  """
155
+
156
+ # Create hourly forecast with classes
157
+ hourly_html = ""
158
  if hourly:
159
  hourly_items = ""
160
  for forecast in hourly:
161
+ # Extract temp and time from the format "HH:MM XXX°C"
162
+ parts = forecast.split(' ')
163
+ time_part = parts[0] if len(parts) > 0 else ""
164
+ temp_part = parts[1].replace('°C', '°') if len(parts) > 1 else ""
165
+
166
+ hourly_items += f"""
167
+ <div style="
168
+ text-align: center;
169
+ flex: 1;
170
+ padding: 20px 10px;
171
+ background: linear-gradient(135deg, rgba(203,77,11,0.1) 0%, rgba(255,140,60,0.05) 100%);
172
+ border-radius: 16px;
173
+ margin: 0 4px;
174
+ border: 1px solid rgba(203,77,11,0.2);
175
+ transition: all 0.3s ease;
176
+ backdrop-filter: blur(10px);
177
+ ">
178
+ <div style="
179
+ font-size: 22px;
180
+ font-weight: 700;
181
+ margin-bottom: 12px;
182
+ color: white;
183
+ background: linear-gradient(135deg, white 0%, rgb(203,77,11) 100%);
184
+ -webkit-background-clip: text;
185
+ -webkit-text-fill-color: transparent;
186
+ background-clip: text;
187
+ ">
188
+ {temp_part}
189
+ </div>
190
+ <div style="font-size: 16px; color: #ccc; font-weight: 500;">
191
+ {time_part}
192
+ </div>
193
+ </div>
194
+ """
195
+
196
+ hourly_html = f"""
197
+ <div class="hourly-forecast" style="
198
+ background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
199
+ padding: 36px;
200
+ border-radius: 24px;
201
+ box-shadow: 0 0 50px rgba(203,77,11,0.15), 0 20px 40px rgba(0,0,0,0.3);
202
+ color: white;
203
+ font-family: 'Inter', 'SF Pro Display', sans-serif;
204
+ margin-top: 24px;
205
+ border: 1px solid rgba(203,77,11,0.2);
206
+ backdrop-filter: blur(20px);
207
+ ">
208
+ <h3 style="
209
+ margin: 0 0 24px 0;
210
+ color: rgb(203,77,11);
211
+ font-size: 24px;
212
+ font-weight: 700;
213
+ letter-spacing: -0.5px;
214
+ ">📊 Hourly Forecast</h3>
215
+ <div class="hourly-items" style="display: flex; gap: 8px; justify-content: space-between;">
216
+ {hourly_items}
217
+ </div>
218
+ </div>
219
+ """
220
+
221
+ return main_display + hourly_html
222
 
223
+ # Custom CSS with media queries for responsiveness
224
  custom_css = """
225
+ body, .gradio-container {
226
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%) !important;
227
+ color: white !important;
228
+ font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif !important;
229
+ }
230
+
231
+ /* Modern search bar styling */
232
+ .search-container input {
233
+ background: linear-gradient(135deg, rgba(203,77,11,0.1) 0%, rgba(255,140,60,0.05) 100%) !important;
234
+ color: white !important;
235
+ border: 2px solid rgba(203,77,11,0.3) !important;
236
+ border-radius: 16px !important;
237
+ padding: 16px 24px !important;
238
+ font-size: 18px !important;
239
+ font-weight: 500 !important;
240
+ backdrop-filter: blur(10px) !important;
241
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
242
+ box-shadow: 0 8px 32px rgba(203,77,11,0.1) !important;
243
+ }
244
+
245
+ .search-container input:focus {
246
+ border-color: rgb(203,77,11) !important;
247
+ box-shadow: 0 0 0 4px rgba(203,77,11,0.2), 0 12px 40px rgba(203,77,11,0.15) !important;
248
+ background: linear-gradient(135deg, rgba(203,77,11,0.15) 0%, rgba(255,140,60,0.08) 100%) !important;
249
+ transform: translateY(-1px) !important;
250
+ }
251
+
252
+ .search-container input::placeholder {
253
+ color: rgba(255,255,255,0.6) !important;
254
+ font-weight: 400 !important;
255
+ }
256
+
257
+ /* 2025 Modern Button */
258
+ .modern-button {
259
+ background: linear-gradient(135deg, rgb(203,77,11) 0%, #ff6b35 100%) !important;
260
+ border: none !important;
261
+ padding: 16px 32px !important;
262
+ border-radius: 16px !important;
263
+ font-weight: 600 !important;
264
+ font-size: 16px !important;
265
+ letter-spacing: 0.5px !important;
266
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
267
+ box-shadow: 0 8px 32px rgba(203,77,11,0.25) !important;
268
+ position: relative !important;
269
+ overflow: hidden !important;
270
+ color: white !important;
271
+ }
272
+
273
+ .modern-button:hover {
274
+ transform: translateY(-3px) !important;
275
+ box-shadow: 0 16px 40px rgba(203,77,11,0.4) !important;
276
+ background: linear-gradient(135deg, #e8550d 0%, #ff7c47 100%) !important;
277
+ }
278
+
279
+ .modern-button:active {
280
+ transform: translateY(-1px) !important;
281
+ }
282
+
283
+ .gradio-html {
284
+ background: transparent !important;
285
+ }
286
+
287
+ /* Enhanced container styling */
288
+ .gradio-container {
289
+ background: radial-gradient(ellipse at top, rgba(203,77,11,0.1) 0%, transparent 70%) !important;
290
+ }
291
+
292
+ /* Media queries for mobile responsiveness */
293
+ @media (max-width: 768px) {
294
+ .header {
295
+ padding: 16px !important;
296
+ }
297
+ .header h1 {
298
+ font-size: 28px !important;
299
+ }
300
+ .header p {
301
+ font-size: 16px !important;
302
+ }
303
+ .search-container input {
304
+ padding: 12px 16px !important;
305
+ font-size: 16px !important;
306
+ }
307
+ .modern-button {
308
+ padding: 12px 24px !important;
309
+ font-size: 14px !important;
310
+ }
311
+ .weather-main {
312
+ padding: 24px !important;
313
+ }
314
+ .weather-flex {
315
+ flex-direction: column !important;
316
+ }
317
+ .weather-flex > div {
318
+ width: 100% !important;
319
+ text-align: center !important;
320
+ }
321
+ .weather-main h1 {
322
+ font-size: 48px !important;
323
+ }
324
+ .weather-main p {
325
+ font-size: 20px !important;
326
+ }
327
+ .weather-icon {
328
+ font-size: 100px !important;
329
+ margin: 20px 0 !important;
330
+ }
331
+ .hourly-forecast {
332
+ padding: 24px !important;
333
+ }
334
+ .hourly-items {
335
+ overflow-x: auto !important;
336
+ flex-wrap: nowrap !important;
337
+ }
338
+ .hourly-items > div {
339
+ min-width: 100px !important;
340
+ margin: 0 4px !important;
341
+ }
342
+ }
343
  """
344
 
345
+ # Create Gradio interface
346
  with gr.Blocks(css=custom_css, title="Weather Wizard", theme=gr.themes.Glass()) as app:
347
  gr.HTML("""
348
+ <div class="header" style="
349
+ text-align: center;
350
+ padding: 32px;
351
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
352
+ border-bottom: 1px solid rgba(203,77,11,0.2);
353
+ ">
354
+ <h1 style="
355
+ color: rgb(203,77,11);
356
+ font-size: 42px;
357
+ margin: 0;
358
+ font-family: 'Inter', 'SF Pro Display', sans-serif;
359
+ font-weight: 800;
360
+ letter-spacing: -1px;
361
+ background: linear-gradient(135deg, rgb(203,77,11) 0%, #ff8c3c 100%);
362
+ -webkit-background-clip: text;
363
+ -webkit-text-fill-color: transparent;
364
+ background-clip: text;
365
+ ">
366
+ 🌦️ Weather Wizard
367
+ </h1>
368
+ <p style="
369
+ color: rgba(255,255,255,0.7);
370
+ font-size: 20px;
371
+ margin: 12px 0 0 0;
372
+ font-weight: 400;
373
+ letter-spacing: 0.5px;
374
+ ">
375
+ AI-powered weather forecasts for the modern world
376
+ </p>
377
  </div>
378
  """)
379
+
380
  with gr.Column(elem_classes=["search-section"]):
381
+ city = gr.Textbox(
382
+ label="",
383
+ placeholder="Search any city worldwide... (e.g. New York, Tokyo, London)",
384
+ container=False,
385
+ elem_classes=["search-container"],
386
+ show_label=False
387
+ )
388
+ btn = gr.Button(
389
+ "✨ Discover Weather",
390
+ variant="primary",
391
+ size="lg",
392
+ elem_classes=["modern-button"]
393
+ )
394
+
395
+ # Weather Display
396
  weather_display = gr.HTML()
397
+
398
+ def update_weather_display(city):
399
+ if not city.strip():
400
+ city = "London" # Default city
401
+
402
+ temp, condition, humidity, wind_speed, icon, hourly, city_name = get_weather(city.strip())
403
+ weather_html = create_weather_display(temp, condition, humidity, wind_speed, icon, hourly, city_name)
404
+ return weather_html
405
+
406
+ # Event handlers
407
+ btn.click(
408
+ fn=update_weather_display,
409
+ inputs=city,
410
+ outputs=weather_display
411
+ )
412
+
413
+ city.submit(
414
+ fn=update_weather_display,
415
+ inputs=city,
416
+ outputs=weather_display
417
+ )
418
+
419
+ # Load default weather on startup
420
+ app.load(
421
+ fn=lambda: update_weather_display("London"),
422
+ outputs=weather_display
423
+ )
424
 
425
  if __name__ == "__main__":
426
+ app.launch()