import os
import gradio as gr
import requests
from openai import OpenAI
import ast
# API Keys
weather_api_key = os.getenv("openweather")
openai_api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI(api_key=openai_api_key)
# Weather fetch
def get_weather(city_name):
if not city_name.strip():
city_name = "Dubai"
try:
url = f"https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={weather_api_key}&units=metric"
data = requests.get(url).json()
if data.get("cod") == 200:
rain = data.get("rain", {}).get("1h", 0)
condition = data["weather"][0]["main"]
emoji_map = {
"Clear": "☀️", "Clouds": "☁️", "Rain": "🌧️",
"Snow": "❄️", "Thunderstorm": "⛈️", "Drizzle": "🌦️",
"Mist": "🌫️", "Haze": "🌁", "Fog": "🌫️"
}
emoji = emoji_map.get(condition, "🌈")
return {
"city": data["name"],
"country": data["sys"]["country"],
"temperature": int(data["main"]["temp"]),
"feels_like": int(data["main"]["feels_like"]),
"humidity": data["main"]["humidity"],
"pressure": data["main"]["pressure"],
"description": f"{data['weather'][0]['description'].title()} {emoji}",
"wind_speed": data["wind"]["speed"],
"visibility": data.get("visibility", 10000) // 1000,
"rain_chance": f"{rain} mm"
}
return None
except Exception:
return None
# Weather display
def format_weather_display(data):
if not data:
return "
❌ City not found. Please try again.
"
font_color = "#2d3436"
card_bg = "#e3f2fd"
main_bg = "#ffffff"
return f"""
📍 {data['city']}, {data['country']}
{data['temperature']}°C
{data['description']}
💧
{data['rain_chance']}
Precipitation
📊
{data['pressure']} mb
Pressure
💨
{data['wind_speed']} km/h
Wind Speed
🌡️
{data['feels_like']}°C
Feels Like
👁️
{data['visibility']} km
Visibility
💦
{data['humidity']}%
Humidity
"""
# Chatbot
def travel_chat(msg, history):
messages = [{"role": "system", "content": "You are a helpful travel assistant. Suggest tourist attractions, activities, and travel tips for any city."}]
for h in history:
messages.append({"role": h["role"], "content": h["content"]})
messages.append({"role": "user", "content": msg})
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
temperature=0.7
)
reply = response.choices[0].message.content
except Exception:
reply = "⚠️ Unable to fetch suggestions right now. Try again later."
history.append({"role": "user", "content": msg})
history.append({"role": "assistant", "content": reply})
return history, history
# Attractions
def get_attractions(city, country, temp, weather_desc):
try:
messages = [
{"role": "system", "content": (
"You are a travel expert. Based on the city, country, and current weather, "
"suggest 6 to 9 top attractions. Return each as a Python tuple like: "
"(\"Burj Khalifa\", \"World's tallest building with stunning views.\")."
)},
{"role": "user", "content": (
f"Suggest attractions for {city}, {country} (Weather: {temp}°C, {weather_desc}). "
"Output as Python tuples only."
)}
]
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
content = response.choices[0].message.content.strip()
attractions = []
for line in content.splitlines():
line = line.strip().rstrip(',')
if line.startswith("(") and line.endswith(")"):
try:
attraction_tuple = ast.literal_eval(line)
if isinstance(attraction_tuple, tuple) and len(attraction_tuple) == 2:
attractions.append(attraction_tuple)
except Exception:
continue
return attractions if attractions else [("No attractions found", "Try another city.")]
except Exception:
return [("Error", "Could not fetch attractions.")]
def format_attraction_card(name, details):
return f"""
"""
# CSS
custom_css = """
body, .gradio-container {
background: linear-gradient(135deg, #f5f7fa 0%, #bbdefb 100%) !important;
font-family: 'Segoe UI', 'Roboto', sans-serif;
min-height: 100vh;
}
#main-title {
text-align: center;
font-size: 2.8rem;
font-weight: 800;
margin: 20px 0;
color: #0d47a1;
}
.section-header {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
color: white;
padding: 12px 20px;
border-radius: 12px 12px 0 0;
margin: 0;
font-size: 1.3rem;
font-weight: 600;
text-align: center;
}
.content-box {
background-color: #ffffff;
border-radius: 0 0 16px 16px;
padding: 25px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
min-height: 520px;
border-top: 3px solid #1976d2;
}
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
margin-top: 25px;
}
.card {
background: #ffffff;
border: 1px solid #bbdefb;
border-radius: 16px;
padding: 20px;
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(25, 118, 210, 0.2);
}
.card-title {
font-size: 20px;
font-weight: 600;
color: #1565c0;
margin-bottom: 12px;
text-align: center;
}
.card-desc {
font-size: 15px;
color: #444;
line-height: 1.5;
text-align: center;
}
.footer {
text-align: center;
font-size: 14px;
padding: 20px;
color: #555;
margin-top: 20px;
}
"""
def generate_attraction_cards(city):
weather = get_weather(city)
if not weather:
return "⚠️ Couldn't fetch attractions due to missing weather data.
"
attractions = get_attractions(
city=weather["city"],
country=weather["country"],
temp=weather["temperature"],
weather_desc=weather["description"]
)
return "" + "".join(format_attraction_card(name, details) for name, details in attractions) + "
"
# UI
def launch_ui():
with gr.Blocks(css=custom_css, title="TripMate AI") as demo:
# Header
gr.HTML("🌍 TripMate AI
")
with gr.Row(equal_height=True):
with gr.Column(scale=1):
gr.HTML("")
with gr.Group(elem_classes="content-box"):
city_input = gr.Textbox(label="🏙️ City Name", value="Dubai")
update_btn = gr.Button("📍 Get Weather Data")
weather_html = gr.HTML()
with gr.Column(scale=1):
gr.HTML("")
with gr.Group(elem_classes="content-box"):
chat = gr.Chatbot(height=350, type="messages")
with gr.Row():
message = gr.Textbox(placeholder="Ask about places to visit, local attractions...", show_label=False, scale=4)
ask_btn = gr.Button("Send", scale=1)
state = gr.State([])
gr.HTML("")
attractions_html = gr.HTML()
def update_all(city):
return format_weather_display(get_weather(city)), generate_attraction_cards(city)
update_btn.click(fn=update_all, inputs=city_input, outputs=[weather_html, attractions_html])
city_input.submit(fn=update_all, inputs=city_input, outputs=[weather_html, attractions_html])
ask_btn.click(fn=travel_chat, inputs=[message, state], outputs=[chat, state])
message.submit(fn=travel_chat, inputs=[message, state], outputs=[chat, state])
demo.load(fn=lambda: update_all("Dubai"), inputs=None, outputs=[weather_html, attractions_html])
# Footer
gr.HTML("")
demo.launch()
launch_ui()