Shafaq25 commited on
Commit
73aff18
Β·
verified Β·
1 Parent(s): c13aa27

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -345
app.py CHANGED
@@ -1,15 +1,14 @@
1
  import os
2
  import gradio as gr
3
  import requests
4
- import openai
5
 
6
- # πŸ” Load API keys from environment variables (set these in Hugging Face Secrets)
7
  weather_api_key = os.getenv("openweather")
8
- groq_api_key = os.getenv("GROQ_API_KEY")
9
- openai.api_key = os.getenv("OPENAI_API_KEY")
10
- serper_api_key = os.getenv("SERPER_API_KEY")
11
 
12
- # 🌀️ Weather Fetch
13
  def get_weather(city_name):
14
  if not city_name.strip():
15
  city_name = "Dubai"
@@ -44,13 +43,14 @@ def get_weather(city_name):
44
  except:
45
  return None
46
 
47
- # πŸ–ΌοΈ Format Weather Display
 
48
  def format_weather_display(data):
49
  if not data:
50
  return "<div style='text-align:center; color: #e74c3c; font-size: 18px; padding: 40px;'>❌ City not found. Please try again.</div>"
51
 
52
  font_color = "#2d3436"
53
- card_bg = "#e8f5e9"
54
  main_bg = "#ffffff"
55
 
56
  return f"""
@@ -60,7 +60,6 @@ def format_weather_display(data):
60
  <h1 style="margin: 10px 0; font-size: 64px; color: {font_color}; font-weight: 300;">{data['temperature']}Β°C</h1>
61
  <p style="margin: 5px 0; color: {font_color}; font-size: 18px; font-weight: 500;">{data['description']}</p>
62
  </div>
63
-
64
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 25px;">
65
  <div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
66
  <div style="font-size: 24px; margin-bottom: 8px;">πŸ’§</div>
@@ -96,114 +95,79 @@ def format_weather_display(data):
96
  </div>
97
  """
98
 
99
- # 🌾 Farming Assistant Chatbot (Groq API)
100
- def agri_chat(msg, history):
101
- prompt = f"You're a smart farming assistant. Help the farmer clearly and briefly.\nUser: {msg}\nAssistant:"
102
- url = "https://api.groq.com/openai/v1/chat/completions"
103
- headers = {"Authorization": f"Bearer {groq_api_key}", "Content-Type": "application/json"}
104
- body = {
105
- "model": "llama3-8b-8192",
106
- "messages": [{"role": "user", "content": prompt}],
107
- "temperature": 0.7
108
- }
109
- try:
110
- res = requests.post(url, headers=headers, json=body).json()
111
- reply = res["choices"][0]["message"]["content"]
112
- except:
113
- reply = "⚠️ Unable to get response. Try again."
114
- history.append({"role": "user", "content": msg})
115
- history.append({"role": "assistant", "content": reply})
116
- return history, history
117
 
118
- # 🌱 Crop Search (OpenAI)
119
- def search_crop(crop_name):
120
- try:
121
- messages = [
122
- {
123
- "role": "system",
124
- "content": (
125
- "You are an expert agronomist. When asked about a crop, provide the following in well-structured HTML:\n\n"
126
- "1. A short paragraph (3–5 sentences) about the crop, including where it's commonly grown and its basic growing needs.\n"
127
- "2. A bullet list of 3–5 benefits of growing or consuming the crop.\n"
128
- "3. A numbered list (5–7 steps) explaining how to grow this crop from seed to harvest.\n\n"
129
- "Use proper HTML structure with <h3>, <p>, <ul>, <ol>, <li> tags."
130
- )
131
- },
132
- {
133
- "role": "user",
134
- "content": f"Tell me about {crop_name}. Include a short paragraph, key benefits, and growing steps."
135
- }
136
- ]
137
 
138
- response = openai.chat.completions.create(
139
- model="gpt-3.5-turbo",
140
- messages=messages
 
 
141
  )
 
 
 
142
 
