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

Update app.py

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