NSamson1's picture
Update app.py
58bec55 verified
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"""
<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 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"""
<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>
"""
# Destination Image Section
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 breakdown
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 conversion link
currency_link = f"""
<a href="https://www.xe.com/currencyconverter/" target="_blank" style="color: #667eea; text-decoration: none; margin-left: 15px;">πŸ’± Currency Converter</a>
"""
# Complete HTML
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
# -------------------- 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("""
<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
)
# Examples section
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")