143
- return response.choices[0].message.content.strip()
144
-
145
- except Exception as e:
146
- return f"<div style='color:red;'>⚠️ Error fetching crop info: {str(e)}</div>"
147
 
148
 
149
- # 🌱 Fetch best crops based on region using OpenAI
150
- def get_best_crops_for_region(city, country, temp, humidity, description):
151
  try:
152
  messages = [
153
- {
154
- "role": "system",
155
- "content": (
156
- "You are an expert agronomist. Based on the region, temperature, humidity, and weather condition, "
157
- "suggest 6 to 9 crops that grow best in this region. Return each crop as a Python tuple like:\n"
158
- "(\"Tomato\", \"Needs warm weather, full sun, and well-drained soil.\")"
159
- )
160
- },
161
- {
162
- "role": "user",
163
- "content": (
164
- f"Suggest best crops for {city}, {country} where the average temperature is {temp}Β°C, "
165
- f"humidity is {humidity}%, and the weather condition is '{description.lower()}'. "
166
- "Output the results as Python tuples."
167
- )
168
- }
169
  ]
170
 
171
- response = openai.chat.completions.create(
172
- model="gpt-3.5-turbo",
173
  messages=messages
174
  )
175
 
176
  content = response.choices[0].message.content.strip()
177
 
178
- crops = []
179
  for line in content.split('\n'):
180
  line = line.strip().rstrip(',')
181
  if line.startswith('(') and line.endswith(')'):
182
  try:
183
- crop_tuple = eval(line)
184
- if isinstance(crop_tuple, tuple) and len(crop_tuple) == 2:
185
- crops.append(crop_tuple)
186
  except:
187
  continue
188
- return crops if crops else [("⚠️ No Crops Found", "Try a different city or adjust your weather input.")]
 
 
189
 
190
- except Exception as e:
191
- return [("⚠️ Error", f"Could not fetch crops: {str(e)}")]
192
 
193
- # 🎨 Format each crop card for the grid layout
194
- def format_crop_card(name, details):
195
  return f"""
196
- <div class='crop-card'>
197
- <div class='crop-name'>{name.title()}</div>
198
- <div class='crop-description'>{details}</div>
199
  </div>
200
  """
201
 
202
 
