File size: 18,974 Bytes
46f21a4
fbf618c
 
 
 
 
 
 
 
 
 
46f21a4
fbf618c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
import streamlit as st
import json
import os
from serpapi import GoogleSearch 
from agno.agent import Agent
from agno.tools.serpapi import SerpApiTools
from agno.models.openai.like import OpenAILike
from datetime import datetime
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage

load_dotenv()

# Set up Streamlit UI with a travel-friendly theme
st.set_page_config(page_title="🌍 AI Travel Planner", layout="wide")

# Define supported languages - MOVED TO THE TOP
languages = [
    "English", "Hindi", "Gujarati", "Bengali", "Tamil", 
    "Telugu", "Kannada", "Malayalam", "Punjabi", "Marathi", 
    "Urdu", "Assamese", "Odia", "Sanskrit", "Korean", 
    "Japanese", "Arabic", "French", "German", "Spanish", 
    "Portuguese", "Russian", "Chinese", "Vietnamese", "Thai", 
    "Indonesian", "Turkish", "Polish", "Ukrainian", "Dutch", 
    "Italian", "Greek", "Hebrew", "Persian", "Swedish", 
    "Norwegian", "Danish", "Finnish", "Czech", "Hungarian", 
    "Romanian", "Bulgarian", "Croatian", "Serbian", "Slovak", 
    "Slovenian", "Estonian", "Latvian", "Lithuanian", "Malay", 
    "Tagalog", "Swahili"
]

# Sidebar Setup - API Keys at Top
st.sidebar.title("🌎 Travel Assistant")

# Language selector in sidebar only
st.sidebar.subheader("🌐 Language Settings")
output_language = st.sidebar.selectbox("Select language for your travel plan:", languages, key="sidebar_language_selector")

# Travel Preferences
st.sidebar.subheader("✈️ Travel Preferences")
budget = st.sidebar.selectbox("πŸ’° Budget Preference:", ["Economy", "Standard", "Luxury"])
flight_class = st.sidebar.selectbox("✈️ Flight Class:", ["Economy", "Business", "First Class"])
hotel_rating = st.sidebar.selectbox("🏨 Preferred Hotel Rating:", ["3⭐", "4⭐", "5⭐"])

# Travel Essentials
st.sidebar.subheader("πŸ›‚ Travel Essentials")
visa_required = st.sidebar.checkbox("πŸ›ƒ Check Visa Requirements")
travel_insurance = st.sidebar.checkbox("πŸ›‘οΈ Get Travel Insurance")
currency_converter = st.sidebar.checkbox("πŸ’± Currency Exchange Rates")

st.markdown(
    """
    <style>
        .title {
            text-align: center;
            font-size: 36px;
            font-weight: bold;
            color: #ff5733;
        }
        .subtitle {
            text-align: center;
            font-size: 20px;
            color: #555;
        }
        .stSlider > div {
            background-color: #f9f9f9;
            padding: 10px;
            border-radius: 10px;
        }
        .translation-box {
            border: 1px solid #ddd;
            border-radius: 10px;
            padding: 10px;
            margin-top: 10px;
            background-color: #f9f9f9;
        }
        .expander-header {
            color: #0066cc;
            font-weight: bold;
        }
    </style>
    """,
    unsafe_allow_html=True,
)

# Title and subtitle
st.markdown(
    f'<h1><img src="https://framerusercontent.com/images/9vH8BcjXKRcC5OrSfkohhSyDgX0.png" width="70"/>  AI-Powered Travel Planner ✈️</h1>', unsafe_allow_html=True)

# User Inputs Section
st.markdown("### 🌍 Where are you headed?")
source = st.text_input("πŸ›« Departure City (IATA Code):", "BOM")  # Example: BOM for Mumbai
destination = st.text_input("πŸ›¬ Destination (IATA Code):", "DEL")  # Example: DEL for Delhi  

st.markdown("### 🎭 Select Your Travel Theme")
travel_theme = st.selectbox(
    "🎭 Select Your Travel Theme:",
    ["πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ Family Vacation", "πŸ”οΈ Adventure Trip", "🧳 Solo Exploration"],
    index=0  # Set Family Vacation as default
)

# Divider for aesthetics
st.markdown("---")

