bharathmunakala commited on
Commit
fbf618c
Β·
verified Β·
1 Parent(s): 190aac6

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
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)