203
- # Launch UI with layout from old code + crop cards and search
204
  custom_css = """
205
  body, .gradio-container {
206
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important;
207
  font-family: 'Segoe UI', 'Roboto', sans-serif;
208
  min-height: 100vh;
209
  }
@@ -212,18 +176,10 @@ body, .gradio-container {
212
  font-size: 2.5rem;
213
  font-weight: 700;
214
  margin-bottom: 10px;
215
- color: #2e7d32;
216
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
217
- }
218
- #subtitle {
219
- text-align: center;
220
- font-size: 1.1rem;
221
- color: #666;
222
- margin-bottom: 30px;
223
- font-weight: 400;
224
  }
225
  .section-header {
226
- background: linear-gradient(135deg, #43a047 0%, #388e3c 100%);
227
  color: white;
228
  padding: 12px 20px;
229
  border-radius: 12px 12px 0 0;
@@ -231,7 +187,6 @@ body, .gradio-container {
231
  font-size: 1.2rem;
232
  font-weight: 600;
233
  text-align: center;
234
- box-shadow: 0 4px 15px rgba(67, 160, 71, 0.3);
235
  }
236
  .content-box {
237
  background-color: #ffffff;
@@ -239,87 +194,7 @@ body, .gradio-container {
239
  padding: 25px;
240
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
241
  min-height: 520px;
242
- border-top: 3px solid #43a047;
243
- }
244
- .weather-controls {
245
- background: #f8f9fa;
246
- padding: 20px;
247
- border-radius: 12px;
248
- margin-bottom: 20px;
249
- border: 1px solid #e9ecef;
250
- }
251
- .footer {
252
- background: linear-gradient(135deg, #2e7d32 0%, #1b5e20 100%);
253
- color: white;
254
- padding: 30px;
255
- border-radius: 16px;
256
- margin-top: 30px;
257
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
258
- }
259
- .footer h3 {
260
- margin: 0 0 15px 0;
261
- font-size: 1.3rem;
262
- font-weight: 600;
263
- color: #ffffff;
264
- }
265
- .footer-content {
266
- display: grid;
267
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
268
- gap: 25px;
269
- margin-bottom: 20px;
270
- }
271
- .footer-section {
272
- background: rgba(255, 255, 255, 0.1);
273
- padding: 20px;
274
- border-radius: 12px;
275
- backdrop-filter: blur(10px);
276
- }
277
- .footer-section h4 {
278
- margin: 0 0 10px 0;
279
- color: #a8e6cf;
280
- font-size: 1.1rem;
281
- }
282
- .footer-section ul {
283
- list-style: none;
284
- padding: 0;
285
- margin: 0;
286
- }
287
- .footer-section li {
288
- margin: 8px 0;
289
- padding-left: 20px;
290
- position: relative;
291
- }
292
- .footer-section li:before {
293
- content: "βœ“";
294
- position: absolute;
295
- left: 0;
296
- color: #a8e6cf;
297
- font-weight: bold;
298
- }
299
- button {
300
- background: linear-gradient(135deg, #43a047 0%, #388e3c 100%) !important;
301
- color: white !important;
302
- border-radius: 10px !important;
303
- border: none !important;
304
- padding: 12px 24px !important;
305
- font-weight: 600 !important;
306
- transition: all 0.3s ease !important;
307
- box-shadow: 0 4px 15px rgba(67, 160, 71, 0.3) !important;
308
- }
309
- button:hover {
310
- transform: translateY(-2px) !important;
311
- box-shadow: 0 6px 20px rgba(67, 160, 71, 0.4) !important;
312
- }
313
- .gradio-textbox input {
314
- border-radius: 10px !important;
315
- border: 2px solid #e9ecef !important;
316
- padding: 12px 16px !important;
317
- font-size: 16px !important;
318
- transition: all 0.3s ease !important;
319
- }
320
- .gradio-textbox input:focus {
321
- border-color: #43a047 !important;
322
- box-shadow: 0 0 0 3px rgba(67, 160, 71, 0.1) !important;
323
  }
324
  .card-grid {
325
  display: grid;
@@ -327,208 +202,86 @@ button:hover {
327
  gap: 18px;
328
  margin-top: 25px;
329
  }
330
-
331
- .crop-card {
332
  background: #ffffff;
333
- border: 1px solid #c8e6c9;
334
  border-radius: 16px;
335
  padding: 20px;
336
- box-shadow: 0 4px 12px rgba(67, 160, 71, 0.1);
337
  transition: transform 0.2s ease, box-shadow 0.2s ease;
338
  }
339
- .crop-card:hover {
340
  transform: translateY(-4px);
341
- box-shadow: 0 8px 20px rgba(67, 160, 71, 0.2);
342
  }
343
- .crop-name {
344
  font-size: 20px;
345
  font-weight: 600;
346
- color: #2e7d32;
347
  margin-bottom: 12px;
348
  text-align: center;
349
  }
350
- .crop-description {
351
  font-size: 15px;
352
  color: #444;
353
  line-height: 1.5;
354
  text-align: center;
355
  }
356
-
357
  """
358
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  def launch_ui():
360
- with gr.Blocks(css=custom_css, title="Shafaq's AgriWeather") as demo:
361
- # Header Section
362
- gr.Markdown("<div id='main-title'>🌿 Shafaq's AgriWeather Hub</div>")
363
- gr.Markdown("<div id='subtitle'>Real-time Weather Data & Smart Farming Assistant</div>")
364
 
365
- # Main Content
366
  with gr.Row(equal_height=True):
367
- # Weather Section
368
  with gr.Column(scale=1):
369
  gr.Markdown("<div class='section-header'>🌀️ Weather Dashboard</div>")
370
  with gr.Group(elem_classes="content-box"):
