NSamson1 commited on
Commit
527d7d9
Β·
verified Β·
1 Parent(s): fb5a20b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -170
app.py CHANGED
@@ -26,14 +26,11 @@ def get_destination_image(city: str) -> str:
26
  """Fetch a real image of the destination using SerpAPI."""
27
  cache_key = city.lower()
28
 
29
- # Check cache first
30
  if cache_key in IMAGE_CACHE:
31
  return IMAGE_CACHE[cache_key]
32
 
33
- # Try SerpAPI for real images
34
  if SERP_API_KEY:
35
  try:
36
- # Search for beautiful images of the city
37
  params = {
38
  "q": f"{city} city landmark beautiful view",
39
  "api_key": SERP_API_KEY,
@@ -53,7 +50,6 @@ def get_destination_image(city: str) -> str:
53
  except Exception as e:
54
  print(f"Image fetch error: {e}")
55
 
56
- # If no image found, return None (don't show fake image)
57
  return None
58
 
59
  # -------------------- WEATHER FUNCTION --------------------
@@ -86,56 +82,32 @@ def get_weather(city: str) -> dict:
86
  except Exception as e:
87
  return {"error": f"Weather error: {str(e)}"}
88
 
89
- # -------------------- ATTRACTIONS FUNCTION --------------------
90
  def get_real_attractions(city: str) -> list:
91
- """Get real attractions using SerpAPI first, then OpenAI as fallback."""
92
 
93
- # Try SerpAPI first (gives real-time search results)
94
- if SERP_API_KEY:
95
- try:
96
- params = {
97
- "q": f"top tourist attractions in {city}",
98
- "api_key": SERP_API_KEY,
99
- "engine": "google",
100
- "num": 6
101
- }
102
- response = requests.get("https://serpapi.com/search", params=params, timeout=10)
103
- data = response.json()
104
-
105
- attractions = []
106
- for result in data.get("organic_results", [])[:6]:
107
- title = result.get("title", "")
108
- # Skip Wikipedia and TripAdvisor pages to get direct attraction names
109
- if "wikipedia" not in title.lower() and "tripadvisor" not in title.lower():
110
- # Clean up the title
111
- name = title.split(" - ")[0].split(" | ")[0].split(":")[0].strip()
112
- if len(name) > 5 and len(name) < 100: # Valid attraction name
113
- attractions.append({
114
- "name": name,
115
- "description": result.get("snippet", f"Popular attraction in {city}"),
116
- "entry_fee": "Check website",
117
- "duration_hours": 2
118
- })
119
-
120
- if attractions and len(attractions) >= 3:
121
- return attractions[:6]
122
- except Exception as e:
123
- print(f"SerpAPI error: {e}")
124
-
125
- # Fallback to OpenAI if SerpAPI fails or no results
126
  if client:
127
  try:
128
  prompt = f"""List the top 6 REAL tourist attractions in {city}. For each attraction, provide:
129
- - Name (actual famous attraction name, not generic like 'city center')
130
- - A brief description (1 sentence)
131
  - Typical entry fee in USD (use numbers, 0 if free)
132
  - Approximate visit duration in hours
133
 
134
  Return ONLY a valid JSON array with keys: name, description, entry_fee, duration_hours.
135
 
136
- Example: [{{"name": "Eiffel Tower", "description": "Iconic iron tower with panoramic views", "entry_fee": 25, "duration_hours": 2.5}}]
 
 
 
 
 
 
 
 
137
 
138
- Important: Use REAL attractions specific to {city}. Do not use generic names."""
139
 
140
  response = client.chat.completions.create(
141
  model="gpt-3.5-turbo",
@@ -184,12 +156,41 @@ Important: Use REAL attractions specific to {city}. Do not use generic names."""
184
  except Exception as e:
185
  print(f"OpenAI attractions error: {e}")
186
 
187
- # Ultimate fallback - generic but informative
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  return [
189
- {"name": f"Historic {city} City Center", "description": f"The historic heart of {city} with beautiful architecture and local culture.", "entry_fee": "Free", "duration_hours": 2},
190
- {"name": f"{city} Cultural Museum", "description": f"Discover the rich history and cultural heritage of {city}.", "entry_fee": "$10", "duration_hours": 2},
191
- {"name": f"{city} Central Park", "description": f"A beautiful green space perfect for relaxation and outdoor activities.", "entry_fee": "Free", "duration_hours": 1.5},
192
- {"name": f"{city} Local Market", "description": f"Experience local life at this vibrant market with fresh produce and crafts.", "entry_fee": "Free", "duration_hours": 1.5}
193
  ]
194
 
195
  # -------------------- BUDGET CALCULATION --------------------
@@ -234,32 +235,23 @@ def generate_itinerary(destination: str, start_date: str, num_days: int,
234
  budget_amount: float, budget_currency: str, departure_city: str = ""):
235
  """Main itinerary generation function."""
236
  try:
237
- # Validate inputs
238
  if not destination:
239
  return "❌ Please enter a destination city."
240
 
241
  if num_days < 1 or num_days > 14:
242
  return "❌ Number of days must be between 1 and 14."
243
 
244
- # Get weather
245
  weather = get_weather(destination)
246
  if "error" in weather:
247
  return f"❌ Weather error: {weather['error']}"
248
 
249
- # Get real attractions
250
  attractions = get_real_attractions(destination)
251
-
252
- # Get destination image
253
  destination_image = get_destination_image(destination)
254
-
255
- # Calculate budget
256
  budget_data = calculate_budget(num_days, budget_amount)
257
 
258
- # Format dates
259
  start = datetime.strptime(start_date, "%Y-%m-%d")
260
  end = start + timedelta(days=int(num_days) - 1)
261
 
262
- # Generate HTML itinerary
263
  html = generate_html_itinerary(
264
  destination, weather, attractions, budget_data,
265
  num_days, start, end, budget_amount, departure_city, destination_image
@@ -272,9 +264,8 @@ def generate_itinerary(destination: str, start_date: str, num_days: int,
272
 
273
  def generate_html_itinerary(destination, weather, attractions, budget_data,
274
  num_days, start_date, end_date, budget_amount, departure_city, destination_image):
275
- """Create beautiful HTML itinerary with image."""
276
 
277
- # Weather details
278
  weather_temp = f"{weather['temperature']:.1f}Β°C" if isinstance(weather['temperature'], (int, float)) else str(weather['temperature'])
279
  weather_condition = weather['condition'].capitalize()
280
 
@@ -288,78 +279,81 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
288
  </div>
289
  """
290
 
291
- # Attractions list
292
- attractions_html = ""
293
- for attr in attractions[:6]:
294
- attractions_html += f"""
295
- <div style="background: #f8f9fa; padding: 15px; border-radius: 10px; margin-bottom: 12px; border-left: 3px solid #667eea;">
296
- <strong style="font-size: 1.1em; color: #333;">πŸ“ {attr['name']}</strong>
297
- <div style="color: #666; margin: 8px 0; font-size: 0.95em;">{attr['description']}</div>
298
- <div style="display: flex; gap: 15px; margin-top: 8px; font-size: 0.85em;">
299
- <span>⏱️ {attr['duration_hours']} hrs</span>
300
- <span>🎟️ {attr['entry_fee']}</span>
301
- </div>
302
- </div>
303
- """
304
-
305
- # Daily itinerary
306
  daily_html = ""
307
- per_day = max(1, len(attractions) // max(1, num_days))
 
 
308
 
309
  for day in range(1, num_days + 1):
310
  current_date = start_date + timedelta(days=day-1)
311
  date_str = current_date.strftime("%A, %B %d")
312
 
313
- start_idx = (day-1) * per_day
314
- end_idx = min(day * per_day, len(attractions))
315
- day_attractions = attractions[start_idx:end_idx]
 
316
 
317
- if day_attractions:
318
- daily_html += f"""
319
- <div style="background: white; border-radius: 12px; margin-bottom: 20px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
320
- <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 12px 20px; color: white;">
321
- <h3 style="margin: 0; font-size: 1.2em;">Day {day} Β· {date_str}</h3>
322
- </div>
323
- <div style="padding: 20px;">
324
- """
325
-
326
- for attr in day_attractions:
327
- daily_html += f"""
328
- <div style="margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 8px;">
329
- <strong>✨ {attr['name']}</strong><br>
330
- <span style="font-size: 0.85em; color: #666;">{attr['description'][:100]}...</span>
331
- <div style="font-size: 0.75em; color: #888; margin-top: 5px;">
332
- ⏱️ {attr['duration_hours']} hrs | 🎟️ {attr['entry_fee']}
333
- </div>
334
- </div>
335
- """
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  daily_html += f"""
338
- <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e0e0e0;">
339
- <div><span style="font-size: 1.1em;">🍽️</span> <strong>Lunch:</strong> Try authentic local cuisine at a nearby restaurant</div>
340
- <div style="margin-top: 8px;"><span style="font-size: 1.1em;">πŸŒ™</span> <strong>Evening:</strong> Explore local markets, enjoy cultural shows, or relax at a cafe</div>
 
 
 
 
 
 
 
 
 
341
  </div>
342
  </div>
343
- </div>
344
  """
 
 
 
 
 
 
 
 
 
345
 
346
- # Destination Image Section (only if real image exists)
347
  image_html = ""
348
  if destination_image:
349
  image_html = f"""
350
  <div style="background: white; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05); text-align: center;">
351
  <h2 style="margin: 0 0 15px 0; color: #667eea;">πŸ“Έ A Glimpse of {destination}</h2>
352
- <div style="font-size: 0.85em; color: #888; margin-bottom: 15px;">πŸ” Real image from {destination} - Let this inspire your journey!</div>
353
  <div style="border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
354
- <img src="{destination_image}"
355
- style="width: 100%; max-height: 400px; object-fit: cover; transition: transform 0.3s;"
356
- onmouseover="this.style.transform='scale(1.02)'"
357
- onmouseout="this.style.transform='scale(1)'"
358
- alt="{destination}">
359
  </div>
360
- <p style="margin-top: 15px; font-size: 0.9em; color: #666;">
361
- 🎯 <strong>Ready for your adventure?</strong> {destination} awaits with unforgettable experiences!
362
- </p>
363
  </div>
364
  """
365
 
@@ -371,7 +365,7 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
371
  <div><strong>🏨 Accommodation</strong><br>${budget_data['accommodation']:.0f}<br><small>(${budget_data['daily']['accommodation']}/day)</small></div>
372
  <div><strong>🍽️ Food & Dining</strong><br>${budget_data['food']:.0f}<br><small>(${budget_data['daily']['food']}/day)</small></div>
373
  <div><strong>πŸš— Local Transport</strong><br>${budget_data['transport']:.0f}<br><small>(${budget_data['daily']['transport']}/day)</small></div>
374
- <div><strong>🎟️ Activities & Tours</strong><br>${budget_data['activities']:.0f}<br><small>(${budget_data['daily']['activities']}/day)</small></div>
375
  <div style="border-top: 2px solid rgba(255,255,255,0.3); padding-top: 10px; grid-column: 1/-1;">
376
  <strong>πŸ’° Total Estimated Cost</strong><br><span style="font-size: 1.2em;">${budget_data['total']:.0f}</span>
377
  {f" (Your budget: ${budget_amount:.0f})" if budget_amount else ""}
@@ -380,19 +374,9 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
380
  </div>
381
  """
382
 
383
- # Travel tips
384
- tips_html = """
385
- <div style="background: #f0f4ff; padding: 20px; border-radius: 12px; margin: 20px 0;">
386
- <h3 style="margin: 0 0 15px 0; color: #667eea;">πŸ’‘ Smart Travel Tips</h3>
387
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
388
- <div>🎫 <strong>Book in Advance</strong><br>Save time and money on popular attractions</div>
389
- <div>πŸš‡ <strong>Public Transport</strong><br>Get day passes for unlimited travel savings</div>
390
- <div>πŸ“± <strong>Offline Maps</strong><br>Download Google Maps before your trip</div>
391
- <div>πŸ’΅ <strong>Local Currency</strong><br>Carry cash for markets and small vendors</div>
392
- <div>🌍 <strong>Learn Basic Phrases</strong><br>A few local words go a long way</div>
393
- <div>πŸ“Έ <strong>Early Bird</strong><br>Visit popular spots early to avoid crowds</div>
394
- </div>
395
- </div>
396
  """
397
 
398
  # Complete HTML
@@ -404,11 +388,6 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
404
  <p style="margin: 10px 0 0; opacity: 0.9; font-size: 1.1em;">
405
  Intelligent Itinerary Generation β€’ Real-time Data β€’ Smart Recommendations
406
  </p>
407
- <div style="margin-top: 15px;">
408
- <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em;">
409
- 🧠 Powered by Multi-Agent AI System
410
- </span>
411
- </div>
412
  <h2 style="margin: 20px 0 0; font-size: 1.8em;">🌍 {destination}</h2>
413
  <p style="margin: 10px 0 0; opacity: 0.9;">
414
  {num_days} Days of Adventure β€’ {start_date.strftime('%B %d')} - {end_date.strftime('%B %d, %Y')}
@@ -416,39 +395,27 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
416
  {f"<p style='margin: 5px 0 0; opacity: 0.8;'>✈️ From: {departure_city}</p>" if departure_city else ""}
417
  </div>
418
 
419
- <!-- AI Agent Badge -->
420
- <div style="text-align: center; margin-bottom: 20px;">
421
- <div style="display: inline-block; background: #e8f0fe; padding: 8px 20px; border-radius: 30px;">
422
- <span style="color: #667eea;">πŸ€–</span>
423
- <strong>AI Agents Active:</strong> Weather Agent β€’ Attraction Scout β€’ Budget Optimizer β€’ Itinerary Planner
424
- </div>
425
- </div>
426
-
427
  <!-- Quick Stats -->
428
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px;">
429
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
430
  <div style="font-size: 2em;">🌀️</div>
431
  <strong>{weather_temp}</strong><br>
432
  <small>{weather_condition}</small>
433
- <div style="font-size: 0.75em; color: #888; margin-top: 5px;">Weather Agent</div>
434
  </div>
435
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
436
  <div style="font-size: 2em;">πŸ“…</div>
437
  <strong>{num_days} Days</strong><br>
438
  <small>Full Itinerary</small>
439
- <div style="font-size: 0.75em; color: #888; margin-top: 5px;">Planner Agent</div>
440
  </div>
441
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
442
  <div style="font-size: 2em;">🎯</div>
443
- <strong>{len(attractions)}+ Attractions</strong><br>
444
  <small>To Explore</small>
445
- <div style="font-size: 0.75em; color: #888; margin-top: 5px;">Scout Agent</div>
446
  </div>
447
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
448
  <div style="font-size: 2em;">πŸ’°</div>
449
  <strong>${budget_data['total']:.0f}</strong><br>
450
- <small>Optimized Budget</small>
451
- <div style="font-size: 0.75em; color: #888; margin-top: 5px;">Budget Agent</div>
452
  </div>
453
  </div>
454
 
@@ -457,25 +424,26 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
457
  <!-- Budget Section -->
458
  {budget_html}
459
 
460
- <!-- Top Attractions -->
461
- <div style="background: white; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05);">
462
- <h2 style="margin: 0 0 15px 0; color: #667eea;">✨ AI-Curated Top Attractions in {destination}</h2>
463
- <div style="font-size: 0.85em; color: #888; margin-bottom: 15px;">πŸ” Scout Agent Analysis β€’ Real-time Data</div>
464
- {attractions_html}
465
- </div>
466
-
467
  <!-- Daily Itinerary -->
468
  <div style="background: white; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05);">
469
  <h2 style="margin: 0 0 15px 0; color: #667eea;">πŸ“… AI-Generated {num_days}-Day Itinerary</h2>
470
- <div style="font-size: 0.85em; color: #888; margin-bottom: 15px;">πŸ€– Planner Agent β€’ Optimized Schedule</div>
471
  {daily_html}
472
  </div>
473
 
474
- <!-- Destination Image (Only if real image exists) -->
475
  {image_html}
476
 
477
  <!-- Travel Tips -->
478
- {tips_html}
 
 
 
 
 
 
 
 
479
 
480
  <!-- Booking Links -->
481
  <div style="text-align: center; padding: 20px; margin-top: 20px; background: #f8f9fa; border-radius: 12px;">
@@ -484,13 +452,13 @@ def generate_html_itinerary(destination, weather, attractions, budget_data,
484
  πŸ›οΈ <a href="https://www.booking.com/searchresults.html?ss={destination.replace(' ', '+')}" target="_blank" style="color: #667eea;">Search Hotels</a> |
485
  ✈️ <a href="https://www.skyscanner.net/" target="_blank" style="color: #667eea;">Search Flights</a> |
486
  🎟️ <a href="https://www.tripadvisor.com/Search?q={destination}" target="_blank" style="color: #667eea;">Read Reviews</a>
 
487
  </p>
488
  </div>
489
 
490
  <!-- Footer -->
491
  <div style="text-align: center; padding: 20px; margin-top: 20px; font-size: 0.85em; color: #666;">
492
- <p>πŸ€– Agentic AI System β€’ Powered by OpenAI, OpenWeather & SerpAPI β€’ Real-time Multi-Agent Processing</p>
493
- <p>⚑ Weather Agent β€’ Scout Agent β€’ Budget Agent β€’ Planner Agent β€’ All Working in Harmony</p>
494
  </div>
495
  </div>
496
  """
@@ -529,18 +497,10 @@ with gr.Blocks(css=css, title="πŸ€– Agentic AI Travel Planner") as demo:
529
  Multi-Agent System β€’ Intelligent Itinerary Generation β€’ Real-time Data Processing
530
  </p>
531
  <div style="margin-top: 15px;">
532
- <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">
533
- 🧠 Weather Agent
534
- </span>
535
- <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">
536
- πŸ” Scout Agent
537
- </span>
538
- <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">
539
- πŸ’° Budget Agent
540
- </span>
541
- <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">
542
- πŸ“… Planner Agent
543
- </span>
544
  </div>
545
  </div>
546
  """)
@@ -567,7 +527,7 @@ with gr.Blocks(css=css, title="πŸ€– Agentic AI Travel Planner") as demo:
567
  num_days = gr.Slider(
568
  label="Duration (Days)",
569
  minimum=1,
570
- maximum=5,
571
  value=3,
572
  step=1
573
  )
@@ -601,7 +561,7 @@ with gr.Blocks(css=css, title="πŸ€– Agentic AI Travel Planner") as demo:
601
  )
602
 
603
  with gr.Row():
604
- generate_btn = gr.Button("πŸš€ Activate AI Agents & Generate Itinerary", variant="primary", size="lg")
605
 
606
  output = gr.HTML()
607
 
 
26
  """Fetch a real image of the destination using SerpAPI."""
27
  cache_key = city.lower()
28
 
 
29
  if cache_key in IMAGE_CACHE:
30
  return IMAGE_CACHE[cache_key]
31
 
 
32
  if SERP_API_KEY:
33
  try:
 
34
  params = {
35
  "q": f"{city} city landmark beautiful view",
36
  "api_key": SERP_API_KEY,
 
50
  except Exception as e:
51
  print(f"Image fetch error: {e}")
52
 
 
53
  return None
54
 
55
  # -------------------- WEATHER FUNCTION --------------------
 
82
  except Exception as e:
83
  return {"error": f"Weather error: {str(e)}"}
84
 
85
+ # -------------------- REAL ATTRACTIONS FUNCTION --------------------
86
  def get_real_attractions(city: str) -> list:
87
+ """Get real attractions using OpenAI first for better quality."""
88
 
89
+ # Try OpenAI first for accurate attraction names and activities
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  if client:
91
  try:
92
  prompt = f"""List the top 6 REAL tourist attractions in {city}. For each attraction, provide:
93
+ - Name (actual famous attraction name)
94
+ - A brief description including specific ACTIVITIES you can do there (use action words like: swimming, hiking, skating, climbing, boating, cycling, shopping, dining, photography, sightseeing, museum visiting, etc.)
95
  - Typical entry fee in USD (use numbers, 0 if free)
96
  - Approximate visit duration in hours
97
 
98
  Return ONLY a valid JSON array with keys: name, description, entry_fee, duration_hours.
99
 
100
+ Example for Paris:
101
+ [
102
+ {{
103
+ "name": "Eiffel Tower",
104
+ "description": "Climb to the top for panoramic views, dine at the restaurant, and enjoy photography at sunset.",
105
+ "entry_fee": 25,
106
+ "duration_hours": 2.5
107
+ }}
108
+ ]
109
 
110
+ Important: Use REAL attractions specific to {city}. Make descriptions include ACTION WORDS like hiking, swimming, exploring, climbing."""
111
 
112
  response = client.chat.completions.create(
113
  model="gpt-3.5-turbo",
 
156
  except Exception as e:
157
  print(f"OpenAI attractions error: {e}")
158
 
159
+ # Fallback to SerpAPI
160
+ if SERP_API_KEY:
161
+ try:
162
+ params = {
163
+ "q": f"top tourist attractions in {city}",
164
+ "api_key": SERP_API_KEY,
165
+ "engine": "google",
166
+ "num": 6
167
+ }
168
+ response = requests.get("https://serpapi.com/search", params=params, timeout=10)
169
+ data = response.json()
170
+
171
+ attractions = []
172
+ for result in data.get("organic_results", [])[:6]:
173
+ title = result.get("title", "")
174
+ if "wikipedia" not in title.lower() and "tripadvisor" not in title.lower():
175
+ name = title.split(" - ")[0].split(" | ")[0].split(":")[0].strip()
176
+ if len(name) > 5 and len(name) < 100:
177
+ attractions.append({
178
+ "name": name,
179
+ "description": result.get("snippet", f"Explore this popular attraction in {city}"),
180
+ "entry_fee": "Check website",
181
+ "duration_hours": 2
182
+ })
183
+
184
+ if attractions and len(attractions) >= 3:
185
+ return attractions[:6]
186
+ except Exception as e:
187
+ print(f"SerpAPI error: {e}")
188
+
189
+ # Ultimate fallback
190
  return [
191
+ {"name": f"Historic {city} City Center", "description": f"Explore the historic heart of {city} with walking tours, shopping, and photography.", "entry_fee": "Free", "duration_hours": 2},
192
+ {"name": f"{city} Cultural Museum", "description": f"Discover art and history through guided tours and interactive exhibits.", "entry_fee": "$10", "duration_hours": 2},
193
+ {"name": f"{city} Central Park", "description": f"Enjoy hiking, picnicking, and outdoor activities in this beautiful green space.", "entry_fee": "Free", "duration_hours": 1.5}
 
194
  ]
195
 
196
  # -------------------- BUDGET CALCULATION --------------------
 
235
  budget_amount: float, budget_currency: str, departure_city: str = ""):
236
  """Main itinerary generation function."""
237
  try:
 
238
  if not destination:
239
  return "❌ Please enter a destination city."
240
 
241
  if num_days < 1 or num_days > 14:
242
  return "❌ Number of days must be between 1 and 14."
243
 
 
244
  weather = get_weather(destination)
245
  if "error" in weather:
246
  return f"❌ Weather error: {weather['error']}"
247
 
 
248
  attractions = get_real_attractions(destination)
 
 
249
  destination_image = get_destination_image(destination)
 
 
250
  budget_data = calculate_budget(num_days, budget_amount)
251
 
 
252
  start = datetime.strptime(start_date, "%Y-%m-%d")
253
  end = start + timedelta(days=int(num_days) - 1)
254
 
 
255
  html = generate_html_itinerary(
256
  destination, weather, attractions, budget_data,
257
  num_days, start, end, budget_amount, departure_city, destination_image
 
264
 
265
  def generate_html_itinerary(destination, weather, attractions, budget_data,
266
  num_days, start_date, end_date, budget_amount, departure_city, destination_image):
267
+ """Create beautiful HTML itinerary with max 3 attractions per day."""
268
 
 
269
  weather_temp = f"{weather['temperature']:.1f}Β°C" if isinstance(weather['temperature'], (int, float)) else str(weather['temperature'])
270
  weather_condition = weather['condition'].capitalize()
271
 
 
279
  </div>
280
  """
281
 
282
+ # Daily itinerary - MAX 3 ATTRACTIONS PER DAY
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  daily_html = ""
284
+ # Distribute attractions: max 3 per day
285
+ attractions_per_day = min(3, len(attractions))
286
+ total_attractions = min(len(attractions), attractions_per_day * num_days)
287
 
288
  for day in range(1, num_days + 1):
289
  current_date = start_date + timedelta(days=day-1)
290
  date_str = current_date.strftime("%A, %B %d")
291
 
292
+ # Get attractions for this day (max 3 different attractions)
293
+ start_idx = (day-1) * attractions_per_day
294
+ end_idx = min(day * attractions_per_day, total_attractions)
295
+ day_attractions = attractions[start_idx:end_idx] if start_idx < len(attractions) else []
296
 
297
+ # If no more unique attractions, use activities based on weather
298
+ if not day_attractions:
299
+ weather_lower = weather_condition.lower()
300
+ if "rain" in weather_lower:
301
+ activities = ["Visit indoor museums", "Enjoy shopping malls", "Try local cooking classes"]
302
+ elif "sun" in weather_lower or "clear" in weather_lower:
303
+ activities = ["Outdoor hiking", "Parks and gardens", "City walking tours"]
304
+ else:
305
+ activities = ["Cultural experiences", "Local food tasting", "Art gallery visits"]
 
 
 
 
 
 
 
 
 
 
306
 
307
+ day_attractions = [
308
+ {"name": activities[0], "description": f"Enjoy {activities[0].lower()} in {destination}", "entry_fee": "Varies", "duration_hours": 2},
309
+ {"name": activities[1], "description": f"Experience {activities[1].lower()} in {destination}", "entry_fee": "Varies", "duration_hours": 2},
310
+ {"name": activities[2], "description": f"Explore {activities[2].lower()} in {destination}", "entry_fee": "Varies", "duration_hours": 2}
311
+ ][:attractions_per_day]
312
+
313
+ daily_html += f"""
314
+ <div style="background: white; border-radius: 12px; margin-bottom: 20px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
315
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 12px 20px; color: white;">
316
+ <h3 style="margin: 0; font-size: 1.2em;">Day {day} Β· {date_str}</h3>
317
+ </div>
318
+ <div style="padding: 20px;">
319
+ """
320
+
321
+ for i, attr in enumerate(day_attractions, 1):
322
  daily_html += f"""
323
+ <div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 8px;">
324
+ <div style="display: flex; align-items: center; gap: 10px;">
325
+ <div style="background: #667eea; color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold;">
326
+ {i}
327
+ </div>
328
+ <div style="flex: 1;">
329
+ <strong style="font-size: 1em;">🎯 {attr['name']}</strong><br>
330
+ <span style="font-size: 0.85em; color: #666;">{attr['description']}</span>
331
+ <div style="font-size: 0.75em; color: #888; margin-top: 5px;">
332
+ ⏱️ {attr['duration_hours']} hrs | 🎟️ {attr['entry_fee']}
333
+ </div>
334
+ </div>
335
  </div>
336
  </div>
 
337
  """
338
+
339
+ daily_html += f"""
340
+ <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e0e0e0;">
341
+ <div><span style="font-size: 1.1em;">🍽️</span> <strong>Lunch:</strong> Try authentic local cuisine at a nearby restaurant</div>
342
+ <div style="margin-top: 8px;"><span style="font-size: 1.1em;">πŸŒ™</span> <strong>Evening:</strong> Explore local markets, enjoy cultural shows, or relax at a cafe</div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ """
347
 
348
+ # Destination Image Section
349
  image_html = ""
350
  if destination_image:
351
  image_html = f"""
352
  <div style="background: white; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05); text-align: center;">
353
  <h2 style="margin: 0 0 15px 0; color: #667eea;">πŸ“Έ A Glimpse of {destination}</h2>
 
354
  <div style="border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
355
+ <img src="{destination_image}" style="width: 100%; max-height: 400px; object-fit: cover;" alt="{destination}">
 
 
 
 
356
  </div>
 
 
 
357
  </div>
358
  """
359
 
 
365
  <div><strong>🏨 Accommodation</strong><br>${budget_data['accommodation']:.0f}<br><small>(${budget_data['daily']['accommodation']}/day)</small></div>
366
  <div><strong>🍽️ Food & Dining</strong><br>${budget_data['food']:.0f}<br><small>(${budget_data['daily']['food']}/day)</small></div>
367
  <div><strong>πŸš— Local Transport</strong><br>${budget_data['transport']:.0f}<br><small>(${budget_data['daily']['transport']}/day)</small></div>
368
+ <div><strong>🎟️ Activities</strong><br>${budget_data['activities']:.0f}<br><small>(${budget_data['daily']['activities']}/day)</small></div>
369
  <div style="border-top: 2px solid rgba(255,255,255,0.3); padding-top: 10px; grid-column: 1/-1;">
370
  <strong>πŸ’° Total Estimated Cost</strong><br><span style="font-size: 1.2em;">${budget_data['total']:.0f}</span>
371
  {f" (Your budget: ${budget_amount:.0f})" if budget_amount else ""}
 
374
  </div>
375
  """
376
 
377
+ # Currency conversion link
378
+ currency_link = f"""
379
+ <a href="https://www.xe.com/currencyconverter/" target="_blank" style="color: #667eea; text-decoration: none; margin-left: 15px;">πŸ’± Currency Converter</a>
 
 
 
 
 
 
 
 
 
 
380
  """
381
 
382
  # Complete HTML
 
388
  <p style="margin: 10px 0 0; opacity: 0.9; font-size: 1.1em;">
389
  Intelligent Itinerary Generation β€’ Real-time Data β€’ Smart Recommendations
390
  </p>
 
 
 
 
 
391
  <h2 style="margin: 20px 0 0; font-size: 1.8em;">🌍 {destination}</h2>
392
  <p style="margin: 10px 0 0; opacity: 0.9;">
393
  {num_days} Days of Adventure β€’ {start_date.strftime('%B %d')} - {end_date.strftime('%B %d, %Y')}
 
395
  {f"<p style='margin: 5px 0 0; opacity: 0.8;'>✈️ From: {departure_city}</p>" if departure_city else ""}
396
  </div>
397
 
 
 
 
 
 
 
 
 
398
  <!-- Quick Stats -->
399
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px;">
400
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
401
  <div style="font-size: 2em;">🌀️</div>
402
  <strong>{weather_temp}</strong><br>
403
  <small>{weather_condition}</small>
 
404
  </div>
405
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
406
  <div style="font-size: 2em;">πŸ“…</div>
407
  <strong>{num_days} Days</strong><br>
408
  <small>Full Itinerary</small>
 
409
  </div>
410
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
411
  <div style="font-size: 2em;">🎯</div>
412
+ <strong>{min(len(attractions), num_days * 3)}+ Activities</strong><br>
413
  <small>To Explore</small>
 
414
  </div>
415
  <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;">
416
  <div style="font-size: 2em;">πŸ’°</div>
417
  <strong>${budget_data['total']:.0f}</strong><br>
418
+ <small>Estimated Total</small>
 
419
  </div>
420
  </div>
421
 
 
424
  <!-- Budget Section -->
425
  {budget_html}
426
 
 
 
 
 
 
 
 
427
  <!-- Daily Itinerary -->
428
  <div style="background: white; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05);">
429
  <h2 style="margin: 0 0 15px 0; color: #667eea;">πŸ“… AI-Generated {num_days}-Day Itinerary</h2>
430
+ <div style="font-size: 0.85em; color: #888; margin-bottom: 15px;">πŸ€– Planner Agent β€’ Optimized Schedule β€’ Max 3 Activities Per Day</div>
431
  {daily_html}
432
  </div>
433
 
434
+ <!-- Destination Image -->
435
  {image_html}
436
 
437
  <!-- Travel Tips -->
438
+ <div style="background: #f0f4ff; padding: 20px; border-radius: 12px; margin: 20px 0;">
439
+ <h3 style="margin: 0 0 15px 0; color: #667eea;">πŸ’‘ Smart Travel Tips</h3>
440
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
441
+ <div>🎫 <strong>Book in Advance</strong><br>Save time and money</div>
442
+ <div>πŸš‡ <strong>Public Transport</strong><br>Get day passes for savings</div>
443
+ <div>πŸ“± <strong>Offline Maps</strong><br>Download before you go</div>
444
+ <div>πŸ’΅ <strong>Local Currency</strong><br>Carry cash for markets</div>
445
+ </div>
446
+ </div>
447
 
448
  <!-- Booking Links -->
449
  <div style="text-align: center; padding: 20px; margin-top: 20px; background: #f8f9fa; border-radius: 12px;">
 
452
  πŸ›οΈ <a href="https://www.booking.com/searchresults.html?ss={destination.replace(' ', '+')}" target="_blank" style="color: #667eea;">Search Hotels</a> |
453
  ✈️ <a href="https://www.skyscanner.net/" target="_blank" style="color: #667eea;">Search Flights</a> |
454
  🎟️ <a href="https://www.tripadvisor.com/Search?q={destination}" target="_blank" style="color: #667eea;">Read Reviews</a>
455
+ {currency_link}
456
  </p>
457
  </div>
458
 
459
  <!-- Footer -->
460
  <div style="text-align: center; padding: 20px; margin-top: 20px; font-size: 0.85em; color: #666;">
461
+ <p>πŸ€– Agentic AI System β€’ Powered by OpenAI, OpenWeather & SerpAPI</p>
 
462
  </div>
463
  </div>
464
  """
 
497
  Multi-Agent System β€’ Intelligent Itinerary Generation β€’ Real-time Data Processing
498
  </p>
499
  <div style="margin-top: 15px;">
500
+ <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">🧠 Weather Agent</span>
501
+ <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">πŸ” Scout Agent</span>
502
+ <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">πŸ’° Budget Agent</span>
503
+ <span style="background: rgba(255,255,255,0.2); padding: 5px 12px; border-radius: 20px; font-size: 0.85em; margin: 0 5px;">πŸ“… Planner Agent</span>
 
 
 
 
 
 
 
 
504
  </div>
505
  </div>
506
  """)
 
527
  num_days = gr.Slider(
528
  label="Duration (Days)",
529
  minimum=1,
530
+ maximum=7,
531
  value=3,
532
  step=1
533
  )
 
561
  )
562
 
563
  with gr.Row():
564
+ generate_btn = gr.Button("✈️ Plan My Trip", variant="primary", size="lg")
565
 
566
  output = gr.HTML()
567