AdithyaSNair commited on
Commit
b4726a6
·
verified ·
1 Parent(s): 796d53f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +732 -0
app.py ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from datetime import datetime, timedelta
3
+ import base64
4
+ import pandas as pd
5
+ import pydeck as pdk
6
+ from travel import (
7
+ destination_research_task, accommodation_task, transportation_task,
8
+ activities_task, dining_task, itinerary_task, chatbot_task,
9
+ run_task
10
+ )
11
+ from geopy.geocoders import Nominatim
12
+ from io import BytesIO
13
+
14
+ st.set_page_config(
15
+ page_title="Your AI Agent for Travelling",
16
+ page_icon="✈️",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded"
19
+ )
20
+
21
+ # ------------------------------------------------------
22
+ # CSS Styles for the App
23
+ # ------------------------------------------------------
24
+ st.markdown("""
25
+ <style>
26
+ :root {
27
+ --primary: #3a86ff;
28
+ --primary-light: #4895ef;
29
+ --primary-dark: #2667ff;
30
+ --secondary: #4cc9f0;
31
+ --accent: #4361ee;
32
+ --background: #f8f9fa;
33
+ --card-bg: #ffffff;
34
+ --text: #212529;
35
+ --text-muted: #495057;
36
+ --border: #e9ecef;
37
+ }
38
+ /* Sidebar custom styles */
39
+ .sidebar-container {
40
+ padding: 20px;
41
+ background-color: var(--card-bg);
42
+ border-radius: 10px;
43
+ margin-bottom: 20px;
44
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
45
+ }
46
+ .sidebar-header {
47
+ text-align: center;
48
+ margin-bottom: 20px;
49
+ }
50
+ .sidebar-header img {
51
+ margin-bottom: 10px;
52
+ }
53
+ .sidebar-header h3 {
54
+ margin-bottom: 5px;
55
+ color: var(--accent);
56
+ font-size: 1.5rem;
57
+ }
58
+ .sidebar-section {
59
+ margin-bottom: 20px;
60
+ }
61
+ .sidebar-section h2 {
62
+ margin-bottom: 10px;
63
+ color: var(--primary-dark);
64
+ border-bottom: 1px solid var(--border);
65
+ padding-bottom: 5px;
66
+ }
67
+ .sidebar-section p {
68
+ font-size: 14px;
69
+ color: var(--text-muted);
70
+ }
71
+ .sidebar-section ul {
72
+ list-style-type: disc;
73
+ margin-left: 20px;
74
+ color: var(--text);
75
+ font-size: 14px;
76
+ }
77
+ .agent-item {
78
+ margin-bottom: 10px;
79
+ }
80
+ .agent-item strong {
81
+ font-size: 18px;
82
+ color: var(--primary-dark);
83
+ }
84
+ .agent-item small {
85
+ font-size: 16px;
86
+ }
87
+ </style>
88
+ """, unsafe_allow_html=True)
89
+
90
+ # ------------------------------------------------------
91
+ # Helper Functions
92
+ # ------------------------------------------------------
93
+ def get_download_link(text_content, filename):
94
+ """Generate a download link for the itinerary text file."""
95
+ b64 = base64.b64encode(text_content.encode()).decode()
96
+ href = f'<a class="download-link" href="data:text/plain;base64,{b64}" download="{filename}"><i>📥</i> Save Your Itinerary</a>'
97
+ return href
98
+
99
+ def display_modern_progress(current_step, total_steps=6):
100
+ """Displays a progress bar and step indicators."""
101
+ if 'progress_steps' not in st.session_state:
102
+ st.session_state.progress_steps = {
103
+ 0: {'status': 'pending', 'name': "Trip Details"},
104
+ 1: {'status': 'pending', 'name': "About"},
105
+ 2: {'status': 'pending', 'name': "Travel Style"},
106
+ 3: {'status': 'pending', 'name': "Live Agent Outputs"},
107
+ 4: {'status': 'pending', 'name': "Download & Share"},
108
+ 5: {'status': 'pending', 'name': "Full Itinerary"}
109
+ }
110
+
111
+ for i in range(total_steps):
112
+ if i < current_step:
113
+ st.session_state.progress_steps[i]['status'] = 'complete'
114
+ elif i == current_step:
115
+ st.session_state.progress_steps[i]['status'] = 'active'
116
+ else:
117
+ st.session_state.progress_steps[i]['status'] = 'pending'
118
+
119
+ progress_percentage = (current_step / total_steps) * 100
120
+ st.progress(progress_percentage / 100)
121
+
122
+ # Display step grid
123
+ st.markdown("""
124
+ <style>
125
+ .compact-progress {
126
+ background: var(--background);
127
+ border-radius: 10px;
128
+ padding: 15px;
129
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
130
+ margin-bottom: 20px;
131
+ }
132
+ .step-grid {
133
+ display: grid;
134
+ grid-template-columns: repeat(3, 1fr);
135
+ gap: 10px;
136
+ }
137
+ .step-item {
138
+ display: flex;
139
+ align-items: center;
140
+ padding: 8px 10px;
141
+ border-radius: 6px;
142
+ background: var(--card-bg);
143
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
144
+ }
145
+ .step-item.complete {
146
+ border-left: 3px solid #4CAF50;
147
+ background: #f1f8e9;
148
+ }
149
+ .step-item.active {
150
+ border-left: 3px solid #2196F3;
151
+ background: #e3f2fd;
152
+ font-weight: bold;
153
+ }
154
+ .step-item.pending {
155
+ border-left: 3px solid #9e9e9e;
156
+ opacity: 0.7;
157
+ }
158
+ .step-icon {
159
+ margin-right: 8px;
160
+ font-size: 14px;
161
+ }
162
+ .step-text {
163
+ font-size: 13px;
164
+ color: var(--text);
165
+ }
166
+ </style>
167
+ <div class="compact-progress">
168
+ <div class="step-grid">
169
+ """, unsafe_allow_html=True)
170
+
171
+ for i, step_info in st.session_state.progress_steps.items():
172
+ status = step_info['status']
173
+ name = step_info['name']
174
+ if status == 'complete':
175
+ icon = "✅"
176
+ status_class = "complete"
177
+ elif status == 'active':
178
+ icon = "🔄"
179
+ status_class = "active"
180
+ else:
181
+ icon = "⭕"
182
+ status_class = "pending"
183
+
184
+ st.markdown(f"""
185
+ <div class="step-item {status_class}">
186
+ <span class="step-icon">{icon}</span>
187
+ <span class="step-text">{name}</span>
188
+ </div>
189
+ """, unsafe_allow_html=True)
190
+
191
+ st.markdown('</div></div>', unsafe_allow_html=True)
192
+ return progress_percentage
193
+
194
+ def update_step_status(step_index, status):
195
+ """Update the status (pending, active, complete) for a given step index."""
196
+ if 'progress_steps' in st.session_state and step_index in st.session_state.progress_steps:
197
+ st.session_state.progress_steps[step_index]['status'] = status
198
+
199
+ def run_task_with_logs(task, input_text, log_container, output_container, results_key=None):
200
+ """Runs a task with logging in a separate container."""
201
+ if 'log_messages' not in st.session_state:
202
+ st.session_state.log_messages = []
203
+
204
+ log_message = f"🤖 Starting {task.agent.role}..."
205
+ st.session_state.log_messages.append(log_message)
206
+
207
+ with log_container:
208
+ st.markdown("### Agent Activity")
209
+ for msg in st.session_state.log_messages:
210
+ st.markdown(msg)
211
+
212
+ result = run_task(task, input_text)
213
+
214
+ if results_key:
215
+ st.session_state.results[results_key] = result
216
+
217
+ log_message = f"✅ {task.agent.role} completed!"
218
+ st.session_state.log_messages.append(log_message)
219
+
220
+ with log_container:
221
+ st.markdown("### Agent Activity")
222
+ for msg in st.session_state.log_messages:
223
+ st.markdown(msg)
224
+
225
+ with output_container:
226
+ st.markdown(f"### {task.agent.role} Output")
227
+ st.markdown("<div class='agent-output'>" + result + "</div>", unsafe_allow_html=True)
228
+
229
+ return result
230
+
231
+ # ------------------------------------------------------
232
+ # Session State Initialization
233
+ # ------------------------------------------------------
234
+ if 'generated_itinerary' not in st.session_state:
235
+ st.session_state.generated_itinerary = None
236
+ if 'generation_complete' not in st.session_state:
237
+ st.session_state.generation_complete = False
238
+ if 'current_step' not in st.session_state:
239
+ st.session_state.current_step = 0
240
+ if 'results' not in st.session_state:
241
+ st.session_state.results = {
242
+ "destination_info": "",
243
+ "accommodation_info": "",
244
+ "transportation_info": "",
245
+ "activities_info": "",
246
+ "dining_info": "",
247
+ "itinerary": "",
248
+ "final_itinerary": ""
249
+ }
250
+ if 'log_messages' not in st.session_state:
251
+ st.session_state.log_messages = []
252
+ if 'current_output' not in st.session_state:
253
+ st.session_state.current_output = None
254
+ if 'form_submitted' not in st.session_state:
255
+ st.session_state.form_submitted = False
256
+
257
+ col1, col2, col3 = st.columns([1,2,1])
258
+ with col2:
259
+ st.image("assets/image.png", width=350)
260
+
261
+ st.markdown(f"""
262
+ <div class="animate-in" style="text-align: center;">
263
+ <h1 class="main-header">Your AI Agent for Travelling</h1>
264
+ <p style="font-size: 1.2rem; color: var(--text-muted); margin-bottom: 25px;">
265
+ ✨ Create your personalized AI-powered travel itinerary in minutes! ✨
266
+ </p>
267
+ </div>
268
+ """, unsafe_allow_html=True)
269
+
270
+
271
+ st.markdown('<hr style="height:3px;border:none;background-color:#f0f0f0;margin-bottom:25px;">', unsafe_allow_html=True)
272
+
273
+ with st.sidebar:
274
+ st.image("assets/image.png", width=250)
275
+ st.markdown("""
276
+ <h3>Namude Yatra: The beginning of your dream journey</h3>
277
+ <p style="font-size: 14px; color: var(--text-muted);">AI-Powered Agentic Travel Planning</p>
278
+ """, unsafe_allow_html=True)
279
+
280
+
281
+ st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
282
+ st.markdown("<h2>About</h2>", unsafe_allow_html=True)
283
+ st.markdown("<p>This tool creates a personalized travel itinerary based on your preferences. Fill in the form on the main page and let our expert travel agents plan your perfect trip!</p>", unsafe_allow_html=True)
284
+ st.markdown('</div>', unsafe_allow_html=True)
285
+
286
+ st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
287
+ st.markdown("<h2>How It Works</h2>", unsafe_allow_html=True)
288
+ st.markdown("""
289
+ <ul>
290
+ <li><b>Enter:</b> Your travel details</li>
291
+ <li><b>Analyze:</b> Preferences by AI</li>
292
+ <li><b>Generate:</b> Your comprehensive itinerary</li>
293
+ <li><b>Download:</b> Save and share your itinerary</li>
294
+ </ul>
295
+ """, unsafe_allow_html=True)
296
+ st.markdown('</div>', unsafe_allow_html=True)
297
+
298
+ st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
299
+ st.markdown("<h2>Travel Agents</h2>", unsafe_allow_html=True)
300
+ agents = [
301
+ ("🔭 Research Specialist", "Finds the best destinations based on your preferences."),
302
+ ("🏨 Accommodation Expert", "Suggests suitable hotels and stays."),
303
+ ("🚆 Transportation Planner", "Plans efficient travel routes."),
304
+ ("🎯 Activities Curator", "Recommends activities tailored to your interests."),
305
+ ("🍽️ Dining Connoisseur", "Finds the best dining experiences."),
306
+ ("📅 Itinerary Creator", "Puts everything together in a daily plan.")
307
+ ]
308
+ for name, desc in agents:
309
+ st.markdown(f'<div class="agent-item"><strong>{name}</strong><br><small>{desc}</small></div>', unsafe_allow_html=True)
310
+ st.markdown('</div>', unsafe_allow_html=True)
311
+
312
+ # ------------------------------------------------------
313
+ # Main Form
314
+ # ------------------------------------------------------
315
+ if not st.session_state.generation_complete:
316
+ st.markdown('<div class="modern-card animate-in">', unsafe_allow_html=True)
317
+ st.markdown("<h3 style='font-weight: 600; color: var(--primary-dark); display: flex; align-items: center; gap: 10px;'><span style='font-size: 20px;'>✈️</span> Create Your Itinerary</h3>", unsafe_allow_html=True)
318
+
319
+ st.markdown("""
320
+ <p style="color: var(--text-muted); margin-bottom: 16px; font-size: 14px;">Complete the form below for a personalized travel plan.</p>
321
+ """, unsafe_allow_html=True)
322
+
323
+ with st.form("travel_form"):
324
+ col1, col2 = st.columns(2)
325
+ with col1:
326
+ st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px;">Trip Details</p>', unsafe_allow_html=True)
327
+ origin = st.text_input("Origin", placeholder="e.g., New York, USA")
328
+ destination = st.text_input("Destination", placeholder="e.g., Paris, France")
329
+ st.markdown('<p style="font-size: 14px;">Travel Dates</p>', unsafe_allow_html=True)
330
+ start_date = st.date_input("Start Date", min_value=datetime.now(), label_visibility="collapsed")
331
+ duration = st.slider("Duration (days)", min_value=1, max_value=30, value=7)
332
+ end_date = start_date + timedelta(days=duration-1)
333
+ st.markdown(f'<p style="font-size: 13px; color: var(--text-muted);">{start_date.strftime("%b %d")} - {end_date.strftime("%b %d, %Y")}</p>', unsafe_allow_html=True)
334
+ with col2:
335
+ st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px;">Preferences</p>', unsafe_allow_html=True)
336
+ travelers = st.number_input("Travelers", min_value=1, max_value=15, value=2)
337
+ budget_options = ["Budget", "Moderate", "Luxury"]
338
+ budget = st.selectbox("Budget", budget_options, help="Budget: Economy options | Moderate: Mid-range | Luxury: High-end experiences")
339
+ travel_style = st.multiselect("Travel Style", options=["Culture", "Adventure", "Relaxation", "Food & Dining", "Nature", "Shopping", "Nightlife", "Family-friendly"], default=["Culture", "Food & Dining"])
340
+ with st.expander("Additional Preferences"):
341
+ preferences = st.text_area("Interests", placeholder="History museums, local cuisine, hiking, art...")
342
+ special_requirements = st.text_area("Special Requirements", placeholder="Dietary restrictions, accessibility needs...")
343
+ submit_button = st.form_submit_button("🚀 Create My Personal Travel Itinerary")
344
+ st.markdown('</div>', unsafe_allow_html=True)
345
+
346
+ if submit_button:
347
+ if not origin or not destination:
348
+ st.error("Please enter both origin and destination.")
349
+ else:
350
+ st.session_state.form_submitted = True
351
+ st.session_state.destination = destination
352
+ user_input = {
353
+ "origin": origin,
354
+ "destination": destination,
355
+ "duration": str(duration),
356
+ "travel_dates": f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}",
357
+ "travelers": str(travelers),
358
+ "budget": budget.lower(),
359
+ "travel_style": ", ".join(travel_style),
360
+ "preferences": preferences,
361
+ "special_requirements": special_requirements
362
+ }
363
+
364
+ st.session_state.user_input = user_input
365
+ input_context = f"""Travel Request Details:
366
+ Origin: {user_input['origin']}
367
+ Destination: {user_input['destination']}
368
+ Duration: {user_input['duration']} days
369
+ Travel Dates: {user_input['travel_dates']}
370
+ Travelers: {user_input['travelers']}
371
+ Budget Level: {user_input['budget']}
372
+ Travel Style: {user_input['travel_style']}
373
+ Preferences/Interests: {user_input['preferences']}
374
+ Special Requirements: {user_input['special_requirements']}
375
+ """
376
+ modified_input_context = "Please output the response in English.\n" + input_context
377
+
378
+ # Processing Animation
379
+ st.markdown("""
380
+ <div class="sleek-processing-container" style="text-align:center; padding:20px 0;">
381
+ <div class="pulse-container" style="position:relative; width:50px; height:50px;">
382
+ <div class="pulse-ring" style="position:absolute; width:100%; height:100%; border:2px solid #4361ee; border-radius:50%; animation:pulse 1.5s ease-out infinite;"></div>
383
+ <div class="pulse-core" style="position:absolute; left:50%; top:50%; transform:translate(-50%, -50%); width:12px; height:12px; background-color:#4361ee; border-radius:50%; box-shadow:0 0 8px rgba(67,97,238,0.6);"></div>
384
+ </div>
385
+ </div>
386
+ <style>
387
+ @keyframes pulse {
388
+ 0% { transform: scale(0.1); opacity: 0; }
389
+ 50% { opacity: 0.5; }
390
+ 100% { transform: scale(1); opacity: 0; }
391
+ }
392
+ </style>
393
+ """, unsafe_allow_html=True)
394
+
395
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
396
+ progress_tab, logs_tab, details_tab = st.tabs(["📊 Progress", "🔄 Live Activity", "📋 Your Travel Request"])
397
+ with details_tab:
398
+ st.markdown("#### Your Travel Request")
399
+ st.markdown("**Destination:** " + user_input['destination'])
400
+ st.markdown("**From:** " + user_input['origin'])
401
+ st.markdown("**When:** " + user_input['travel_dates'] + " (" + user_input['duration'] + " days)")
402
+ st.markdown("**Budget:** " + user_input['budget'].title())
403
+ st.markdown("**Travel Style:** " + user_input['travel_style'])
404
+ if user_input['preferences']:
405
+ st.markdown("**Interests:** " + user_input['preferences'])
406
+ if user_input['special_requirements']:
407
+ st.markdown("**Special Requirements:** " + user_input['special_requirements'])
408
+ with progress_tab:
409
+ if 'progress_placeholder' not in st.session_state:
410
+ st.session_state.progress_placeholder = st.empty()
411
+ with st.session_state.progress_placeholder.container():
412
+ display_modern_progress(st.session_state.current_step)
413
+ with logs_tab:
414
+ log_container = st.container()
415
+ st.session_state.log_messages = []
416
+ st.markdown('</div>', unsafe_allow_html=True)
417
+
418
+ # Output container for the Agents
419
+ output_container = st.container()
420
+ with output_container:
421
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
422
+ st.markdown("### Live Agent Outputs")
423
+ st.info("Our AI agents will show their work here as they create your itinerary")
424
+ st.markdown('</div>', unsafe_allow_html=True)
425
+
426
+ # Steps
427
+ st.session_state.current_step = 0
428
+
429
+ # Step 1: Destination Research
430
+ update_step_status(0, 'active')
431
+ with st.session_state.progress_placeholder.container():
432
+ display_modern_progress(st.session_state.current_step)
433
+ destination_info = run_task_with_logs(
434
+ destination_research_task,
435
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
436
+ log_container,
437
+ output_container,
438
+ "destination_info"
439
+ )
440
+ update_step_status(0, 'complete')
441
+ st.session_state.current_step = 1
442
+
443
+ # Step 2: Accommodation
444
+ update_step_status(1, 'active')
445
+ with st.session_state.progress_placeholder.container():
446
+ display_modern_progress(st.session_state.current_step)
447
+ accommodation_info = run_task_with_logs(
448
+ accommodation_task,
449
+ modified_input_context.format(destination=user_input['destination'], budget=user_input['budget'], preferences=user_input['preferences']),
450
+ log_container,
451
+ output_container,
452
+ "accommodation_info"
453
+ )
454
+ update_step_status(1, 'complete')
455
+ st.session_state.current_step = 2
456
+
457
+ # Step 3: Transportation
458
+ update_step_status(2, 'active')
459
+ with st.session_state.progress_placeholder.container():
460
+ display_modern_progress(st.session_state.current_step)
461
+ transportation_info = run_task_with_logs(
462
+ transportation_task,
463
+ modified_input_context.format(origin=user_input['origin'], destination=user_input['destination']),
464
+ log_container,
465
+ output_container,
466
+ "transportation_info"
467
+ )
468
+ update_step_status(2, 'complete')
469
+ st.session_state.current_step = 3
470
+
471
+ # Step 4: Activities
472
+ update_step_status(3, 'active')
473
+ with st.session_state.progress_placeholder.container():
474
+ display_modern_progress(st.session_state.current_step)
475
+ activities_info = run_task_with_logs(
476
+ activities_task,
477
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
478
+ log_container,
479
+ output_container,
480
+ "activities_info"
481
+ )
482
+ update_step_status(3, 'complete')
483
+ st.session_state.current_step = 4
484
+
485
+ # Step 5: Dining
486
+ update_step_status(4, 'active')
487
+ with st.session_state.progress_placeholder.container():
488
+ display_modern_progress(st.session_state.current_step)
489
+ dining_info = run_task_with_logs(
490
+ dining_task,
491
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
492
+ log_container,
493
+ output_container,
494
+ "dining_info"
495
+ )
496
+ update_step_status(4, 'complete')
497
+ st.session_state.current_step = 5
498
+
499
+ # Step 6: Final Itinerary
500
+ update_step_status(5, 'active')
501
+ with st.session_state.progress_placeholder.container():
502
+ display_modern_progress(st.session_state.current_step)
503
+ combined_info = f"""{input_context}
504
+
505
+ Destination Information:
506
+ {destination_info}
507
+
508
+ Accommodation Options:
509
+ {accommodation_info}
510
+
511
+ Transportation Plan:
512
+ {transportation_info}
513
+
514
+ Recommended Activities:
515
+ {activities_info}
516
+
517
+ Dining Recommendations:
518
+ {dining_info}
519
+ """
520
+ itinerary = run_task_with_logs(
521
+ itinerary_task,
522
+ combined_info.format(duration=user_input['duration'], origin=user_input['origin'], destination=user_input['destination']),
523
+ log_container,
524
+ output_container,
525
+ "itinerary"
526
+ )
527
+ update_step_status(5, 'complete')
528
+ st.session_state.current_step = 6
529
+ with st.session_state.progress_placeholder.container():
530
+ display_modern_progress(st.session_state.current_step)
531
+
532
+ # Store the final itinerary
533
+ st.session_state.generated_itinerary = itinerary
534
+ st.session_state.generation_complete = True
535
+ date_str = datetime.now().strftime("%Y-%m-%d")
536
+ st.session_state.filename = f"{user_input['destination'].replace(' ', '_')}_{date_str}_itinerary.txt"
537
+
538
+ # ------------------------------------------------------
539
+ # Display after Itinerary Generation
540
+ # ------------------------------------------------------
541
+ if st.session_state.generation_complete:
542
+ # Success Animation
543
+ st.markdown("""
544
+ <div class="modern-card animate-in">
545
+ <div style="display: flex; justify-content: center; margin-bottom: 20px;">
546
+ <div class="success-animation">
547
+ <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
548
+ <circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" />
549
+ <path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8" />
550
+ </svg>
551
+ </div>
552
+ </div>
553
+ <h2 style="text-align: center; color: var(--primary-dark);">Your Travel Itinerary is Ready! 🎉</h2>
554
+ <p style="text-align: center; color: var(--text-muted); margin-bottom: 20px;">We've created a personalized travel experience just for you. Explore your itinerary below.</p>
555
+ </div>
556
+
557
+ <style>
558
+ .success-animation {
559
+ width: 100px;
560
+ height: 100px;
561
+ position: relative;
562
+ }
563
+ .checkmark {
564
+ width: 100px;
565
+ height: 100px;
566
+ border-radius: 50%;
567
+ display: block;
568
+ stroke-width: 2;
569
+ stroke: var(--primary-dark);
570
+ stroke-miterlimit: 10;
571
+ box-shadow: 0 0 20px rgba(38,103,255,0.3);
572
+ animation: fill 0.4s ease-in-out 0.4s forwards, scale 0.3s ease-in-out 0.9s both;
573
+ }
574
+ .checkmark__circle {
575
+ stroke-dasharray: 166;
576
+ stroke-dashoffset: 166;
577
+ stroke-width: 2;
578
+ stroke-miterlimit: 10;
579
+ stroke: var(--primary-dark);
580
+ fill: none;
581
+ animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
582
+ }
583
+ .checkmark__check {
584
+ transform-origin: 50% 50%;
585
+ stroke-dasharray: 48;
586
+ stroke-dashoffset: 48;
587
+ animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
588
+ }
589
+ @keyframes stroke {
590
+ 100% { stroke-dashoffset: 0; }
591
+ }
592
+ @keyframes scale {
593
+ 0%, 100% { transform: none; }
594
+ 50% { transform: scale3d(1.1, 1.1, 1); }
595
+ }
596
+ @keyframes fill {
597
+ 100% { box-shadow: 0 0 20px rgba(38,103,255,0.3); }
598
+ }
599
+ </style>
600
+ """, unsafe_allow_html=True)
601
+
602
+ # Tabs
603
+ itinerary_tab, details_tab, download_tab, map_tab, chatbot_tab = st.tabs([
604
+ "🗒️ Full Itinerary",
605
+ "💼 Details",
606
+ "💾 Download & Share",
607
+ "🗺️ Map & Visualization",
608
+ "🤖 Chatbot Interface"
609
+ ])
610
+
611
+ # Full Itinerary
612
+ with itinerary_tab:
613
+ st.text_area("Your Itinerary", st.session_state.generated_itinerary, height=600)
614
+
615
+ # Details (Destination, Accommodation, etc.)
616
+ with details_tab:
617
+ agent_tabs = st.tabs(["🌎 Destination", "🏨 Accommodation", "🚗 Transportation", "🎭 Activities", "🍽️ Dining"])
618
+ with agent_tabs[0]:
619
+ st.markdown("### 🌎 Destination Research")
620
+ st.markdown(st.session_state.results["destination_info"])
621
+ with agent_tabs[1]:
622
+ st.markdown("### 🏨 Accommodation Options")
623
+ st.markdown(st.session_state.results["accommodation_info"])
624
+ with agent_tabs[2]:
625
+ st.markdown("### 🚗 Transportation Plan")
626
+ st.markdown(st.session_state.results["transportation_info"])
627
+ with agent_tabs[3]:
628
+ st.markdown("### 🎭 Recommended Activities")
629
+ st.markdown(st.session_state.results["activities_info"])
630
+ with agent_tabs[4]:
631
+ st.markdown("### 🍽️ Dining Recommendations")
632
+ st.markdown(st.session_state.results["dining_info"])
633
+
634
+ # Download & Share Tab (No QR code)
635
+ with download_tab:
636
+ col1, col2 = st.columns([2, 1])
637
+ with col1:
638
+ st.markdown("### Save Your Itinerary")
639
+ st.markdown("Download your personalized travel plan to access it offline or share with your travel companions.")
640
+ st.markdown("""
641
+ <div style="background-color: var(--background); padding: 15px; border-radius: 10px; margin-top: 20px;">
642
+ <h4>Your Itinerary File</h4>
643
+ <p style="font-size: 0.9rem; color: var(--text-muted);">Text format - Can be opened in any text editor</p>
644
+ """, unsafe_allow_html=True)
645
+
646
+ # Download link
647
+ st.markdown(
648
+ "<div style='margin: 10px 0;'>"
649
+ + get_download_link(st.session_state.generated_itinerary, st.session_state.filename)
650
+ + "</div>",
651
+ unsafe_allow_html=True
652
+ )
653
+ st.markdown("</div>", unsafe_allow_html=True)
654
+
655
+ st.markdown("### Share Your Itinerary")
656
+ st.markdown("*Coming soon: Additional sharing features (email, phone, etc.)*")
657
+
658
+ with col2:
659
+ st.markdown("### Save for Mobile")
660
+ st.markdown("*Coming soon: Additional mobile features such as a dedicated app or push notifications*")
661
+
662
+ # Map & Visualization Tab
663
+ with map_tab:
664
+ st.markdown("### Destination Map")
665
+ dest = st.session_state.get("destination", "Paris")
666
+ try:
667
+ geolocator = Nominatim(user_agent="travel_app")
668
+ location = geolocator.geocode(dest)
669
+ if location:
670
+ lat, lon = location.latitude, location.longitude
671
+ else:
672
+ st.error("The destination could not be found. Using default coordinates.")
673
+ lat, lon = 48.8566, 2.3522
674
+ except Exception as e:
675
+ st.error("Geocoding error: " + str(e))
676
+ lat, lon = 48.8566, 2.3522
677
+
678
+ map_data = pd.DataFrame({
679
+ "lat": [lat],
680
+ "lon": [lon],
681
+ "name": [dest]
682
+ })
683
+ st.map(map_data)
684
+
685
+ st.markdown("#### Interactive Map with Pydeck")
686
+ layer = pdk.Layer(
687
+ "ScatterplotLayer",
688
+ data=map_data,
689
+ get_position='[lon, lat]',
690
+ get_color='[200, 30, 0, 160]',
691
+ get_radius=200,
692
+ )
693
+ view_state = pdk.ViewState(
694
+ latitude=lat,
695
+ longitude=lon,
696
+ zoom=12,
697
+ pitch=50,
698
+ )
699
+ deck_chart = pdk.Deck(layers=[layer], initial_view_state=view_state)
700
+ st.pydeck_chart(deck_chart)
701
+
702
+ # Chatbot Interface
703
+ with chatbot_tab:
704
+ st.markdown("### AI Chatbot Interface")
705
+ if "chat_history" not in st.session_state:
706
+ st.session_state.chat_history = []
707
+ user_message = st.text_input("Enter your message:", key="chat_input")
708
+ if st.button("Send", key="send_button"):
709
+ if user_message:
710
+ response = run_task(chatbot_task, user_message)
711
+ st.session_state.chat_history.append({
712
+ "speaker": "User",
713
+ "message": user_message,
714
+ "time": datetime.now()
715
+ })
716
+ st.session_state.chat_history.append({
717
+ "speaker": "AI",
718
+ "message": response,
719
+ "time": datetime.now()
720
+ })
721
+ st.markdown("<div style='max-height:400px; overflow-y:auto; padding:10px; border:1px solid var(--border); border-radius:6px;'>", unsafe_allow_html=True)
722
+ for chat in st.session_state.chat_history:
723
+ time_str = chat["time"].strftime("%H:%M:%S")
724
+ st.markdown(f"**{chat['speaker']}** ({time_str}): {chat['message']}")
725
+ st.markdown("</div>", unsafe_allow_html=True)
726
+
727
+ # Footer
728
+ st.markdown("""
729
+ <div style="margin-top: 50px; text-align: center; padding: 20px; color: var(--text-muted); font-size: 0.8rem;">
730
+ <p>Built with ❤️ for you</p>
731
+ </div>
732
+ """, unsafe_allow_html=True)