371
- with gr.Group(elem_classes="weather-controls"):
372
- city_input = gr.Textbox(
373
- label="πŸ™οΈ City Name",
374
- value="Dubai",
375
- placeholder="Enter city name (e.g., Dubai, London, Tokyo)",
376
- info="Get real-time weather data for any city worldwide"
377
- )
378
- update_btn = gr.Button("πŸ“ Get Weather Data", variant="primary")
379
  weather_html = gr.HTML()
380
 
381
- # Chat Section
382
  with gr.Column(scale=1):
383
- gr.Markdown("<div class='section-header'>🌾 AgriBot Assistant</div>")
384
  with gr.Group(elem_classes="content-box"):
385
- chat = gr.Chatbot(
386
- height=350,
387
- type="messages",
388
- show_label=False,
389
- bubble_full_width=False,
390
- avatar_images=("πŸ‘¨β€πŸŒΎ", "πŸ€–")
391
- )
392
  with gr.Row():
393
- message = gr.Textbox(
394
- placeholder="Ask about crops, weather impact, farming tips...",
395
- show_label=False,
396
- scale=4
397
- )
398
- ask_btn = gr.Button("Send", variant="primary", scale=1)
399
- # Quick suggestions
400
- gr.Markdown("""
401
- **πŸ’‘ Quick Questions:**
402
- - "What crops grow best in hot weather?"
403
- - "How does humidity affect plant growth?"
404
- - "Best irrigation practices for dry season?"
405
- """)
406
-
407
  state = gr.State([])
408
 
409
- # --- Dynamic Crop Cards Section ---
410
- gr.Markdown("<div class='section-header'>🌿 Crops That Grow Best in Your Region</div>")
411
- crop_cards_html = gr.HTML()
412
-
413
- def generate_crop_cards(city):
414
- weather = get_weather(city)
415
- if not weather:
416
- return "<div style='padding:20px; color:red;'>⚠️ Couldn't fetch crops due to missing weather data.</div>"
417
-
418
- crops = get_best_crops_for_region(
419
- city=weather["city"],
420
- country=weather["country"],
421
- temp=weather["temperature"],
422
- humidity=weather["humidity"],
423
- description=weather["description"]
424
- )
425
- return "<div class='card-grid'>" + "".join(format_crop_card(name, details) for name, details in crops) + "</div>"
426
-
427
-
428
- # --- Crop search section ---
429
- gr.Markdown("<div class='section-header'>πŸ”Ž Search for Crop Growing Conditions</div>")
430
- with gr.Group(elem_classes="content-box"):
431
- with gr.Row():
432
- crop_search_input = gr.Textbox(
433
- placeholder="Type a crop name (e.g., Tomato, Wheat)...",
434
- show_label=False,
435
- scale=5
436
- )
437
- crop_search_btn = gr.Button("πŸ” Search", variant="primary", scale=1)
438
- crop_search_output = gr.HTML()
439
-
440
-
441
- # --- Coming Soon Plant Doc Feature Section ---
442
- gr.Markdown("<div class='section-header'>🌿 Coming Soon: Plant Doctor Feature</div>")
443
- with gr.Group(elem_classes="content-box"):
444
- gr.HTML("""
445
- <div style="text-align: center; padding: 50px; color: #555; font-size: 1.2rem;">
446
- <h3>Future Feature: Diagnose Plant Diseases!</h3>
447
- <p>Upload an image of your plant, and our AI-powered Plant Doctor will identify potential diseases and suggest treatments. Stay tuned!</p>
448
- <div style="margin-top: 30px;">
449
- <span style="font-size: 60px;">🌱</span>
450
- <span style="font-size: 60px; margin-left: 20px;">πŸ”¬</span>
451
- <span style="font-size: 60px; margin-left: 20px;">πŸ’‘</span>
452
- </div>
453
- </div>
454
- """)
455
-
456
-
457
- # Footer Section (fixed)
458
- gr.HTML("""
459
- <div class='footer'>
460
- <h3>🌱 About Shafaq's AgriWeather Hub</h3>
461
- <div class='footer-content'>
462
- <div class='footer-section'>
463
- <h4>🌀️ Weather Features</h4>
464
- <ul>
465
- <li>Real-time weather updates for any city</li>
466
- <li>Detailed forecasts: temperature, wind, humidity, etc.</li>
467
- <li>Smart visual indicators with icons</li>
468
- <li>Location-based crop recommendations</li>
469
- </ul>
470
- </div>
471
-
472
- <div class='footer-section'>
473
- <h4>πŸ€– AgriBot Capabilities</h4>
474
- <ul>
475
- <li>Ask farming-related questions interactively</li>
476
- <li>Get irrigation, soil & seasonal guidance</li>
477
- <li>Pest, disease & crop management tips</li>
478
- <li>Uses advanced AI (Groq + OpenAI)</li>
479
- </ul>
480
- </div>
481
-
482
- <div class='footer-section'>
483
- <h4>πŸ”Ž Crop Search Intelligence</h4>
484
- <ul>
485
- <li>Search growing conditions for any crop</li>
486
- <li>Uses trusted sources via Serper API</li>
487
- <li>Clean layout with links to learn more</li>
488
- <li>Get contextual crop care information</li>
489
- </ul>
490
- </div>
491
-
492
- <div class='footer-section'>
493
- <h4>πŸš€ Quick Usage Tips</h4>
494
- <ul>
495
- <li>Start by entering a city to get crop advice</li>
496
- <li>Ask specific crop or weather questions</li>
497
- <li>Use the crop search for deeper research</li>
498
- <li>Combine data for informed decisions</li>
499
- </ul>
500
- </div>
501
- </div>
502
 
503
- <div style='text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.2); color: rgba(255,255,255,0.85); font-size: 14px;'>
504
- <p>πŸ’š Built by Shafaq Mandha | Powered by OpenWeather, Groq, OpenAI, and Serper APIs | All rights reserved Β© 2025</p>
505
- </div>
506
- </div>
507
- """)
508
-
509
-
510
- # Event bindings
511
- update_btn.click(
512
- fn=lambda city: (format_weather_display(get_weather(city)), generate_crop_cards(city)),
513
- inputs=city_input,
514
- outputs=[weather_html, crop_cards_html]
515
- )
516
-
517
- city_input.submit(
518
- fn=lambda city: (format_weather_display(get_weather(city)), generate_crop_cards(city)),
519
- inputs=city_input,
520
- outputs=[weather_html, crop_cards_html]
521
- )
522
 
