import os import json import requests from datetime import datetime, timedelta from functools import lru_cache import gradio as gr import openai import random # -------------------- CONFIGURATION -------------------- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "") SERP_API_KEY = os.getenv("SERPAPI_API_KEY", "") if not OPENAI_API_KEY: print("⚠️ OPENAI_API_KEY not set. AI itinerary generation will fall back to manual mode.") client = None else: client = openai.OpenAI(api_key=OPENAI_API_KEY) # Image cache to avoid repeated API calls IMAGE_CACHE = {} # -------------------- IMAGE FUNCTION -------------------- def get_destination_image(city: str) -> str: """Fetch a real image of the destination using SerpAPI.""" cache_key = city.lower() if cache_key in IMAGE_CACHE: return IMAGE_CACHE[cache_key] if SERP_API_KEY: try: params = { "q": f"{city} city landmark beautiful view", "api_key": SERP_API_KEY, "engine": "google", "tbm": "isch", "num": 1 } response = requests.get("https://serpapi.com/search", params=params, timeout=10) data = response.json() images = data.get("images_results", []) if images and len(images) > 0: img_url = images[0].get("original", images[0].get("thumbnail")) if img_url: IMAGE_CACHE[cache_key] = img_url return img_url except Exception as e: print(f"Image fetch error: {e}") return None # -------------------- WEATHER FUNCTION -------------------- def get_weather(city: str) -> dict: """Fetch real-time weather data for any city.""" if not OPENWEATHER_API_KEY: return { "city": city, "temperature": 22, "condition": "sunny", "humidity": 60, "wind_speed": 10, "note": "Add OpenWeather API key for real data" } try: url = "https://api.openweathermap.org/data/2.5/weather" params = {'q': city, 'appid': OPENWEATHER_API_KEY, 'units': 'metric'} response = requests.get(url, params=params, timeout=10) data = response.json() if response.status_code != 200: return {"error": f"Weather API error: {data.get('message', 'unknown')}"} return { "city": city, "temperature": data['main']['temp'], "condition": data['weather'][0]['description'], "humidity": data['main']['humidity'], "wind_speed": data['wind']['speed'], "precipitation": data.get('rain', {}).get('1h', 0) } except Exception as e: return {"error": f"Weather error: {str(e)}"} # -------------------- REAL ATTRACTIONS FUNCTION -------------------- def get_real_attractions(city: str) -> list: """Get real attractions using OpenAI first for better quality.""" # Try OpenAI first for accurate attraction names and activities if client: try: prompt = f"""List the top 6 REAL tourist attractions in {city}. For each attraction, provide: - Name (actual famous attraction name) - 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.) - Typical entry fee in USD (use numbers, 0 if free) - Approximate visit duration in hours Return ONLY a valid JSON array with keys: name, description, entry_fee, duration_hours. Example for Paris: [ {{ "name": "Eiffel Tower", "description": "Climb to the top for panoramic views, dine at the restaurant, and enjoy photography at sunset.", "entry_fee": 25, "duration_hours": 2.5 }} ] Important: Use REAL attractions specific to {city}. Make descriptions include ACTION WORDS like hiking, swimming, exploring, climbing.""" response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], temperature=0.7, max_tokens=1000 ) content = response.choices[0].message.content # Clean response content = content.strip() if content.startswith('```json'): content = content[7:] if content.startswith('```'): content = content[3:] if content.endswith('```'): content = content[:-3] attractions = json.loads(content) if isinstance(attractions, dict) and "attractions" in attractions: attractions = attractions["attractions"] elif not isinstance(attractions, list): attractions = [attractions] result = [] for a in attractions[:6]: fee = a.get("entry_fee", 0) if isinstance(fee, (int, float)): if fee == 0: fee_display = "Free" else: fee_display = f"${fee}" else: fee_display = str(fee) result.append({ "name": a.get("name", "Unknown"), "description": a.get("description", f"A must-visit attraction in {city}"), "entry_fee": fee_display, "duration_hours": a.get("duration_hours", 2) }) if result: return result except Exception as e: print(f"OpenAI attractions error: {e}") # Fallback to SerpAPI if SERP_API_KEY: try: params = { "q": f"top tourist attractions in {city}", "api_key": SERP_API_KEY, "engine": "google", "num": 6 } response = requests.get("https://serpapi.com/search", params=params, timeout=10) data = response.json() attractions = [] for result in data.get("organic_results", [])[:6]: title = result.get("title", "") if "wikipedia" not in title.lower() and "tripadvisor" not in title.lower(): name = title.split(" - ")[0].split(" | ")[0].split(":")[0].strip() if len(name) > 5 and len(name) < 100: attractions.append({ "name": name, "description": result.get("snippet", f"Explore this popular attraction in {city}"), "entry_fee": "Check website", "duration_hours": 2 }) if attractions and len(attractions) >= 3: return attractions[:6] except Exception as e: print(f"SerpAPI error: {e}") # Ultimate fallback return [ {"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}, {"name": f"{city} Cultural Museum", "description": f"Discover art and history through guided tours and interactive exhibits.", "entry_fee": "$10", "duration_hours": 2}, {"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} ] # -------------------- BUDGET CALCULATION -------------------- def calculate_budget(num_days: int, budget_amount: float = None) -> dict: """Calculate budget based on days and user input.""" if budget_amount: daily_budget = budget_amount / num_days if daily_budget < 100: level = "Budget" daily_rates = {"accommodation": 50, "food": 35, "transport": 15, "activities": 10} elif daily_budget < 200: level = "Moderate" daily_rates = {"accommodation": 100, "food": 60, "transport": 25, "activities": 20} elif daily_budget < 400: level = "Comfortable" daily_rates = {"accommodation": 180, "food": 100, "transport": 35, "activities": 35} else: level = "Luxury" daily_rates = {"accommodation": 300, "food": 150, "transport": 50, "activities": 60} else: level = "Moderate" daily_rates = {"accommodation": 100, "food": 60, "transport": 25, "activities": 20} accommodation = daily_rates["accommodation"] * num_days food = daily_rates["food"] * num_days transport = daily_rates["transport"] * num_days activities = daily_rates["activities"] * num_days total = accommodation + food + transport + activities return { "level": level, "accommodation": accommodation, "food": food, "transport": transport, "activities": activities, "total": total, "daily": daily_rates } # -------------------- ITINERARY GENERATION -------------------- def generate_itinerary(destination: str, start_date: str, num_days: int, budget_amount: float, budget_currency: str, departure_city: str = ""): """Main itinerary generation function.""" try: if not destination: return "❌ Please enter a destination city." if num_days < 1 or num_days > 14: return "❌ Number of days must be between 1 and 14." weather = get_weather(destination) if "error" in weather: return f"❌ Weather error: {weather['error']}" attractions = get_real_attractions(destination) destination_image = get_destination_image(destination) budget_data = calculate_budget(num_days, budget_amount) start = datetime.strptime(start_date, "%Y-%m-%d") end = start + timedelta(days=int(num_days) - 1) html = generate_html_itinerary( destination, weather, attractions, budget_data, num_days, start, end, budget_amount, departure_city, destination_image ) return html except Exception as e: return f"❌ An unexpected error occurred: {str(e)}" def generate_html_itinerary(destination, weather, attractions, budget_data, num_days, start_date, end_date, budget_amount, departure_city, destination_image): """Create beautiful HTML itinerary with max 3 attractions per day.""" weather_temp = f"{weather['temperature']:.1f}°C" if isinstance(weather['temperature'], (int, float)) else str(weather['temperature']) weather_condition = weather['condition'].capitalize() # Budget warning budget_warning = "" if budget_amount and budget_data['total'] > budget_amount: budget_warning = f"""
⚠️ Budget Alert: Estimated costs (${budget_data['total']:.0f}) exceed your budget (${budget_amount:.0f}). Consider reducing days or choosing budget-friendly options.
""" # Daily itinerary - MAX 3 ATTRACTIONS PER DAY daily_html = "" # Distribute attractions: max 3 per day attractions_per_day = min(3, len(attractions)) total_attractions = min(len(attractions), attractions_per_day * num_days) for day in range(1, num_days + 1): current_date = start_date + timedelta(days=day-1) date_str = current_date.strftime("%A, %B %d") # Get attractions for this day (max 3 different attractions) start_idx = (day-1) * attractions_per_day end_idx = min(day * attractions_per_day, total_attractions) day_attractions = attractions[start_idx:end_idx] if start_idx < len(attractions) else [] # If no more unique attractions, use activities based on weather if not day_attractions: weather_lower = weather_condition.lower() if "rain" in weather_lower: activities = ["Visit indoor museums", "Enjoy shopping malls", "Try local cooking classes"] elif "sun" in weather_lower or "clear" in weather_lower: activities = ["Outdoor hiking", "Parks and gardens", "City walking tours"] else: activities = ["Cultural experiences", "Local food tasting", "Art gallery visits"] day_attractions = [ {"name": activities[0], "description": f"Enjoy {activities[0].lower()} in {destination}", "entry_fee": "Varies", "duration_hours": 2}, {"name": activities[1], "description": f"Experience {activities[1].lower()} in {destination}", "entry_fee": "Varies", "duration_hours": 2}, {"name": activities[2], "description": f"Explore {activities[2].lower()} in {destination}", "entry_fee": "Varies", "duration_hours": 2} ][:attractions_per_day] daily_html += f"""

