Abdulla Fahem commited on
Commit
96f51f8
Β·
1 Parent(s): e6f1a3c

Add application file

Browse files
Files changed (1) hide show
  1. app.py +588 -0
app.py ADDED
@@ -0,0 +1,588 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ['KMP_DUPLICATE_LIB_OK']='TRUE'
3
+
4
+ import streamlit as st
5
+ import pandas as pd
6
+ import torch
7
+ import random
8
+ from transformers import (
9
+ T5ForConditionalGeneration,
10
+ T5Tokenizer,
11
+ Trainer,
12
+ TrainingArguments,
13
+ DataCollatorForSeq2Seq
14
+ )
15
+ from torch.utils.data import Dataset
16
+ from datetime import datetime
17
+ import numpy as np
18
+ from random import choice
19
+
20
+ class TravelDataset(Dataset):
21
+ def __init__(self, data, tokenizer, max_length=512):
22
+ """
23
+ data: DataFrame with columns ['destination', 'days', 'budget', 'interests', 'travel_plan']
24
+ """
25
+ self.tokenizer = tokenizer
26
+ self.data = data
27
+ self.max_length = max_length
28
+
29
+ def __len__(self):
30
+ return len(self.data)
31
+
32
+ def __getitem__(self, idx):
33
+ row = self.data.iloc[idx]
34
+ input_text = self.format_input_text(row)
35
+ target_text = row['travel_plan']
36
+
37
+ # Tokenize inputs
38
+ input_encodings = self.tokenizer(
39
+ input_text,
40
+ max_length=self.max_length,
41
+ padding='max_length',
42
+ truncation=True,
43
+ return_tensors='pt'
44
+ )
45
+
46
+ # Tokenize targets
47
+ target_encodings = self.tokenizer(
48
+ target_text,
49
+ max_length=self.max_length,
50
+ padding='max_length',
51
+ truncation=True,
52
+ return_tensors='pt'
53
+ )
54
+
55
+ return {
56
+ 'input_ids': input_encodings['input_ids'].squeeze(),
57
+ 'attention_mask': input_encodings['attention_mask'].squeeze(),
58
+ 'labels': target_encodings['input_ids'].squeeze()
59
+ }
60
+
61
+ @staticmethod
62
+ def format_input_text(row):
63
+ return f"Plan a trip to {row['destination']} for {row['days']} days with a {row['budget']} budget. Include activities related to: {row['interests']}"
64
+
65
+ def create_sample_data():
66
+ """Create sample training data for travel plans ranging from 1 to 14 days"""
67
+ destinations = ['Paris', 'Tokyo', 'New York', 'London', 'Rome']
68
+ budgets = ['Budget', 'Moderate', 'Luxury']
69
+ interests_list = [
70
+ 'Culture, History',
71
+ 'Food, Shopping',
72
+ 'Art, Museums',
73
+ 'Nature, Adventure',
74
+ 'Relaxation, Food'
75
+ ]
76
+
77
+ # Activity templates for different interests
78
+ activities = {
79
+ 'Culture': ['Visit historical sites', 'Explore local traditions', 'Attend cultural events',
80
+ 'Visit ancient monuments', 'Experience local festivals'],
81
+ 'History': ['Tour ancient ruins', 'Visit museums', 'Explore historic districts',
82
+ 'Join guided history walks', 'Visit heritage sites'],
83
+ 'Food': ['Try local cuisine', 'Join cooking classes', 'Visit food markets',
84
+ 'Dine at famous restaurants', 'Food tasting tours'],
85
+ 'Shopping': ['Browse local markets', 'Visit shopping districts', 'Shop at boutiques',
86
+ 'Explore artisan shops', 'Visit shopping centers'],
87
+ 'Art': ['Visit art galleries', 'Attend art exhibitions', 'Join art workshops',
88
+ 'Visit artist studios', 'Explore street art'],
89
+ 'Museums': ['Tour famous museums', 'Visit specialty museums', 'Join museum tours',
90
+ 'Explore art collections', 'Visit cultural institutes'],
91
+ 'Nature': ['Visit parks', 'Nature walks', 'Explore gardens', 'Visit natural landmarks',
92
+ 'Outdoor activities'],
93
+ 'Adventure': ['Join adventure tours', 'Try outdoor sports', 'Explore hidden spots',
94
+ 'Take scenic hikes', 'Adventure activities'],
95
+ 'Relaxation': ['Spa treatments', 'Visit peaceful gardens', 'Leisure activities',
96
+ 'Relaxing sightseeing', 'Peaceful excursions']
97
+ }
98
+
99
+ def generate_daily_plan(day, total_days, interests, budget_level, destination):
100
+ """Generate a single day's plan based on interests and duration"""
101
+ interest1, interest2 = [i.strip() for i in interests.split(',')]
102
+
103
+ # Select activities based on interests
104
+ activity1 = choice(activities[interest1])
105
+ activity2 = choice(activities[interest2])
106
+
107
+ if total_days <= 3:
108
+ # For short trips, pack more activities per day
109
+ return f"Day {day}: {activity1} in the morning. {activity2} in the afternoon/evening. Experience {destination}'s {budget_level.lower()} offerings."
110
+ elif total_days <= 7:
111
+ # Medium trips have a moderate pace
112
+ return f"Day {day}: Focus on {activity1}. Later, enjoy {activity2}."
113
+ else:
114
+ # Longer trips have a more relaxed pace
115
+ return f"Day {day}: {'Start with' if day == 1 else 'Continue with'} {activity1}. Optional: {activity2}."
116
+
117
+ data = []
118
+ for dest in destinations:
119
+ for days in range(1, 15): # 1 to 14 days
120
+ for budget in budgets:
121
+ for interests in interests_list:
122
+ # Generate multi-day plan
123
+ daily_plans = []
124
+ for day in range(1, days + 1):
125
+ daily_plan = generate_daily_plan(day, days, interests, budget, dest)
126
+ daily_plans.append(daily_plan)
127
+
128
+ # Combine all days into one plan
129
+ full_plan = "\n".join(daily_plans)
130
+
131
+ data.append({
132
+ 'destination': dest,
133
+ 'days': days,
134
+ 'budget': budget,
135
+ 'interests': interests,
136
+ 'travel_plan': full_plan
137
+ })
138
+
139
+ return pd.DataFrame(data)
140
+
141
+ def train_model():
142
+ """Train the T5 model on travel planning data"""
143
+ try:
144
+ # Initialize model and tokenizer
145
+ tokenizer = T5Tokenizer.from_pretrained('t5-base')
146
+ model = T5ForConditionalGeneration.from_pretrained('t5-base')
147
+
148
+ # Create or load training data
149
+ if os.path.exists('travel_data.csv'):
150
+ data = pd.read_csv('travel_data.csv')
151
+ else:
152
+ data = create_sample_data()
153
+ data.to_csv('travel_data.csv', index=False)
154
+
155
+ # Split data into train and validation
156
+ train_size = int(0.8 * len(data))
157
+ train_data = data[:train_size]
158
+ val_data = data[train_size:]
159
+
160
+ # Create datasets
161
+ train_dataset = TravelDataset(train_data, tokenizer)
162
+ val_dataset = TravelDataset(val_data, tokenizer)
163
+
164
+ # Training arguments
165
+ training_args = TrainingArguments(
166
+ output_dir=f"./travel_planner_model_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
167
+ num_train_epochs=3,
168
+ per_device_train_batch_size=4,
169
+ per_device_eval_batch_size=4,
170
+ warmup_steps=500,
171
+ weight_decay=0.01,
172
+ logging_dir="./logs",
173
+ logging_steps=10,
174
+ evaluation_strategy="steps",
175
+ eval_steps=50,
176
+ save_steps=100,
177
+ load_best_model_at_end=True,
178
+ )
179
+
180
+ # Data collator
181
+ data_collator = DataCollatorForSeq2Seq(
182
+ tokenizer=tokenizer,
183
+ model=model,
184
+ padding=True
185
+ )
186
+
187
+ # Initialize trainer
188
+ trainer = Trainer(
189
+ model=model,
190
+ args=training_args,
191
+ train_dataset=train_dataset,
192
+ eval_dataset=val_dataset,
193
+ data_collator=data_collator,
194
+ )
195
+
196
+ # Train the model
197
+ trainer.train()
198
+
199
+ # Save the model and tokenizer
200
+ model_path = "./trained_travel_planner"
201
+ model.save_pretrained(model_path)
202
+ tokenizer.save_pretrained(model_path)
203
+
204
+ return model, tokenizer
205
+
206
+ except Exception as e:
207
+ st.error(f"Error during model training: {str(e)}")
208
+ return None, None
209
+
210
+ @st.cache_resource
211
+ def load_or_train_model():
212
+ """Load trained model or train new one"""
213
+ model_path = "./trained_travel_planner"
214
+
215
+ if os.path.exists(model_path):
216
+ try:
217
+ model = T5ForConditionalGeneration.from_pretrained(model_path)
218
+ tokenizer = T5Tokenizer.from_pretrained(model_path)
219
+ if torch.cuda.is_available():
220
+ model = model.cuda()
221
+ return model, tokenizer
222
+ except Exception as e:
223
+ st.error(f"Error loading trained model: {str(e)}")
224
+
225
+ # If no trained model exists or loading fails, train new model
226
+ return train_model()
227
+
228
+ def generate_travel_plan(destination, days, interests, budget, model, tokenizer):
229
+ """Generate a travel plan using the trained model with enhanced features"""
230
+ try:
231
+ # Format interests into a string, limit to top 3 if more are provided
232
+ interests = interests[:3] # Limit to top 3 interests for better results
233
+ interests_str = ', '.join(interests)
234
+
235
+ # Format input prompt to match training data format
236
+ prompt = f"Plan a trip to {destination} for {days} days with a {budget} budget. Include activities related to: {interests_str}"
237
+
238
+ # Tokenize input with padding
239
+ inputs = tokenizer(
240
+ prompt,
241
+ return_tensors="pt",
242
+ max_length=512,
243
+ padding="max_length",
244
+ truncation=True
245
+ )
246
+
247
+ # Move inputs to GPU if available
248
+ if torch.cuda.is_available():
249
+ inputs = {k: v.cuda() for k, v in inputs.items()}
250
+ model = model.cuda()
251
+
252
+ # Generate output with carefully tuned parameters
253
+ outputs = model.generate(
254
+ **inputs,
255
+ max_length=512,
256
+ min_length=100, # Ensure reasonable length output
257
+ num_beams=4, # Beam search for better quality
258
+ no_repeat_ngram_size=3, # Avoid repetition
259
+ length_penalty=1.2, # Favor longer sequences
260
+ early_stopping=True,
261
+ temperature=0.8, # Slightly random but still focused
262
+ top_k=50,
263
+ top_p=0.9,
264
+ do_sample=True,
265
+ repetition_penalty=1.2, # Additional repetition avoidance
266
+ num_return_sequences=1
267
+ )
268
+
269
+ # Decode output
270
+ travel_plan = tokenizer.decode(outputs[0], skip_special_tokens=True)
271
+
272
+ # Handle empty output
273
+ if not travel_plan.strip():
274
+ raise ValueError("Generated plan is empty")
275
+
276
+ # Ensure the plan has the correct number of days
277
+ plan_lines = travel_plan.split('\n')
278
+ formatted_lines = []
279
+ activity_templates = {
280
+ 'Budget': [
281
+ "Explore local free attractions",
282
+ "Visit public parks and gardens",
283
+ "Take self-guided walking tours",
284
+ "Visit markets and street food venues",
285
+ "Use public transportation"
286
+ ],
287
+ 'Moderate': [
288
+ "Join group tours and activities",
289
+ "Visit popular attractions",
290
+ "Try local restaurants",
291
+ "Use mix of public and private transport",
292
+ "Book mid-range accommodations"
293
+ ],
294
+ 'Luxury': [
295
+ "Book private guided tours",
296
+ "Experience fine dining",
297
+ "Visit exclusive attractions",
298
+ "Use private transportation",
299
+ "Stay at luxury accommodations"
300
+ ]
301
+ }
302
+
303
+ # Ensure we have enough content for each day
304
+ for day in range(1, days + 1):
305
+ day_content = next(
306
+ (line for line in plan_lines if f"Day {day}:" in line),
307
+ None
308
+ )
309
+
310
+ if day_content:
311
+ formatted_lines.append(day_content)
312
+ else:
313
+ # Generate fallback content based on budget and interests
314
+ budget_activities = activity_templates[budget]
315
+ interests_activities = [
316
+ f"Explore {destination}'s {interest.lower()} attractions"
317
+ for interest in interests
318
+ ]
319
+ activities = budget_activities + interests_activities
320
+
321
+ fallback_content = (
322
+ f"Day {day}: {random.choice(activities)}. "
323
+ f"Also {random.choice(activities).lower()}."
324
+ )
325
+ formatted_lines.append(fallback_content)
326
+
327
+ # Join the lines back together
328
+ final_plan = '\n'.join(formatted_lines)
329
+
330
+ # Add a trip overview at the beginning
331
+ overview = (
332
+ f"Trip Overview:\n"
333
+ f"Destination: {destination}\n"
334
+ f"Duration: {days} days\n"
335
+ f"Budget Level: {budget}\n"
336
+ f"Interests: {interests_str}\n\n"
337
+ )
338
+
339
+ final_plan = overview + final_plan
340
+
341
+ # Log successful generation
342
+ print(f"Successfully generated plan for {destination} ({days} days)")
343
+
344
+ return final_plan
345
+
346
+ except Exception as e:
347
+ error_msg = f"Error generating travel plan: {str(e)}"
348
+ print(error_msg) # Log the error
349
+
350
+ # Generate a basic fallback plan
351
+ fallback_plan = generate_fallback_plan(destination, days, interests, budget)
352
+ return fallback_plan
353
+
354
+ def generate_fallback_plan(destination, days, interests, budget):
355
+ """Generate a basic fallback plan if the model fails"""
356
+ fallback_plan = f"# Emergency Travel Plan for {destination}\n\n"
357
+
358
+ # Basic activity templates
359
+ basic_activities = {
360
+ 'Culture': ['Visit museums', 'Explore historical sites', 'Attend local events'],
361
+ 'History': ['Tour historic landmarks', 'Visit ancient sites', 'Join history walks'],
362
+ 'Food': ['Try local cuisine', 'Visit food markets', 'Take cooking classes'],
363
+ 'Nature': ['Visit parks', 'Go hiking', 'Explore gardens'],
364
+ 'Shopping': ['Visit markets', 'Shop at local stores', 'Explore shopping districts'],
365
+ 'Adventure': ['Join tours', 'Try outdoor activities', 'Explore surroundings'],
366
+ 'Relaxation': ['Visit spa', 'Relax in parks', 'Enjoy scenic views'],
367
+ 'Art': ['Visit galleries', 'See street art', 'Attend exhibitions'],
368
+ 'Museums': ['Visit main museums', 'Join guided tours', 'See special exhibits']
369
+ }
370
+
371
+ for day in range(1, days + 1):
372
+ fallback_plan += f"\n## Day {day}\n"
373
+ # Select activities based on interests
374
+ day_activities = []
375
+ for interest in interests[:2]: # Use up to 2 interests per day
376
+ if interest in basic_activities:
377
+ activity = random.choice(basic_activities[interest])
378
+ day_activities.append(activity)
379
+
380
+ # Add budget-appropriate text
381
+ budget_text = {
382
+ 'Budget': 'Focus on free and affordable activities.',
383
+ 'Moderate': 'Mix of affordable and premium experiences.',
384
+ 'Luxury': 'Premium experiences and exclusive access.'
385
+ }.get(budget, '')
386
+
387
+ fallback_plan += f"Morning: {day_activities[0] if day_activities else 'Explore the area'}\n"
388
+ if len(day_activities) > 1:
389
+ fallback_plan += f"Afternoon/Evening: {day_activities[1]}\n"
390
+ fallback_plan += f"Note: {budget_text}\n"
391
+
392
+ return fallback_plan
393
+
394
+ def format_travel_plan(plan, days):
395
+ """Format the generated travel plan into a readable structure"""
396
+ formatted_plan = "# Your Travel Itinerary\n\n"
397
+
398
+ # Split the plan into days (split by newlines)
399
+ day_plans = plan.split('\n')
400
+
401
+ # Filter out empty lines and ensure we don't exceed the requested number of days
402
+ day_plans = [plan.strip() for plan in day_plans if plan.strip()][:days]
403
+
404
+ # Format each day
405
+ for day_plan in day_plans:
406
+ if day_plan.startswith("Day"):
407
+ # Extract day number
408
+ day_num = day_plan.split(':')[0].replace('Day ', '')
409
+ # Extract activities
410
+ activities = day_plan.split(':', 1)[1].strip()
411
+
412
+ formatted_plan += f"\n## Day {day_num}\n"
413
+ formatted_plan += f"{activities}\n"
414
+
415
+ return formatted_plan
416
+
417
+ def main():
418
+ st.set_page_config(
419
+ page_title="AI Travel Planner",
420
+ page_icon="✈️",
421
+ layout="wide"
422
+ )
423
+
424
+ st.title("✈️ AI Travel Planner")
425
+ st.markdown("### Plan your perfect trip with AI assistance!")
426
+
427
+ # Add training section in sidebar
428
+ with st.sidebar:
429
+ st.header("Model Management")
430
+ if st.button("Train New Model"):
431
+ with st.spinner("Training new model... This will take a while..."):
432
+ model, tokenizer = train_model()
433
+ if model is not None:
434
+ st.session_state.model = model
435
+ st.session_state.tokenizer = tokenizer
436
+ st.success("Model training completed!")
437
+
438
+ # Add model information
439
+ st.markdown("### Model Information")
440
+ if 'model' in st.session_state:
441
+ st.success("βœ“ Model loaded")
442
+ st.info("""
443
+ This model was trained on travel plans for:
444
+ - 5 destinations
445
+ - 1-14 days duration
446
+ - 3 budget levels
447
+ - 5 interest combinations
448
+ """)
449
+
450
+ # Load or train model
451
+ if 'model' not in st.session_state:
452
+ with st.spinner("Loading AI model... Please wait..."):
453
+ model, tokenizer = load_or_train_model()
454
+ if model is None or tokenizer is None:
455
+ st.error("Failed to load/train the AI model. Please try again.")
456
+ return
457
+ st.session_state.model = model
458
+ st.session_state.tokenizer = tokenizer
459
+
460
+ # Create two columns for input form
461
+ col1, col2 = st.columns([2, 1])
462
+
463
+ with col1:
464
+ # Input form in a card-like container
465
+ with st.container():
466
+ st.markdown("### 🎯 Plan Your Trip")
467
+
468
+ # Destination and Duration row
469
+ dest_col, days_col = st.columns(2)
470
+ with dest_col:
471
+ destination = st.text_input(
472
+ "🌍 Destination",
473
+ placeholder="e.g., Paris, Tokyo, New York...",
474
+ help="Enter the city you want to visit"
475
+ )
476
+
477
+ with days_col:
478
+ days = st.slider(
479
+ "πŸ“… Number of days",
480
+ min_value=1,
481
+ max_value=14,
482
+ value=3,
483
+ help="Select the duration of your trip"
484
+ )
485
+
486
+ # Budget and Interests row
487
+ budget_col, interests_col = st.columns(2)
488
+ with budget_col:
489
+ budget = st.selectbox(
490
+ "πŸ’° Budget Level",
491
+ ["Budget", "Moderate", "Luxury"],
492
+ help="Select your preferred budget level"
493
+ )
494
+
495
+ with interests_col:
496
+ interests = st.multiselect(
497
+ "🎯 Interests",
498
+ ["Culture", "History", "Food", "Nature", "Shopping",
499
+ "Adventure", "Relaxation", "Art", "Museums"],
500
+ ["Culture", "Food"],
501
+ help="Select up to three interests to personalize your plan"
502
+ )
503
+
504
+ with col2:
505
+ # Tips and information
506
+ st.markdown("### πŸ’‘ Travel Tips")
507
+ st.info("""
508
+ - Choose up to 3 interests for best results
509
+ - Consider your travel season
510
+ - Budget levels affect activity suggestions
511
+ - Plans are customizable after generation
512
+ """)
513
+
514
+ # Generate button centered
515
+ col1, col2, col3 = st.columns([1, 2, 1])
516
+ with col2:
517
+ generate_button = st.button(
518
+ "🎨 Generate Travel Plan",
519
+ type="primary",
520
+ use_container_width=True
521
+ )
522
+
523
+ if generate_button:
524
+ if not destination:
525
+ st.error("Please enter a destination!")
526
+ return
527
+
528
+ if not interests:
529
+ st.error("Please select at least one interest!")
530
+ return
531
+
532
+ if len(interests) > 3:
533
+ st.warning("For best results, please select up to 3 interests.")
534
+
535
+ with st.spinner("πŸ€– Creating your personalized travel plan..."):
536
+ travel_plan = generate_travel_plan(
537
+ destination,
538
+ days,
539
+ interests,
540
+ budget,
541
+ st.session_state.model,
542
+ st.session_state.tokenizer
543
+ )
544
+
545
+ st.success("✨ Your travel plan is ready!")
546
+
547
+ # Display the plan in tabs
548
+ plan_tab, summary_tab = st.tabs(["πŸ“‹ Travel Plan", "ℹ️ Trip Summary"])
549
+
550
+ with plan_tab:
551
+ st.markdown(travel_plan)
552
+
553
+ # Add export options
554
+ st.download_button(
555
+ label="πŸ“₯ Download Plan",
556
+ data=travel_plan,
557
+ file_name=f"travel_plan_{destination.lower().replace(' ', '_')}.md",
558
+ mime="text/markdown"
559
+ )
560
+
561
+ with summary_tab:
562
+ # Create three columns for summary information
563
+ sum_col1, sum_col2, sum_col3 = st.columns(3)
564
+
565
+ with sum_col1:
566
+ st.markdown("### πŸ“ Destination")
567
+ st.markdown(f"**{destination}**")
568
+ st.markdown("### ⏱️ Duration")
569
+ st.markdown(f"**{days} days**")
570
+
571
+ with sum_col2:
572
+ st.markdown("### πŸ’° Budget")
573
+ st.markdown(f"**{budget}**")
574
+ st.markdown("### 🎯 Interests")
575
+ for interest in interests:
576
+ st.markdown(f"- {interest}")
577
+
578
+ with sum_col3:
579
+ st.markdown("### ⚠️ Important Notes")
580
+ st.info(
581
+ "- Verify opening hours\n"
582
+ "- Check current prices\n"
583
+ "- Confirm availability\n"
584
+ "- Consider seasonal factors"
585
+ )
586
+
587
+ if __name__ == "__main__":
588
+ main()