523
- ask_btn.click(fn=agri_chat, inputs=[message, state], outputs=[chat, state])
524
- message.submit(fn=agri_chat, inputs=[message, state], outputs=[chat, state])
525
- crop_search_btn.click(fn=search_crop, inputs=crop_search_input, outputs=crop_search_output)
526
 
527
- # Load initial weather on launch
528
- demo.load(fn=lambda: (format_weather_display(get_weather("Dubai")), generate_crop_cards("Dubai")),
529
- inputs=None,
530
- outputs=[weather_html, crop_cards_html])
531
 
 
532
 
533
  demo.launch()
534
 
 
1
  import os
2
  import gradio as gr
3
  import requests
4
+ from openai import OpenAI
5
 
6
+ # API keys
7
  weather_api_key = os.getenv("openweather")
8
+ openai_api_key = os.getenv("OPENAI_API_KEY")
9
+ client = OpenAI(api_key=openai_api_key)
 
10
 
11
+ # Fetch Weather
12
  def get_weather(city_name):
13
  if not city_name.strip():
14
  city_name = "Dubai"
 
43
  except:
44
  return None
45
 
46
+
47
+ # Format Weather
48
  def format_weather_display(data):
49
  if not data:
50
  return "<div style='text-align:center; color: #e74c3c; font-size: 18px; padding: 40px;'>❌ City not found. Please try again.</div>"
51
 
52
  font_color = "#2d3436"
53
+ card_bg = "#e3f2fd"
54
  main_bg = "#ffffff"
