Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python | |
| # coding=utf-8 | |
| """ | |
| Travel Catalogue Creator - AI Agent for Personalized Travel Planning | |
| Creates comprehensive travel guides with weather, itineraries, packing lists & images | |
| """ | |
| import os | |
| import re | |
| import json | |
| import requests | |
| import yaml | |
| from typing import Optional | |
| from smolagents import CodeAgent, DuckDuckGoSearchTool, HfApiModel, tool | |
| from Gradio_UI import GradioUI | |
| # ====================== | |
| # TRAVEL TOOLS (SMOLAGENTS-COMPLIANT) | |
| # ====================== | |
| def get_weather_forecast(location: str, travel_dates: str) -> str: | |
| """ | |
| Get weather forecast for destination using wttr.in API. | |
| Args: | |
| location: Destination city name (e.g., "Barcelona") | |
| travel_dates: Travel date range string (e.g., "October 15-19") | |
| Returns: | |
| Weather summary string with temperature and packing recommendations | |
| """ | |
| try: | |
| # Clean location name for API | |
| clean = re.sub(r"[^a-zA-Z0-9\s]", "", location).replace(" ", "+") | |
| resp = requests.get(f"http://wttr.in/{clean}?format=j1", timeout=10) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| cur = data["current_condition"][0] | |
| temp = int(cur["temp_C"]) | |
| cond = cur["lang_en"][0]["value"].lower() | |
| # Generate packing recommendations based on temperature | |
| if temp > 25: | |
| pack = "Light clothing, shorts, breathable fabrics, sunscreen, hat" | |
| elif temp > 15: | |
| pack = "Light layers, long sleeves, light jacket" | |
| else: | |
| pack = "Warm layers, jacket, beanie recommended" | |
| # Add rain gear if needed | |
| if "rain" in cond or "shower" in cond: | |
| pack += " + compact umbrella + waterproof jacket" | |
| return f"{location} forecast ({travel_dates}): {temp}°C, {cond}. Packing: {pack}" | |
| except Exception as e: | |
| # Fallback to generic forecast | |
| return f"{location} typical weather: 15-25°C. Pack versatile layers, light jacket, and compact umbrella." | |
| def convert_currency(amount: float, from_currency: str, to_currency: str) -> str: | |
| """ | |
| Convert currency amount using Frankfurter API (no API key required). | |
| Args: | |
| amount: Numeric amount to convert (e.g., 1200.0) | |
| from_currency: Source currency ISO code (e.g., "USD") | |
| to_currency: Target currency ISO code (e.g., "EUR") | |
| Returns: | |
| Formatted string showing conversion result and exchange rate | |
| """ | |
| try: | |
| from_curr = from_currency.upper()[:3] | |
| to_curr = to_currency.upper()[:3] | |
| # Handle same currency | |
| if from_curr == to_curr: | |
| return f"{amount:,.0f} {from_curr} = {amount:,.0f} {to_curr} (1 {from_curr} = 1.00 {to_curr})" | |
| # Get exchange rate | |
| resp = requests.get( | |
| f"https://api.frankfurter.app/latest", | |
| params={"from": from_curr, "to": to_curr}, | |
| timeout=10 | |
| ) | |
| resp.raise_for_status() | |
| rate = resp.json()["rates"][to_curr] | |
| converted = amount * rate | |
| return f"{amount:,.0f} {from_curr} = {converted:,.0f} {to_curr} (1 {from_curr} = {rate:.2f} {to_curr})" | |
| except Exception as e: | |
| # Fallback to showing amount without conversion | |
| return f"{amount:,.0f} {from_currency} = {amount:,.0f} {to_currency} (rate unavailable)" | |
| def get_time_difference(origin_city: str, destination_city: str) -> str: | |
| """ | |
| Calculate time difference between origin and destination cities using heuristic mapping. | |
| Args: | |
| origin_city: Traveler's home city name (e.g., "New York") | |
| destination_city: Travel destination city name (e.g., "Paris") | |
| Returns: | |
| Human-readable string describing time difference and jet lag advice | |
| """ | |
| # Extended timezone mapping (UTC offsets) | |
| city_tz = { | |
| "new york": -5, "london": 0, "paris": 1, "tokyo": 9, "sydney": 10, | |
| "los angeles": -8, "chicago": -6, "mumbai": 5.5, "dubai": 4, | |
| "singapore": 8, "berlin": 1, "rome": 1, "barcelona": 1, "madrid": 1, | |
| "amsterdam": 1, "vienna": 1, "prague": 1, "budapest": 1, "warsaw": 1, | |
| "stockholm": 1, "oslo": 1, "copenhagen": 1, "helsinki": 2, "athens": 2, | |
| "istanbul": 3, "cairo": 2, "bangkok": 7, "seoul": 9, "beijing": 8, | |
| "shanghai": 8, "hong kong": 8, "manila": 8, "kuala lumpur": 8, | |
| "jakarta": 7, "delhi": 5.5, "rio de janeiro": -3, "sao paulo": -3, | |
| "buenos aires": -3, "mexico city": -6, "toronto": -5, "vancouver": -8, | |
| "lisbon": 0, "porto": 0, "brussels": 1, "zurich": 1, "geneva": 1, | |
| "milan": 1, "florence": 1, "venice": 1, "naples": 1, "dublin": 0, | |
| "edinburgh": 0, "glasgow": 0, "manchester": 0, "birmingham": 0, | |
| "krakow": 1, "gdansk": 1, "wroclaw": 1, "riga": 2, "vilnius": 2, | |
| "tallinn": 2, "sofia": 2, "bucharest": 2, "zagreb": 1, "ljubljana": 1, | |
| "belgrade": 1, "sarajevo": 1, "nicosia": 2, "valletta": 1, "reykjavik": 0, | |
| } | |
| origin_key = origin_city.lower().strip() | |
| dest_key = destination_city.lower().strip() | |
| origin_tz = city_tz.get(origin_key, 0) | |
| dest_tz = city_tz.get(dest_key, 0) | |
| diff = dest_tz - origin_tz | |
| if diff == 0: | |
| return f"No time difference between {origin_city} and {destination_city}." | |
| elif diff > 0: | |
| return f"{destination_city} is {diff} hours ahead of {origin_city}. Prepare for eastward jet lag (may cause fatigue)." | |
| else: | |
| return f"{destination_city} is {abs(diff)} hours behind {origin_city}. Prepare for westward jet lag (may cause insomnia)." | |
| def generate_packing_list(destination: str, weather_summary: str, trip_days: int, trip_type: str) -> str: | |
| """ | |
| Generate customized packing checklist based on destination, weather, duration and trip type. | |
| Args: | |
| destination: Destination city or region name (e.g., "Barcelona") | |
| weather_summary: Weather forecast string containing temperature and conditions | |
| trip_days: Total number of travel days (integer >= 1) | |
| trip_type: Type of trip: "city", "beach", "mountain", or "mixed" | |
| Returns: | |
| Formatted multi-section packing checklist as string | |
| """ | |
| weather_lower = weather_summary.lower() | |
| # Analyze weather conditions | |
| cold = any(w in weather_lower for w in ["cold", "cool", "10°c", "11°c", "12°c", "13°c", "14°c", "jacket", "below 15"]) | |
| rain = any(w in weather_lower for w in ["rain", "shower", "drizzle", "umbrella", "precipitation"]) | |
| hot = any(w in weather_lower for w in ["hot", "warm", "25°c", "26°c", "27°c", "28°c", "29°c", "30°c", "above 25"]) | |
| # Calculate clothing quantities | |
| tops = min(trip_days, trip_days // 2 + 2) | |
| bottoms = min(trip_days // 2 + 1, 4) | |
| # Build clothing list | |
| clothes = [f"• Tops ({tops})", f"• Bottoms ({bottoms})"] | |
| if cold: | |
| clothes.extend([ | |
| "• Warm jacket/coat", | |
| "• Long underwear (if very cold)", | |
| "• Warm socks (x3)" | |
| ]) | |
| elif hot: | |
| clothes.extend([ | |
| "• Light breathable fabrics", | |
| "• Sun hat", | |
| "• Sunglasses", | |
| "• Reef-safe sunscreen (SPF 50+)" | |
| ]) | |
| else: | |
| clothes.append("• Light jacket or sweater (essential)") | |
| if rain: | |
| clothes.extend([ | |
| "• Compact travel umbrella", | |
| "• Quick-dry clothing", | |
| "• Waterproof shoes or sandals" | |
| ]) | |
| # Add trip-type specific items | |
| if trip_type == "beach": | |
| clothes.extend([ | |
| "• Swimsuit (x2)", | |
| "• Beach towel", | |
| "• Flip-flops/sandals", | |
| "• Beach bag" | |
| ]) | |
| elif trip_type == "mountain": | |
| clothes.extend([ | |
| "• Sturdy hiking shoes", | |
| "• Moisture-wicking base layers", | |
| "• Trekking socks (x3)", | |
| "• Daypack" | |
| ]) | |
| return ( | |
| f"🎒 SMART PACKING LIST ({trip_days}-day {trip_type} trip to {destination})\n" | |
| "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" | |
| "ESSENTIALS\n" | |
| "• Passport + photocopies + digital scans in cloud\n" | |
| "• Credit/debit cards + small amount of local currency\n" | |
| "• Universal power adapter\n" | |
| "• Phone + portable charger (10,000mAh+) + cables\n" | |
| "• Travel insurance documents (digital + physical)\n" | |
| "\nCLOTHING\n" + "\n".join(clothes) + "\n" | |
| "\nHEALTH & HYGIENE\n" | |
| "• Prescription medications (in original packaging + doctor's note)\n" | |
| "• Basic first-aid kit (bandages, antiseptic, pain relievers)\n" | |
| "• Hand sanitizer (60ml travel size)\n" | |
| "• Travel-sized toiletries (toothbrush, toothpaste, deodorant)\n" | |
| "\n💡 Pro tip: Roll clothes to save space. Pack one versatile outfit per day + 1 extra day buffer." | |
| ) | |
| def build_itinerary(destination: str, attractions: str, budget_local: float, days: int) -> str: | |
| """ | |
| Create realistic day-by-day travel itinerary with time allocations and budget guidance. | |
| Args: | |
| destination: Destination city name (e.g., "Barcelona") | |
| attractions: Comma-separated list of attraction names (e.g., "Sagrada Familia, Park Guell") | |
| budget_local: Daily budget in local currency (float) | |
| days: Number of full travel days (integer >= 1) | |
| Returns: | |
| Formatted multi-day itinerary with time slots and daily budget allocation | |
| """ | |
| # Parse attractions list | |
| att_list = [a.strip() for a in attractions.split(",") if a.strip()] | |
| if not att_list: | |
| att_list = ["Old Town exploration", "Local museum", "Scenic viewpoint", "Local market"] | |
| lines = [ | |
| f"🗓️ {days}-DAY REALISTIC ITINERARY: {destination}", | |
| "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| ] | |
| # Generate day-by-day schedule | |
| for d in range(1, days + 1): | |
| primary = att_list[(d - 1) % len(att_list)] | |
| secondary = att_list[d % len(att_list)] if len(att_list) > 1 else "Leisurely café break" | |
| lines.extend([ | |
| f"\nDAY {d} | Budget: ~{budget_local:,.0f} local currency", | |
| f" 09:00 - 12:00 {primary} (arrive early to avoid crowds)", | |
| f" 12:30 - 14:00 Lunch at local spot (budget: ~{budget_local * 0.25:,.0f})", | |
| f" 14:30 - 17:00 {secondary}", | |
| f" 19:00+ Dinner + evening stroll (budget: ~{budget_local * 0.35:,.0f})" | |
| ]) | |
| lines.append("\n💡 Pro tips: Book major attractions online in advance. Use public transport day passes for 30%+ savings.") | |
| return "\n".join(lines) | |
| def generate_travel_images(destination: str) -> str: | |
| """ | |
| Generate two travel image URLs using free stock photo services. | |
| Args: | |
| destination: Destination city name (e.g., "Lisbon") | |
| Returns: | |
| JSON-formatted string containing two image URLs with keys "landmark_image" and "street_scene_image" | |
| """ | |
| # Map popular destinations to actual quality travel photos | |
| # Using Pixabay's CDN which hosts free travel images | |
| destination_lower = destination.lower().strip() | |
| # High-quality travel images for popular destinations | |
| image_map = { | |
| "barcelona": { | |
| "landmark": "https://cdn.pixabay.com/photo/2017/01/31/21/23/architecture-2025080_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/11/14/04/45/elephant-1822636_960_720.jpg" | |
| }, | |
| "paris": { | |
| "landmark": "https://cdn.pixabay.com/photo/2015/10/06/18/26/eiffel-tower-975004_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/11/18/15/44/paris-1835941_960_720.jpg" | |
| }, | |
| "london": { | |
| "landmark": "https://cdn.pixabay.com/photo/2014/11/13/23/34/london-530055_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/11/29/04/19/bridge-1867744_960_720.jpg" | |
| }, | |
| "rome": { | |
| "landmark": "https://cdn.pixabay.com/photo/2016/11/14/05/21/rome-1822559_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/03/02/20/13/rome-1232437_960_720.jpg" | |
| }, | |
| "tokyo": { | |
| "landmark": "https://cdn.pixabay.com/photo/2019/04/20/11/39/japan-4141578_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2017/01/28/02/24/japan-2014616_960_720.jpg" | |
| }, | |
| "new york": { | |
| "landmark": "https://cdn.pixabay.com/photo/2017/06/07/15/47/new-york-city-2380683_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/01/19/17/41/statue-of-liberty-1149887_960_720.jpg" | |
| }, | |
| "amsterdam": { | |
| "landmark": "https://cdn.pixabay.com/photo/2017/07/31/11/59/canal-2558009_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/11/29/12/13/architecture-1869547_960_720.jpg" | |
| }, | |
| "lisbon": { | |
| "landmark": "https://cdn.pixabay.com/photo/2018/11/29/21/19/hamburg-3846525_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2019/08/28/10/37/portugal-4436951_960_720.jpg" | |
| }, | |
| "berlin": { | |
| "landmark": "https://cdn.pixabay.com/photo/2016/11/29/03/37/architecture-1867187_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2016/02/17/23/03/germany-1206011_960_720.jpg" | |
| }, | |
| "madrid": { | |
| "landmark": "https://cdn.pixabay.com/photo/2020/06/15/01/06/architecture-5299558_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2019/10/20/09/03/madrid-4563537_960_720.jpg" | |
| }, | |
| "prague": { | |
| "landmark": "https://cdn.pixabay.com/photo/2016/11/23/15/32/prague-1853890_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2017/09/05/10/19/prague-2717166_960_720.jpg" | |
| }, | |
| "dubai": { | |
| "landmark": "https://cdn.pixabay.com/photo/2016/11/22/23/40/burj-khalifa-1851204_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2020/02/08/10/37/dubai-4829188_960_720.jpg" | |
| }, | |
| "singapore": { | |
| "landmark": "https://cdn.pixabay.com/photo/2016/03/27/21/43/singapore-1284628_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2017/12/10/17/40/singapore-3010803_960_720.jpg" | |
| }, | |
| "istanbul": { | |
| "landmark": "https://cdn.pixabay.com/photo/2018/04/25/09/14/istanbul-3349451_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2020/06/28/14/20/istanbul-5349952_960_720.jpg" | |
| }, | |
| "athens": { | |
| "landmark": "https://cdn.pixabay.com/photo/2018/10/30/16/06/water-3784022_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2019/09/15/00/10/athens-4477350_960_720.jpg" | |
| }, | |
| "vienna": { | |
| "landmark": "https://cdn.pixabay.com/photo/2017/06/28/10/53/schonbrunn-palace-2450019_960_720.jpg", | |
| "street": "https://cdn.pixabay.com/photo/2018/05/30/00/24/thunderstorm-3440450_960_720.jpg" | |
| } | |
| } | |
| # Check if we have specific images for this destination | |
| if destination_lower in image_map: | |
| images = image_map[destination_lower] | |
| return json.dumps({ | |
| "landmark_image": images["landmark"], | |
| "street_scene_image": images["street"] | |
| }) | |
| # Generic high-quality travel images as fallback | |
| return json.dumps({ | |
| "landmark_image": "https://cdn.pixabay.com/photo/2016/11/29/12/13/architecture-1869547_960_720.jpg", | |
| "street_scene_image": "https://cdn.pixabay.com/photo/2016/11/22/19/15/hand-1850120_960_720.jpg" | |
| }) | |
| def assemble_catalogue( | |
| destination: str, | |
| origin: str, | |
| dates: str, | |
| budget_summary: str, | |
| weather: str, | |
| timezone_info: str, | |
| itinerary: str, | |
| packing_list: str, | |
| image_urls_json: str | |
| ) -> str: | |
| """ | |
| Compile all travel research into a beautiful, structured Markdown travel catalogue. | |
| Args: | |
| destination: Destination city name (e.g., "Lisbon") | |
| origin: Traveler's home city (e.g., "London") | |
| dates: Travel date range (e.g., "Sep 20-22, 2026") | |
| budget_summary: Formatted budget conversion string | |
| weather: Weather forecast with packing advice | |
| timezone_info: Time difference and jet lag guidance | |
| itinerary: Day-by-day schedule with time allocations | |
| packing_list: Complete customized packing checklist | |
| image_urls_json: JSON string with "landmark_image" and "street_scene_image" URLs | |
| Returns: | |
| Complete Markdown-formatted travel catalogue with embedded images | |
| """ | |
| # Parse image URLs | |
| try: | |
| images = json.loads(image_urls_json) | |
| img1 = images.get("landmark_image", "https://picsum.photos/800/600?random=1") | |
| img2 = images.get("street_scene_image", "https://picsum.photos/800/600?random=2") | |
| except: | |
| img1 = "https://picsum.photos/800/600?random=1" | |
| img2 = "https://picsum.photos/800/600?random=2" | |
| # Count days in itinerary | |
| day_count = len(re.findall(r'DAY\s+\d+', itinerary)) | |
| # Build catalogue with proper Gradio-compatible Markdown | |
| catalogue = f"""# 🌍 {destination} Travel Catalogue | |
| *Planned from {origin} • {dates} • {budget_summary}* | |
| --- | |
| ## ⏰ Time Zone Adjustment | |
| {timezone_info} | |
| --- | |
| ## 🌤️ Weather Forecast & Packing Guidance | |
| {weather} | |
| --- | |
| ## 🗓️ Your Personalized {day_count}-Day Itinerary | |
| {itinerary} | |
| --- | |
| ## 🎒 Complete Packing Checklist | |
| {packing_list} | |
| --- | |
| ## 📸 Visual Inspiration | |
|  | |
| *Iconic {destination} landmark* | |
|  | |
| *Vibrant local atmosphere* | |
| --- | |
| > 💡 **Travel Pro Tips** | |
| > | |
| > • Download offline Google Maps before departure | |
| > • Learn 5 basic phrases in the local language | |
| > • Keep digital copies of passport/insurance in cloud storage | |
| > • Budget 10-15% extra for spontaneous experiences | |
| > • Public transport passes often save 30%+ vs single tickets | |
| --- | |
| **Happy Travels! ✈️** | |
| """ | |
| return catalogue | |
| # ====================== | |
| # AGENT SETUP | |
| # ====================== | |
| def create_agent(): | |
| """Initialize and return the Travel Catalogue Creator agent""" | |
| print("🚀 Initializing Travel Catalogue Creator...") | |
| # Load system prompt from YAML | |
| with open("prompts.yaml", "r") as f: | |
| prompt_config = yaml.safe_load(f) | |
| # Pre-instantiate DuckDuckGo search tool | |
| web_search = DuckDuckGoSearchTool() | |
| # Configure model with error handling | |
| model = HfApiModel( | |
| max_tokens=2048, | |
| temperature=0.3, | |
| model_id="Qwen/Qwen2.5-Coder-32B-Instruct", | |
| ) | |
| # Create agent with all tools | |
| agent = CodeAgent( | |
| model=model, | |
| tools=[ | |
| web_search, # Pre-instantiated search tool | |
| get_weather_forecast, | |
| convert_currency, | |
| get_time_difference, | |
| generate_packing_list, | |
| build_itinerary, | |
| generate_travel_images, | |
| assemble_catalogue, | |
| ], | |
| max_steps=15, | |
| verbosity_level=1, | |
| name="TravelCatalogueCreator", | |
| description="Creates comprehensive, personalized travel catalogues with weather forecasts, custom itineraries, packing lists and visual inspiration" | |
| ) | |
| print("✅ Agent initialized successfully!") | |
| return agent | |
| if __name__ == "__main__": | |
| # Create agent | |
| agent = create_agent() | |
| # Create uploads directory | |
| os.makedirs("./uploads", exist_ok=True) | |
| # Launch Gradio UI | |
| print("🌐 Launching Gradio interface...") | |
| ui = GradioUI(agent, file_upload_folder="./uploads") | |
| ui.launch(share=False, server_name="0.0.0.0", server_port=7860) | |