Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -3,13 +3,16 @@ 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",
|
@@ -18,6 +21,44 @@ st.set_page_config(
|
|
18 |
initial_sidebar_state="expanded"
|
19 |
)
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
st.markdown("""
|
22 |
<style>
|
23 |
:root {
|
@@ -32,66 +73,14 @@ st.markdown("""
|
|
32 |
--text-muted: #495057;
|
33 |
--border: #e9ecef;
|
34 |
}
|
35 |
-
/* Sidebar custom styles */
|
36 |
-
.sidebar-container {
|
37 |
-
padding: 20px;
|
38 |
-
background-color: var(--card-bg);
|
39 |
-
border-radius: 10px;
|
40 |
-
margin-bottom: 20px;
|
41 |
-
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
42 |
-
}
|
43 |
-
.sidebar-header {
|
44 |
-
text-align: center;
|
45 |
-
margin-bottom: 20px;
|
46 |
-
}
|
47 |
-
.sidebar-header img {
|
48 |
-
margin-bottom: 10px;
|
49 |
-
}
|
50 |
-
.sidebar-header h3 {
|
51 |
-
margin-bottom: 5px;
|
52 |
-
color: var(--accent);
|
53 |
-
font-size: 1.5rem;
|
54 |
-
}
|
55 |
-
.sidebar-section {
|
56 |
-
margin-bottom: 20px;
|
57 |
-
}
|
58 |
-
.sidebar-section h2 {
|
59 |
-
margin-bottom: 10px;
|
60 |
-
color: var(--primary-dark);
|
61 |
-
border-bottom: 1px solid var(--border);
|
62 |
-
padding-bottom: 5px;
|
63 |
-
}
|
64 |
-
.sidebar-section p {
|
65 |
-
font-size: 14px;
|
66 |
-
color: var(--text-muted);
|
67 |
-
}
|
68 |
-
.sidebar-section ul {
|
69 |
-
list-style-type: disc;
|
70 |
-
margin-left: 20px;
|
71 |
-
color: var(--text);
|
72 |
-
font-size: 14px;
|
73 |
-
}
|
74 |
-
.agent-item {
|
75 |
-
margin-bottom: 10px;
|
76 |
-
}
|
77 |
-
.agent-item strong {
|
78 |
-
font-size: 18px;
|
79 |
-
color: var(--primary-dark);
|
80 |
-
}
|
81 |
-
.agent-item small {
|
82 |
-
font-size: 16px;
|
83 |
-
}
|
84 |
</style>
|
85 |
""", unsafe_allow_html=True)
|
86 |
|
87 |
def get_download_link(text_content, filename):
|
88 |
-
"""Generate a download link for the itinerary text file."""
|
89 |
b64 = base64.b64encode(text_content.encode()).decode()
|
90 |
-
|
91 |
-
return href
|
92 |
|
93 |
def display_modern_progress(current_step, total_steps=6):
|
94 |
-
"""Displays a progress bar and step indicators."""
|
95 |
if 'progress_steps' not in st.session_state:
|
96 |
st.session_state.progress_steps = {
|
97 |
0: {'status': 'pending', 'name': "Trip Details"},
|
@@ -113,7 +102,6 @@ def display_modern_progress(current_step, total_steps=6):
|
|
113 |
progress_percentage = (current_step / total_steps) * 100
|
114 |
st.progress(progress_percentage / 100)
|
115 |
|
116 |
-
# Display step grid
|
117 |
st.markdown("""
|
118 |
<style>
|
119 |
.compact-progress {
|
@@ -174,7 +162,6 @@ def display_modern_progress(current_step, total_steps=6):
|
|
174 |
else:
|
175 |
icon = "⭕"
|
176 |
status_class = "pending"
|
177 |
-
|
178 |
st.markdown(f"""
|
179 |
<div class="step-item {status_class}">
|
180 |
<span class="step-icon">{icon}</span>
|
@@ -186,15 +173,12 @@ def display_modern_progress(current_step, total_steps=6):
|
|
186 |
return progress_percentage
|
187 |
|
188 |
def update_step_status(step_index, status):
|
189 |
-
"""Update the status (pending, active, complete) for a given step index."""
|
190 |
if 'progress_steps' in st.session_state and step_index in st.session_state.progress_steps:
|
191 |
st.session_state.progress_steps[step_index]['status'] = status
|
192 |
|
193 |
def run_task_with_logs(task, input_text, log_container, output_container, results_key=None):
|
194 |
-
"""Runs a task with logging in a separate container."""
|
195 |
if 'log_messages' not in st.session_state:
|
196 |
st.session_state.log_messages = []
|
197 |
-
|
198 |
log_message = f"🤖 Starting {task.agent.role}..."
|
199 |
st.session_state.log_messages.append(log_message)
|
200 |
|
@@ -207,7 +191,6 @@ def run_task_with_logs(task, input_text, log_container, output_container, result
|
|
207 |
|
208 |
if results_key:
|
209 |
st.session_state.results[results_key] = result
|
210 |
-
|
211 |
log_message = f"✅ {task.agent.role} completed!"
|
212 |
st.session_state.log_messages.append(log_message)
|
213 |
|
@@ -219,7 +202,6 @@ def run_task_with_logs(task, input_text, log_container, output_container, result
|
|
219 |
with output_container:
|
220 |
st.markdown(f"### {task.agent.role} Output")
|
221 |
st.markdown("<div class='agent-output'>" + result + "</div>", unsafe_allow_html=True)
|
222 |
-
|
223 |
return result
|
224 |
|
225 |
if 'generated_itinerary' not in st.session_state:
|
@@ -258,56 +240,14 @@ st.markdown(f"""
|
|
258 |
</div>
|
259 |
""", unsafe_allow_html=True)
|
260 |
|
261 |
-
|
262 |
st.markdown('<hr style="height:3px;border:none;background-color:#f0f0f0;margin-bottom:25px;">', unsafe_allow_html=True)
|
263 |
|
264 |
-
with st.sidebar:
|
265 |
-
st.image("image.png", width=250)
|
266 |
-
st.markdown("""
|
267 |
-
<h3>Namude Yatra: The beginning of your dream journey</h3>
|
268 |
-
<p style="font-size: 14px; color: var(--text-muted);">AI-Powered Agentic Travel Planning</p>
|
269 |
-
""", unsafe_allow_html=True)
|
270 |
-
|
271 |
-
|
272 |
-
st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
|
273 |
-
st.markdown("<h2>About</h2>", unsafe_allow_html=True)
|
274 |
-
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)
|
275 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
276 |
-
|
277 |
-
st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
|
278 |
-
st.markdown("<h2>How It Works</h2>", unsafe_allow_html=True)
|
279 |
-
st.markdown("""
|
280 |
-
<ul>
|
281 |
-
<li><b>Enter:</b> Your travel details</li>
|
282 |
-
<li><b>Analyze:</b> Preferences by AI</li>
|
283 |
-
<li><b>Generate:</b> Your comprehensive itinerary</li>
|
284 |
-
<li><b>Download:</b> Save and share your itinerary</li>
|
285 |
-
</ul>
|
286 |
-
""", unsafe_allow_html=True)
|
287 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
288 |
-
|
289 |
-
st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
|
290 |
-
st.markdown("<h2>Travel Agents</h2>", unsafe_allow_html=True)
|
291 |
-
agents = [
|
292 |
-
("🔭 Research Specialist", "Finds the best destinations based on your preferences."),
|
293 |
-
("🏨 Accommodation Expert", "Suggests suitable hotels and stays."),
|
294 |
-
("🚆 Transportation Planner", "Plans efficient travel routes."),
|
295 |
-
("🎯 Activities Curator", "Recommends activities tailored to your interests."),
|
296 |
-
("🍽️ Dining Connoisseur", "Finds the best dining experiences."),
|
297 |
-
("📅 Itinerary Creator", "Puts everything together in a daily plan.")
|
298 |
-
]
|
299 |
-
for name, desc in agents:
|
300 |
-
st.markdown(f'<div class="agent-item"><strong>{name}</strong><br><small>{desc}</small></div>', unsafe_allow_html=True)
|
301 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
302 |
-
|
303 |
if not st.session_state.generation_complete:
|
304 |
st.markdown('<div class="modern-card animate-in">', unsafe_allow_html=True)
|
305 |
-
st.markdown("<h3 style='font-weight: 600; color: var(--primary-dark);
|
306 |
-
|
307 |
st.markdown("""
|
308 |
-
<p style="color: var(--text-muted);
|
309 |
""", unsafe_allow_html=True)
|
310 |
-
|
311 |
with st.form("travel_form"):
|
312 |
col1, col2 = st.columns(2)
|
313 |
with col1:
|
@@ -318,7 +258,7 @@ if not st.session_state.generation_complete:
|
|
318 |
start_date = st.date_input("Start Date", min_value=datetime.now(), label_visibility="collapsed")
|
319 |
duration = st.slider("Duration (days)", min_value=1, max_value=30, value=7)
|
320 |
end_date = start_date + timedelta(days=duration-1)
|
321 |
-
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)
|
322 |
with col2:
|
323 |
st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px;">Preferences</p>', unsafe_allow_html=True)
|
324 |
travelers = st.number_input("Travelers", min_value=1, max_value=15, value=2)
|
@@ -348,8 +288,7 @@ if not st.session_state.generation_complete:
|
|
348 |
"preferences": preferences,
|
349 |
"special_requirements": special_requirements
|
350 |
}
|
351 |
-
|
352 |
-
st.session_state.user_input = user_input
|
353 |
input_context = f"""Travel Request Details:
|
354 |
Origin: {user_input['origin']}
|
355 |
Destination: {user_input['destination']}
|
@@ -362,13 +301,13 @@ Preferences/Interests: {user_input['preferences']}
|
|
362 |
Special Requirements: {user_input['special_requirements']}
|
363 |
"""
|
364 |
modified_input_context = "Please output the response in English.\n" + input_context
|
365 |
-
|
366 |
# Processing Animation
|
367 |
st.markdown("""
|
368 |
-
<div
|
369 |
-
<div
|
370 |
-
<div
|
371 |
-
<div
|
372 |
</div>
|
373 |
</div>
|
374 |
<style>
|
@@ -379,7 +318,7 @@ Special Requirements: {user_input['special_requirements']}
|
|
379 |
}
|
380 |
</style>
|
381 |
""", unsafe_allow_html=True)
|
382 |
-
|
383 |
st.markdown('<div class="modern-card">', unsafe_allow_html=True)
|
384 |
progress_tab, logs_tab, details_tab = st.tabs(["📊 Progress", "🔄 Live Activity", "📋 Your Travel Request"])
|
385 |
with details_tab:
|
@@ -403,7 +342,6 @@ Special Requirements: {user_input['special_requirements']}
|
|
403 |
st.session_state.log_messages = []
|
404 |
st.markdown('</div>', unsafe_allow_html=True)
|
405 |
|
406 |
-
# Output container for the Agents
|
407 |
output_container = st.container()
|
408 |
with output_container:
|
409 |
st.markdown('<div class="modern-card">', unsafe_allow_html=True)
|
@@ -411,7 +349,6 @@ Special Requirements: {user_input['special_requirements']}
|
|
411 |
st.info("Our AI agents will show their work here as they create your itinerary")
|
412 |
st.markdown('</div>', unsafe_allow_html=True)
|
413 |
|
414 |
-
# Steps
|
415 |
st.session_state.current_step = 0
|
416 |
|
417 |
# Step 1: Destination Research
|
@@ -419,7 +356,7 @@ Special Requirements: {user_input['special_requirements']}
|
|
419 |
with st.session_state.progress_placeholder.container():
|
420 |
display_modern_progress(st.session_state.current_step)
|
421 |
destination_info = run_task_with_logs(
|
422 |
-
destination_research_task,
|
423 |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
|
424 |
log_container,
|
425 |
output_container,
|
@@ -433,7 +370,7 @@ Special Requirements: {user_input['special_requirements']}
|
|
433 |
with st.session_state.progress_placeholder.container():
|
434 |
display_modern_progress(st.session_state.current_step)
|
435 |
accommodation_info = run_task_with_logs(
|
436 |
-
accommodation_task,
|
437 |
modified_input_context.format(destination=user_input['destination'], budget=user_input['budget'], preferences=user_input['preferences']),
|
438 |
log_container,
|
439 |
output_container,
|
@@ -447,7 +384,7 @@ Special Requirements: {user_input['special_requirements']}
|
|
447 |
with st.session_state.progress_placeholder.container():
|
448 |
display_modern_progress(st.session_state.current_step)
|
449 |
transportation_info = run_task_with_logs(
|
450 |
-
transportation_task,
|
451 |
modified_input_context.format(origin=user_input['origin'], destination=user_input['destination']),
|
452 |
log_container,
|
453 |
output_container,
|
@@ -461,7 +398,7 @@ Special Requirements: {user_input['special_requirements']}
|
|
461 |
with st.session_state.progress_placeholder.container():
|
462 |
display_modern_progress(st.session_state.current_step)
|
463 |
activities_info = run_task_with_logs(
|
464 |
-
activities_task,
|
465 |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
|
466 |
log_container,
|
467 |
output_container,
|
@@ -475,7 +412,7 @@ Special Requirements: {user_input['special_requirements']}
|
|
475 |
with st.session_state.progress_placeholder.container():
|
476 |
display_modern_progress(st.session_state.current_step)
|
477 |
dining_info = run_task_with_logs(
|
478 |
-
dining_task,
|
479 |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
|
480 |
log_container,
|
481 |
output_container,
|
@@ -506,7 +443,7 @@ Dining Recommendations:
|
|
506 |
{dining_info}
|
507 |
"""
|
508 |
itinerary = run_task_with_logs(
|
509 |
-
itinerary_task,
|
510 |
combined_info.format(duration=user_input['duration'], origin=user_input['origin'], destination=user_input['destination']),
|
511 |
log_container,
|
512 |
output_container,
|
@@ -517,14 +454,13 @@ Dining Recommendations:
|
|
517 |
with st.session_state.progress_placeholder.container():
|
518 |
display_modern_progress(st.session_state.current_step)
|
519 |
|
520 |
-
# Store the final itinerary
|
521 |
st.session_state.generated_itinerary = itinerary
|
522 |
st.session_state.generation_complete = True
|
523 |
date_str = datetime.now().strftime("%Y-%m-%d")
|
524 |
st.session_state.filename = f"{user_input['destination'].replace(' ', '_')}_{date_str}_itinerary.txt"
|
525 |
|
|
|
526 |
if st.session_state.generation_complete:
|
527 |
-
# Success Animation
|
528 |
st.markdown("""
|
529 |
<div class="modern-card animate-in">
|
530 |
<div style="display: flex; justify-content: center; margin-bottom: 20px;">
|
@@ -538,7 +474,6 @@ if st.session_state.generation_complete:
|
|
538 |
<h2 style="text-align: center; color: var(--primary-dark);">Your Travel Itinerary is Ready! 🎉</h2>
|
539 |
<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>
|
540 |
</div>
|
541 |
-
|
542 |
<style>
|
543 |
.success-animation {
|
544 |
width: 100px;
|
@@ -584,7 +519,6 @@ if st.session_state.generation_complete:
|
|
584 |
</style>
|
585 |
""", unsafe_allow_html=True)
|
586 |
|
587 |
-
# Tabs
|
588 |
itinerary_tab, details_tab, download_tab, map_tab, chatbot_tab = st.tabs([
|
589 |
"🗒️ Full Itinerary",
|
590 |
"💼 Details",
|
@@ -614,7 +548,6 @@ if st.session_state.generation_complete:
|
|
614 |
st.markdown("### 🍽️ Dining Recommendations")
|
615 |
st.markdown(st.session_state.results["dining_info"])
|
616 |
|
617 |
-
# Download & Share Tab (No QR code)
|
618 |
with download_tab:
|
619 |
col1, col2 = st.columns([2, 1])
|
620 |
with col1:
|
@@ -624,25 +557,20 @@ if st.session_state.generation_complete:
|
|
624 |
<div style="background-color: var(--background); padding: 15px; border-radius: 10px; margin-top: 20px;">
|
625 |
<h4>Your Itinerary File</h4>
|
626 |
<p style="font-size: 0.9rem; color: var(--text-muted);">Text format - Can be opened in any text editor</p>
|
|
|
627 |
""", unsafe_allow_html=True)
|
628 |
-
|
629 |
-
# Download link
|
630 |
st.markdown(
|
631 |
"<div style='margin: 10px 0;'>"
|
632 |
+ get_download_link(st.session_state.generated_itinerary, st.session_state.filename)
|
633 |
+ "</div>",
|
634 |
unsafe_allow_html=True
|
635 |
)
|
636 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
637 |
-
|
638 |
st.markdown("### Share Your Itinerary")
|
639 |
st.markdown("*Coming soon: Additional sharing features (email, phone, etc.)*")
|
640 |
-
|
641 |
with col2:
|
642 |
st.markdown("### Save for Mobile")
|
643 |
st.markdown("*Coming soon: Additional mobile features such as a dedicated app or push notifications*")
|
644 |
|
645 |
-
# Map & Visualization Tab
|
646 |
with map_tab:
|
647 |
st.markdown("### Destination Map")
|
648 |
dest = st.session_state.get("destination", "Paris")
|
@@ -664,7 +592,6 @@ if st.session_state.generation_complete:
|
|
664 |
"name": [dest]
|
665 |
})
|
666 |
st.map(map_data)
|
667 |
-
|
668 |
st.markdown("#### Interactive Map with Pydeck")
|
669 |
layer = pdk.Layer(
|
670 |
"ScatterplotLayer",
|
@@ -682,7 +609,6 @@ if st.session_state.generation_complete:
|
|
682 |
deck_chart = pdk.Deck(layers=[layer], initial_view_state=view_state)
|
683 |
st.pydeck_chart(deck_chart)
|
684 |
|
685 |
-
# Chatbot Interface
|
686 |
with chatbot_tab:
|
687 |
st.markdown("### AI Chatbot Interface")
|
688 |
if "chat_history" not in st.session_state:
|
@@ -707,7 +633,6 @@ if st.session_state.generation_complete:
|
|
707 |
st.markdown(f"**{chat['speaker']}** ({time_str}): {chat['message']}")
|
708 |
st.markdown("</div>", unsafe_allow_html=True)
|
709 |
|
710 |
-
# Footer
|
711 |
st.markdown("""
|
712 |
<div style="margin-top: 50px; text-align: center; padding: 20px; color: var(--text-muted); font-size: 0.8rem;">
|
713 |
<p>Built with ❤️ for you</p>
|
|
|
3 |
import base64
|
4 |
import pandas as pd
|
5 |
import pydeck as pdk
|
6 |
+
from geopy.geocoders import Nominatim
|
7 |
+
import qrcode
|
8 |
+
from io import BytesIO
|
9 |
+
|
10 |
+
# Import functions and tasks from travel.py
|
11 |
from travel import (
|
12 |
destination_research_task, accommodation_task, transportation_task,
|
13 |
activities_task, dining_task, itinerary_task, chatbot_task,
|
14 |
+
run_task, update_llm
|
15 |
)
|
|
|
|
|
16 |
|
17 |
st.set_page_config(
|
18 |
page_title="Your AI Agent for Travelling",
|
|
|
21 |
initial_sidebar_state="expanded"
|
22 |
)
|
23 |
|
24 |
+
with st.sidebar:
|
25 |
+
st.image("image.png", width=250)
|
26 |
+
st.markdown("""
|
27 |
+
<h3>Namude Yatra: The beginning of your dream journey</h3>
|
28 |
+
<p style="font-size: 14px; color: var(--text-muted);">AI-Powered Agentic Travel Planning</p>
|
29 |
+
""", unsafe_allow_html=True)
|
30 |
+
|
31 |
+
# API Key Input (update dynamically)
|
32 |
+
api_key = st.text_input("Enter your GEMINI API Key", type="password")
|
33 |
+
if api_key:
|
34 |
+
update_llm(api_key)
|
35 |
+
st.success("API Key updated!")
|
36 |
+
|
37 |
+
st.markdown("""
|
38 |
+
<div class="sidebar-section">
|
39 |
+
<h2>About</h2>
|
40 |
+
<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>
|
41 |
+
</div>
|
42 |
+
<div class="sidebar-section">
|
43 |
+
<h2>How It Works</h2>
|
44 |
+
<ul>
|
45 |
+
<li><b>Enter:</b> Your travel details</li>
|
46 |
+
<li><b>Analyze:</b> Preferences by AI</li>
|
47 |
+
<li><b>Generate:</b> Your comprehensive itinerary</li>
|
48 |
+
<li><b>Download:</b> Save and share your itinerary</li>
|
49 |
+
</ul>
|
50 |
+
</div>
|
51 |
+
<div class="sidebar-section">
|
52 |
+
<h2>Travel Agents</h2>
|
53 |
+
<div class="agent-item"><strong>🔭 Research Specialist</strong><br><small>Finds the best destinations based on your preferences.</small></div>
|
54 |
+
<div class="agent-item"><strong>🏨 Accommodation Expert</strong><br><small>Suggests suitable hotels and stays.</small></div>
|
55 |
+
<div class="agent-item"><strong>🚆 Transportation Planner</strong><br><small>Plans efficient travel routes.</small></div>
|
56 |
+
<div class="agent-item"><strong>🎯 Activities Curator</strong><br><small>Recommends activities tailored to your interests.</small></div>
|
57 |
+
<div class="agent-item"><strong>🍽️ Dining Connoisseur</strong><br><small>Finds the best dining experiences.</small></div>
|
58 |
+
<div class="agent-item"><strong>📅 Itinerary Creator</strong><br><small>Puts everything together in a daily plan.</small></div>
|
59 |
+
</div>
|
60 |
+
""", unsafe_allow_html=True)
|
61 |
+
|
62 |
st.markdown("""
|
63 |
<style>
|
64 |
:root {
|
|
|
73 |
--text-muted: #495057;
|
74 |
--border: #e9ecef;
|
75 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
</style>
|
77 |
""", unsafe_allow_html=True)
|
78 |
|
79 |
def get_download_link(text_content, filename):
|
|
|
80 |
b64 = base64.b64encode(text_content.encode()).decode()
|
81 |
+
return f'<a class="download-link" href="data:text/plain;base64,{b64}" download="{filename}"><i>📥</i> Save Your Itinerary</a>'
|
|
|
82 |
|
83 |
def display_modern_progress(current_step, total_steps=6):
|
|
|
84 |
if 'progress_steps' not in st.session_state:
|
85 |
st.session_state.progress_steps = {
|
86 |
0: {'status': 'pending', 'name': "Trip Details"},
|
|
|
102 |
progress_percentage = (current_step / total_steps) * 100
|
103 |
st.progress(progress_percentage / 100)
|
104 |
|
|
|
105 |
st.markdown("""
|
106 |
<style>
|
107 |
.compact-progress {
|
|
|
162 |
else:
|
163 |
icon = "⭕"
|
164 |
status_class = "pending"
|
|
|
165 |
st.markdown(f"""
|
166 |
<div class="step-item {status_class}">
|
167 |
<span class="step-icon">{icon}</span>
|
|
|
173 |
return progress_percentage
|
174 |
|
175 |
def update_step_status(step_index, status):
|
|
|
176 |
if 'progress_steps' in st.session_state and step_index in st.session_state.progress_steps:
|
177 |
st.session_state.progress_steps[step_index]['status'] = status
|
178 |
|
179 |
def run_task_with_logs(task, input_text, log_container, output_container, results_key=None):
|
|
|
180 |
if 'log_messages' not in st.session_state:
|
181 |
st.session_state.log_messages = []
|
|
|
182 |
log_message = f"🤖 Starting {task.agent.role}..."
|
183 |
st.session_state.log_messages.append(log_message)
|
184 |
|
|
|
191 |
|
192 |
if results_key:
|
193 |
st.session_state.results[results_key] = result
|
|
|
194 |
log_message = f"✅ {task.agent.role} completed!"
|
195 |
st.session_state.log_messages.append(log_message)
|
196 |
|
|
|
202 |
with output_container:
|
203 |
st.markdown(f"### {task.agent.role} Output")
|
204 |
st.markdown("<div class='agent-output'>" + result + "</div>", unsafe_allow_html=True)
|
|
|
205 |
return result
|
206 |
|
207 |
if 'generated_itinerary' not in st.session_state:
|
|
|
240 |
</div>
|
241 |
""", unsafe_allow_html=True)
|
242 |
|
|
|
243 |
st.markdown('<hr style="height:3px;border:none;background-color:#f0f0f0;margin-bottom:25px;">', unsafe_allow_html=True)
|
244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
if not st.session_state.generation_complete:
|
246 |
st.markdown('<div class="modern-card animate-in">', unsafe_allow_html=True)
|
247 |
+
st.markdown("<h3 style='font-weight: 600; color: var(--primary-dark); text-align: center;'>✈️ Create Your Itinerary</h3>", unsafe_allow_html=True)
|
|
|
248 |
st.markdown("""
|
249 |
+
<p style="text-align: center; color: var(--text-muted); font-size: 14px;">Complete the form below for a personalized travel plan.</p>
|
250 |
""", unsafe_allow_html=True)
|
|
|
251 |
with st.form("travel_form"):
|
252 |
col1, col2 = st.columns(2)
|
253 |
with col1:
|
|
|
258 |
start_date = st.date_input("Start Date", min_value=datetime.now(), label_visibility="collapsed")
|
259 |
duration = st.slider("Duration (days)", min_value=1, max_value=30, value=7)
|
260 |
end_date = start_date + timedelta(days=duration-1)
|
261 |
+
st.markdown(f'<p style="font-size: 13px; color: var(--text-muted); text-align: center;">{start_date.strftime("%b %d")} - {end_date.strftime("%b %d, %Y")}</p>', unsafe_allow_html=True)
|
262 |
with col2:
|
263 |
st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px;">Preferences</p>', unsafe_allow_html=True)
|
264 |
travelers = st.number_input("Travelers", min_value=1, max_value=15, value=2)
|
|
|
288 |
"preferences": preferences,
|
289 |
"special_requirements": special_requirements
|
290 |
}
|
291 |
+
st.session_state.user_input = user_input
|
|
|
292 |
input_context = f"""Travel Request Details:
|
293 |
Origin: {user_input['origin']}
|
294 |
Destination: {user_input['destination']}
|
|
|
301 |
Special Requirements: {user_input['special_requirements']}
|
302 |
"""
|
303 |
modified_input_context = "Please output the response in English.\n" + input_context
|
304 |
+
|
305 |
# Processing Animation
|
306 |
st.markdown("""
|
307 |
+
<div style="text-align: center; padding:20px 0;">
|
308 |
+
<div style="position:relative; width:50px; height:50px;">
|
309 |
+
<div style="position:absolute; width:100%; height:100%; border:2px solid #4361ee; border-radius:50%; animation:pulse 1.5s ease-out infinite;"></div>
|
310 |
+
<div 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>
|
311 |
</div>
|
312 |
</div>
|
313 |
<style>
|
|
|
318 |
}
|
319 |
</style>
|
320 |
""", unsafe_allow_html=True)
|
321 |
+
|
322 |
st.markdown('<div class="modern-card">', unsafe_allow_html=True)
|
323 |
progress_tab, logs_tab, details_tab = st.tabs(["📊 Progress", "🔄 Live Activity", "📋 Your Travel Request"])
|
324 |
with details_tab:
|
|
|
342 |
st.session_state.log_messages = []
|
343 |
st.markdown('</div>', unsafe_allow_html=True)
|
344 |
|
|
|
345 |
output_container = st.container()
|
346 |
with output_container:
|
347 |
st.markdown('<div class="modern-card">', unsafe_allow_html=True)
|
|
|
349 |
st.info("Our AI agents will show their work here as they create your itinerary")
|
350 |
st.markdown('</div>', unsafe_allow_html=True)
|
351 |
|
|
|
352 |
st.session_state.current_step = 0
|
353 |
|
354 |
# Step 1: Destination Research
|
|
|
356 |
with st.session_state.progress_placeholder.container():
|
357 |
display_modern_progress(st.session_state.current_step)
|
358 |
destination_info = run_task_with_logs(
|
359 |
+
destination_research_task,
|
360 |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
|
361 |
log_container,
|
362 |
output_container,
|
|
|
370 |
with st.session_state.progress_placeholder.container():
|
371 |
display_modern_progress(st.session_state.current_step)
|
372 |
accommodation_info = run_task_with_logs(
|
373 |
+
accommodation_task,
|
374 |
modified_input_context.format(destination=user_input['destination'], budget=user_input['budget'], preferences=user_input['preferences']),
|
375 |
log_container,
|
376 |
output_container,
|
|
|
384 |
with st.session_state.progress_placeholder.container():
|
385 |
display_modern_progress(st.session_state.current_step)
|
386 |
transportation_info = run_task_with_logs(
|
387 |
+
transportation_task,
|
388 |
modified_input_context.format(origin=user_input['origin'], destination=user_input['destination']),
|
389 |
log_container,
|
390 |
output_container,
|
|
|
398 |
with st.session_state.progress_placeholder.container():
|
399 |
display_modern_progress(st.session_state.current_step)
|
400 |
activities_info = run_task_with_logs(
|
401 |
+
activities_task,
|
402 |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
|
403 |
log_container,
|
404 |
output_container,
|
|
|
412 |
with st.session_state.progress_placeholder.container():
|
413 |
display_modern_progress(st.session_state.current_step)
|
414 |
dining_info = run_task_with_logs(
|
415 |
+
dining_task,
|
416 |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
|
417 |
log_container,
|
418 |
output_container,
|
|
|
443 |
{dining_info}
|
444 |
"""
|
445 |
itinerary = run_task_with_logs(
|
446 |
+
itinerary_task,
|
447 |
combined_info.format(duration=user_input['duration'], origin=user_input['origin'], destination=user_input['destination']),
|
448 |
log_container,
|
449 |
output_container,
|
|
|
454 |
with st.session_state.progress_placeholder.container():
|
455 |
display_modern_progress(st.session_state.current_step)
|
456 |
|
|
|
457 |
st.session_state.generated_itinerary = itinerary
|
458 |
st.session_state.generation_complete = True
|
459 |
date_str = datetime.now().strftime("%Y-%m-%d")
|
460 |
st.session_state.filename = f"{user_input['destination'].replace(' ', '_')}_{date_str}_itinerary.txt"
|
461 |
|
462 |
+
|
463 |
if st.session_state.generation_complete:
|
|
|
464 |
st.markdown("""
|
465 |
<div class="modern-card animate-in">
|
466 |
<div style="display: flex; justify-content: center; margin-bottom: 20px;">
|
|
|
474 |
<h2 style="text-align: center; color: var(--primary-dark);">Your Travel Itinerary is Ready! 🎉</h2>
|
475 |
<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>
|
476 |
</div>
|
|
|
477 |
<style>
|
478 |
.success-animation {
|
479 |
width: 100px;
|
|
|
519 |
</style>
|
520 |
""", unsafe_allow_html=True)
|
521 |
|
|
|
522 |
itinerary_tab, details_tab, download_tab, map_tab, chatbot_tab = st.tabs([
|
523 |
"🗒️ Full Itinerary",
|
524 |
"💼 Details",
|
|
|
548 |
st.markdown("### 🍽️ Dining Recommendations")
|
549 |
st.markdown(st.session_state.results["dining_info"])
|
550 |
|
|
|
551 |
with download_tab:
|
552 |
col1, col2 = st.columns([2, 1])
|
553 |
with col1:
|
|
|
557 |
<div style="background-color: var(--background); padding: 15px; border-radius: 10px; margin-top: 20px;">
|
558 |
<h4>Your Itinerary File</h4>
|
559 |
<p style="font-size: 0.9rem; color: var(--text-muted);">Text format - Can be opened in any text editor</p>
|
560 |
+
</div>
|
561 |
""", unsafe_allow_html=True)
|
|
|
|
|
562 |
st.markdown(
|
563 |
"<div style='margin: 10px 0;'>"
|
564 |
+ get_download_link(st.session_state.generated_itinerary, st.session_state.filename)
|
565 |
+ "</div>",
|
566 |
unsafe_allow_html=True
|
567 |
)
|
|
|
|
|
568 |
st.markdown("### Share Your Itinerary")
|
569 |
st.markdown("*Coming soon: Additional sharing features (email, phone, etc.)*")
|
|
|
570 |
with col2:
|
571 |
st.markdown("### Save for Mobile")
|
572 |
st.markdown("*Coming soon: Additional mobile features such as a dedicated app or push notifications*")
|
573 |
|
|
|
574 |
with map_tab:
|
575 |
st.markdown("### Destination Map")
|
576 |
dest = st.session_state.get("destination", "Paris")
|
|
|
592 |
"name": [dest]
|
593 |
})
|
594 |
st.map(map_data)
|
|
|
595 |
st.markdown("#### Interactive Map with Pydeck")
|
596 |
layer = pdk.Layer(
|
597 |
"ScatterplotLayer",
|
|
|
609 |
deck_chart = pdk.Deck(layers=[layer], initial_view_state=view_state)
|
610 |
st.pydeck_chart(deck_chart)
|
611 |
|
|
|
612 |
with chatbot_tab:
|
613 |
st.markdown("### AI Chatbot Interface")
|
614 |
if "chat_history" not in st.session_state:
|
|
|
633 |
st.markdown(f"**{chat['speaker']}** ({time_str}): {chat['message']}")
|
634 |
st.markdown("</div>", unsafe_allow_html=True)
|
635 |
|
|
|
636 |
st.markdown("""
|
637 |
<div style="margin-top: 50px; text-align: center; padding: 20px; color: var(--text-muted); font-size: 0.8rem;">
|
638 |
<p>Built with ❤️ for you</p>
|