Spaces:
Sleeping
Sleeping
import os | |
import gradio as gr | |
import requests | |
import openai | |
# Load API keys | |
weather_api_key = os.getenv("openweather") | |
openai.api_key = os.getenv("OPENAI_API_KEY") | |
# ------------- WEATHER FETCH FUNCTION ------------- | |
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["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" | |
} | |
else: | |
return None | |
except: | |
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 = "#e8f5e9" | |
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};"> | |
π§<br><strong>{data['rain_chance']}</strong><br><span>Precipitation</span> | |
</div> | |
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};"> | |
π<br><strong>{data['pressure']} mb</strong><br><span>Pressure</span> | |
</div> | |
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};"> | |
π¨<br><strong>{data['wind_speed']} km/h</strong><br><span>Wind Speed</span> | |
</div> | |
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};"> | |
π‘οΈ<br><strong>{data['feels_like']}Β°C</strong><br><span>Feels Like</span> | |
</div> | |
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};"> | |
ποΈ<br><strong>{data['visibility']} km</strong><br><span>Visibility</span> | |
</div> | |
<div style="background: {card_bg}; border-radius: 12px; padding: 16px; text-align: center; color: {font_color};"> | |
π¦<br><strong>{data['humidity']}%</strong><br><span>Humidity</span> | |
</div> | |
</div> | |
</div> | |
""" | |
# ------------- CHATBOT FUNCTION (OpenAI) ------------- | |
def travel_chat(user_input, history): | |
try: | |
messages = [{"role": "system", "content": "You are TripMate AI, a helpful travel assistant. Provide travel tips, cultural insights, and activity suggestions."}] | |
for h in history: | |
messages.append({"role": "user", "content": h["role"] == "user" and h["content"] or ""}) | |
messages.append({"role": "assistant", "content": h["role"] == "assistant" and h["content"] or ""}) | |
messages.append({"role": "user", "content": user_input}) | |
response = openai.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=messages | |
) | |
reply = response.choices[0].message.content | |
except: | |
reply = "β οΈ Unable to get a response. Try again." | |
history.append({"role": "user", "content": user_input}) | |
history.append({"role": "assistant", "content": reply}) | |
return history, history | |
# ------------- PLACE SUGGESTIONS (OpenAI) ------------- | |
def get_places_to_visit(city, country, temp, description): | |
prompt = f""" | |
Suggest 6-9 must-visit attractions or experiences in {city}, {country} considering that the weather is {description}, temperature is {temp}Β°C. | |
Return the results as Python tuples in this format: | |
("Place Name", "Short description of the place and why itβs worth visiting") | |
""" | |
try: | |
response = openai.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=[{"role": "user", "content": prompt}] | |
) | |
content = response.choices[0].message.content.strip() | |
places = [] | |
for line in content.split('\n'): | |
line = line.strip().rstrip(',') | |
if line.startswith('(') and line.endswith(')'): | |
try: | |
place_tuple = eval(line) | |
if isinstance(place_tuple, tuple) and len(place_tuple) == 2: | |
places.append(place_tuple) | |
except: | |
continue | |
return places | |
except Exception as e: | |
return [("Error", str(e))] | |
# Format place card | |
def format_place_card(name, details): | |
return f""" | |
<div class='crop-card'> | |
<div class='crop-name'>{name}</div> | |
<div class='crop-description'>{details}</div> | |
</div> | |
""" | |
# Generate place cards | |
def generate_place_cards(city): | |
weather = get_weather(city) | |
if not weather: | |
return "<div style='padding:20px; color:red;'>β οΈ Couldn't fetch places due to missing weather data.</div>" | |
places = get_places_to_visit( | |
city=weather["city"], | |
country=weather["country"], | |
temp=weather["temperature"], | |
description=weather["description"] | |
) | |
return "<div class='card-grid'>" + "".join(format_place_card(name, details) for name, details in places) + "</div>" | |
# ------------- CSS ------------- | |
custom_css = """ | |
body, .gradio-container { | |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; | |
font-family: 'Segoe UI', 'Roboto', sans-serif; | |
min-height: 100vh; | |
} | |
#main-title { | |
text-align: center; | |
font-size: 2.5rem; | |
font-weight: 700; | |
margin-bottom: 10px; | |
color: #2e7d32; | |
} | |
.section-header { | |
background: linear-gradient(135deg, #43a047 0%, #388e3c 100%); | |
color: white; | |
padding: 12px 20px; | |
border-radius: 12px 12px 0 0; | |
text-align: center; | |
} | |
.content-box { | |
background-color: #ffffff; | |
border-radius: 0 0 16px 16px; | |
padding: 25px; | |
min-height: 520px; | |
} | |
.card-grid { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 18px; | |
margin-top: 25px; | |
} | |
.crop-card { | |
background: #ffffff; | |
border: 1px solid #c8e6c9; | |
border-radius: 16px; | |
padding: 20px; | |
} | |
.crop-name { | |
font-size: 20px; | |
font-weight: 600; | |
color: #2e7d32; | |
margin-bottom: 12px; | |
text-align: center; | |
} | |
.crop-description { | |
font-size: 15px; | |
color: #444; | |
line-height: 1.5; | |
text-align: center; | |
} | |
.footer { | |
background: linear-gradient(135deg, #2e7d32 0%, #1b5e20 100%); | |
color: white; | |
padding: 30px; | |
border-radius: 16px; | |
margin-top: 30px; | |
text-align: center; | |
font-size: 14px; | |
} | |
""" | |
# ------------- UI ------------- | |
def launch_ui(): | |
with gr.Blocks(css=custom_css, title="TripMate AI") as demo: | |
gr.Markdown("<div id='main-title'>π TripMate AI</div>") | |
with gr.Row(equal_height=True): | |
# Weather | |
with gr.Column(scale=1): | |
gr.Markdown("<div class='section-header'>π€οΈ Weather</div>") | |
with gr.Group(elem_classes="content-box"): | |
city_input = gr.Textbox(label="City", value="Dubai", placeholder="Enter city name") | |
update_btn = gr.Button("Get Weather") | |
weather_html = gr.HTML() | |
# Chat | |
with gr.Column(scale=1): | |
gr.Markdown("<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 travel tips...", show_label=False, scale=4) | |
ask_btn = gr.Button("Send", scale=1) | |
state = gr.State([]) | |
gr.Markdown("<div class='section-header'>πΊοΈ Places to Visit</div>") | |
place_cards_html = gr.HTML() | |
gr.HTML(""" | |
<div class='footer'> | |
π Built by Shafaq Mandha | Powered by OpenWeather & OpenAI | TripMate AI Β© 2025 | |
</div> | |
""") | |
update_btn.click( | |
fn=lambda city: (format_weather_display(get_weather(city)), generate_place_cards(city)), | |
inputs=city_input, | |
outputs=[weather_html, place_cards_html] | |
) | |
city_input.submit( | |
fn=lambda city: (format_weather_display(get_weather(city)), generate_place_cards(city)), | |
inputs=city_input, | |
outputs=[weather_html, place_cards_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: (format_weather_display(get_weather("Dubai")), generate_place_cards("Dubai")), | |
inputs=None, | |
outputs=[weather_html, place_cards_html]) | |
demo.launch() | |
launch_ui() | |