55
 
56
  return f"""
 
60
  <h1 style="margin: 10px 0; font-size: 64px; color: {font_color}; font-weight: 300;">{data['temperature']}Β°C</h1>
61
  <p style="margin: 5px 0; color: {font_color}; font-size: 18px; font-weight: 500;">{data['description']}</p>
62
  </div>
 
63
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 25px;">
64
  <div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
65
  <div style="font-size: 24px; margin-bottom: 8px;">πŸ’§</div>
 
95
  </div>
96
  """
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
+ # Chatbot using OpenAI
100
+ def travel_chat(msg, history):
101
+ messages = [{"role": "system", "content": "You are a helpful travel assistant. Suggest tourist attractions, activities, and travel tips for any city."}]
102
+ for h in history:
103
+ messages.append({"role": h["role"], "content": h["content"]})
104
+ messages.append({"role": "user", "content": msg})
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ try:
107
+ response = client.chat.completions.create(
108
+ model="gpt-4o-mini",
109
+ messages=messages,
110
+ temperature=0.7
111
  )
112
+ reply = response.choices[0].message.content
113
+ except Exception:
114
+ reply = "⚠️ Unable to fetch suggestions right now. Try again later."
115
 
116
+ history.append({"role": "user", "content": msg})
117
+ history.append({"role": "assistant", "content": reply})
118
+ return history, history
 
119
 
120
 
121
+ # Fetch attractions for city
122
+ def get_attractions(city, country, temp, weather_desc):
123
  try:
124
  messages = [
125
+ {"role": "system", "content": (
126
+ "You are a travel expert. Based on the city, country, and current weather, "
127
+ "suggest 6 to 9 top attractions. Return each as a Python tuple like: "
128
+ "(\"Burj Khalifa\", \"World's tallest building with stunning views.\")."
129
+ )},
130
+ {"role": "user", "content": (
131
+ f"Suggest attractions for {city}, {country} (Weather: {temp}Β°C, {weather_desc}). "
132
+ "Output as Python tuples only."
133
+ )}
 
 
 
 
 
 
 
134
  ]
135
 
136
+ response = client.chat.completions.create(
137
+ model="gpt-4o-mini",
138
  messages=messages
139
  )
140
 
141
  content = response.choices[0].message.content.strip()
142
 
143
+ attractions = []
144
  for line in content.split('\n'):
145
  line = line.strip().rstrip(',')
146
  if line.startswith('(') and line.endswith(')'):
147
  try:
148
+ attraction_tuple = eval(line)
149
+ if isinstance(attraction_tuple, tuple) and len(attraction_tuple) == 2:
150
+ attractions.append(attraction_tuple)
151
  except:
152
  continue
153
+ return attractions if attractions else [("No attractions found", "Try another city.")]
154
+ except:
155
+ return [("Error", "Could not fetch attractions.")]
156
 
 
 
157
 
158
+ def format_attraction_card(name, details):
 
159
  return f"""
160
+ <div class='card'>
161
+ <div class='card-title'>{name}</div>
162
+ <div class='card-desc'>{details}</div>
163
  </div>
164
  """
165
 
166
 
