| import os |
| import json |
| import requests |
| from datetime import datetime, timedelta |
| from functools import lru_cache |
| import gradio as gr |
| import openai |
| import random |
|
|
| |
| 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 = {} |
|
|
| |
| 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 |
|
|
| |
| 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)}"} |
|
|
| |
| def get_real_attractions(city: str) -> list: |
| """Get real attractions using OpenAI first for better quality.""" |
| |
| |
| 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 |
| |
| |
| 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}") |
| |
| |
| 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}") |
| |
| |
| 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} |
| ] |
|
|
| |
| 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 |
| } |
|
|
| |
| 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 = "" |
| if budget_amount and budget_data['total'] > budget_amount: |
| budget_warning = f""" |
| <div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; border-radius: 8px; margin: 15px 0;"> |
| <strong>β οΈ Budget Alert:</strong> Estimated costs (${budget_data['total']:.0f}) exceed your budget (${budget_amount:.0f}). |
| Consider reducing days or choosing budget-friendly options. |
| </div> |
| """ |
| |
| |
| daily_html = "" |
| |
| 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") |
| |
| |
| 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 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""" |
| <div style="background: white; border-radius: 12px; margin-bottom: 20px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05);"> |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 12px 20px; color: white;"> |
| <h3 style="margin: 0; font-size: 1.2em;">Day {day} Β· {date_str}</h3> |
| </div> |
| <div style="padding: 20px;"> |
| """ |
| |
| for i, attr in enumerate(day_attractions, 1): |
| daily_html += f""" |
| <div style="margin-bottom: 15px; padding: 12px; background: #f8f9fa; border-radius: 8px;"> |
| <div style="display: flex; align-items: center; gap: 10px;"> |
| <div style="background: #667eea; color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold;"> |
| {i} |
| </div> |
| <div style="flex: 1;"> |
| <strong style="font-size: 1em;">π― {attr['name']}</strong><br> |
| <span style="font-size: 0.85em; color: #666;">{attr['description']}</span> |
| <div style="font-size: 0.75em; color: #888; margin-top: 5px;"> |
| β±οΈ {attr['duration_hours']} hrs | ποΈ {attr['entry_fee']} |
| </div> |
| </div> |
| </div> |
| </div> |
| """ |
| |
| daily_html += f""" |
| <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e0e0e0;"> |
| <div><span style="font-size: 1.1em;">π½οΈ</span> <strong>Lunch:</strong> Try authentic local cuisine at a nearby restaurant</div> |
| <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> |
| </div> |
| </div> |
| </div> |
| """ |
| |
| |
| image_html = "" |
| if destination_image: |
| image_html = f""" |
| <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;"> |
| <h2 style="margin: 0 0 15px 0; color: #667eea;">πΈ A Glimpse of {destination}</h2> |
| <div style="border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> |
| <img src="{destination_image}" style="width: 100%; max-height: 400px; object-fit: cover;" alt="{destination}"> |
| </div> |
| </div> |
| """ |
| |
| |
| budget_html = f""" |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 12px; color: white; margin: 20px 0;"> |
| <h3 style="margin: 0 0 15px 0;">π° Budget Breakdown ({budget_data['level']} Travel Style)</h3> |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px;"> |
| <div><strong>π¨ Accommodation</strong><br>${budget_data['accommodation']:.0f}<br><small>(${budget_data['daily']['accommodation']}/day)</small></div> |
| <div><strong>π½οΈ Food & Dining</strong><br>${budget_data['food']:.0f}<br><small>(${budget_data['daily']['food']}/day)</small></div> |
| <div><strong>π Local Transport</strong><br>${budget_data['transport']:.0f}<br><small>(${budget_data['daily']['transport']}/day)</small></div> |
| <div><strong>ποΈ Activities</strong><br>${budget_data['activities']:.0f}<br><small>(${budget_data['daily']['activities']}/day)</small></div> |
| <div style="border-top: 2px solid rgba(255,255,255,0.3); padding-top: 10px; grid-column: 1/-1;"> |
| <strong>π° Total Estimated Cost</strong><br><span style="font-size: 1.2em;">${budget_data['total']:.0f}</span> |
| {f" (Your budget: ${budget_amount:.0f})" if budget_amount else ""} |
| </div> |
| </div> |
| </div> |
| """ |
| |
| |
| currency_link = f""" |
| <a href="https://www.xe.com/currencyconverter/" target="_blank" style="color: #667eea; text-decoration: none; margin-left: 15px;">π± Currency Converter</a> |
| """ |
| |
| |
| full_html = f""" |
| <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 100%;"> |
| <!-- Hero Section --> |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px; border-radius: 20px; color: white; margin-bottom: 30px; text-align: center;"> |
| <h1 style="margin: 0; font-size: 2.5em;">π€ Agentic AI Travel Planner</h1> |
| <p style="margin: 10px 0 0; opacity: 0.9; font-size: 1.1em;"> |
| Intelligent Itinerary Generation β’ Real-time Data β’ Smart Recommendations |
| </p> |
| <h2 style="margin: 20px 0 0; font-size: 1.8em;">π {destination}</h2> |
| <p style="margin: 10px 0 0; opacity: 0.9;"> |
| {num_days} Days of Adventure β’ {start_date.strftime('%B %d')} - {end_date.strftime('%B %d, %Y')} |
| </p> |
| {f"<p style='margin: 5px 0 0; opacity: 0.8;'>βοΈ From: {departure_city}</p>" if departure_city else ""} |
| </div> |
| |
| <!-- Quick Stats --> |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px;"> |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;"> |
| <div style="font-size: 2em;">π€οΈ</div> |
| <strong>{weather_temp}</strong><br> |
| <small>{weather_condition}</small> |
| </div> |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;"> |
| <div style="font-size: 2em;">π
</div> |
| <strong>{num_days} Days</strong><br> |
| <small>Full Itinerary</small> |
| </div> |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;"> |
| <div style="font-size: 2em;">π―</div> |
| <strong>{min(len(attractions), num_days * 3)}+ Activities</strong><br> |
| <small>To Explore</small> |
| </div> |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 12px; text-align: center;"> |
| <div style="font-size: 2em;">π°</div> |
| <strong>${budget_data['total']:.0f}</strong><br> |
| <small>Estimated Total</small> |
| </div> |
| </div> |
| |
| {budget_warning} |
| |
| <!-- Budget Section --> |
| {budget_html} |
| |
| <!-- Daily Itinerary --> |
| <div style="background: white; padding: 20px; border-radius: 12px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05);"> |
| <h2 style="margin: 0 0 15px 0; color: #667eea;">π
AI-Generated {num_days}-Day Itinerary</h2> |
| <div style="font-size: 0.85em; color: #888; margin-bottom: 15px;">π€ Planner Agent β’ Optimized Schedule β’ Max 3 Activities Per Day</div> |
| {daily_html} |
| </div> |
| |
| <!-- Destination Image --> |
| {image_html} |
| |
| <!-- Travel Tips --> |
| <div style="background: #f0f4ff; padding: 20px; border-radius: 12px; margin: 20px 0;"> |
| <h3 style="margin: 0 0 15px 0; color: #667eea;">π‘ Smart Travel Tips</h3> |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;"> |
| <div>π« <strong>Book in Advance</strong><br>Save time and money</div> |
| <div>π <strong>Public Transport</strong><br>Get day passes for savings</div> |
| <div>π± <strong>Offline Maps</strong><br>Download before you go</div> |
| <div>π΅ <strong>Local Currency</strong><br>Carry cash for markets</div> |
| </div> |
| </div> |
| |
| <!-- Booking Links --> |
| <div style="text-align: center; padding: 20px; margin-top: 20px; background: #f8f9fa; border-radius: 12px;"> |
| <h3 style="margin: 0 0 15px 0;">Ready to Book Your Trip?</h3> |
| <p> |
| ποΈ <a href="https://www.booking.com/searchresults.html?ss={destination.replace(' ', '+')}" target="_blank" style="color: #667eea;">Search Hotels</a> | |
| βοΈ <a href="https://www.skyscanner.net/" target="_blank" style="color: #667eea;">Search Flights</a> | |
| ποΈ <a href="https://www.tripadvisor.com/Search?q={destination}" target="_blank" style="color: #667eea;">Read Reviews</a> |
| {currency_link} |
| </p> |
| </div> |
| |
| <!-- Footer --> |
| <div style="text-align: center; padding: 20px; margin-top: 20px; font-size: 0.85em; color: #666;"> |
| <p>π€ Agentic AI System β’ Powered by OpenAI, OpenWeather & SerpAPI</p> |
| </div> |
| </div> |
| """ |
| |
| return full_html |
|
|
| |
| 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(""" |
| <div style="text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px; border-radius: 20px; margin-bottom: 30px;"> |
| <h1 style="color: white; margin: 0; font-size: 2.5em;">π€ TRAVEL WITH ME</h1> |
| <p style="color: white; margin: 10px 0 0; opacity: 0.95; font-size: 1.1em;"> |
| Multi-Agent System β’ Intelligent Itinerary Generation β’ Real-time Data Processing |
| </p> |
| <div style="margin-top: 15px;"> |
| <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> |
| <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> |
| <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> |
| <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> |
| </div> |
| </div> |
| """) |
| |
| 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(""" |
| <div style="background: #e8f0fe; padding: 10px; border-radius: 8px; margin-top: 10px;"> |
| <small>π‘ <strong>Budget Agent:</strong> Automatically optimizes recommendations based on your budget!</small> |
| </div> |
| """) |
| |
| 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 |
| ) |
| |
| |
| gr.HTML(""" |
| <div style="text-align: center; padding: 20px; margin-top: 20px; border-top: 1px solid #eee;"> |
| <h3>π Try These Destinations</h3> |
| <div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;"> |
| <button onclick="document.querySelector('#destination input').value='Paris';" style="background: #f0f4ff; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;">πΌ Paris</button> |
| <button onclick="document.querySelector('#destination input').value='Tokyo';" style="background: #f0f4ff; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;">πΎ Tokyo</button> |
| <button onclick="document.querySelector('#destination input').value='Nairobi';" style="background: #f0f4ff; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;">π¦ Nairobi</button> |
| <button onclick="document.querySelector('#destination input').value='New York';" style="background: #f0f4ff; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;">π½ New York</button> |
| <button onclick="document.querySelector('#destination input').value='Rome';" style="background: #f0f4ff; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;">ποΈ Rome</button> |
| <button onclick="document.querySelector('#destination input').value='Bangkok';" style="background: #f0f4ff; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;">π Bangkok</button> |
| </div> |
| </div> |
| |
| <div style="text-align: center; padding: 20px; margin-top: 20px; border-top: 1px solid #eee; color: #666;"> |
| <small>π€ Multi-Agent AI System β’ Weather Agent | Scout Agent | Budget Agent | Planner Agent β’ Working in Harmony β¨</small> |
| </div> |
| """) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=False, server_name="0.0.0.0") |