Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +464 -38
src/streamlit_app.py
CHANGED
@@ -1,40 +1,466 @@
|
|
1 |
-
import altair as alt
|
2 |
-
import numpy as np
|
3 |
-
import pandas as pd
|
4 |
import streamlit as st
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
from serpapi import GoogleSearch
|
5 |
+
from agno.agent import Agent
|
6 |
+
from agno.tools.serpapi import SerpApiTools
|
7 |
+
from agno.models.openai.like import OpenAILike
|
8 |
+
from datetime import datetime
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from langchain_openai import ChatOpenAI
|
11 |
+
from langchain.schema import HumanMessage
|
12 |
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Set up Streamlit UI with a travel-friendly theme
|
16 |
+
st.set_page_config(page_title="π AI Travel Planner", layout="wide")
|
17 |
+
|
18 |
+
# Define supported languages - MOVED TO THE TOP
|
19 |
+
languages = [
|
20 |
+
"English", "Hindi", "Gujarati", "Bengali", "Tamil",
|
21 |
+
"Telugu", "Kannada", "Malayalam", "Punjabi", "Marathi",
|
22 |
+
"Urdu", "Assamese", "Odia", "Sanskrit", "Korean",
|
23 |
+
"Japanese", "Arabic", "French", "German", "Spanish",
|
24 |
+
"Portuguese", "Russian", "Chinese", "Vietnamese", "Thai",
|
25 |
+
"Indonesian", "Turkish", "Polish", "Ukrainian", "Dutch",
|
26 |
+
"Italian", "Greek", "Hebrew", "Persian", "Swedish",
|
27 |
+
"Norwegian", "Danish", "Finnish", "Czech", "Hungarian",
|
28 |
+
"Romanian", "Bulgarian", "Croatian", "Serbian", "Slovak",
|
29 |
+
"Slovenian", "Estonian", "Latvian", "Lithuanian", "Malay",
|
30 |
+
"Tagalog", "Swahili"
|
31 |
+
]
|
32 |
+
|
33 |
+
# Sidebar Setup - API Keys at Top
|
34 |
+
st.sidebar.title("π Travel Assistant")
|
35 |
+
|
36 |
+
# API Keys Configuration at Top of Sidebar
|
37 |
+
st.sidebar.subheader("π API Configuration")
|
38 |
+
st.sidebar.markdown("This app requires two API keys to function:")
|
39 |
+
|
40 |
+
# Sutra API Key
|
41 |
+
st.sidebar.markdown("1. **π Sutra API Key**")
|
42 |
+
st.sidebar.markdown("Get your free key from [Sutra API](https://www.two.ai/sutra/api)")
|
43 |
+
sutra_api_key = st.sidebar.text_input("Enter your Sutra API Key:", type="password", key="sutra_key")
|
44 |
+
|
45 |
+
# SerpAPI Key
|
46 |
+
st.sidebar.markdown("2. **π SerpAPI Key**")
|
47 |
+
st.sidebar.markdown("Get your key from [SerpAPI](https://serpapi.com/)")
|
48 |
+
serpapi_key_input = st.sidebar.text_input("Enter your SerpAPI Key:", type="password", key="serpapi_key")
|
49 |
+
|
50 |
+
# Language selector in sidebar only
|
51 |
+
st.sidebar.subheader("π Language Settings")
|
52 |
+
output_language = st.sidebar.selectbox("Select language for your travel plan:", languages, key="sidebar_language_selector")
|
53 |
+
|
54 |
+
# Travel Preferences
|
55 |
+
st.sidebar.subheader("βοΈ Travel Preferences")
|
56 |
+
budget = st.sidebar.selectbox("π° Budget Preference:", ["Economy", "Standard", "Luxury"])
|
57 |
+
flight_class = st.sidebar.selectbox("βοΈ Flight Class:", ["Economy", "Business", "First Class"])
|
58 |
+
hotel_rating = st.sidebar.selectbox("π¨ Preferred Hotel Rating:", ["3β", "4β", "5β"])
|
59 |
+
|
60 |
+
# Travel Essentials
|
61 |
+
st.sidebar.subheader("π Travel Essentials")
|
62 |
+
visa_required = st.sidebar.checkbox("π Check Visa Requirements")
|
63 |
+
travel_insurance = st.sidebar.checkbox("π‘οΈ Get Travel Insurance")
|
64 |
+
currency_converter = st.sidebar.checkbox("π± Currency Exchange Rates")
|
65 |
+
|
66 |
+
st.markdown(
|
67 |
+
"""
|
68 |
+
<style>
|
69 |
+
.title {
|
70 |
+
text-align: center;
|
71 |
+
font-size: 36px;
|
72 |
+
font-weight: bold;
|
73 |
+
color: #ff5733;
|
74 |
+
}
|
75 |
+
.subtitle {
|
76 |
+
text-align: center;
|
77 |
+
font-size: 20px;
|
78 |
+
color: #555;
|
79 |
+
}
|
80 |
+
.stSlider > div {
|
81 |
+
background-color: #f9f9f9;
|
82 |
+
padding: 10px;
|
83 |
+
border-radius: 10px;
|
84 |
+
}
|
85 |
+
.translation-box {
|
86 |
+
border: 1px solid #ddd;
|
87 |
+
border-radius: 10px;
|
88 |
+
padding: 10px;
|
89 |
+
margin-top: 10px;
|
90 |
+
background-color: #f9f9f9;
|
91 |
+
}
|
92 |
+
.expander-header {
|
93 |
+
color: #0066cc;
|
94 |
+
font-weight: bold;
|
95 |
+
}
|
96 |
+
</style>
|
97 |
+
""",
|
98 |
+
unsafe_allow_html=True,
|
99 |
+
)
|
100 |
+
|
101 |
+
# Title and subtitle
|
102 |
+
st.markdown(
|
103 |
+
f'<h1><img src="https://framerusercontent.com/images/9vH8BcjXKRcC5OrSfkohhSyDgX0.png" width="70"/> AI-Powered Travel Planner βοΈ</h1>', unsafe_allow_html=True)
|
104 |
+
|
105 |
+
# User Inputs Section
|
106 |
+
st.markdown("### π Where are you headed?")
|
107 |
+
source = st.text_input("π« Departure City (IATA Code):", "BOM") # Example: BOM for Mumbai
|
108 |
+
destination = st.text_input("π¬ Destination (IATA Code):", "DEL") # Example: DEL for Delhi
|
109 |
+
|
110 |
+
st.markdown("### π Select Your Travel Theme")
|
111 |
+
travel_theme = st.selectbox(
|
112 |
+
"π Select Your Travel Theme:",
|
113 |
+
["π¨βπ©βπ§βπ¦ Family Vacation", "ποΈ Adventure Trip", "π§³ Solo Exploration"],
|
114 |
+
index=0 # Set Family Vacation as default
|
115 |
+
)
|
116 |
+
|
117 |
+
# Divider for aesthetics
|
118 |
+
st.markdown("---")
|
119 |
+
|
120 |
+
st.markdown(
|
121 |
+
f"""
|
122 |
+
<div style="
|
123 |
+
text-align: center;
|
124 |
+
padding: 15px;
|
125 |
+
background-color: #d2d2d4;
|
126 |
+
border-radius: 10px;
|
127 |
+
color: black;
|
128 |
+
margin-top: 20px;
|
129 |
+
">
|
130 |
+
<h3>π Your {travel_theme} to {destination} is about to begin! π</h3>
|
131 |
+
<p>Let's find the best flights, stays, and experiences for your unforgettable journey.</p>
|
132 |
+
</div>
|
133 |
+
""",
|
134 |
+
unsafe_allow_html=True,
|
135 |
+
)
|
136 |
+
|
137 |
+
def format_datetime(iso_string):
|
138 |
+
try:
|
139 |
+
dt = datetime.strptime(iso_string, "%Y-%m-%d %H:%M")
|
140 |
+
return dt.strftime("%b-%d, %Y | %I:%M %p") # Example: Mar-06, 2025 | 6:20 PM
|
141 |
+
except:
|
142 |
+
return "N/A"
|
143 |
+
|
144 |
+
activity_preferences = st.text_area(
|
145 |
+
"π What activities do you enjoy? (e.g., relaxing on the beach, exploring historical sites, nightlife, adventure)",
|
146 |
+
"Relaxing on the beach, exploring historical sites"
|
147 |
+
)
|
148 |
+
|
149 |
+
departure_date = st.date_input("Departure Date")
|
150 |
+
return_date = st.date_input("Return Date")
|
151 |
+
|
152 |
+
# Get API keys from environment variables or UI inputs
|
153 |
+
SERPAPI_KEY = os.getenv("SERPAPI_KEY") or serpapi_key_input
|
154 |
+
SUTRA_API_KEY = os.getenv("SUTRA_API_KEY") or sutra_api_key
|
155 |
+
|
156 |
+
# Validate API keys
|
157 |
+
if not SERPAPI_KEY:
|
158 |
+
st.sidebar.error("β οΈ SerpAPI key is required.")
|
159 |
+
|
160 |
+
if not SUTRA_API_KEY:
|
161 |
+
st.sidebar.error("β οΈ Sutra API key is required.")
|
162 |
+
|
163 |
+
# Initialize Sutra model for translations
|
164 |
+
@st.cache_resource
|
165 |
+
def get_sutra_model(api_key):
|
166 |
+
return ChatOpenAI(
|
167 |
+
api_key=api_key,
|
168 |
+
base_url="https://api.two.ai/v2",
|
169 |
+
model="sutra-v2",
|
170 |
+
temperature=0.7,
|
171 |
+
)
|
172 |
+
|
173 |
+
# Function to translate text using Sutra LLM
|
174 |
+
def translate_text(text, target_language, sutra_api_key):
|
175 |
+
if not sutra_api_key or target_language == "English":
|
176 |
+
return text
|
177 |
+
|
178 |
+
try:
|
179 |
+
sutra = get_sutra_model(sutra_api_key)
|
180 |
+
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}"
|
181 |
+
|
182 |
+
messages = [HumanMessage(content=translation_prompt)]
|
183 |
+
response = sutra.invoke(messages)
|
184 |
+
|
185 |
+
return response.content
|
186 |
+
except Exception as e:
|
187 |
+
st.warning(f"Translation error: {str(e)}. Showing original text.")
|
188 |
+
return text
|
189 |
+
|
190 |
+
# Function to fetch flight data
|
191 |
+
def fetch_flights(source, destination, departure_date, return_date):
|
192 |
+
params = {
|
193 |
+
"engine": "google_flights",
|
194 |
+
"departure_id": source,
|
195 |
+
"arrival_id": destination,
|
196 |
+
"outbound_date": str(departure_date),
|
197 |
+
"return_date": str(return_date),
|
198 |
+
"currency": "INR",
|
199 |
+
"hl": "en",
|
200 |
+
"api_key": SERPAPI_KEY
|
201 |
+
}
|
202 |
+
search = GoogleSearch(params)
|
203 |
+
results = search.get_dict()
|
204 |
+
return results
|
205 |
+
|
206 |
+
# Function to extract top 3 cheapest flights
|
207 |
+
def extract_cheapest_flights(flight_data):
|
208 |
+
best_flights = flight_data.get("best_flights", [])
|
209 |
+
sorted_flights = sorted(best_flights, key=lambda x: x.get("price", float("inf")))[:3] # Get top 3 cheapest
|
210 |
+
return sorted_flights
|
211 |
+
|
212 |
+
# Function to create optimized prompts for Sutra
|
213 |
+
def create_optimized_prompt(action, destination, preferences, constraints):
|
214 |
+
"""
|
215 |
+
Creates a token-efficient prompt for Sutra by focusing on essential information
|
216 |
+
|
217 |
+
Args:
|
218 |
+
action (str): What the agent needs to do (research, plan, find)
|
219 |
+
destination (str): The travel destination
|
220 |
+
preferences (str): User preferences and interests
|
221 |
+
constraints (str): Budget, time, and other constraints
|
222 |
+
|
223 |
+
Returns:
|
224 |
+
str: An optimized prompt
|
225 |
+
"""
|
226 |
+
# Create a focused prompt that emphasizes what's most important
|
227 |
+
return f"{action} for {destination}. Preferences: {preferences}. Constraints: {constraints}. BE CONCISE."
|
228 |
+
|
229 |
+
# AI Agents with Sutra model
|
230 |
+
def create_sutra_agent(name, instructions):
|
231 |
+
# Check if API key is available
|
232 |
+
if not SUTRA_API_KEY:
|
233 |
+
st.sidebar.error(f"β οΈ Can't initialize {name} agent: No Sutra API key provided.")
|
234 |
+
return None
|
235 |
+
|
236 |
+
# Create agent with Sutra model via OpenAILike wrapper
|
237 |
+
return Agent(
|
238 |
+
name=name,
|
239 |
+
instructions=instructions,
|
240 |
+
model=OpenAILike(
|
241 |
+
id="sutra-v2",
|
242 |
+
api_key=SUTRA_API_KEY,
|
243 |
+
base_url="https://api.two.ai/v2"
|
244 |
+
),
|
245 |
+
tools=[SerpApiTools(api_key=SERPAPI_KEY)] if name != "Planner" else None,
|
246 |
+
add_datetime_to_instructions=True,
|
247 |
+
markdown=True
|
248 |
+
)
|
249 |
+
|
250 |
+
# Function to display content with optional translation expander
|
251 |
+
# This function is no longer needed since we're showing directly in preferred language
|
252 |
+
# Removing this function as it's not needed anymore
|
253 |
+
|
254 |
+
# Initialize agents
|
255 |
+
researcher_instructions = [
|
256 |
+
"FOCUS on key attractions, safety, and local information.",
|
257 |
+
"PRIORITIZE reliable sources and official travel guides.",
|
258 |
+
"BE CONCISE - focus on must-know facts only.",
|
259 |
+
"LIMIT output to 5-7 key attractions or activities."
|
260 |
+
]
|
261 |
+
|
262 |
+
planner_instructions = [
|
263 |
+
"CREATE brief day-by-day itinerary with morning/afternoon/evening blocks.",
|
264 |
+
"INCLUDE only 2-3 activities per time block.",
|
265 |
+
"FOCUS on logistics, timing, and estimated costs.",
|
266 |
+
"KEEP descriptions brief but informative."
|
267 |
+
]
|
268 |
+
|
269 |
+
hotel_restaurant_instructions = [
|
270 |
+
"FIND 3-5 top hotels and restaurants near popular attractions.",
|
271 |
+
"INCLUDE price range, rating, and 1-2 key features for each.",
|
272 |
+
"PRIORITIZE results matching user preferences.",
|
273 |
+
"FORMAT as concise bullet points."
|
274 |
+
]
|
275 |
+
|
276 |
+
# Generate Travel Plan
|
277 |
+
if st.button("π Generate Travel Plan"):
|
278 |
+
# Check API keys first
|
279 |
+
if not SERPAPI_KEY or not SUTRA_API_KEY:
|
280 |
+
st.error("β οΈ Both SerpAPI and Sutra API keys are required to generate a travel plan.")
|
281 |
+
st.stop()
|
282 |
+
|
283 |
+
# Calculate number of days
|
284 |
+
num_days = (return_date - departure_date).days + 1 # Add 1 to include both departure and return days
|
285 |
+
|
286 |
+
# Create agents with Sutra model
|
287 |
+
researcher = create_sutra_agent("Researcher", researcher_instructions)
|
288 |
+
planner = create_sutra_agent("Planner", planner_instructions)
|
289 |
+
hotel_restaurant_finder = create_sutra_agent("Hotel & Restaurant Finder", hotel_restaurant_instructions)
|
290 |
+
|
291 |
+
if not researcher or not planner or not hotel_restaurant_finder:
|
292 |
+
st.error("β οΈ Failed to initialize AI agents. Please check your API keys.")
|
293 |
+
st.stop()
|
294 |
+
|
295 |
+
with st.spinner("βοΈ Fetching best flight options..."):
|
296 |
+
flight_data = fetch_flights(source, destination, departure_date, return_date)
|
297 |
+
cheapest_flights = extract_cheapest_flights(flight_data)
|
298 |
+
|
299 |
+
# Hide intermediate processing and only show final results
|
300 |
+
with st.spinner("π Processing your travel plan..."):
|
301 |
+
# Research attractions & activities
|
302 |
+
research_prompt = create_optimized_prompt(
|
303 |
+
action="Research top attractions and activities",
|
304 |
+
destination=destination,
|
305 |
+
preferences=f"Trip type: {travel_theme}. Activities: {activity_preferences}.",
|
306 |
+
constraints=f"Budget: {budget}. Duration: {num_days} days."
|
307 |
+
)
|
308 |
+
|
309 |
+
# Get research output without streaming
|
310 |
+
research_response = researcher.run(research_prompt, stream=False)
|
311 |
+
research_output = research_response.content if hasattr(research_response, 'content') else str(research_response)
|
312 |
+
|
313 |
+
# Hotels & restaurants
|
314 |
+
hotel_restaurant_prompt = create_optimized_prompt(
|
315 |
+
action="Find top hotels and restaurants",
|
316 |
+
destination=destination,
|
317 |
+
preferences=f"Trip type: {travel_theme}. Hotel rating: {hotel_rating}.",
|
318 |
+
constraints=f"Budget: {budget}. Activities nearby: {activity_preferences}."
|
319 |
+
)
|
320 |
+
|
321 |
+
# Get hotel output without streaming
|
322 |
+
hotel_response = hotel_restaurant_finder.run(hotel_restaurant_prompt, stream=False)
|
323 |
+
hotel_output = hotel_response.content if hasattr(hotel_response, 'content') else str(hotel_response)
|
324 |
+
|
325 |
+
# Simplified flight data for token efficiency
|
326 |
+
simplified_flights = []
|
327 |
+
for flight in cheapest_flights:
|
328 |
+
simplified_flights.append({
|
329 |
+
"airline": flight.get("airline", "Unknown"),
|
330 |
+
"price": flight.get("price", "N/A"),
|
331 |
+
"duration": flight.get("total_duration", "N/A")
|
332 |
+
})
|
333 |
+
|
334 |
+
# Create itinerary
|
335 |
+
planning_prompt = create_optimized_prompt(
|
336 |
+
action=f"Create {num_days}-day itinerary",
|
337 |
+
destination=destination,
|
338 |
+
preferences=f"Trip type: {travel_theme}. Activities: {activity_preferences}.",
|
339 |
+
constraints=f"Budget: {budget}. Class: {flight_class}. Hotel: {hotel_rating}."
|
340 |
+
)
|
341 |
+
|
342 |
+
# Add research data but keep it brief
|
343 |
+
planning_prompt += f" Based on: {research_output[:500]}... {hotel_output[:500]}..."
|
344 |
+
|
345 |
+
# Get itinerary output without streaming
|
346 |
+
itinerary_response = planner.run(planning_prompt, stream=False)
|
347 |
+
itinerary_output = itinerary_response.content if hasattr(itinerary_response, 'content') else str(itinerary_response)
|
348 |
+
|
349 |
+
# Translate all content to preferred language if not English
|
350 |
+
if output_language != "English":
|
351 |
+
research_output = translate_text(research_output, output_language, SUTRA_API_KEY)
|
352 |
+
hotel_output = translate_text(hotel_output, output_language, SUTRA_API_KEY)
|
353 |
+
itinerary_output = translate_text(itinerary_output, output_language, SUTRA_API_KEY)
|
354 |
+
|
355 |
+
# Display Results - directly in preferred language, no expandable sections
|
356 |
+
st.subheader("βοΈ Cheapest Flight Options")
|
357 |
+
if cheapest_flights:
|
358 |
+
cols = st.columns(len(cheapest_flights))
|
359 |
+
for idx, flight in enumerate(cheapest_flights):
|
360 |
+
with cols[idx]:
|
361 |
+
airline_logo = flight.get("airline_logo", "")
|
362 |
+
airline_name = flight.get("airline", "Unknown Airline")
|
363 |
+
price = flight.get("price", "Not Available")
|
364 |
+
total_duration = flight.get("total_duration", "N/A")
|
365 |
+
|
366 |
+
flights_info = flight.get("flights", [{}])
|
367 |
+
departure = flights_info[0].get("departure_airport", {})
|
368 |
+
arrival = flights_info[-1].get("arrival_airport", {})
|
369 |
+
airline_name = flights_info[0].get("airline", "Unknown Airline")
|
370 |
+
|
371 |
+
departure_time = format_datetime(departure.get("time", "N/A"))
|
372 |
+
arrival_time = format_datetime(arrival.get("time", "N/A"))
|
373 |
+
|
374 |
+
departure_token = flight.get("departure_token", "")
|
375 |
+
|
376 |
+
# Use translated labels based on selected language
|
377 |
+
if output_language != "English":
|
378 |
+
departure_label = translate_text("Departure:", output_language, SUTRA_API_KEY)
|
379 |
+
arrival_label = translate_text("Arrival:", output_language, SUTRA_API_KEY)
|
380 |
+
duration_label = translate_text("Duration:", output_language, SUTRA_API_KEY)
|
381 |
+
book_now = translate_text("Book Now", output_language, SUTRA_API_KEY)
|
382 |
+
else:
|
383 |
+
departure_label = "Departure:"
|
384 |
+
arrival_label = "Arrival:"
|
385 |
+
duration_label = "Duration:"
|
386 |
+
book_now = "Book Now"
|
387 |
+
|
388 |
+
booking_link = "#" # Default value
|
389 |
+
if departure_token:
|
390 |
+
try:
|
391 |
+
params = {
|
392 |
+
"engine": "google_flights",
|
393 |
+
"departure_id": source,
|
394 |
+
"arrival_id": destination,
|
395 |
+
"outbound_date": str(departure_date),
|
396 |
+
"return_date": str(return_date),
|
397 |
+
"currency": "INR",
|
398 |
+
"hl": "en",
|
399 |
+
"departure_token": departure_token,
|
400 |
+
"api_key": SERPAPI_KEY
|
401 |
+
}
|
402 |
+
search_with_token = GoogleSearch(params)
|
403 |
+
results_with_booking = search_with_token.get_dict()
|
404 |
+
|
405 |
+
# Check if we have valid booking data
|
406 |
+
if 'best_flights' in results_with_booking and len(results_with_booking['best_flights']) > idx:
|
407 |
+
booking_token = results_with_booking['best_flights'][idx].get('booking_token')
|
408 |
+
if booking_token:
|
409 |
+
booking_link = f"https://www.google.com/travel/flights?tfs={booking_token}"
|
410 |
+
except Exception as e:
|
411 |
+
st.warning(f"Could not fetch booking link: {str(e)}")
|
412 |
+
booking_link = "#"
|
413 |
+
|
414 |
+
# Flight card layout with improved visibility for dark mode
|
415 |
+
st.markdown(
|
416 |
+
f"""
|
417 |
+
<div style="
|
418 |
+
border: 2px solid #ddd;
|
419 |
+
border-radius: 10px;
|
420 |
+
padding: 15px;
|
421 |
+
text-align: center;
|
422 |
+
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
423 |
+
background-color: rgba(249, 249, 249, 0.9);
|
424 |
+
margin-bottom: 20px;
|
425 |
+
">
|
426 |
+
<img src="{airline_logo}" width="100" alt="Flight Logo" style="background-color: white; padding: 5px; border-radius: 5px;" />
|
427 |
+
<h3 style="margin: 10px 0; color: #333;">{airline_name}</h3>
|
428 |
+
<p style="color: #333;"><strong>{departure_label}</strong> {departure_time}</p>
|
429 |
+
<p style="color: #333;"><strong>{arrival_label}</strong> {arrival_time}</p>
|
430 |
+
<p style="color: #333;"><strong>{duration_label}</strong> {total_duration} min</p>
|
431 |
+
<h2 style="color: #008000;">π° {price}</h2>
|
432 |
+
<a href="{booking_link}" target="_blank" style="
|
433 |
+
display: inline-block;
|
434 |
+
padding: 10px 20px;
|
435 |
+
font-size: 16px;
|
436 |
+
font-weight: bold;
|
437 |
+
color: #fff;
|
438 |
+
background-color: #007bff;
|
439 |
+
text-decoration: none;
|
440 |
+
border-radius: 5px;
|
441 |
+
margin-top: 10px;
|
442 |
+
">π {book_now}</a>
|
443 |
+
</div>
|
444 |
+
""",
|
445 |
+
unsafe_allow_html=True
|
446 |
+
)
|
447 |
+
else:
|
448 |
+
no_flights_message = "β οΈ No flight data available."
|
449 |
+
if output_language != "English":
|
450 |
+
no_flights_message = translate_text(no_flights_message, output_language, SUTRA_API_KEY)
|
451 |
+
st.warning(no_flights_message)
|
452 |
+
|
453 |
+
# Display content directly in preferred language
|
454 |
+
st.subheader("π¨ Hotels & Restaurants")
|
455 |
+
st.markdown(hotel_output)
|
456 |
+
|
457 |
+
st.subheader("πΊοΈ Your Personalized Itinerary")
|
458 |
+
st.markdown(itinerary_output)
|
459 |
+
|
460 |
+
st.subheader("π Destination Research")
|
461 |
+
st.markdown(research_output)
|
462 |
+
|
463 |
+
success_message = "β
Travel plan generated successfully!"
|
464 |
+
if output_language != "English":
|
465 |
+
success_message = translate_text(success_message, output_language, SUTRA_API_KEY)
|
466 |
+
st.success(success_message)
|