167
+ # CSS
168
  custom_css = """
169
  body, .gradio-container {
170
+ background: linear-gradient(135deg, #f5f7fa 0%, #bbdefb 100%) !important;
171
  font-family: 'Segoe UI', 'Roboto', sans-serif;
172
  min-height: 100vh;
173
  }
 
176
  font-size: 2.5rem;
177
  font-weight: 700;
178
  margin-bottom: 10px;
179
+ color: #1565c0;
 
 
 
 
 
 
 
 
180
  }
181
  .section-header {
182
+ background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
183
  color: white;
184
  padding: 12px 20px;
185
  border-radius: 12px 12px 0 0;
 
187
  font-size: 1.2rem;
188
  font-weight: 600;
189
  text-align: center;
 
190
  }
191
  .content-box {
192
  background-color: #ffffff;
 
194
  padding: 25px;
195
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
196
  min-height: 520px;
197
+ border-top: 3px solid #1976d2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  }
199
  .card-grid {
200
  display: grid;
 
202
  gap: 18px;
203
  margin-top: 25px;
204
  }
205
+ .card {
 
206
  background: #ffffff;
207
+ border: 1px solid #bbdefb;
208
  border-radius: 16px;
209
  padding: 20px;
210
+ box-shadow: 0 4px 12px rgba(25, 118, 210, 0.1);
211
  transition: transform 0.2s ease, box-shadow 0.2s ease;
212
  }
213
+ .card:hover {
214
  transform: translateY(-4px);
215
+ box-shadow: 0 8px 20px rgba(25, 118, 210, 0.2);
216
  }
217
+ .card-title {
218
  font-size: 20px;
219
  font-weight: 600;
220
+ color: #1565c0;
221
  margin-bottom: 12px;
222
  text-align: center;
223
  }
224
+ .card-desc {
225
  font-size: 15px;
226
  color: #444;
227
  line-height: 1.5;
228
  text-align: center;
229
  }
 
230
  """
231
 
232
+ # Generate attractions cards
233
+ def generate_attraction_cards(city):
234
+ weather = get_weather(city)
235
+ if not weather:
236
+ return "<div style='padding:20px; color:red;'>⚠️ Couldn't fetch attractions due to missing weather data.</div>"
237
+
238
+ attractions = get_attractions(
239
+ city=weather["city"],
240
+ country=weather["country"],
241
+ temp=weather["temperature"],
242
+ weather_desc=weather["description"]
243
+ )
244
+ return "<div class='card-grid'>" + "".join(format_attraction_card(name, details) for name, details in attractions) + "</div>"
245
+
246
+
247
+ # UI
248
  def launch_ui():
249
+ with gr.Blocks(css=custom_css, title="Travel Weather Guide") as demo:
250
+ gr.Markdown("<div id='main-title'>🌍 Travel Weather Guide</div>")
 
 
251
 
 
252
  with gr.Row(equal_height=True):
253
+ # Weather section
254
  with gr.Column(scale=1):
255
  gr.Markdown("<div class='section-header'>🌀️ Weather Dashboard</div>")
256
  with gr.Group(elem_classes="content-box"):
257
+ city_input = gr.Textbox(label="πŸ™οΈ City Name", value="Dubai")
258
+ update_btn = gr.Button("πŸ“ Get Weather Data")
 
 
 
 
 
 
259
  weather_html = gr.HTML()
260
 
261
+ # Chatbot section
262
  with gr.Column(scale=1):
263
+ gr.Markdown("<div class='section-header'>✈️ Travel Assistant</div>")
264
  with gr.Group(elem_classes="content-box"):
265
+ chat = gr.Chatbot(height=350, type="messages")
 
 
 
 
 
 
266
  with gr.Row():
267
+ message = gr.Textbox(placeholder="Ask about places to visit, local attractions...", show_label=False, scale=4)
268
+ ask_btn = gr.Button("Send", scale=1)
 
 
 
 
 
 
 
 
 
 
 
 
269
  state = gr.State([])
270
 
271
+ # Attractions section
272
+ gr.Markdown("<div class='section-header'>πŸ–οΈ Top Attractions You Can Visit</div>")
273
+ attractions_html = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
+ def update_all(city):
276
+ return format_weather_display(get_weather(city)), generate_attraction_cards(city)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ update_btn.click(fn=update_all, inputs=city_input, outputs=[weather_html, attractions_html])
279
+ city_input.submit(fn=update_all, inputs=city_input, outputs=[weather_html, attractions_html])
 
280
 
281
+ ask_btn.click(fn=travel_chat, inputs=[message, state], outputs=[chat, state])
282
+ message.submit(fn=travel_chat, inputs=[message, state], outputs=[chat, state])
 
 
283
 
284
+ demo.load(fn=lambda: update_all("Dubai"), inputs=None, outputs=[weather_html, attractions_html])
285
 
286
  demo.launch()
287