st.markdown(
    f"""
    <div style="
        text-align: center; 
        padding: 15px; 
        background-color: #d2d2d4; 
        border-radius: 10px;
        color: black; 
        margin-top: 20px;
    ">
        <h3>🌟 Your {travel_theme} to {destination} is about to begin! 🌟</h3>
        <p>Let's find the best flights, stays, and experiences for your unforgettable journey.</p>
    </div>
    """,
    unsafe_allow_html=True,
)

def format_datetime(iso_string):
    try:
        dt = datetime.strptime(iso_string, "%Y-%m-%d %H:%M")
        return dt.strftime("%b-%d, %Y | %I:%M %p")  # Example: Mar-06, 2025 | 6:20 PM
    except:
        return "N/A"

activity_preferences = st.text_area(
    "🌍 What activities do you enjoy? (e.g., relaxing on the beach, exploring historical sites, nightlife, adventure)",
    "Relaxing on the beach, exploring historical sites"
)

departure_date = st.date_input("Departure Date")
return_date = st.date_input("Return Date")

# Get API keys from environment variables or UI inputs
SERPAPI_KEY = os.getenv("SERPAPI_KEY") or serpapi_key_input
SUTRA_API_KEY = os.getenv("SUTRA_API_KEY") or sutra_api_key

# Validate API keys
if not SERPAPI_KEY:
    st.sidebar.error("⚠️ SerpAPI key is required.")

if not SUTRA_API_KEY:
    st.sidebar.error("⚠️ Sutra API key is required.")

# Initialize Sutra model for translations
@st.cache_resource
def get_sutra_model(api_key):
    return ChatOpenAI(
        api_key=api_key,
        base_url="https://api.two.ai/v2",
        model="sutra-v2",
        temperature=0.7,
    )

# Function to translate text using Sutra LLM
def translate_text(text, target_language, sutra_api_key):
    if not sutra_api_key or target_language == "English":
        return text
    
    try:
        sutra = get_sutra_model(sutra_api_key)
        translation_prompt = f"Translate the following text to {target_language}. Keep all formatting including bullet points, numbers, and emojis intact. Here's the text:\n\n{text}"
        
        messages = [HumanMessage(content=translation_prompt)]
        response = sutra.invoke(messages)
        
        return response.content
    except Exception as e:
        st.warning(f"Translation error: {str(e)}. Showing original text.")
        return text

# Function to fetch flight data
def fetch_flights(source, destination, departure_date, return_date):
    params = {
        "engine": "google_flights",
        "departure_id": source,
        "arrival_id": destination,
        "outbound_date": str(departure_date),
        "return_date": str(return_date),
        "currency": "INR",
        "hl": "en",
        "api_key": SERPAPI_KEY
    }
    search = GoogleSearch(params)
    results = search.get_dict()
    return results

# Function to extract top 3 cheapest flights
def extract_cheapest_flights(flight_data):
    best_flights = flight_data.get("best_flights", [])
    sorted_flights = sorted(best_flights, key=lambda x: x.get("price", float("inf")))[:3]  # Get top 3 cheapest
    return sorted_flights

# Function to create optimized prompts for Sutra
def create_optimized_prompt(action, destination, preferences, constraints):
    """
    Creates a token-efficient prompt for Sutra by focusing on essential information
    
    Args:
        action (str): What the agent needs to do (research, plan, find)
        destination (str): The travel destination
        preferences (str): User preferences and interests
        constraints (str): Budget, time, and other constraints
        
    Returns:
        str: An optimized prompt
    """
    # Create a focused prompt that emphasizes what's most important
    return f"{action} for {destination}. Preferences: {preferences}. Constraints: {constraints}. BE CONCISE."

# AI Agents with Sutra model
def create_sutra_agent(name, instructions):
    # Check if API key is available
    if not SUTRA_API_KEY:
        st.sidebar.error(f"⚠️ Can't initialize {name} agent: No Sutra API key provided.")
        return None
        
    # Create agent with Sutra model via OpenAILike wrapper
    return Agent(
        name=name,
        instructions=instructions,
        model=OpenAILike(
            id="sutra-v2",
            api_key=SUTRA_API_KEY,
            base_url="https://api.two.ai/v2"
        ),
        tools=[SerpApiTools(api_key=SERPAPI_KEY)] if name != "Planner" else None,
        add_datetime_to_instructions=True,
        markdown=True
    )

# Function to display content with optional translation expander
# This function is no longer needed since we're showing directly in preferred language
# Removing this function as it's not needed anymore

