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"""
{name}
{details}
""" # 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("
🌤️ Weather Dashboard
") 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("
🤖 Travel Assistant
") 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("
🏖️ Top Attractions You Can Visit
") 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()