Day {day} · {date_str}

""" for i, attr in enumerate(day_attractions, 1): daily_html += f"""
{i}
🎯 {attr['name']}
{attr['description']}
⏱️ {attr['duration_hours']} hrs | 🎟️ {attr['entry_fee']}
""" daily_html += f"""
🍽️ Lunch: Try authentic local cuisine at a nearby restaurant
🌙 Evening: Explore local markets, enjoy cultural shows, or relax at a cafe
""" # Destination Image Section image_html = "" if destination_image: image_html = f"""

📸 A Glimpse of {destination}

{destination}
""" # Budget breakdown budget_html = f"""

💰 Budget Breakdown ({budget_data['level']} Travel Style)

🏨 Accommodation
${budget_data['accommodation']:.0f}
(${budget_data['daily']['accommodation']}/day)
🍽️ Food & Dining
${budget_data['food']:.0f}
(${budget_data['daily']['food']}/day)
🚗 Local Transport
${budget_data['transport']:.0f}
(${budget_data['daily']['transport']}/day)
🎟️ Activities
${budget_data['activities']:.0f}
(${budget_data['daily']['activities']}/day)
💰 Total Estimated Cost
${budget_data['total']:.0f} {f" (Your budget: ${budget_amount:.0f})" if budget_amount else ""}
""" # Currency conversion link currency_link = f""" 💱 Currency Converter """ # Complete HTML full_html = f"""

