TripMate_AI / app.py
Shafaq25's picture
Update app.py
c34b1be verified
raw
history blame
11.6 kB
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 "<div style='text-align:center; color: #e74c3c; font-size: 18px; padding: 40px;'>❌ City not found. Please try again.</div>"
font_color = "#2d3436"
card_bg = "#e3f2fd"
main_bg = "#ffffff"
return f"""
<div style="background: {main_bg}; border-radius: 16px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
<div style="text-align: center; margin-bottom: 25px;">
<h2 style="margin: 0; color: {font_color}; font-size: 24px; font-weight: 600;">πŸ“ {data['city']}, {data['country']}</h2>
<h1 style="margin: 10px 0; font-size: 64px; color: {font_color}; font-weight: 300;">{data['temperature']}Β°C</h1>
<p style="margin: 5px 0; color: {font_color}; font-size: 18px; font-weight: 500;">{data['description']}</p>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-top: 25px;">
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
<div style="font-size: 24px; margin-bottom: 8px;">πŸ’§</div>
<strong style="font-size: 16px; display: block;">{data['rain_chance']}</strong>
<span style="font-size: 12px; opacity: 0.8;">Precipitation</span>
</div>
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
<div style="font-size: 24px; margin-bottom: 8px;">πŸ“Š</div>
<strong style="font-size: 16px; display: block;">{data['pressure']} mb</strong>
<span style="font-size: 12px; opacity: 0.8;">Pressure</span>
</div>
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
<div style="font-size: 24px; margin-bottom: 8px;">πŸ’¨</div>
<strong style="font-size: 16px; display: block;">{data['wind_speed']} km/h</strong>
<span style="font-size: 12px; opacity: 0.8;">Wind Speed</span>
</div>
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
<div style="font-size: 24px; margin-bottom: 8px;">🌑️</div>
<strong style="font-size: 16px; display: block;">{data['feels_like']}Β°C</strong>
<span style="font-size: 12px; opacity: 0.8;">Feels Like</span>
</div>
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
<div style="font-size: 24px; margin-bottom: 8px;">πŸ‘οΈ</div>
<strong style="font-size: 16px; display: block;">{data['visibility']} km</strong>
<span style="font-size: 12px; opacity: 0.8;">Visibility</span>
</div>
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};">
<div style="font-size: 24px; margin-bottom: 8px;">πŸ’¦</div>
<strong style="font-size: 16px; display: block;">{data['humidity']}%</strong>
<span style="font-size: 12px; opacity: 0.8;">Humidity</span>
</div>
</div>
</div>
"""
# 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"""
<div class='card'>
<div class='card-title'>{name}</div>
<div class='card-desc'>{details}</div>
</div>
"""
# 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 "<div style='padding:20px; color:red;'>⚠️ Couldn't fetch attractions due to missing weather data.</div>"
attractions = get_attractions(
city=weather["city"],
country=weather["country"],
temp=weather["temperature"],
weather_desc=weather["description"]
)
return "<div class='card-grid'>" + "".join(format_attraction_card(name, details) for name, details in attractions) + "</div>"
# UI
def launch_ui():
with gr.Blocks(css=custom_css, title="TripMate AI") as demo:
# Header
gr.HTML("<div id='main-title'>🌍 TripMate AI</div>")
with gr.Row(equal_height=True):
with gr.Column(scale=1):
gr.HTML("<div class='section-header'>🌀️ Weather Dashboard</div>")
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("<div class='section-header'>πŸ€– Travel Assistant</div>")
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("<div class='section-header'>πŸ–οΈ Top Attractions You Can Visit</div>")
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("<div class='footer'>Β© 2025 TripMate AI – Your AI-powered travel companion.</div>")
demo.launch()
launch_ui()