# Initialize agents
researcher_instructions = [
    "FOCUS on key attractions, safety, and local information.",
    "PRIORITIZE reliable sources and official travel guides.",
    "BE CONCISE - focus on must-know facts only.",
    "LIMIT output to 5-7 key attractions or activities."
]

planner_instructions = [
    "CREATE brief day-by-day itinerary with morning/afternoon/evening blocks.",
    "INCLUDE only 2-3 activities per time block.",
    "FOCUS on logistics, timing, and estimated costs.",
    "KEEP descriptions brief but informative."
]

hotel_restaurant_instructions = [
    "FIND 3-5 top hotels and restaurants near popular attractions.",
    "INCLUDE price range, rating, and 1-2 key features for each.",
    "PRIORITIZE results matching user preferences.",
    "FORMAT as concise bullet points."
]

# Generate Travel Plan
if st.button("πŸš€ Generate Travel Plan"):
    # Check API keys first
    if not SERPAPI_KEY or not SUTRA_API_KEY:
        st.error("⚠️ Both SerpAPI and Sutra API keys are required to generate a travel plan.")
        st.stop()
    
    # Calculate number of days
    num_days = (return_date - departure_date).days + 1  # Add 1 to include both departure and return days
    
    # Create agents with Sutra model
    researcher = create_sutra_agent("Researcher", researcher_instructions)
    planner = create_sutra_agent("Planner", planner_instructions) 
    hotel_restaurant_finder = create_sutra_agent("Hotel & Restaurant Finder", hotel_restaurant_instructions)
    
    if not researcher or not planner or not hotel_restaurant_finder:
        st.error("⚠️ Failed to initialize AI agents. Please check your API keys.")
        st.stop()
    
    with st.spinner("✈️ Fetching best flight options..."):
        flight_data = fetch_flights(source, destination, departure_date, return_date)
        cheapest_flights = extract_cheapest_flights(flight_data)

    # Hide intermediate processing and only show final results
    with st.spinner("πŸ” Processing your travel plan..."):
        # Research attractions & activities
        research_prompt = create_optimized_prompt(
            action="Research top attractions and activities",
            destination=destination,
            preferences=f"Trip type: {travel_theme}. Activities: {activity_preferences}.",
            constraints=f"Budget: {budget}. Duration: {num_days} days."
        )
        
        # Get research output without streaming
        research_response = researcher.run(research_prompt, stream=False)
        research_output = research_response.content if hasattr(research_response, 'content') else str(research_response)
        
        # Hotels & restaurants
        hotel_restaurant_prompt = create_optimized_prompt(
            action="Find top hotels and restaurants",
            destination=destination,
            preferences=f"Trip type: {travel_theme}. Hotel rating: {hotel_rating}.",
            constraints=f"Budget: {budget}. Activities nearby: {activity_preferences}."
        )
        
        # Get hotel output without streaming
        hotel_response = hotel_restaurant_finder.run(hotel_restaurant_prompt, stream=False)
        hotel_output = hotel_response.content if hasattr(hotel_response, 'content') else str(hotel_response)
        
        # Simplified flight data for token efficiency
        simplified_flights = []
        for flight in cheapest_flights:
            simplified_flights.append({
                "airline": flight.get("airline", "Unknown"),
                "price": flight.get("price", "N/A"),
                "duration": flight.get("total_duration", "N/A")
            })
        
        # Create itinerary
        planning_prompt = create_optimized_prompt(
            action=f"Create {num_days}-day itinerary",
            destination=destination,
            preferences=f"Trip type: {travel_theme}. Activities: {activity_preferences}.",
            constraints=f"Budget: {budget}. Class: {flight_class}. Hotel: {hotel_rating}."
        )
        
        # Add research data but keep it brief
        planning_prompt += f" Based on: {research_output[:500]}... {hotel_output[:500]}..."
        
        # Get itinerary output without streaming
        itinerary_response = planner.run(planning_prompt, stream=False)
        itinerary_output = itinerary_response.content if hasattr(itinerary_response, 'content') else str(itinerary_response)
        
        # Translate all content to preferred language if not English
        if output_language != "English":
            research_output = translate_text(research_output, output_language, SUTRA_API_KEY)
            hotel_output = translate_text(hotel_output, output_language, SUTRA_API_KEY)
            itinerary_output = translate_text(itinerary_output, output_language, SUTRA_API_KEY)

    # Display Results - directly in preferred language, no expandable sections
    st.subheader("✈️ Cheapest Flight Options")
    if cheapest_flights:
        cols = st.columns(len(cheapest_flights))
        for idx, flight in enumerate(cheapest_flights):
            with cols[idx]:
                airline_logo = flight.get("airline_logo", "")
                airline_name = flight.get("airline", "Unknown Airline")
                price = flight.get("price", "Not Available")
                total_duration = flight.get("total_duration", "N/A")
                
                flights_info = flight.get("flights", [{}])
                departure = flights_info[0].get("departure_airport", {})
                arrival = flights_info[-1].get("arrival_airport", {})
                airline_name = flights_info[0].get("airline", "Unknown Airline") 
                
                departure_time = format_datetime(departure.get("time", "N/A"))
                arrival_time = format_datetime(arrival.get("time", "N/A"))
                
                departure_token = flight.get("departure_token", "")

                # Use translated labels based on selected language
                if output_language != "English":
                    departure_label = translate_text("Departure:", output_language, SUTRA_API_KEY)
                    arrival_label = translate_text("Arrival:", output_language, SUTRA_API_KEY)
                    duration_label = translate_text("Duration:", output_language, SUTRA_API_KEY)
                    book_now = translate_text("Book Now", output_language, SUTRA_API_KEY)
                else:
                    departure_label = "Departure:"
                    arrival_label = "Arrival:"
                    duration_label = "Duration:"
                    book_now = "Book Now"

                booking_link = "#"  # Default value
                if departure_token:
                    try:
                        params = {
                            "engine": "google_flights",
                            "departure_id": source,
                            "arrival_id": destination,
                            "outbound_date": str(departure_date),
                            "return_date": str(return_date),
                            "currency": "INR",
                            "hl": "en",
                            "departure_token": departure_token,
                            "api_key": SERPAPI_KEY
                        }
                        search_with_token = GoogleSearch(params)
                        results_with_booking = search_with_token.get_dict()
                        
                        # Check if we have valid booking data
                        if 'best_flights' in results_with_booking and len(results_with_booking['best_flights']) > idx:
                            booking_token = results_with_booking['best_flights'][idx].get('booking_token')
                            if booking_token:
                                booking_link = f"https://www.google.com/travel/flights?tfs={booking_token}"
                    except Exception as e:
                        st.warning(f"Could not fetch booking link: {str(e)}")
                        booking_link = "#"

                # Flight card layout with improved visibility for dark mode
                st.markdown(
                    f"""
                    <div style="
                        border: 2px solid #ddd; 
                        border-radius: 10px; 
                        padding: 15px; 
                        text-align: center;
                        box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
                        background-color: rgba(249, 249, 249, 0.9);
                        margin-bottom: 20px;
                    ">
                        <img src="{airline_logo}" width="100" alt="Flight Logo" style="background-color: white; padding: 5px; border-radius: 5px;" />
                        <h3 style="margin: 10px 0; color: #333;">{airline_name}</h3>
                        <p style="color: #333;"><strong>{departure_label}</strong> {departure_time}</p>
                        <p style="color: #333;"><strong>{arrival_label}</strong> {arrival_time}</p>
                        <p style="color: #333;"><strong>{duration_label}</strong> {total_duration} min</p>
                        <h2 style="color: #008000;">πŸ’° {price}</h2>
                        <a href="{booking_link}" target="_blank" style="
                            display: inline-block;
                            padding: 10px 20px;
                            font-size: 16px;
                            font-weight: bold;
                            color: #fff;
                            background-color: #007bff;
                            text-decoration: none;
                            border-radius: 5px;
                            margin-top: 10px;
                        ">πŸ”— {book_now}</a>
                    </div>
                    """,
                    unsafe_allow_html=True
                )
    else:
        no_flights_message = "⚠️ No flight data available."
        if output_language != "English":
            no_flights_message = translate_text(no_flights_message, output_language, SUTRA_API_KEY)
        st.warning(no_flights_message)

    # Display content directly in preferred language
    st.subheader("🏨 Hotels & Restaurants")
    st.markdown(hotel_output)
    
    st.subheader("πŸ—ΊοΈ Your Personalized Itinerary")
    st.markdown(itinerary_output)
    
    st.subheader("πŸ” Destination Research")
    st.markdown(research_output)

    success_message = "βœ… Travel plan generated successfully!"
    if output_language != "English":
        success_message = translate_text(success_message, output_language, SUTRA_API_KEY)
    st.success(success_message)