🤖 Agentic AI Travel Planner

Intelligent Itinerary Generation • Real-time Data • Smart Recommendations

🌍 {destination}

{num_days} Days of Adventure • {start_date.strftime('%B %d')} - {end_date.strftime('%B %d, %Y')}

{f"

✈️ From: {departure_city}

" if departure_city else ""}
🌤️
{weather_temp}
{weather_condition}
📅
{num_days} Days
Full Itinerary
🎯
{min(len(attractions), num_days * 3)}+ Activities
To Explore
💰
${budget_data['total']:.0f}
Estimated Total
{budget_warning} {budget_html}

📅 AI-Generated {num_days}-Day Itinerary

🤖 Planner Agent • Optimized Schedule • Max 3 Activities Per Day
{daily_html}
{image_html}

💡 Smart Travel Tips

🎫 Book in Advance
Save time and money
🚇 Public Transport
Get day passes for savings
📱 Offline Maps
Download before you go
💵 Local Currency
Carry cash for markets

Ready to Book Your Trip?

🛏️ Search Hotels | ✈️ Search Flights | 🎟️ Read Reviews {currency_link}

🤖 Agentic AI System • Powered by OpenAI, OpenWeather & SerpAPI

""" return full_html # -------------------- GRADIO INTERFACE -------------------- css = """ .gradio-container { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1200px; margin: 0 auto; } .gr-button-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; font-weight: bold !important; padding: 12px 30px !important; font-size: 1.1em !important; transition: transform 0.2s !important; } .gr-button-primary:hover { transform: translateY(-2px) !important; } input, select, textarea { border-radius: 8px !important; border: 1px solid #e0e0e0 !important; } """ with gr.Blocks(css=css, title="🤖 TRAVEL WITH ME ") as demo: gr.HTML("""

🤖 TRAVEL WITH ME

Multi-Agent System • Intelligent Itinerary Generation • Real-time Data Processing

🧠 Weather Agent 🔍 Scout Agent 💰 Budget Agent 📅 Planner Agent
""") with gr.Row(equal_height=True): with gr.Column(scale=2): with gr.Group(): gr.Markdown("### 🎯 Destination Input") destination = gr.Textbox( label="Enter City", placeholder="e.g., Paris, Tokyo, Nairobi, New York, Rome...", lines=1, show_label=False ) with gr.Group(): gr.Markdown("### 📅 Travel Details") with gr.Row(): start_date = gr.Textbox( label="Start Date", value=datetime.now().strftime("%Y-%m-%d"), placeholder="YYYY-MM-DD" ) num_days = gr.Slider( label="Duration (Days)", minimum=1, maximum=7, value=3, step=1 ) with gr.Column(scale=1): with gr.Group(): gr.Markdown("### 💰 Budget Optimization") with gr.Row(): budget_amount = gr.Number( label="Total Budget (Optional)", placeholder="Enter amount", value=None ) budget_currency = gr.Dropdown( ["USD", "EUR", "GBP", "JPY", "CAD", "AUD"], label="Currency", value="USD" ) gr.HTML("""
💡 Budget Agent: Automatically optimizes recommendations based on your budget!
""") with gr.Group(): gr.Markdown("### ✈️ Departure Info") departure_city = gr.Textbox( label="Departure City (Optional)", placeholder="e.g., New York, London", lines=1 ) with gr.Row(): generate_btn = gr.Button("✈️ Plan My Trip", variant="primary", size="lg") output = gr.HTML() generate_btn.click( fn=generate_itinerary, inputs=[destination, start_date, num_days, budget_amount, budget_currency, departure_city], outputs=output ) # Examples section gr.HTML("""

🌟 Try These Destinations

🤖 Multi-Agent AI System • Weather Agent | Scout Agent | Budget Agent | Planner Agent • Working in Harmony ✨
""") if __name__ == "__main__": demo.launch(share=False, server_name="0.0.0.0")