Abs6187 commited on
Commit
7431576
·
verified ·
1 Parent(s): 827fc02

Upload 5 files

Browse files
Files changed (5) hide show
  1. .gitattributes +35 -35
  2. app-backup.py +1234 -0
  3. app.py +1331 -0
  4. requirements.txt +5 -0
  5. travel.py +480 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
app-backup.py ADDED
@@ -0,0 +1,1234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import json
4
+ from datetime import datetime, timedelta
5
+ import base64
6
+ import pandas as pd
7
+ import pydeck as pdk
8
+ from travel import (
9
+ destination_research_task, accommodation_task, transportation_task,
10
+ activities_task, dining_task, itinerary_task, chatbot_task,
11
+ run_task
12
+ )
13
+
14
+ # st.set_page_config()는 다른 Streamlit 함수보다 가장 먼저 실행되어야 합니다.
15
+ st.set_page_config(
16
+ page_title="Your AI Agent for Travelling",
17
+ page_icon="✈️",
18
+ layout="wide",
19
+ initial_sidebar_state="expanded"
20
+ )
21
+
22
+ # ------------------------------------------
23
+ # 다국어 지원을 위한 번역 사전 및 헬퍼 함수
24
+ # ------------------------------------------
25
+ translations = {
26
+ "en": {
27
+ "page_title": "Your AI Agent for Travelling",
28
+ "header": "Your AI Agent for Travelling",
29
+ "create_itinerary": "Create Your Itinerary",
30
+ "trip_details": "Trip Details",
31
+ "origin": "Origin",
32
+ "destination": "Destination",
33
+ "travel_dates": "Travel Dates",
34
+ "duration": "Duration (days)",
35
+ "preferences": "Preferences",
36
+ "additional_preferences": "Additional Preferences",
37
+ "interests": "Interests",
38
+ "special_requirements": "Special Requirements",
39
+ "submit": "🚀 Create My Personal Travel Itinerary",
40
+ "request_details": "Your Travel Request",
41
+ "from": "From",
42
+ "when": "When",
43
+ "budget": "Budget",
44
+ "travel_style": "Travel Style",
45
+ "live_agent_outputs": "Live Agent Outputs",
46
+ "full_itinerary": "Full Itinerary",
47
+ "details": "Details",
48
+ "download_share": "Download & Share",
49
+ "save_itinerary": "Save Your Itinerary",
50
+ "plan_another_trip": "🔄 Plan Another Trip",
51
+ "about": "About",
52
+ "how_it_works": "How it works",
53
+ "travel_agents": "Travel Agents",
54
+ "share_itinerary": "Share Your Itinerary",
55
+ "save_for_mobile": "Save for Mobile",
56
+ "built_with": "Built with ❤️ for you",
57
+ # 출력 관련 추가 텍스트
58
+ "itinerary_ready": "Your Travel Itinerary is Ready! 🎉",
59
+ "personalized_experience": "We've created a personalized travel experience just for you. Explore your itinerary below.",
60
+ "agent_activity": "Agent Activity",
61
+ "error_origin_destination": "Please enter both origin and destination.",
62
+ "your_itinerary_file": "Your Itinerary File",
63
+ "text_format": "Text format - Can be opened in any text editor"
64
+ },
65
+ "ko": {
66
+ "page_title": "당신의 여행을 위한 AI 에이전트",
67
+ "header": "당신의 여행을 위한 AI 에이전트",
68
+ "create_itinerary": "여행 일정 생성",
69
+ "trip_details": "여행 세부 정보",
70
+ "origin": "출발지",
71
+ "destination": "목적지",
72
+ "travel_dates": "여행 날짜",
73
+ "duration": "기간 (일수)",
74
+ "preferences": "선호사항",
75
+ "additional_preferences": "추가 선호사항",
76
+ "interests": "관심사",
77
+ "special_requirements": "특별 요구사항",
78
+ "submit": "🚀 나만의 여행 일정 생성",
79
+ "request_details": "여행 요청 정보",
80
+ "from": "출발지",
81
+ "when": "여행 기간",
82
+ "budget": "예산",
83
+ "travel_style": "여행 스타일",
84
+ "live_agent_outputs": "실시간 에이전트 결과",
85
+ "full_itinerary": "전체 일정",
86
+ "details": "세부사항",
87
+ "download_share": "다운로드 및 공유",
88
+ "save_itinerary": "일정 저장",
89
+ "plan_another_trip": "🔄 다른 여행 계획",
90
+ "about": "소개",
91
+ "how_it_works": "작동 방식",
92
+ "travel_agents": "여행 에이전트",
93
+ "share_itinerary": "일정 공유",
94
+ "save_for_mobile": "모바일 저장",
95
+ "built_with": "당신을 위해 ❤️ 만들어졌습니다",
96
+ # 출력 관련 추가 텍스트
97
+ "itinerary_ready": "여행 일정이 준비되었습니다! 🎉",
98
+ "personalized_experience": "당신만을 위한 맞춤형 여행 경험이 만들어졌습니다. 아래에서 일정을 확인하세요.",
99
+ "agent_activity": "에이전트 활동",
100
+ "error_origin_destination": "출발지와 목적지를 모두 입력하세요.",
101
+ "your_itinerary_file": "당신의 여행 일정 파일",
102
+ "text_format": "텍스트 형식 - 모든 텍스트 편집기에서 열 수 있습니다."
103
+ },
104
+ "ja": {
105
+ "page_title": "あなたの旅行のためのAIエージェント",
106
+ "header": "あなたの旅行のためのAIエージェント",
107
+ "create_itinerary": "旅行プラン作成",
108
+ "trip_details": "旅行詳細",
109
+ "origin": "出発地",
110
+ "destination": "目���地",
111
+ "travel_dates": "旅行日程",
112
+ "duration": "期間(日数)",
113
+ "preferences": "好み",
114
+ "additional_preferences": "追加の好み",
115
+ "interests": "興味",
116
+ "special_requirements": "特別な要件",
117
+ "submit": "🚀 私のための旅行プラン作成",
118
+ "request_details": "旅行リクエスト",
119
+ "from": "出発地",
120
+ "when": "旅行期間",
121
+ "budget": "予算",
122
+ "travel_style": "旅行スタイル",
123
+ "live_agent_outputs": "リアルタイムエージェント出力",
124
+ "full_itinerary": "全行程",
125
+ "details": "詳細",
126
+ "download_share": "ダウンロードと共有",
127
+ "save_itinerary": "旅行プランを保存",
128
+ "plan_another_trip": "🔄 他の旅行を計画",
129
+ "about": "概要",
130
+ "how_it_works": "使い方",
131
+ "travel_agents": "旅行エージェント",
132
+ "share_itinerary": "旅行プランを共有",
133
+ "save_for_mobile": "モバイル保存",
134
+ "built_with": "愛を込めて作られました",
135
+ # 출력 관련 추가 텍스트
136
+ "itinerary_ready": "旅行プランの準備ができました! 🎉",
137
+ "personalized_experience": "あなたのためにパーソナライズされた旅行体験を作成しました。下のプランをご覧ください。",
138
+ "agent_activity": "エージェントアクティビティ",
139
+ "error_origin_destination": "出発地と目的地の両方を入力してください。",
140
+ "your_itinerary_file": "あなたの旅行プランファイル",
141
+ "text_format": "テキスト形式 - 任意のテキストエディタで開けます。"
142
+ },
143
+ "zh": {
144
+ "page_title": "您的旅行 AI 代理",
145
+ "header": "您的旅行 AI 代理",
146
+ "create_itinerary": "创建您的行程",
147
+ "trip_details": "旅行详情",
148
+ "origin": "出发地",
149
+ "destination": "目的地",
150
+ "travel_dates": "旅行日期",
151
+ "duration": "天数",
152
+ "preferences": "偏好",
153
+ "additional_preferences": "其他偏好",
154
+ "interests": "兴趣",
155
+ "special_requirements": "特殊需求",
156
+ "submit": "🚀 创建我的个性化行程",
157
+ "request_details": "您的旅行请求",
158
+ "from": "出发地",
159
+ "when": "旅行时间",
160
+ "budget": "预算",
161
+ "travel_style": "旅行风格",
162
+ "live_agent_outputs": "实时代理输出",
163
+ "full_itinerary": "完整行程",
164
+ "details": "详情",
165
+ "download_share": "下载与分享",
166
+ "save_itinerary": "保存行程",
167
+ "plan_another_trip": "🔄 计划另一趟旅行",
168
+ "about": "关于",
169
+ "how_it_works": "工作原理",
170
+ "travel_agents": "旅行代理",
171
+ "share_itinerary": "分享行程",
172
+ "save_for_mobile": "保存到手机",
173
+ "built_with": "用❤️为您制作",
174
+ # 출력 관련 추가 텍스트
175
+ "itinerary_ready": "您的旅行行程已准备就绪! 🎉",
176
+ "personalized_experience": "我们已为您创建了个性化的旅行体验,请在下方查看您的行程。",
177
+ "agent_activity": "代理活动",
178
+ "error_origin_destination": "请输入出发地和目的地。",
179
+ "your_itinerary_file": "您的行程文件",
180
+ "text_format": "文本格式 - 可在任何文本编辑器中打开。"
181
+ },
182
+ "es": {
183
+ "page_title": " Tu Agente de IA para Viajar",
184
+ "header": " Tu Agente de IA para Viajar",
185
+ "create_itinerary": "Crea Tu Itinerario",
186
+ "trip_details": "Detalles del Viaje",
187
+ "origin": "Origen",
188
+ "destination": "Destino",
189
+ "travel_dates": "Fechas del Viaje",
190
+ "duration": "Duración (días)",
191
+ "preferences": "Preferencias",
192
+ "additional_preferences": "Preferencias Adicionales",
193
+ "interests": "Intereses",
194
+ "special_requirements": "Requisitos Especiales",
195
+ "submit": "🚀 Crea Mi Itinerario Personalizado",
196
+ "request_details": "Tu Solicitud de Viaje",
197
+ "from": "Desde",
198
+ "when": "Cuándo",
199
+ "budget": "Presupuesto",
200
+ "travel_style": "Estilo de Viaje",
201
+ "live_agent_outputs": "Salidas en Vivo del Agente",
202
+ "full_itinerary": "Itinerario Completo",
203
+ "details": "Detalles",
204
+ "download_share": "Descargar y Compartir",
205
+ "save_itinerary": "Guardar Itinerario",
206
+ "plan_another_trip": "🔄 Planear Otro Viaje",
207
+ "about": "Acerca de",
208
+ "how_it_works": "Cómo Funciona",
209
+ "travel_agents": "Agentes de Viaje",
210
+ "share_itinerary": "Compartir Itinerario",
211
+ "save_for_mobile": "Guardar para Móvil",
212
+ "built_with": "Hecho con ❤️ para ti",
213
+ # 출력 관련 추가 텍스트
214
+ "itinerary_ready": "¡Tu itinerario de viaje está listo! 🎉",
215
+ "personalized_experience": "Hemos creado una experiencia de viaje personalizada solo para ti. Explora tu itinerario a continuación.",
216
+ "agent_activity": "Actividad del Agente",
217
+ "error_origin_destination": "Por favor, ingresa tanto el origen como el destino.",
218
+ "your_itinerary_file": "Tu Archivo de Itinerario",
219
+ "text_format": "Formato de texto - Se puede abrir en cualquier editor de texto."
220
+ },
221
+ "fr": {
222
+ "page_title": " Votre Agent IA pour Voyager",
223
+ "header": " Votre Agent IA pour Voyager",
224
+ "create_itinerary": "Créez Votre Itinéraire",
225
+ "trip_details": "Détails du Voyage",
226
+ "origin": "Origine",
227
+ "destination": "Destination",
228
+ "travel_dates": "Dates du Voyage",
229
+ "duration": "Durée (jours)",
230
+ "preferences": "Préférences",
231
+ "additional_preferences": "Préférences Supplémentaires",
232
+ "interests": "Centres d'intérêt",
233
+ "special_requirements": "Exigences Spéciales",
234
+ "submit": "🚀 Créez Mon Itinéraire Personnalisé",
235
+ "request_details": "Votre Demande de Voyage",
236
+ "from": "De",
237
+ "when": "Quand",
238
+ "budget": "Budget",
239
+ "travel_style": "Style de Voyage",
240
+ "live_agent_outputs": "Résultats en Direct de l'Agent",
241
+ "full_itinerary": "Itinéraire Complet",
242
+ "details": "Détails",
243
+ "download_share": "Télécharger et Partager",
244
+ "save_itinerary": "Enregistrer l'Itinéraire",
245
+ "plan_another_trip": "🔄 Planifier un Autre Voyage",
246
+ "about": "À Propos",
247
+ "how_it_works": "Fonctionnement",
248
+ "travel_agents": "Agents de Voyage",
249
+ "share_itinerary": "Partager l'Itinéraire",
250
+ "save_for_mobile": "Enregistrer pour Mobile",
251
+ "built_with": "Conçu avec ❤️ pour vous",
252
+ # 출력 관련 추가 텍스트
253
+ "itinerary_ready": "Votre itinéraire de voyage est prêt ! 🎉",
254
+ "personalized_experience": "Nous avons créé une expérience de voyage personnalisée rien que pour vous. Découvrez votre itinéraire ci-dessous.",
255
+ "agent_activity": "Activité de l'Agent",
256
+ "error_origin_destination": "Veuillez saisir à la fois le lieu de départ et la destination.",
257
+ "your_itinerary_file": "Votre Fichier d'Itinéraire",
258
+ "text_format": "Format texte - Peut être ouvert dans n'importe quel éditeur de texte."
259
+ },
260
+ "de": {
261
+ "page_title": "Ihr KI-Reiseassistent",
262
+ "header": " Ihr KI-Reiseassistent",
263
+ "create_itinerary": "Erstellen Sie Ihre Reiseroute",
264
+ "trip_details": "Reisedetails",
265
+ "origin": "Abfahrtsort",
266
+ "destination": "Zielort",
267
+ "travel_dates": "Reisedaten",
268
+ "duration": "Dauer (Tage)",
269
+ "preferences": "Vorlieben",
270
+ "additional_preferences": "Zusätzliche Vorlieben",
271
+ "interests": "Interessen",
272
+ "special_requirements": "Besondere Anforderungen",
273
+ "submit": "🚀 Erstellen Sie meine personalisierte Reiseroute",
274
+ "request_details": "Ihre Reiseanfrage",
275
+ "from": "Von",
276
+ "when": "Wann",
277
+ "budget": "Budget",
278
+ "travel_style": "Reisestil",
279
+ "live_agent_outputs": "Live Agent Ausgaben",
280
+ "full_itinerary": "Komplette Reiseroute",
281
+ "details": "Details",
282
+ "download_share": "Herunterladen & Teilen",
283
+ "save_itinerary": "Reiseroute speichern",
284
+ "plan_another_trip": "🔄 Plane eine weitere Reise",
285
+ "about": "Über",
286
+ "how_it_works": "Wie es funktioniert",
287
+ "travel_agents": "Reiseassistenten",
288
+ "share_itinerary": "Reiseroute teilen",
289
+ "save_for_mobile": "Für Mobilgeräte speichern",
290
+ "built_with": "Mit ❤️ für Sie gebaut",
291
+ # 출력 관련 추가 텍스트
292
+ "itinerary_ready": "Ihre Reiseroute ist fertig! 🎉",
293
+ "personalized_experience": "Wir haben eine personalisierte Reiseerfahrung nur für Sie erstellt. Entdecken Sie Ihre Reiseroute unten.",
294
+ "agent_activity": "Agentenaktivität",
295
+ "error_origin_destination": "Bitte geben Sie sowohl den Abfahrtsort als auch das Ziel ein.",
296
+ "your_itinerary_file": "Ihre Reise-Datei",
297
+ "text_format": "Textformat – Kann in jedem Texteditor geöffnet werden."
298
+ },
299
+ "ar": {
300
+ "page_title": " وكيل السفر الذكي الخاص بك",
301
+ "header": " وكيل السفر الذكي الخاص بك",
302
+ "create_itinerary": "إنشاء خط سير الرحلة",
303
+ "trip_details": "تفاصيل الرحلة",
304
+ "origin": "المغادرة من",
305
+ "destination": "الوجهة",
306
+ "travel_dates": "تواريخ السفر",
307
+ "duration": "المدة (بالأيام)",
308
+ "preferences": "التفضيلات",
309
+ "additional_preferences": "تفضيلات إضافية",
310
+ "interests": "الاهتمامات",
311
+ "special_requirements": "المتطلبات الخاصة",
312
+ "submit": "🚀 إنشاء خط سير الرحلة الشخصي",
313
+ "request_details": "طلب السفر الخاص بك",
314
+ "from": "من",
315
+ "when": "متى",
316
+ "budget": "الميزانية",
317
+ "travel_style": "أسلوب السفر",
318
+ "live_agent_outputs": "مخرجات الوكيل المباشرة",
319
+ "full_itinerary": "خط سير الرحلة الكامل",
320
+ "details": "التفاصيل",
321
+ "download_share": "تنزيل ومشاركة",
322
+ "save_itinerary": "حفظ خط سير الرحلة",
323
+ "plan_another_trip": "🔄 خطط لرحلة أخرى",
324
+ "about": "حول",
325
+ "how_it_works": "كيف يعمل",
326
+ "travel_agents": "وكلاء السفر",
327
+ "share_itinerary": "شارك خط سير الرحلة",
328
+ "save_for_mobile": "حفظ للهاتف المحمول",
329
+ "built_with": "مصنوع بحب من أجلك",
330
+ # 출력 관련 추가 텍스트
331
+ "itinerary_ready": "تم تجهيز خط سير رحلتك! 🎉",
332
+ "personalized_experience": "لقد أنشأنا تجربة سفر مخصصة لك. استعرض خط سير رحلتك أدناه.",
333
+ "agent_activity": "نشاط الوكيل",
334
+ "error_origin_destination": "يرجى إدخال نقطة الانطلاق والوجهة.",
335
+ "your_itinerary_file": "ملف خط سير رحلتك",
336
+ "text_format": "تنسيق نصي - يمكن فتحه في أي محرر نصوص."
337
+ }
338
+ }
339
+
340
+ def t(key):
341
+ lang = st.session_state.get("selected_language", "en")
342
+ return translations[lang].get(key, key)
343
+
344
+ # ---------------------------
345
+ # 세션 초기화
346
+ # ---------------------------
347
+ if 'selected_language' not in st.session_state:
348
+ st.session_state.selected_language = "en" # 기본은 영어
349
+
350
+ # ------------------------------------------
351
+ # 사이드바에 언어 선택 위젯 추가
352
+ # ------------------------------------------
353
+ with st.sidebar:
354
+ language = st.selectbox(
355
+ "Language / 언어 / 言語 / 语言 / Idioma / Langue / Sprache / اللغة",
356
+ ["English", "한국어", "日本語", "中文", "Español", "Français", "Deutsch", "العربية"]
357
+ )
358
+ lang_map = {
359
+ "English": "en",
360
+ "한국어": "ko",
361
+ "日本語": "ja",
362
+ "中文": "zh",
363
+ "Español": "es",
364
+ "Français": "fr",
365
+ "Deutsch": "de",
366
+ "العربية": "ar"
367
+ }
368
+ st.session_state.selected_language = lang_map.get(language, "en")
369
+
370
+ # ------------------------------------------
371
+ # 이후 Streamlit UI 코드 시작
372
+ # ------------------------------------------
373
+
374
+ # Modern CSS with refined color scheme and sleek animations
375
+ st.markdown("""
376
+ <style>
377
+ /* Sleek Color Palette */
378
+ :root {
379
+ --primary: #3a86ff;
380
+ --primary-light: #4895ef;
381
+ --primary-dark: #2667ff;
382
+ --secondary: #4cc9f0;
383
+ --accent: #4361ee;
384
+ --background: #f8f9fa;
385
+ --card-bg: #ffffff;
386
+ --text: #212529;
387
+ --text-light: #6c757d;
388
+ --text-muted: #adb5bd;
389
+ --border: #e9ecef;
390
+ --success: #2ecc71;
391
+ --warning: #f39c12;
392
+ --info: #3498db;
393
+ }
394
+
395
+ /* Refined Animations */
396
+ @keyframes smoothFadeIn {
397
+ from { opacity: 0; transform: translateY(10px); }
398
+ to { opacity: 1; transform: translateY(0); }
399
+ }
400
+
401
+ @keyframes slideInRight {
402
+ from { opacity: 0; transform: translateX(20px); }
403
+ to { opacity: 1; transform: translateX(0); }
404
+ }
405
+
406
+ .animate-in {
407
+ animation: smoothFadeIn 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
408
+ }
409
+
410
+ .slide-in {
411
+ animation: slideInRight 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
412
+ }
413
+
414
+ /* Sleek Header Styles */
415
+ .main-header {
416
+ font-size: 2.5rem;
417
+ color: var(--primary-dark);
418
+ text-align: center;
419
+ margin-bottom: 0.8rem;
420
+ font-weight: 700;
421
+ letter-spacing: -0.5px;
422
+ }
423
+
424
+ .sub-header {
425
+ font-size: 1.4rem;
426
+ color: var(--accent);
427
+ font-weight: 600;
428
+ margin-top: 1.8rem;
429
+ margin-bottom: 0.8rem;
430
+ border-bottom: 1px solid var(--border);
431
+ padding-bottom: 0.4rem;
432
+ }
433
+
434
+ /* Sleek Card Styles */
435
+ .modern-card {
436
+ background-color: var(--card-bg);
437
+ border-radius: 10px;
438
+ padding: 1.2rem;
439
+ margin-bottom: 1.2rem;
440
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
441
+ transition: all 0.25s ease;
442
+ border: 1px solid var(--border);
443
+ }
444
+
445
+ .modern-card:hover {
446
+ transform: translateY(-3px);
447
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
448
+ }
449
+
450
+ /* Refined Form Styles */
451
+ .stTextInput > div > div > input,
452
+ .stDateInput > div > div > input,
453
+ .stTextArea > div > div > textarea {
454
+ border-radius: 6px;
455
+ border: 1px solid var(--border);
456
+ padding: 10px 12px;
457
+ font-size: 14px;
458
+ transition: all 0.2s ease;
459
+ box-shadow: none;
460
+ }
461
+
462
+ .stTextInput > div > div > input:focus,
463
+ .stDateInput > div > div > input:focus,
464
+ .stTextArea > div > div > textarea:focus {
465
+ border: 1px solid var(--primary);
466
+ box-shadow: 0 0 0 1px rgba(58, 134, 255, 0.15);
467
+ }
468
+
469
+ /* Sleek Button Styles */
470
+ .stButton > button {
471
+ background-color: var(--primary);
472
+ color: white;
473
+ font-weight: 500;
474
+ padding: 0.5rem 1.2rem;
475
+ border-radius: 6px;
476
+ border: none;
477
+ transition: all 0.2s ease;
478
+ font-size: 14px;
479
+ letter-spacing: 0.3px;
480
+ }
481
+
482
+ .stButton > button:hover {
483
+ background-color: var(--primary-dark);
484
+ transform: translateY(-1px);
485
+ box-shadow: 0 3px 8px rgba(58, 134, 255, 0.25);
486
+ }
487
+
488
+ /* Sleek Tab Styles */
489
+ .stTabs [data-baseweb="tab-list"] {
490
+ gap: 2px;
491
+ background-color: var(--background);
492
+ border-radius: 8px;
493
+ padding: 2px;
494
+ }
495
+
496
+ .stTabs [data-baseweb="tab"] {
497
+ border-radius: 6px;
498
+ padding: 8px 16px;
499
+ font-size: 14px;
500
+ font-weight: 500;
501
+ }
502
+
503
+ .stTabs [aria-selected="true"] {
504
+ background-color: var(--primary);
505
+ color: white !important;
506
+ }
507
+
508
+ /* Progress Bar Styles */
509
+ .stProgress > div > div > div > div {
510
+ background-color: var(--primary);
511
+ }
512
+
513
+ /* Progress Styles */
514
+ .progress-container {
515
+ margin: 1.2rem 0;
516
+ background-color: var(--background);
517
+ border-radius: 8px;
518
+ padding: 0.8rem;
519
+ border: 1px solid var(--border);
520
+ }
521
+
522
+ .step-complete {
523
+ color: #4CAF50;
524
+ font-weight: 600;
525
+ }
526
+
527
+ .step-pending {
528
+ color: #9E9E9E;
529
+ }
530
+
531
+ .step-active {
532
+ color: var(--primary);
533
+ font-weight: 600;
534
+ }
535
+
536
+ /* Agent Output */
537
+ .agent-output {
538
+ background-color: #f8f9fa;
539
+ border-left: 5px solid var(--primary);
540
+ padding: 1.2rem;
541
+ margin: 1rem 0;
542
+ border-radius: 10px;
543
+ max-height: 400px;
544
+ overflow-y: auto;
545
+ }
546
+
547
+ /* Footer */
548
+ .footer {
549
+ text-align: center;
550
+ margin-top: 3rem;
551
+ color: var(--text-light);
552
+ font-size: 0.9rem;
553
+ padding: 1rem;
554
+ border-top: 1px solid #eaeaea;
555
+ }
556
+
557
+ /* Agent Log */
558
+ .agent-log {
559
+ background-color: #F5F5F5;
560
+ border-left: 3px solid var(--primary);
561
+ padding: 0.5rem;
562
+ margin-bottom: 0.5rem;
563
+ font-family: monospace;
564
+ border-radius: 4px;
565
+ }
566
+
567
+ /* Info and Success Boxes */
568
+ .info-box {
569
+ background-color: var(--primary-light);
570
+ color: white;
571
+ padding: 1rem;
572
+ border-radius: 0.5rem;
573
+ margin-bottom: 1rem;
574
+ }
575
+
576
+ .success-box {
577
+ background-color: #E8F5E9;
578
+ padding: 1rem;
579
+ border-radius: 0.5rem;
580
+ margin-bottom: 1rem;
581
+ border-left: 5px solid #4CAF50;
582
+ }
583
+ </style>
584
+ """, unsafe_allow_html=True)
585
+
586
+ # Helper function to download HTML file
587
+ def get_download_link(text_content, filename):
588
+ b64 = base64.b64encode(text_content.encode()).decode()
589
+ href = f'<a class="download-link" href="data:text/plain;base64,{b64}" download="{filename}"><i>📥</i> {t("save_itinerary")}</a>'
590
+ return href
591
+
592
+ # Updated helper function to display modern progress with a single UI element
593
+ def display_modern_progress(current_step, total_steps=6):
594
+ if 'progress_steps' not in st.session_state:
595
+ st.session_state.progress_steps = {
596
+ 0: {'status': 'pending', 'name': t("trip_details")},
597
+ 1: {'status': 'pending', 'name': t("about")},
598
+ 2: {'status': 'pending', 'name': t("travel_style")},
599
+ 3: {'status': 'pending', 'name': t("live_agent_outputs")},
600
+ 4: {'status': 'pending', 'name': t("download_share")},
601
+ 5: {'status': 'pending', 'name': t("full_itinerary")}
602
+ }
603
+
604
+ for i in range(total_steps):
605
+ if i < current_step:
606
+ st.session_state.progress_steps[i]['status'] = 'complete'
607
+ elif i == current_step:
608
+ st.session_state.progress_steps[i]['status'] = 'active'
609
+ else:
610
+ st.session_state.progress_steps[i]['status'] = 'pending'
611
+
612
+ progress_percentage = (current_step / total_steps) * 100
613
+ st.progress(progress_percentage / 100)
614
+
615
+ st.markdown("""
616
+ <style>
617
+ .compact-progress {
618
+ background: white;
619
+ border-radius: 10px;
620
+ padding: 15px;
621
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
622
+ margin-bottom: 20px;
623
+ }
624
+ .progress-title {
625
+ font-size: 16px;
626
+ font-weight: bold;
627
+ margin-bottom: 15px;
628
+ color: #333;
629
+ border-bottom: 1px solid #eee;
630
+ padding-bottom: 10px;
631
+ }
632
+ .step-grid {
633
+ display: grid;
634
+ grid-template-columns: repeat(3, 1fr);
635
+ gap: 10px;
636
+ }
637
+ .step-item {
638
+ display: flex;
639
+ align-items: center;
640
+ padding: 8px 10px;
641
+ border-radius: 6px;
642
+ background: #f8f9fa;
643
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
644
+ }
645
+ .step-item.complete {
646
+ border-left: 3px solid #4CAF50;
647
+ background: #f1f8e9;
648
+ }
649
+ .step-item.active {
650
+ border-left: 3px solid #2196F3;
651
+ background: #e3f2fd;
652
+ font-weight: bold;
653
+ }
654
+ .step-item.pending {
655
+ border-left: 3px solid #9e9e9e;
656
+ opacity: 0.7;
657
+ }
658
+ .step-icon {
659
+ margin-right: 8px;
660
+ font-size: 14px;
661
+ }
662
+ .step-text {
663
+ font-size: 13px;
664
+ white-space: nowrap;
665
+ overflow: hidden;
666
+ text-overflow: ellipsis;
667
+ }
668
+ </style>
669
+ <div class="compact-progress">
670
+ """, unsafe_allow_html=True)
671
+
672
+ st.markdown('<div class="step-grid">', unsafe_allow_html=True)
673
+ for i, step_info in st.session_state.progress_steps.items():
674
+ status = step_info['status']
675
+ name = step_info['name']
676
+ if status == 'complete':
677
+ icon = "✅"
678
+ status_class = "complete"
679
+ elif status == 'active':
680
+ icon = "🔄"
681
+ status_class = "active"
682
+ else:
683
+ icon = "⭕"
684
+ status_class = "pending"
685
+
686
+ st.markdown(f"""
687
+ <div class="step-item {status_class}">
688
+ <span class="step-icon">{icon}</span>
689
+ <span class="step-text">{name}</span>
690
+ </div>
691
+ """, unsafe_allow_html=True)
692
+
693
+ st.markdown('</div></div>', unsafe_allow_html=True)
694
+ return progress_percentage
695
+
696
+ def update_step_status(step_index, status):
697
+ if 'progress_steps' in st.session_state and step_index in st.session_state.progress_steps:
698
+ st.session_state.progress_steps[step_index]['status'] = status
699
+
700
+ def run_task_with_logs(task, input_text, log_container, output_container, results_key=None):
701
+ log_message = f"🤖 Starting {task.agent.role}..."
702
+ st.session_state.log_messages.append(log_message)
703
+
704
+ with log_container:
705
+ st.markdown("### " + t("agent_activity"))
706
+ for msg in st.session_state.log_messages:
707
+ st.markdown(msg)
708
+
709
+ result = run_task(task, input_text)
710
+
711
+ if results_key:
712
+ st.session_state.results[results_key] = result
713
+
714
+ log_message = f"✅ {task.agent.role} completed!"
715
+ st.session_state.log_messages.append(log_message)
716
+
717
+ with log_container:
718
+ st.markdown("### " + t("agent_activity"))
719
+ for msg in st.session_state.log_messages:
720
+ st.markdown(msg)
721
+
722
+ with output_container:
723
+ st.markdown(f"### {task.agent.role} Output")
724
+ st.markdown("<div class='agent-output'>" + result + "</div>", unsafe_allow_html=True)
725
+
726
+ return result
727
+
728
+ # ------------------------------------------
729
+ # Session state 초기화
730
+ # ------------------------------------------
731
+ if 'generated_itinerary' not in st.session_state:
732
+ st.session_state.generated_itinerary = None
733
+ if 'generation_complete' not in st.session_state:
734
+ st.session_state.generation_complete = False
735
+ if 'current_step' not in st.session_state:
736
+ st.session_state.current_step = 0
737
+ if 'results' not in st.session_state:
738
+ st.session_state.results = {
739
+ "destination_info": "",
740
+ "accommodation_info": "",
741
+ "transportation_info": "",
742
+ "activities_info": "",
743
+ "dining_info": "",
744
+ "itinerary": "",
745
+ "final_itinerary": ""
746
+ }
747
+ if 'log_messages' not in st.session_state:
748
+ st.session_state.log_messages = []
749
+ if 'current_output' not in st.session_state:
750
+ st.session_state.current_output = None
751
+ if 'form_submitted' not in st.session_state:
752
+ st.session_state.form_submitted = False
753
+
754
+ # Modern animated header
755
+ st.markdown(f"""
756
+ <div class="animate-in" style="text-align: center;">
757
+ <div style="margin-bottom: 20px;">
758
+ <img src="https://img.icons8.com/fluency/96/travel-card.png" width="90" style="filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));">
759
+ </div>
760
+ <h1 class="main-header">{t("header")}</h1>
761
+ <p style="font-size: 1.2rem; color: #6c757d; margin-bottom: 25px;">
762
+ ✨ Create your personalized AI-powered travel itinerary in minutes! ✨
763
+ </p>
764
+ </div>
765
+ """, unsafe_allow_html=True)
766
+
767
+ st.markdown('<hr style="height:3px;border:none;background-color:#f0f0f0;margin-bottom:25px;">', unsafe_allow_html=True)
768
+
769
+ with st.sidebar:
770
+ st.markdown("""
771
+ <div style="text-align: center; padding: 20px 0; margin-bottom: 20px; border-bottom: 1px solid #eaeaea;">
772
+ <img src="https://img.icons8.com/fluency/96/travel-card.png" width="80" style="margin-bottom: 15px;">
773
+ <h3 style="margin-bottom: 5px; color: #4361ee;">Your AI Agent for Travelling</h3>
774
+ <p style="color: #6c757d; font-size: 0.9rem;">AI-Powered Travel Planning</p>
775
+ </div>
776
+ """, unsafe_allow_html=True)
777
+
778
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
779
+ st.markdown("### 🌟 " + t("about"))
780
+ st.info("This AI-powered tool creates a personalized travel itinerary based on your preferences. Fill in the form and let our specialized travel agents plan your perfect trip!")
781
+ st.markdown('</div>', unsafe_allow_html=True)
782
+
783
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
784
+ st.markdown("### 🔍 " + t("how_it_works"))
785
+ st.markdown("""
786
+ <ol style="padding-left: 25px;">
787
+ <li><b>🖊️ Enter</b> your travel details</li>
788
+ <li><b>🧠 AI analysis</b> of your preferences</li>
789
+ <li><b>📋 Generate</b> comprehensive itinerary</li>
790
+ <li><b>📥 Download</b> and enjoy your trip!</li>
791
+ </ol>
792
+ """, unsafe_allow_html=True)
793
+ st.markdown('</div>', unsafe_allow_html=True)
794
+
795
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
796
+ st.markdown("### 🤖 Travel Agents")
797
+ agents = [
798
+ ("🔭 Research Specialist", "Finds the best destinations based on your preferences"),
799
+ ("🏨 Accommodation Expert", "Suggests suitable hotels and stays"),
800
+ ("🚆 Transportation Planner", "Plans efficient travel routes"),
801
+ ("🎯 Activities Curator", "Recommends activities tailored to your interests"),
802
+ ("🍽️ Dining Connoisseur", "Finds the best dining experiences"),
803
+ ("📅 Itinerary Creator", "Puts everything together in a daily plan")
804
+ ]
805
+ for name, desc in agents:
806
+ st.markdown("**" + name + "**")
807
+ st.markdown("<small>" + desc + "</small>", unsafe_allow_html=True)
808
+ st.markdown('</div>', unsafe_allow_html=True)
809
+
810
+ if not st.session_state.generation_complete:
811
+ st.markdown('<div class="modern-card animate-in">', unsafe_allow_html=True)
812
+ st.markdown("<h3 style='font-weight: 600; color: var(--primary-dark); display: flex; align-items: center; gap: 10px;'><span style='font-size: 20px;'>✈️</span> " + t("create_itinerary") + "</h3>", unsafe_allow_html=True)
813
+
814
+ st.markdown("""
815
+ <p style="color: var(--text-light); margin-bottom: 16px; font-size: 14px; font-weight: 400;">Complete the form below for a personalized travel plan.</p>
816
+ """, unsafe_allow_html=True)
817
+
818
+ with st.form("travel_form"):
819
+ col1, col2 = st.columns(2)
820
+ with col1:
821
+ st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px; margin-bottom: 12px;">Trip Details</p>', unsafe_allow_html=True)
822
+ origin = st.text_input(t("origin"), placeholder="e.g., New York, USA")
823
+ destination = st.text_input(t("destination"), placeholder="e.g., Paris, France")
824
+ st.markdown('<p style="margin-bottom: 5px; font-size: 14px;">Travel Dates</p>', unsafe_allow_html=True)
825
+ start_date = st.date_input("Start Date", min_value=datetime.now(), label_visibility="collapsed")
826
+ duration = st.slider(t("duration"), min_value=1, max_value=30, value=7)
827
+ end_date = start_date + timedelta(days=duration-1)
828
+ st.markdown('<p style="font-size: 13px; color: var(--text-muted); margin-top: 5px;">' + start_date.strftime("%b %d") + " - " + end_date.strftime("%b %d, %Y") + '</p>', unsafe_allow_html=True)
829
+ with col2:
830
+ st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px; margin-bottom: 12px;">Preferences</p>', unsafe_allow_html=True)
831
+ travelers = st.number_input("Travelers", min_value=1, max_value=15, value=2)
832
+ budget_options = ["Budget", "Moderate", "Luxury"]
833
+ budget = st.selectbox("Budget", budget_options, help="Budget: Economy options | Moderate: Mid-range | Luxury: High-end experiences")
834
+ travel_style = st.multiselect("🌈 Travel Style", options=["Culture", "Adventure", "Relaxation", "Food & Dining", "Nature", "Shopping", "Nightlife", "Family-friendly"], default=["Culture", "Food & Dining"])
835
+ with st.expander("Additional Preferences", expanded=False):
836
+ preferences = st.text_area("Interests", placeholder="History museums, local cuisine, hiking, art...")
837
+ special_requirements = st.text_area("Special Requirements", placeholder="Dietary restrictions, accessibility needs...")
838
+ submit_button = st.form_submit_button(t("submit"))
839
+ st.markdown('</div>', unsafe_allow_html=True)
840
+
841
+ if submit_button:
842
+ if not origin or not destination:
843
+ st.error(t("error_origin_destination"))
844
+ else:
845
+ st.session_state.form_submitted = True
846
+ user_input = {
847
+ "origin": origin,
848
+ "destination": destination,
849
+ "duration": str(duration),
850
+ "travel_dates": f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}",
851
+ "travelers": str(travelers),
852
+ "budget": budget.lower(),
853
+ "travel_style": ", ".join(travel_style),
854
+ "preferences": preferences,
855
+ "special_requirements": special_requirements
856
+ }
857
+ # 기존의 여행 요청 프롬프트
858
+ input_context = f"""Travel Request Details:
859
+ Origin: {user_input['origin']}
860
+ Destination: {user_input['destination']}
861
+ Duration: {user_input['duration']} days
862
+ Travel Dates: {user_input['travel_dates']}
863
+ Travelers: {user_input['travelers']}
864
+ Budget Level: {user_input['budget']}
865
+ Travel Style: {user_input['travel_style']}
866
+ Preferences/Interests: {user_input['preferences']}
867
+ Special Requirements: {user_input['special_requirements']}
868
+ """
869
+ # LLM에 전달할 프롬프트에 언어 지시문 추가
870
+ llm_language_instructions = {
871
+ "en": "Please output the response in English.",
872
+ "ko": "한국어로 출력해 주세요.",
873
+ "ja": "日本語で出力してください。",
874
+ "zh": "请用中文输出。",
875
+ "es": "Por favor, responda en español.",
876
+ "fr": "Veuillez répondre en français.",
877
+ "de": "Bitte antworten Sie auf Deutsch.",
878
+ "ar": "يرجى الرد باللغة العربية."
879
+ }
880
+ selected_lang = st.session_state.get("selected_language", "en")
881
+ language_instruction = llm_language_instructions.get(selected_lang, "Please output the response in English.")
882
+ modified_input_context = language_instruction + "\n" + input_context
883
+
884
+ st.markdown("""
885
+ <div class="sleek-processing-container">
886
+ <div class="pulse-container">
887
+ <div class="pulse-ring"></div>
888
+ <div class="pulse-core"></div>
889
+ </div>
890
+ </div>
891
+ <style>
892
+ .sleek-processing-container {
893
+ display: flex;
894
+ justify-content: center;
895
+ align-items: center;
896
+ padding: 20px 0;
897
+ }
898
+ .pulse-container {
899
+ position: relative;
900
+ width: 50px;
901
+ height: 50px;
902
+ }
903
+ .pulse-core {
904
+ position: absolute;
905
+ left: 50%;
906
+ top: 50%;
907
+ transform: translate(-50%, -50%);
908
+ width: 12px;
909
+ height: 12px;
910
+ background-color: #4361ee;
911
+ border-radius: 50%;
912
+ box-shadow: 0 0 8px rgba(67, 97, 238, 0.6);
913
+ }
914
+ .pulse-ring {
915
+ position: absolute;
916
+ left: 0;
917
+ top: 0;
918
+ width: 100%;
919
+ height: 100%;
920
+ border: 2px solid #4361ee;
921
+ border-radius: 50%;
922
+ animation: pulse 1.5s ease-out infinite;
923
+ opacity: 0;
924
+ }
925
+ @keyframes pulse {
926
+ 0% { transform: scale(0.1); opacity: 0; }
927
+ 50% { opacity: 0.5; }
928
+ 100% { transform: scale(1); opacity: 0; }
929
+ }
930
+ </style>
931
+ """, unsafe_allow_html=True)
932
+
933
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
934
+ progress_tab, logs_tab, details_tab = st.tabs(["📊 Progress", "🔄 Live Activity", "📋 " + t("request_details")])
935
+ with details_tab:
936
+ st.markdown("#### " + t("request_details"))
937
+ st.markdown("**" + t("destination") + ":** " + user_input['destination'])
938
+ st.markdown("**" + t("from") + ":** " + user_input['origin'])
939
+ st.markdown("**" + t("when") + ":** " + user_input['travel_dates'] + " (" + user_input['duration'] + " days)")
940
+ st.markdown("**" + t("budget") + ":** " + user_input['budget'].title())
941
+ st.markdown("**" + t("travel_style") + ":** " + user_input['travel_style'])
942
+ if user_input['preferences']:
943
+ st.markdown("**Interests:** " + user_input['preferences'])
944
+ if user_input['special_requirements']:
945
+ st.markdown("**Special Requirements:** " + user_input['special_requirements'])
946
+ with progress_tab:
947
+ if 'progress_placeholder' not in st.session_state:
948
+ st.session_state.progress_placeholder = st.empty()
949
+ with st.session_state.progress_placeholder.container():
950
+ display_modern_progress(0)
951
+ with logs_tab:
952
+ log_container = st.container()
953
+ st.session_state.log_messages = []
954
+ st.markdown('</div>', unsafe_allow_html=True)
955
+ output_container = st.container()
956
+ with output_container:
957
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
958
+ st.markdown("### 🌟 " + t("live_agent_outputs"))
959
+ st.info("Our AI agents will show their work here as they create your itinerary")
960
+ st.markdown('</div>', unsafe_allow_html=True)
961
+ st.session_state.current_step = 0
962
+
963
+ update_step_status(0, 'active')
964
+ with st.session_state.progress_placeholder.container():
965
+ display_modern_progress(st.session_state.current_step)
966
+ destination_info = run_task_with_logs(
967
+ destination_research_task,
968
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
969
+ log_container,
970
+ output_container,
971
+ "destination_info"
972
+ )
973
+ update_step_status(0, 'complete')
974
+ st.session_state.current_step = 1
975
+ update_step_status(1, 'active')
976
+ with st.session_state.progress_placeholder.container():
977
+ display_modern_progress(st.session_state.current_step)
978
+ accommodation_info = run_task_with_logs(
979
+ accommodation_task,
980
+ modified_input_context.format(destination=user_input['destination'], budget=user_input['budget'], preferences=user_input['preferences']),
981
+ log_container,
982
+ output_container,
983
+ "accommodation_info"
984
+ )
985
+ update_step_status(1, 'complete')
986
+ st.session_state.current_step = 2
987
+ update_step_status(2, 'active')
988
+ with st.session_state.progress_placeholder.container():
989
+ display_modern_progress(st.session_state.current_step)
990
+ transportation_info = run_task_with_logs(
991
+ transportation_task,
992
+ modified_input_context.format(origin=user_input['origin'], destination=user_input['destination']),
993
+ log_container,
994
+ output_container,
995
+ "transportation_info"
996
+ )
997
+ update_step_status(2, 'complete')
998
+ st.session_state.current_step = 3
999
+ update_step_status(3, 'active')
1000
+ with st.session_state.progress_placeholder.container():
1001
+ display_modern_progress(st.session_state.current_step)
1002
+ activities_info = run_task_with_logs(
1003
+ activities_task,
1004
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
1005
+ log_container,
1006
+ output_container,
1007
+ "activities_info"
1008
+ )
1009
+ update_step_status(3, 'complete')
1010
+ st.session_state.current_step = 4
1011
+ update_step_status(4, 'active')
1012
+ with st.session_state.progress_placeholder.container():
1013
+ display_modern_progress(st.session_state.current_step)
1014
+ dining_info = run_task_with_logs(
1015
+ dining_task,
1016
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
1017
+ log_container,
1018
+ output_container,
1019
+ "dining_info"
1020
+ )
1021
+ update_step_status(4, 'complete')
1022
+ st.session_state.current_step = 5
1023
+ update_step_status(5, 'active')
1024
+ with st.session_state.progress_placeholder.container():
1025
+ display_modern_progress(st.session_state.current_step)
1026
+ combined_info = f"""{input_context}
1027
+
1028
+ Destination Information:
1029
+ {destination_info}
1030
+
1031
+ Accommodation Options:
1032
+ {accommodation_info}
1033
+
1034
+ Transportation Plan:
1035
+ {transportation_info}
1036
+
1037
+ Recommended Activities:
1038
+ {activities_info}
1039
+
1040
+ Dining Recommendations:
1041
+ {dining_info}
1042
+ """
1043
+ itinerary = run_task_with_logs(
1044
+ itinerary_task,
1045
+ combined_info.format(duration=user_input['duration'], origin=user_input['origin'], destination=user_input['destination']),
1046
+ log_container,
1047
+ output_container,
1048
+ "itinerary"
1049
+ )
1050
+ update_step_status(5, 'complete')
1051
+ st.session_state.current_step = 6
1052
+ with st.session_state.progress_placeholder.container():
1053
+ display_modern_progress(st.session_state.current_step)
1054
+ st.session_state.generated_itinerary = itinerary
1055
+ st.session_state.generation_complete = True
1056
+ date_str = datetime.now().strftime("%Y-%m-%d")
1057
+ st.session_state.filename = f"{user_input['destination'].replace(' ', '_')}_{date_str}_itinerary.txt"
1058
+
1059
+ if st.session_state.generation_complete:
1060
+ st.markdown("""
1061
+ <div class="modern-card animate-in">
1062
+ <div style="display: flex; justify-content: center; margin-bottom: 20px;">
1063
+ <div class="success-animation">
1064
+ <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
1065
+ <circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" />
1066
+ <path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8" />
1067
+ </svg>
1068
+ </div>
1069
+ </div>
1070
+ <h2 style="text-align: center; color: #4361ee;">""" + t("itinerary_ready") + """</h2>
1071
+ <p style="text-align: center; color: #6c757d; margin-bottom: 20px;">""" + t("personalized_experience") + """</p>
1072
+ </div>
1073
+
1074
+ <style>
1075
+ .success-animation {
1076
+ width: 100px;
1077
+ height: 100px;
1078
+ position: relative;
1079
+ }
1080
+ .checkmark {
1081
+ width: 100px;
1082
+ height: 100px;
1083
+ border-radius: 50%;
1084
+ display: block;
1085
+ stroke-width: 2;
1086
+ stroke: #4361ee;
1087
+ stroke-miterlimit: 10;
1088
+ box-shadow: 0 0 20px rgba(67, 97, 238, 0.3);
1089
+ animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
1090
+ }
1091
+ .checkmark__circle {
1092
+ stroke-dasharray: 166;
1093
+ stroke-dashoffset: 166;
1094
+ stroke-width: 2;
1095
+ stroke-miterlimit: 10;
1096
+ stroke: #4361ee;
1097
+ fill: none;
1098
+ animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
1099
+ }
1100
+ .checkmark__check {
1101
+ transform-origin: 50% 50%;
1102
+ stroke-dasharray: 48;
1103
+ stroke-dashoffset: 48;
1104
+ animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
1105
+ }
1106
+ @keyframes stroke {
1107
+ 100% { stroke-dashoffset: 0; }
1108
+ }
1109
+ @keyframes scale {
1110
+ 0%, 100% { transform: none; }
1111
+ 50% { transform: scale3d(1.1, 1.1, 1); }
1112
+ }
1113
+ @keyframes fill {
1114
+ 100% { box-shadow: 0 0 20px rgba(67, 97, 238, 0.3); }
1115
+ }
1116
+ </style>
1117
+ """, unsafe_allow_html=True)
1118
+
1119
+ # 추가된 탭: 전체 일정, 상세 정보, 다운로드/공유, 지도 및 시각화, AI 챗봇 인터페이스
1120
+ itinerary_tab, details_tab, download_tab, map_tab, chatbot_tab = st.tabs([
1121
+ "🗒️ " + t("full_itinerary"),
1122
+ "💼 " + t("details"),
1123
+ "💾 " + t("download_share"),
1124
+ "🗺️ 지도 및 시각화",
1125
+ "🤖 챗봇 인터페이스"
1126
+ ])
1127
+
1128
+ # 일정 탭
1129
+ with itinerary_tab:
1130
+ st.text_area("Your Itinerary", st.session_state.generated_itinerary, height=600)
1131
+
1132
+ # 상세 정보 탭
1133
+ with details_tab:
1134
+ agent_tabs = st.tabs(["🌎 Destination", "🏨 Accommodation", "🚗 Transportation", "🎭 Activities", "🍽️ Dining"])
1135
+ with agent_tabs[0]:
1136
+ st.markdown("### 🌎 Destination Research")
1137
+ st.markdown(st.session_state.results["destination_info"])
1138
+ with agent_tabs[1]:
1139
+ st.markdown("### 🏨 Accommodation Options")
1140
+ st.markdown(st.session_state.results["accommodation_info"])
1141
+ with agent_tabs[2]:
1142
+ st.markdown("### 🚗 Transportation Plan")
1143
+ st.markdown(st.session_state.results["transportation_info"])
1144
+ with agent_tabs[3]:
1145
+ st.markdown("### 🎭 Recommended Activities")
1146
+ st.markdown(st.session_state.results["activities_info"])
1147
+ with agent_tabs[4]:
1148
+ st.markdown("### 🍽️ Dining Recommendations")
1149
+ st.markdown(st.session_state.results["dining_info"])
1150
+
1151
+ # 다운로드 및 공유 탭
1152
+ with download_tab:
1153
+ col1, col2 = st.columns([2, 1])
1154
+ with col1:
1155
+ st.markdown("### " + t("save_itinerary"))
1156
+ st.markdown("Download your personalized travel plan to access it offline or share with your travel companions.")
1157
+ st.markdown("""
1158
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px; margin-top: 20px;">
1159
+ <h4 style="margin-top: 0;">""" + t("your_itinerary_file") + """</h4>
1160
+ <p style="font-size: 0.9rem; color: #6c757d;">""" + t("text_format") + """</p>
1161
+ """, unsafe_allow_html=True)
1162
+ st.markdown("<div style='margin: 10px 0;'>" + get_download_link(st.session_state.generated_itinerary, st.session_state.filename) + "</div>", unsafe_allow_html=True)
1163
+ st.markdown("</div>", unsafe_allow_html=True)
1164
+ st.markdown("### " + t("share_itinerary"))
1165
+ st.markdown("*Coming soon: Email your itinerary or share via social media.*")
1166
+ with col2:
1167
+ st.markdown("### " + t("save_for_mobile"))
1168
+ st.markdown("*Coming soon: QR code for easy access on your phone*")
1169
+
1170
+ # 인터랙티브 지도 및 시각화 탭
1171
+ with map_tab:
1172
+ st.markdown("### 목적지 지도")
1173
+ # 예���: 목적지 주변의 주요 명소 좌표 데이터 (실제 API나 DB를 통해 동적으로 가져올 수 있음)
1174
+ map_data = pd.DataFrame({
1175
+ "lat": [48.8584, 48.8606, 48.8529],
1176
+ "lon": [2.2945, 2.3376, 2.3500],
1177
+ "name": ["Eiffel Tower", "Louvre Museum", "Notre Dame"]
1178
+ })
1179
+ # 기본 지도 출력 (st.map)
1180
+ st.map(map_data)
1181
+
1182
+ st.markdown("#### Pydeck을 활용한 인터랙티브 지도 예시")
1183
+ layer = pdk.Layer(
1184
+ "ScatterplotLayer",
1185
+ data=map_data,
1186
+ get_position='[lon, lat]',
1187
+ get_color='[200, 30, 0, 160]',
1188
+ get_radius=200,
1189
+ )
1190
+ view_state = pdk.ViewState(
1191
+ latitude=48.8566,
1192
+ longitude=2.3522,
1193
+ zoom=12,
1194
+ pitch=50,
1195
+ )
1196
+ deck_chart = pdk.Deck(layers=[layer], initial_view_state=view_state)
1197
+ st.pydeck_chart(deck_chart)
1198
+
1199
+ # AI 챗봇 인터페이스 탭 (제미나이 적용)
1200
+ with chatbot_tab:
1201
+ st.markdown("### AI 챗봇 인터페이스")
1202
+ # 대화 기록을 세션 상태에 저장 (메시지, 발신자, 타임스탬프)
1203
+ if "chat_history" not in st.session_state:
1204
+ st.session_state.chat_history = []
1205
+
1206
+ # 사용자 입력창 및 전송 버튼
1207
+ user_message = st.text_input("메시지를 입력하세요:", key="chat_input")
1208
+ if st.button("전송", key="send_button"):
1209
+ if user_message:
1210
+ # 제미나이 기반 챗봇 응답: run_task()를 활용하여 chatbot_task에 질의
1211
+ response = run_task(chatbot_task, user_message)
1212
+ st.session_state.chat_history.append({
1213
+ "speaker": "사용자",
1214
+ "message": user_message,
1215
+ "time": datetime.now()
1216
+ })
1217
+ st.session_state.chat_history.append({
1218
+ "speaker": "AI",
1219
+ "message": response,
1220
+ "time": datetime.now()
1221
+ })
1222
+
1223
+ # 대화 기록 출력 (타임스탬프 포함, 스크롤 가능한 영역)
1224
+ st.markdown("<div style='max-height:400px; overflow-y:auto; padding:10px; border:1px solid #eaeaea; border-radius:6px;'>", unsafe_allow_html=True)
1225
+ for chat in st.session_state.chat_history:
1226
+ time_str = chat["time"].strftime("%H:%M:%S")
1227
+ st.markdown(f"**{chat['speaker']}** ({time_str}): {chat['message']}")
1228
+ st.markdown("</div>", unsafe_allow_html=True)
1229
+
1230
+ st.markdown("""
1231
+ <div style="margin-top: 50px; text-align: center; padding: 20px; color: #6c757d; font-size: 0.8rem;">
1232
+ <p>""" + t("built_with") + """</p>
1233
+ </div>
1234
+ """, unsafe_allow_html=True)
app.py ADDED
@@ -0,0 +1,1331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ # AgentX-Travel India
3
+ # ------------------
4
+ # An AI-powered travel assistant application tailored for the Indian market
5
+ # Created by TechMatrix Solvers for IIITDMJ HackByte3.0 (April 4-6, 2025)
6
+ #
7
+ # Features:
8
+ # - Personalized travel itinerary generation using AI agents
9
+ # - Bilingual support (English and Hindi)
10
+ # - India-specific travel recommendations
11
+ # - Interactive maps and visualizations
12
+ # - Downloadable travel plans
13
+ #
14
+ # Team:
15
+ # - Abhay Gupta (Team Leader)
16
+ # - Jay Kumar
17
+ # - Kripanshu Gupta
18
+ # - Aditi Soni
19
+ #
20
+ # This application uses:
21
+ # - Streamlit for the frontend
22
+ # - LangChain for AI orchestration
23
+ # - Google Generative AI (Gemini) for language processing
24
+ # - Geopy for location services
25
+ # - Pydeck for map visualizations
26
+ """
27
+
28
+ import streamlit as st
29
+ import os
30
+ import json
31
+ from datetime import datetime, timedelta
32
+ import base64
33
+ import pandas as pd
34
+ import pydeck as pdk
35
+ from travel import (
36
+ destination_research_task, accommodation_task, transportation_task,
37
+ activities_task, dining_task, itinerary_task, chatbot_task,
38
+ run_task
39
+ )
40
+ from geopy.geocoders import Nominatim
41
+
42
+
43
+ st.set_page_config(
44
+ page_title="Your AI Travel Assistant",
45
+ page_icon="✈️",
46
+ layout="wide",
47
+ initial_sidebar_state="expanded"
48
+ )
49
+
50
+ # ------------------------------------------
51
+ # Translation dictionary and helper functions
52
+ # ------------------------------------------
53
+ translations = {
54
+ "en": {
55
+ "page_title": "Your AI Travel Assistant",
56
+ "header": "Your AI Travel Assistant",
57
+ "create_itinerary": "Create Your Itinerary",
58
+ "trip_details": "Trip Details",
59
+ "origin": "Origin",
60
+ "destination": "Destination",
61
+ "travel_dates": "Travel Dates",
62
+ "duration": "Duration (days)",
63
+ "preferences": "Preferences",
64
+ "additional_preferences": "Additional Preferences",
65
+ "interests": "Interests",
66
+ "special_requirements": "Special Requirements",
67
+ "submit": "🚀 Create My Personal Travel Itinerary",
68
+ "request_details": "Your Travel Request",
69
+ "from": "From",
70
+ "when": "When",
71
+ "budget": "Budget",
72
+ "travel_style": "Travel Style",
73
+ "live_agent_outputs": "Live Agent Outputs",
74
+ "full_itinerary": "Full Itinerary",
75
+ "details": "Details",
76
+ "download_share": "Download & Share",
77
+ "save_itinerary": "Save Your Itinerary",
78
+ "plan_another_trip": "🔄 Plan Another Trip",
79
+ "about": "About",
80
+ "how_it_works": "How it works",
81
+ "travel_agents": "Travel Agents",
82
+ "share_itinerary": "Share Your Itinerary",
83
+ "save_for_mobile": "Save for Mobile",
84
+ "built_with": "Built with ❤️ in India",
85
+ "itinerary_ready": "Your Travel Itinerary is Ready! 🎉",
86
+ "personalized_experience": "We've created a personalized travel experience just for you. Explore your itinerary below.",
87
+ "agent_activity": "Agent Activity",
88
+ "error_origin_destination": "Please enter both origin and destination.",
89
+ "your_itinerary_file": "Your Itinerary File",
90
+ "text_format": "Text format - Can be opened in any text editor",
91
+ "settings": "Settings"
92
+ },
93
+ "hi": {
94
+ "page_title": "आपका AI यात्रा सहायक",
95
+ "header": "आपका AI यात्रा सहायक",
96
+ "create_itinerary": "अपना यात्रा कार्यक्रम बनाएं",
97
+ "trip_details": "यात्रा विवरण",
98
+ "origin": "प्रस्थान स्थान",
99
+ "destination": "गंतव्य स्थान",
100
+ "travel_dates": "यात्रा की तारीखें",
101
+ "duration": "अवधि (दिन)",
102
+ "preferences": "प्राथमिकताएं",
103
+ "additional_preferences": "अतिरिक्त प्राथमिकताएं",
104
+ "interests": "रुचियां",
105
+ "special_requirements": "विशेष आवश्यकताएं",
106
+ "submit": "🚀 मेरा व्यक्तिगत यात्रा कार्यक्रम बनाएं",
107
+ "request_details": "आपका यात्रा अनुरोध",
108
+ "from": "से",
109
+ "when": "कब",
110
+ "budget": "बजट",
111
+ "travel_style": "यात्रा शैली",
112
+ "live_agent_outputs": "लाइव एजेंट आउटपुट",
113
+ "full_itinerary": "पूर्ण यात्रा कार्यक्रम",
114
+ "details": "विवरण",
115
+ "download_share": "डाउनलोड और शेयर",
116
+ "save_itinerary": "अपना यात्रा कार्यक्रम सहेजें",
117
+ "plan_another_trip": "🔄 दूसरी यात्रा की योजना बनाएं",
118
+ "about": "परिचय",
119
+ "how_it_works": "यह कैसे काम करता है",
120
+ "travel_agents": "यात्रा एजेंट",
121
+ "share_itinerary": "अपना यात्रा कार्यक्रम साझा करें",
122
+ "save_for_mobile": "मोबाइल के लिए सहेजें",
123
+ "built_with": "भारत में ❤️ के साथ बनाया गया",
124
+ "itinerary_ready": "आपका यात्रा कार्यक्रम तैयार है! 🎉",
125
+ "personalized_experience": "हमने आपके लिए एक व्यक्तिगत यात्रा अनुभव बनाया है। नीचे अपना यात्रा कार्यक्रम देखें।",
126
+ "agent_activity": "एजेंट गतिविधि",
127
+ "error_origin_destination": "कृपया प्रस्थान और गंतव्य दोनों दर्ज करें।",
128
+ "your_itinerary_file": "आपकी यात्रा कार्यक्रम फाइल",
129
+ "text_format": "टेक्स्ट फॉर्मेट - किसी भी टेक्स्ट एडिटर में खोला जा सकता है",
130
+ "settings": "सेटिंग्स"
131
+ },
132
+ "bn": {
133
+ "page_title": "আপনার AI ভ্রমণ সহকারী",
134
+ "header": "আপনার AI ভ্রমণ সহকারী",
135
+ "create_itinerary": "আপনার ভ্রমণ তিট্টানি তৈরি করুন",
136
+ "trip_details": "ভ্রমণের বিবরণ",
137
+ "origin": "উৎপত্তি স্থান",
138
+ "destination": "গন্তব্য",
139
+ "travel_dates": "ভ্রমণের তারিখ",
140
+ "duration": "সময়কাল (দিন)",
141
+ "preferences": "পছন্দসমূহ",
142
+ "additional_preferences": "অতিরিক্ত পছন্দসমূহ",
143
+ "interests": "আগ্রহ",
144
+ "special_requirements": "বিশেষ প্রয়োজনীয়তা",
145
+ "submit": "🚀 আমার ব্যক্তিগত ভ্রমণ তিট্টানি তৈরি করুন",
146
+ "request_details": "আপনার ভ্রমণ অভয়রথন",
147
+ "from": "থেকে",
148
+ "when": "কখন",
149
+ "budget": "বাজট",
150
+ "travel_style": "ভ্রমণ শৈলী",
151
+ "live_agent_outputs": "লাইভ এজংট আউটপুট",
152
+ "full_itinerary": "সম্পূর্ণ ভ্রমণ তিট্টানি",
153
+ "details": "বিবরণ",
154
+ "download_share": "ডাউনলোড এবং শায়র",
155
+ "save_itinerary": "আপনার ভ্রমণ তিট্টানি সংরক্ষণ করুন",
156
+ "plan_another_trip": "🔄 অন্য ভ্রমণ পরিকল্পনা করুন",
157
+ "about": "বদদল",
158
+ "how_it_works": "এটি কিভাবে কাজ করে",
159
+ "travel_agents": "ভ্রমণ এজংট",
160
+ "share_itinerary": "আপনার ভ্রমণ তিট্টানি শায়র করুন",
161
+ "save_for_mobile": "মোবাইলসাঠী জন্য সংরক্ষণ করুন",
162
+ "built_with": "ভারতদিশংলো ❤️ দিয়ে তৈরি",
163
+ "itinerary_ready": "আপনার ভ্রমণ তিট্টানি প্রসতুত! 🎉",
164
+ "personalized_experience": "আমরা আপনার জন্য একটি ব্যক্তিগত ভ্রমণ অনুভবান্নি তৈরি করেছি। দিগু঵ আপনার ভ্রমণ তিট্টানি অন্বয়ন করুন।",
165
+ "agent_activity": "এজংট করযাকলাপ",
166
+ "error_origin_destination": "দয়া করে উৎপত্তি এবং গন্তব্য উভয়ই লিখুন।",
167
+ "your_itinerary_file": "আপনার ভ্রমণ তিট্টানি ফাইল",
168
+ "text_format": "টেক্সট ফরমাট - কোনাহো টেক্সট এডিটরে উঘডতা যাবে",
169
+ "settings": "সেটিংস"
170
+ },
171
+ "ta": {
172
+ "page_title": "உங்கள் AI ப்ரயாண உதவியாளர்",
173
+ "header": "உங்கள் AI ப்ரயாண உதவியாளர்",
174
+ "create_itinerary": "உங்கள் ப்ரயாண திட்டம் உருவாக்குங்கள்",
175
+ "trip_details": "ப்ரயாண விவரங்கள்",
176
+ "origin": "தொடக்க இடம்",
177
+ "destination": "இலக்கு",
178
+ "travel_dates": "ப்ரயாண தேதிகள்",
179
+ "duration": "காலம் (நாட்கள்)",
180
+ "preferences": "விருப்பங்கள்",
181
+ "additional_preferences": "கூடுதல் விருப்பங்கள்",
182
+ "interests": "ஆர்வங்கள்",
183
+ "special_requirements": "சிறப்பு தேவைகள்",
184
+ "submit": "🚀 எனது தனிப்பட்ட ப்ரயாண திட்டம் உருவாக்கு",
185
+ "request_details": "உங்கள் ப்ரயாண கோரிக்கை",
186
+ "from": "இருந்து",
187
+ "when": "எப்போது",
188
+ "budget": "பட்ஜெட்",
189
+ "travel_style": "ப்ரயாண பாணி",
190
+ "live_agent_outputs": "நேரடி முகவர் வெளியீடுகள்",
191
+ "full_itinerary": "முழு ப்ரயாண திட்டம்",
192
+ "details": "விவரங்கள்",
193
+ "download_share": "பதிவிறக்கம் & பகிர்",
194
+ "save_itinerary": "உங்கள் ப்ரயாண திட்டம் சேமிக்கவும்",
195
+ "plan_another_trip": "🔄 மற்றொரு ப்ரயாணத்தை திட்டமிடுங்கள்",
196
+ "about": "பற்றி",
197
+ "how_it_works": "இது எப்படி பநி செய்கிறது",
198
+ "travel_agents": "ப்ரயாண முகவர்கள்",
199
+ "share_itinerary": "உங்கள் ப்ரயாண திட்டம் பகிர் செய்யவும்",
200
+ "save_for_mobile": "மொபைலுக்கு சேமிக்கவும்",
201
+ "built_with": "இந்தியாவில் ❤️ உடன் உருவாக்கப்பட்டது",
202
+ "itinerary_ready": "உங்கள் ப்ரயாண திட்டம் தயாராக உள்ளது! 🎉",
203
+ "personalized_experience": "மேமு மீ கோஸாக ஒரு தனிப்பயனாக்கப்பட்ட ப்ரயாண அனுபவத்தை உருவாக்கியுள்ளோம். கீழே மீ ப்ரயாண திட்டம் அந்வேஷிச்சுங்கள்.",
204
+ "agent_activity": "முகவர் செயல்பாடு",
205
+ "error_origin_destination": "தயவுசெய்து தொடக்க இடம் மற்றும் இலக்கு இரண்டையும் உள்ளிடவும்.",
206
+ "your_itinerary_file": "மீ ப்ரயாண திட்ட கோப்பு",
207
+ "text_format": "உரை வடிவம் - எந்த உரை திருத்தியிலும் திறக்கலாம்",
208
+ "settings": "அமைப்புகள்"
209
+ },
210
+ "te": {
211
+ "page_title": "మీ AI ప్రయాణ సహాయకుడు",
212
+ "header": "మీ AI ప్రయాణ సహాయకుడు",
213
+ "create_itinerary": "మీ ప్రయాణ కార్యక్రమాన్ని సృష్టించండి",
214
+ "trip_details": "ప్రయాణ వివరాలు",
215
+ "origin": "ప్రారంభ స్థానం",
216
+ "destination": "గమ్యస్థానం",
217
+ "travel_dates": "ప్రయాణ తేదీలు",
218
+ "duration": "వ్యవధి (రోజులు)",
219
+ "preferences": "ప్రాధాన్యతలు",
220
+ "additional_preferences": "అదనపు ప్రాధాన్యతలు",
221
+ "interests": "ఆసక్తులు",
222
+ "special_requirements": "ప్రత్యేక అవసరాలు",
223
+ "submit": "🚀 నా వ్యక్తిగత ప్రయాణ కార్యక్రమాన్ని సృష్టించండి",
224
+ "request_details": "మీ ప్రయాణ అభ్యర్థన",
225
+ "from": "నుండి",
226
+ "when": "ఎప్పుడు",
227
+ "budget": "బడ్జెట్",
228
+ "travel_style": "ప్రయాణ శైలి",
229
+ "live_agent_outputs": "లైవ్ ఏజంట్ ఆఉట్పుట్లు",
230
+ "full_itinerary": "పూర్తి ప్రయాణ కార్యక్రమం",
231
+ "details": "వివరాలు",
232
+ "download_share": "డాఉన్లోడ్ & షేర్",
233
+ "save_itinerary": "మీ ప్రయాణ కార్యక���రమాన్ని సేవ్ చేయండి",
234
+ "plan_another_trip": "🔄 మరొక ప్రయాణాన్ని ప్లాన్ చేయండి",
235
+ "about": "గురించి",
236
+ "how_it_works": "ఇది ఎలా పని చేస్తుంది",
237
+ "travel_agents": "ప్రయాణ ఏజంట్లు",
238
+ "share_itinerary": "మీ ప్రయాణ కార్యక్రమాన్ని షేర్ చేయండి",
239
+ "save_for_mobile": "మొబాయల్ కోసం సేవ్ చేయండి",
240
+ "built_with": "భారతదేశంలో ❤️ తో నిర్మించబడింది",
241
+ "itinerary_ready": "మీ ప్రయాణ కార్యక్రమం సిద్ధంగా ఉంది! 🎉",
242
+ "personalized_experience": "మేము మీ కోసం ఒక వ్యక్తిగతీకరించిన ప్రయాణ అనుభవాన్ని సృష్టించాము. దిగువ మీ ప్రయాణ కార్యక్రమాన్ని అన్వేషించండి.",
243
+ "agent_activity": "ఏజంట్ కార్యాచరణ",
244
+ "error_origin_destination": "దయచేసి ప్రారంభ స్థానం మరియు గమ్యస్థానం రెండింటినీ నమోదు చేయండి.",
245
+ "your_itinerary_file": "మీ ప్రయాణ కార్యక్రమ ఫైల్",
246
+ "text_format": "టెక్స్ట్ ఫార్మాట్ - ఏదైనా టెక్స్ట్ ఎడిటర్‌లో తెరవచ్చు",
247
+ "settings": "సెట్టింగ్‌లు"
248
+ },
249
+ "mr": {
250
+ "page_title": "आपला AI प्रवास सहाय्यक",
251
+ "header": "आपला AI प्रवास सहाय्यक",
252
+ "create_itinerary": "आपले प्रवास कार्यक्रम तयार करा",
253
+ "trip_details": "प्रवास तपशील",
254
+ "origin": "प्रारंभ स्थान",
255
+ "destination": "गंतव्य स्थान",
256
+ "travel_dates": "प्रवास तारखा",
257
+ "duration": "कालावधी (दिवस)",
258
+ "preferences": "प्राधान्ये",
259
+ "additional_preferences": "अतिरिक्त प्राधान्ये",
260
+ "interests": "आवडी",
261
+ "special_requirements": "विशेष आवश्यकता",
262
+ "submit": "🚀 माझा वैयक्तिक प्रवास कार्यक्रम तयार करा",
263
+ "request_details": "आपली प्रवास विनंती",
264
+ "from": "पासून",
265
+ "when": "कधी",
266
+ "budget": "बजेट",
267
+ "travel_style": "प्रवास शैली",
268
+ "live_agent_outputs": "लाइव्ह एजंट आउटपुट",
269
+ "full_itinerary": "संपूर्ण प्रवास कार्यक्रम",
270
+ "details": "तपशील",
271
+ "download_share": "डाउनलोड आणि शेअर",
272
+ "save_itinerary": "आपला प्रवास कार्यक्रम सेव्ह करा",
273
+ "plan_another_trip": "🔄 दुसरा प्रवास नियोजित करा",
274
+ "about": "बद्दल",
275
+ "how_it_works": "हे कसे कार्य करते",
276
+ "travel_agents": "प्रवास एजंट",
277
+ "share_itinerary": "आपला प्रवास कार्यक्रम शेअर करा",
278
+ "save_for_mobile": "मोबाईलसाठी सेव्ह करा",
279
+ "built_with": "भारतात ❤️ सह तयार केले",
280
+ "itinerary_ready": "आपला प्रवास कार्यक्रम तयार आहे! 🎉",
281
+ "personalized_experience": "आम्ही आपल्यासाठी एक वैयक्तिक प्रवास अनुभव तयार केला आहे. खाली आपला प्रवास कार्यक्रम पहा.",
282
+ "agent_activity": "एजंट क्रियाकलाप",
283
+ "error_origin_destination": "कृपया प्रारंभ आणि गंतव्य दोन्ही प्रविष्ट करा.",
284
+ "your_itinerary_file": "आपली प्रवास कार्यक्रम फाईल",
285
+ "text_format": "टेक्स्ट फॉरमॅट - कोणत्याही टेक्स्ट एडिटरमध्ये उघडता येईल",
286
+ "settings": "सेटिंग्ज"
287
+ }
288
+ }
289
+
290
+ def t(key):
291
+ lang = st.session_state.get('language', 'en')
292
+ return translations.get(lang, {}).get(key, key)
293
+
294
+ # Initialize session state variables
295
+ if 'generated_itinerary' not in st.session_state:
296
+ st.session_state.generated_itinerary = None
297
+
298
+ if 'step_results' not in st.session_state:
299
+ st.session_state.step_results = {}
300
+
301
+ if 'language' not in st.session_state:
302
+ st.session_state.language = 'en'
303
+
304
+ language_options = {
305
+ 'en': 'English',
306
+ 'hi': 'हिन्दी (Hindi)',
307
+ 'bn': 'বাংলা (Bengali)',
308
+ 'ta': 'தமிழ் (Tamil)',
309
+ 'te': 'తెలుగు (Telugu)',
310
+ 'mr': 'मराठी (Marathi)'
311
+ }
312
+
313
+ with st.sidebar:
314
+ st.title("🛠️ " + t("settings"))
315
+
316
+ selected_lang = st.selectbox(
317
+ "🌐 Language / भाषा",
318
+ options=list(language_options.keys()),
319
+ format_func=lambda x: language_options[x],
320
+ index=list(language_options.keys()).index(st.session_state.language)
321
+ )
322
+
323
+ if selected_lang != st.session_state.language:
324
+ st.session_state.language = selected_lang
325
+ st.rerun()
326
+
327
+ st.divider()
328
+
329
+ st.markdown("### " + t("about"))
330
+ st.markdown(t("how_it_works"))
331
+ st.markdown("""
332
+ 1. Enter your destination and preferences
333
+ 2. Our AI agents will research and plan your trip
334
+ 3. Review and save your customized itinerary
335
+ """)
336
+
337
+ st.markdown("### " + t("travel_agents"))
338
+ st.markdown("""
339
+ - 🧠 **Destination Research Agent**
340
+ - 🏨 **Accommodation Agent**
341
+ - 🚆 **Transportation Agent**
342
+ - 🎭 **Activities Agent**
343
+ - 🍽️ **Dining Agent**
344
+ - 📅 **Itinerary Integration Agent**
345
+ """)
346
+
347
+ st.markdown(f"<div style='text-align: center; margin-top: 20px; font-size: 0.8rem;'>{t('built_with')}</div>", unsafe_allow_html=True)
348
+
349
+ # ---------------------------
350
+ # 세션 초기화
351
+ # ---------------------------
352
+ if 'selected_language' not in st.session_state:
353
+ st.session_state.selected_language = "en" # 기본은 영어
354
+
355
+ # ------------------------------------------
356
+ # 사이드바에 언어 선택 위젯 추가
357
+ # ------------------------------------------
358
+ with st.sidebar:
359
+ language = st.selectbox(
360
+ "Language / 언어 / 言語 / 语言 / Idioma / Langue / Sprache / اللغة",
361
+ ["English", "한국어", "日本語", "中文", "Español", "Français", "Deutsch", "العربية"]
362
+ )
363
+ lang_map = {
364
+ "English": "en",
365
+ "한국어": "ko",
366
+ "日本語": "ja",
367
+ "中文": "zh",
368
+ "Español": "es",
369
+ "Français": "fr",
370
+ "Deutsch": "de",
371
+ "العربية": "ar"
372
+ }
373
+ st.session_state.selected_language = lang_map.get(language, "en")
374
+
375
+ # ------------------------------------------
376
+ # 이후 Streamlit UI 코드 시작
377
+ # ------------------------------------------
378
+
379
+ # Modern CSS with refined color scheme and sleek animations
380
+ st.markdown("""
381
+ <style>
382
+ /* Sleek Color Palette */
383
+ :root {
384
+ --primary: #3a86ff;
385
+ --primary-light: #4895ef;
386
+ --primary-dark: #2667ff;
387
+ --secondary: #4cc9f0;
388
+ --accent: #4361ee;
389
+ --background: #f8f9fa;
390
+ --card-bg: #ffffff;
391
+ --text: #212529;
392
+ --text-light: #6c757d;
393
+ --text-muted: #adb5bd;
394
+ --border: #e9ecef;
395
+ --success: #2ecc71;
396
+ --warning: #f39c12;
397
+ --info: #3498db;
398
+ }
399
+
400
+ /* Refined Animations */
401
+ @keyframes smoothFadeIn {
402
+ from { opacity: 0; transform: translateY(10px); }
403
+ to { opacity: 1; transform: translateY(0); }
404
+ }
405
+
406
+ @keyframes slideInRight {
407
+ from { opacity: 0; transform: translateX(20px); }
408
+ to { opacity: 1; transform: translateX(0); }
409
+ }
410
+
411
+ .animate-in {
412
+ animation: smoothFadeIn 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
413
+ }
414
+
415
+ .slide-in {
416
+ animation: slideInRight 0.5s cubic-bezier(0.215, 0.61, 0.355, 1);
417
+ }
418
+
419
+ /* Sleek Header Styles */
420
+ .main-header {
421
+ font-size: 2.5rem;
422
+ color: #FF671F;
423
+ text-align: center;
424
+ margin-bottom: 0.2rem;
425
+ font-weight: 600;
426
+ }
427
+
428
+ .sub-header {
429
+ font-size: 1.5rem;
430
+ color: #046A38;
431
+ text-align: center;
432
+ margin-bottom: 1rem;
433
+ }
434
+
435
+ /* Sleek Card Styles */
436
+ .modern-card {
437
+ background-color: var(--card-bg);
438
+ border-radius: 10px;
439
+ padding: 1.2rem;
440
+ margin-bottom: 1.2rem;
441
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
442
+ transition: all 0.25s ease;
443
+ border: 1px solid var(--border);
444
+ }
445
+
446
+ .modern-card:hover {
447
+ transform: translateY(-3px);
448
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
449
+ }
450
+
451
+ /* Refined Form Styles */
452
+ .stTextInput > div > div > input,
453
+ .stDateInput > div > div > input,
454
+ .stTextArea > div > div > textarea {
455
+ border-radius: 6px;
456
+ border: 1px solid var(--border);
457
+ padding: 10px 12px;
458
+ font-size: 14px;
459
+ transition: all 0.2s ease;
460
+ box-shadow: none;
461
+ }
462
+
463
+ .stTextInput > div > div > input:focus,
464
+ .stDateInput > div > div > input:focus,
465
+ .stTextArea > div > div > textarea:focus {
466
+ border: 1px solid var(--primary);
467
+ box-shadow: 0 0 0 1px rgba(58, 134, 255, 0.15);
468
+ }
469
+
470
+ /* Sleek Button Styles */
471
+ .stButton > button {
472
+ background-color: #FF671F;
473
+ color: white;
474
+ font-weight: 500;
475
+ padding: 10px 20px;
476
+ border-radius: 20px;
477
+ border: none;
478
+ transition: all 0.2s ease;
479
+ font-size: 14px;
480
+ letter-spacing: 0.3px;
481
+ }
482
+
483
+ .stButton > button:hover {
484
+ background-color: #E05A1C;
485
+ transform: translateY(-1px);
486
+ box-shadow: 0 3px 8px rgba(58, 134, 255, 0.25);
487
+ }
488
+
489
+ /* Sleek Tab Styles */
490
+ .stTabs [data-baseweb="tab-list"] {
491
+ gap: 10px;
492
+ }
493
+
494
+ .stTabs [data-baseweb="tab"] {
495
+ height: 50px;
496
+ white-space: pre-wrap;
497
+ background-color: #f0f2f6;
498
+ border-radius: 5px 5px 0px 0px;
499
+ gap: 1px;
500
+ padding-top: 10px;
501
+ padding-bottom: 10px;
502
+ }
503
+
504
+ .stTabs [aria-selected="true"] {
505
+ background-color: #FF671F;
506
+ color: white;
507
+ }
508
+
509
+ /* Progress Bar Styles */
510
+ .stProgress > div > div > div > div {
511
+ background-color: var(--primary);
512
+ }
513
+
514
+ /* Progress Styles */
515
+ .progress-container {
516
+ border-radius: 10px;
517
+ padding: 10px;
518
+ margin-top: 20px;
519
+ margin-bottom: 20px;
520
+ background-color: #f8f9fa;
521
+ border: 1px solid #e9ecef;
522
+ }
523
+
524
+ .step-complete {
525
+ color: #4CAF50;
526
+ font-weight: 600;
527
+ }
528
+
529
+ .step-pending {
530
+ color: #9E9E9E;
531
+ }
532
+
533
+ .step-active {
534
+ color: var(--primary);
535
+ font-weight: 600;
536
+ }
537
+
538
+ /* Agent Output */
539
+ .agent-output {
540
+ background-color: #f8f9fa;
541
+ border-left: 5px solid var(--primary);
542
+ padding: 1.2rem;
543
+ margin: 1rem 0;
544
+ border-radius: 10px;
545
+ max-height: 400px;
546
+ overflow-y: auto;
547
+ }
548
+
549
+ /* Footer */
550
+ .footer {
551
+ text-align: center;
552
+ margin-top: 3rem;
553
+ color: var(--text-light);
554
+ font-size: 0.9rem;
555
+ padding: 1rem;
556
+ border-top: 1px solid #eaeaea;
557
+ }
558
+
559
+ /* Agent Log */
560
+ .agent-log {
561
+ background-color: #F5F5F5;
562
+ border-left: 3px solid var(--primary);
563
+ padding: 0.5rem;
564
+ margin-bottom: 0.5rem;
565
+ font-family: monospace;
566
+ border-radius: 4px;
567
+ }
568
+
569
+ /* Info and Success Boxes */
570
+ .info-box {
571
+ background-color: var(--primary-light);
572
+ color: white;
573
+ padding: 1rem;
574
+ border-radius: 0.5rem;
575
+ margin-bottom: 1rem;
576
+ }
577
+
578
+ .success-box {
579
+ background-color: #E8F5E9;
580
+ padding: 1rem;
581
+ border-radius: 0.5rem;
582
+ margin-bottom: 1rem;
583
+ border-left: 5px solid #4CAF50;
584
+ }
585
+
586
+ .result-container {
587
+ border-radius: 10px;
588
+ padding: 20px;
589
+ margin-top: 20px;
590
+ margin-bottom: 20px;
591
+ background-color: #f8f9fa;
592
+ border-left: 4px solid #FF671F;
593
+ }
594
+
595
+ .success-container {
596
+ border-radius: 10px;
597
+ padding: 20px;
598
+ margin-top: 20px;
599
+ margin-bottom: 20px;
600
+ background-color: #f8f9fa;
601
+ border-left: 4px solid #046A38;
602
+ }
603
+
604
+ .map-container {
605
+ border-radius: 10px;
606
+ overflow: hidden;
607
+ border: 1px solid #e6e6e6;
608
+ margin-top: 10px;
609
+ }
610
+
611
+ div[data-testid="stVerticalBlock"] div[style*="flex-direction: column;"] div[data-testid="stVerticalBlock"] {
612
+ background-color: #FFFFFF;
613
+ padding: 10px;
614
+ border-radius: 5px;
615
+ margin-bottom: 10px;
616
+ border: 1px solid #e6e6e6;
617
+ }
618
+ </style>
619
+ """, unsafe_allow_html=True)
620
+
621
+ # Helper function to download HTML file
622
+ def get_download_link(text_content, filename):
623
+ b64 = base64.b64encode(text_content.encode()).decode()
624
+ href = f'<a class="download-link" href="data:text/plain;base64,{b64}" download="{filename}"><i>📥</i> {t("save_itinerary")}</a>'
625
+ return href
626
+
627
+ # Updated helper function to display modern progress with a single UI element
628
+ def display_modern_progress(current_step, total_steps=6):
629
+ if 'progress_steps' not in st.session_state:
630
+ st.session_state.progress_steps = {
631
+ 0: {'status': 'pending', 'name': t("trip_details")},
632
+ 1: {'status': 'pending', 'name': t("about")},
633
+ 2: {'status': 'pending', 'name': t("travel_style")},
634
+ 3: {'status': 'pending', 'name': t("live_agent_outputs")},
635
+ 4: {'status': 'pending', 'name': t("download_share")},
636
+ 5: {'status': 'pending', 'name': t("full_itinerary")}
637
+ }
638
+
639
+ for i in range(total_steps):
640
+ if i < current_step:
641
+ st.session_state.progress_steps[i]['status'] = 'complete'
642
+ elif i == current_step:
643
+ st.session_state.progress_steps[i]['status'] = 'active'
644
+ else:
645
+ st.session_state.progress_steps[i]['status'] = 'pending'
646
+
647
+ progress_percentage = (current_step / total_steps) * 100
648
+ st.progress(progress_percentage / 100)
649
+
650
+ st.markdown("""
651
+ <style>
652
+ .compact-progress {
653
+ background: white;
654
+ border-radius: 10px;
655
+ padding: 15px;
656
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
657
+ margin-bottom: 20px;
658
+ }
659
+ .progress-title {
660
+ font-size: 16px;
661
+ font-weight: bold;
662
+ margin-bottom: 15px;
663
+ color: #333;
664
+ border-bottom: 1px solid #eee;
665
+ padding-bottom: 10px;
666
+ }
667
+ .step-grid {
668
+ display: grid;
669
+ grid-template-columns: repeat(3, 1fr);
670
+ gap: 10px;
671
+ }
672
+ .step-item {
673
+ display: flex;
674
+ align-items: center;
675
+ padding: 8px 10px;
676
+ border-radius: 6px;
677
+ background: #f8f9fa;
678
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
679
+ }
680
+ .step-item.complete {
681
+ border-left: 3px solid #4CAF50;
682
+ background: #f1f8e9;
683
+ }
684
+ .step-item.active {
685
+ border-left: 3px solid #2196F3;
686
+ background: #e3f2fd;
687
+ font-weight: bold;
688
+ }
689
+ .step-item.pending {
690
+ border-left: 3px solid #9e9e9e;
691
+ opacity: 0.7;
692
+ }
693
+ .step-icon {
694
+ margin-right: 8px;
695
+ font-size: 14px;
696
+ }
697
+ .step-text {
698
+ font-size: 13px;
699
+ white-space: nowrap;
700
+ overflow: hidden;
701
+ text-overflow: ellipsis;
702
+ }
703
+ </style>
704
+ <div class="compact-progress">
705
+ """, unsafe_allow_html=True)
706
+
707
+ st.markdown('<div class="step-grid">', unsafe_allow_html=True)
708
+ for i, step_info in st.session_state.progress_steps.items():
709
+ status = step_info['status']
710
+ name = step_info['name']
711
+ if status == 'complete':
712
+ icon = "✅"
713
+ status_class = "complete"
714
+ elif status == 'active':
715
+ icon = "🔄"
716
+ status_class = "active"
717
+ else:
718
+ icon = "⭕"
719
+ status_class = "pending"
720
+
721
+ st.markdown(f"""
722
+ <div class="step-item {status_class}">
723
+ <span class="step-icon">{icon}</span>
724
+ <span class="step-text">{name}</span>
725
+ </div>
726
+ """, unsafe_allow_html=True)
727
+
728
+ st.markdown('</div></div>', unsafe_allow_html=True)
729
+ return progress_percentage
730
+
731
+ def update_step_status(step_index, status):
732
+ if 'progress_steps' in st.session_state and step_index in st.session_state.progress_steps:
733
+ st.session_state.progress_steps[step_index]['status'] = status
734
+
735
+ def run_task_with_logs(task, input_text, log_container, output_container, results_key=None):
736
+ log_message = f"🤖 Starting {task.agent.role}..."
737
+ st.session_state.log_messages.append(log_message)
738
+
739
+ with log_container:
740
+ st.markdown("### " + t("agent_activity"))
741
+ for msg in st.session_state.log_messages:
742
+ st.markdown(msg)
743
+
744
+ result = run_task(task, input_text)
745
+
746
+ if results_key:
747
+ st.session_state.results[results_key] = result
748
+
749
+ log_message = f"✅ {task.agent.role} completed!"
750
+ st.session_state.log_messages.append(log_message)
751
+
752
+ with log_container:
753
+ st.markdown("### " + t("agent_activity"))
754
+ for msg in st.session_state.log_messages:
755
+ st.markdown(msg)
756
+
757
+ with output_container:
758
+ st.markdown(f"### {task.agent.role} Output")
759
+ st.markdown("<div class='agent-output'>" + result + "</div>", unsafe_allow_html=True)
760
+
761
+ return result
762
+
763
+ # ------------------------------------------
764
+ # Session state 초기화
765
+ # ------------------------------------------
766
+ if 'generated_itinerary' not in st.session_state:
767
+ st.session_state.generated_itinerary = None
768
+ if 'generation_complete' not in st.session_state:
769
+ st.session_state.generation_complete = False
770
+ if 'current_step' not in st.session_state:
771
+ st.session_state.current_step = 0
772
+ if 'results' not in st.session_state:
773
+ st.session_state.results = {
774
+ "destination_info": "",
775
+ "accommodation_info": "",
776
+ "transportation_info": "",
777
+ "activities_info": "",
778
+ "dining_info": "",
779
+ "itinerary": "",
780
+ "final_itinerary": ""
781
+ }
782
+ if 'log_messages' not in st.session_state:
783
+ st.session_state.log_messages = []
784
+ if 'current_output' not in st.session_state:
785
+ st.session_state.current_output = None
786
+ if 'form_submitted' not in st.session_state:
787
+ st.session_state.form_submitted = False
788
+ if "itinerary_creation_started" not in st.session_state:
789
+ st.session_state.itinerary_creation_started = False
790
+
791
+ # Add style customization for better Indic script rendering
792
+ st.markdown("""
793
+ <style>
794
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&family=Noto+Sans+Bengali:wght@400;700&family=Noto+Sans+Devanagari:wght@400;700&family=Noto+Sans+Tamil:wght@400;700&family=Noto+Sans+Telugu:wght@400;700&display=swap');
795
+
796
+ body {
797
+ font-family: 'Noto Sans', sans-serif;
798
+ }
799
+
800
+ .hindi-text {
801
+ font-family: 'Noto Sans Devanagari', sans-serif;
802
+ }
803
+
804
+ .bengali-text {
805
+ font-family: 'Noto Sans Bengali', sans-serif;
806
+ }
807
+
808
+ .tamil-text {
809
+ font-family: 'Noto Sans Tamil', sans-serif;
810
+ }
811
+
812
+ .telugu-text {
813
+ font-family: 'Noto Sans Telugu', sans-serif;
814
+ }
815
+
816
+ .marathi-text {
817
+ font-family: 'Noto Sans Devanagari', sans-serif;
818
+ }
819
+
820
+ .stSelectbox [data-baseweb=select] div {
821
+ font-size: 16px !important;
822
+ line-height: 1.6 !important;
823
+ }
824
+ </style>
825
+ """, unsafe_allow_html=True)
826
+
827
+ # Modern animated header
828
+ st.markdown(f"""
829
+ <div class="animate-in" style="text-align: center;">
830
+ <div style="margin-bottom: 20px;">
831
+ <img src="https://img.icons8.com/fluency/96/travel-card.png" width="90" style="filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));">
832
+ </div>
833
+ <h1 class="main-header">{t("header")}</h1>
834
+ <p style="font-size: 1.2rem; color: #6c757d; margin-bottom: 25px;">
835
+ ✨ Create your personalized AI-powered travel itinerary in minutes! ✨
836
+ </p>
837
+ </div>
838
+ """, unsafe_allow_html=True)
839
+
840
+ st.markdown('<hr style="height:3px;border:none;background-color:#f0f0f0;margin-bottom:25px;">', unsafe_allow_html=True)
841
+
842
+ with st.sidebar:
843
+ st.markdown("""
844
+ <div style="text-align: center; padding: 20px 0; margin-bottom: 20px; border-bottom: 1px solid #eaeaea;">
845
+ <img src="https://img.icons8.com/fluency/96/travel-card.png" width="80" style="margin-bottom: 15px;">
846
+ <h3 style="margin-bottom: 5px; color: #4361ee;">AI Agent X: Travelling</h3>
847
+ <p style="color: #6c757d; font-size: 0.9rem;">AI-Powered Travel Planning</p>
848
+ </div>
849
+ """, unsafe_allow_html=True)
850
+
851
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
852
+ st.markdown("### 🌟 " + t("about"))
853
+ st.info("This AI-powered tool creates a personalized travel itinerary based on your preferences. Fill in the form and let our specialized travel agents plan your perfect trip!")
854
+ st.markdown('</div>', unsafe_allow_html=True)
855
+
856
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
857
+ st.markdown("### 🔍 " + t("how_it_works"))
858
+ st.markdown("""
859
+ <ol style="padding-left: 25px;">
860
+ <li><b>🖊️ Enter</b> your travel details</li>
861
+ <li><b>🧠 AI analysis</b> of your preferences</li>
862
+ <li><b>📋 Generate</b> comprehensive itinerary</li>
863
+ <li><b>📥 Download</b> and enjoy your trip!</li>
864
+ </ol>
865
+ """, unsafe_allow_html=True)
866
+ st.markdown('</div>', unsafe_allow_html=True)
867
+
868
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
869
+ st.markdown("### 🤖 Travel Agents")
870
+ agents = [
871
+ ("🧠 **Destination Research Agent**", "Finds the best destinations based on your preferences"),
872
+ ("🏨 **Accommodation Agent**", "Suggests suitable hotels and stays"),
873
+ ("🚆 **Transportation Agent**", "Plans efficient travel routes"),
874
+ ("🎭 **Activities Agent**", "Recommends activities tailored to your interests"),
875
+ ("🍽️ **Dining Agent**", "Finds the best dining experiences"),
876
+ ("📅 **Itinerary Integration Agent**", "Puts everything together in a daily plan")
877
+ ]
878
+ for name, desc in agents:
879
+ st.markdown("**" + name + "**")
880
+ st.markdown("<small>" + desc + "</small>", unsafe_allow_html=True)
881
+ st.markdown('</div>', unsafe_allow_html=True)
882
+
883
+ if not st.session_state.generation_complete:
884
+ st.markdown('<div class="modern-card animate-in">', unsafe_allow_html=True)
885
+ st.markdown("<h3 style='font-weight: 600; color: var(--primary-dark); display: flex; align-items: center; gap: 10px;'><span style='font-size: 20px;'>✈️</span> " + t("create_itinerary") + "</h3>", unsafe_allow_html=True)
886
+
887
+ st.markdown("""
888
+ <p style="color: var(--text-light); margin-bottom: 16px; font-size: 14px; font-weight: 400;">Complete the form below for a personalized travel plan.</p>
889
+ """, unsafe_allow_html=True)
890
+
891
+ with st.form("travel_form"):
892
+ col1, col2 = st.columns(2)
893
+ with col1:
894
+ st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px; margin-bottom: 12px;">Trip Details</p>', unsafe_allow_html=True)
895
+ origin = st.text_input(t("origin"), placeholder="e.g., New York, USA")
896
+ destination = st.text_input(t("destination"), placeholder="e.g., Paris, France")
897
+ st.markdown('<p style="margin-bottom: 5px; font-size: 14px;">Travel Dates</p>', unsafe_allow_html=True)
898
+ start_date = st.date_input("Start Date", min_value=datetime.now(), label_visibility="collapsed")
899
+ duration = st.slider(t("duration"), min_value=1, max_value=30, value=7)
900
+ end_date = start_date + timedelta(days=duration-1)
901
+ st.markdown('<p style="font-size: 13px; color: var(--text-muted); margin-top: 5px;">' + start_date.strftime("%b %d") + " - " + end_date.strftime("%b %d, %Y") + '</p>', unsafe_allow_html=True)
902
+ with col2:
903
+ st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px; margin-bottom: 12px;">Preferences</p>', unsafe_allow_html=True)
904
+ travelers = st.number_input("Travelers", min_value=1, max_value=15, value=2)
905
+ budget_options = ["Budget", "Moderate", "Luxury"]
906
+ budget = st.selectbox("Budget", budget_options, help="Budget: Economy options | Moderate: Mid-range | Luxury: High-end experiences")
907
+ travel_style = st.multiselect("🌈 Travel Style", options=["Culture", "Adventure", "Relaxation", "Food & Dining", "Nature", "Shopping", "Nightlife", "Family-friendly"], default=["Culture", "Food & Dining"])
908
+ with st.expander("Additional Preferences", expanded=False):
909
+ preferences = st.text_area("Interests", placeholder="History museums, local cuisine, hiking, art...")
910
+ special_requirements = st.text_area("Special Requirements", placeholder="Dietary restrictions, accessibility needs...")
911
+ submit_button = st.form_submit_button(t("submit"))
912
+ st.markdown('</div>', unsafe_allow_html=True)
913
+
914
+ if submit_button:
915
+ if not origin or not destination:
916
+ st.error(t("error_origin_destination"))
917
+ else:
918
+ st.session_state.form_submitted = True
919
+ st.session_state.destination = destination # 목적지를 session_state에 저장
920
+ user_input = {
921
+ "origin": origin,
922
+ "destination": destination,
923
+ "duration": str(duration),
924
+ "travel_dates": f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}",
925
+ "travelers": str(travelers),
926
+ "budget": budget.lower(),
927
+ "travel_style": ", ".join(travel_style),
928
+ "preferences": preferences,
929
+ "special_requirements": special_requirements
930
+ }
931
+
932
+ st.session_state.user_input = user_input # 저장하여 이후 지도에 활용
933
+ # 기존의 여행 요청 프롬프트
934
+ input_context = f"""Travel Request Details:
935
+ Origin: {user_input['origin']}
936
+ Destination: {user_input['destination']}
937
+ Duration: {user_input['duration']} days
938
+ Travel Dates: {user_input['travel_dates']}
939
+ Travelers: {user_input['travelers']}
940
+ Budget Level: {user_input['budget']}
941
+ Travel Style: {user_input['travel_style']}
942
+ Preferences/Interests: {user_input['preferences']}
943
+ Special Requirements: {user_input['special_requirements']}
944
+ """
945
+ # LLM에 전달할 프롬프트에 언어 지시문 추가
946
+ llm_language_instructions = {
947
+ "en": "Please output the response in English.",
948
+ "hi": "कृपया हिंदी में उत्तर दें।",
949
+ "bn": "অনুগ্রহ করে বাংলায় উত্তর দিন।",
950
+ "ta": "தயவுசெய்து தமிழில் பதிலளிக்கவும்.",
951
+ "te": "దయచేసి తెలుగులో సమాధానం ఇవ్వండి.",
952
+ "mr": "कृपया मराठी मध्ये उत्तर द्या."
953
+ }
954
+ selected_lang = st.session_state.get("selected_language", "en")
955
+ language_instruction = llm_language_instructions.get(selected_lang, "Please output the response in English.")
956
+ modified_input_context = language_instruction + "\n" + input_context
957
+
958
+ st.markdown("""
959
+ <div class="sleek-processing-container">
960
+ <div class="pulse-container">
961
+ <div class="pulse-ring"></div>
962
+ <div class="pulse-core"></div>
963
+ </div>
964
+ </div>
965
+ <style>
966
+ .sleek-processing-container {
967
+ display: flex;
968
+ justify-content: center;
969
+ align-items: center;
970
+ padding: 20px 0;
971
+ }
972
+ .pulse-container {
973
+ position: relative;
974
+ width: 50px;
975
+ height: 50px;
976
+ }
977
+ .pulse-core {
978
+ position: absolute;
979
+ left: 50%;
980
+ top: 50%;
981
+ transform: translate(-50%, -50%);
982
+ width: 12px;
983
+ height: 12px;
984
+ background-color: #4361ee;
985
+ border-radius: 50%;
986
+ box-shadow: 0 0 8px rgba(67, 97, 238, 0.6);
987
+ }
988
+ .pulse-ring {
989
+ position: absolute;
990
+ left: 0;
991
+ top: 0;
992
+ width: 100%;
993
+ height: 100%;
994
+ border: 2px solid #4361ee;
995
+ border-radius: 50%;
996
+ animation: pulse 1.5s ease-out infinite;
997
+ opacity: 0;
998
+ }
999
+ @keyframes pulse {
1000
+ 0% { transform: scale(0.1); opacity: 0; }
1001
+ 50% { opacity: 0.5; }
1002
+ 100% { transform: scale(1); opacity: 0; }
1003
+ }
1004
+ </style>
1005
+ """, unsafe_allow_html=True)
1006
+
1007
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
1008
+ progress_tab, logs_tab, details_tab = st.tabs(["📊 Progress", "🔄 Live Activity", "📋 " + t("request_details")])
1009
+ with details_tab:
1010
+ st.markdown("#### " + t("request_details"))
1011
+ st.markdown("**" + t("destination") + ":** " + user_input['destination'])
1012
+ st.markdown("**" + t("from") + ":** " + user_input['origin'])
1013
+ st.markdown("**" + t("when") + ":** " + user_input['travel_dates'] + " (" + user_input['duration'] + " days)")
1014
+ st.markdown("**" + t("budget") + ":** " + user_input['budget'].title())
1015
+ st.markdown("**" + t("travel_style") + ":** " + user_input['travel_style'])
1016
+ if user_input['preferences']:
1017
+ st.markdown("**Interests:** " + user_input['preferences'])
1018
+ if user_input['special_requirements']:
1019
+ st.markdown("**Special Requirements:** " + user_input['special_requirements'])
1020
+ with progress_tab:
1021
+ if 'progress_placeholder' not in st.session_state:
1022
+ st.session_state.progress_placeholder = st.empty()
1023
+ with st.session_state.progress_placeholder.container():
1024
+ display_modern_progress(st.session_state.current_step)
1025
+ with logs_tab:
1026
+ log_container = st.container()
1027
+ st.session_state.log_messages = []
1028
+ st.markdown('</div>', unsafe_allow_html=True)
1029
+ output_container = st.container()
1030
+ with output_container:
1031
+ st.markdown('<div class="modern-card">', unsafe_allow_html=True)
1032
+ st.markdown("### 🌟 " + t("live_agent_outputs"))
1033
+ st.info("Our AI agents will show their work here as they create your itinerary")
1034
+ st.markdown('</div>', unsafe_allow_html=True)
1035
+ st.session_state.current_step = 0
1036
+
1037
+ update_step_status(0, 'active')
1038
+ with st.session_state.progress_placeholder.container():
1039
+ display_modern_progress(st.session_state.current_step)
1040
+ destination_info = run_task_with_logs(
1041
+ destination_research_task,
1042
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
1043
+ log_container,
1044
+ output_container,
1045
+ "destination_info"
1046
+ )
1047
+ update_step_status(0, 'complete')
1048
+ st.session_state.current_step = 1
1049
+ update_step_status(1, 'active')
1050
+ with st.session_state.progress_placeholder.container():
1051
+ display_modern_progress(st.session_state.current_step)
1052
+ accommodation_info = run_task_with_logs(
1053
+ accommodation_task,
1054
+ modified_input_context.format(destination=user_input['destination'], budget=user_input['budget'], preferences=user_input['preferences']),
1055
+ log_container,
1056
+ output_container,
1057
+ "accommodation_info"
1058
+ )
1059
+ update_step_status(1, 'complete')
1060
+ st.session_state.current_step = 2
1061
+ update_step_status(2, 'active')
1062
+ with st.session_state.progress_placeholder.container():
1063
+ display_modern_progress(st.session_state.current_step)
1064
+ transportation_info = run_task_with_logs(
1065
+ transportation_task,
1066
+ modified_input_context.format(origin=user_input['origin'], destination=user_input['destination']),
1067
+ log_container,
1068
+ output_container,
1069
+ "transportation_info"
1070
+ )
1071
+ update_step_status(2, 'complete')
1072
+ st.session_state.current_step = 3
1073
+ update_step_status(3, 'active')
1074
+ with st.session_state.progress_placeholder.container():
1075
+ display_modern_progress(st.session_state.current_step)
1076
+ activities_info = run_task_with_logs(
1077
+ activities_task,
1078
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
1079
+ log_container,
1080
+ output_container,
1081
+ "activities_info"
1082
+ )
1083
+ update_step_status(3, 'complete')
1084
+ st.session_state.current_step = 4
1085
+ update_step_status(4, 'active')
1086
+ with st.session_state.progress_placeholder.container():
1087
+ display_modern_progress(st.session_state.current_step)
1088
+ dining_info = run_task_with_logs(
1089
+ dining_task,
1090
+ modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']),
1091
+ log_container,
1092
+ output_container,
1093
+ "dining_info"
1094
+ )
1095
+ update_step_status(4, 'complete')
1096
+ st.session_state.current_step = 5
1097
+ update_step_status(5, 'active')
1098
+ with st.session_state.progress_placeholder.container():
1099
+ display_modern_progress(st.session_state.current_step)
1100
+ combined_info = f"""{input_context}
1101
+
1102
+ Destination Information:
1103
+ {destination_info}
1104
+
1105
+ Accommodation Options:
1106
+ {accommodation_info}
1107
+
1108
+ Transportation Plan:
1109
+ {transportation_info}
1110
+
1111
+ Recommended Activities:
1112
+ {activities_info}
1113
+
1114
+ Dining Recommendations:
1115
+ {dining_info}
1116
+ """
1117
+ itinerary = run_task_with_logs(
1118
+ itinerary_task,
1119
+ combined_info.format(duration=user_input['duration'], origin=user_input['origin'], destination=user_input['destination']),
1120
+ log_container,
1121
+ output_container,
1122
+ "itinerary"
1123
+ )
1124
+ update_step_status(5, 'complete')
1125
+ st.session_state.current_step = 6
1126
+ with st.session_state.progress_placeholder.container():
1127
+ display_modern_progress(st.session_state.current_step)
1128
+ st.session_state.generated_itinerary = itinerary
1129
+ st.session_state.generation_complete = True
1130
+ date_str = datetime.now().strftime("%Y-%m-%d")
1131
+ st.session_state.filename = f"{user_input['destination'].replace(' ', '_')}_{date_str}_itinerary.txt"
1132
+
1133
+ if st.session_state.generation_complete:
1134
+ st.markdown("""
1135
+ <div class="modern-card animate-in">
1136
+ <div style="display: flex; justify-content: center; margin-bottom: 20px;">
1137
+ <div class="success-animation">
1138
+ <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
1139
+ <circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" />
1140
+ <path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8" />
1141
+ </svg>
1142
+ </div>
1143
+ </div>
1144
+ <h2 style="text-align: center; color: #4361ee;">""" + t("itinerary_ready") + """</h2>
1145
+ <p style="text-align: center; color: #6c757d; margin-bottom: 20px;">""" + t("personalized_experience") + """</p>
1146
+ </div>
1147
+
1148
+ <style>
1149
+ .success-animation {
1150
+ width: 100px;
1151
+ height: 100px;
1152
+ position: relative;
1153
+ }
1154
+ .checkmark {
1155
+ width: 100px;
1156
+ height: 100px;
1157
+ border-radius: 50%;
1158
+ display: block;
1159
+ stroke-width: 2;
1160
+ stroke: #4361ee;
1161
+ stroke-miterlimit: 10;
1162
+ box-shadow: 0 0 20px rgba(67, 97, 238, 0.3);
1163
+ animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
1164
+ }
1165
+ .checkmark__circle {
1166
+ stroke-dasharray: 166;
1167
+ stroke-dashoffset: 166;
1168
+ stroke-width: 2;
1169
+ stroke-miterlimit: 10;
1170
+ stroke: #4361ee;
1171
+ fill: none;
1172
+ animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
1173
+ }
1174
+ .checkmark__check {
1175
+ transform-origin: 50% 50%;
1176
+ stroke-dasharray: 48;
1177
+ stroke-dashoffset: 48;
1178
+ animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
1179
+ }
1180
+ @keyframes stroke {
1181
+ 100% { stroke-dashoffset: 0; }
1182
+ }
1183
+ @keyframes scale {
1184
+ 0%, 100% { transform: none; }
1185
+ 50% { transform: scale3d(1.1, 1.1, 1); }
1186
+ }
1187
+ @keyframes fill {
1188
+ 100% { box-shadow: 0 0 20px rgba(67, 97, 238, 0.3); }
1189
+ }
1190
+ </style>
1191
+ """, unsafe_allow_html=True)
1192
+
1193
+ # 탭 생성 (챗봇 탭 포함)
1194
+ itinerary_tab, details_tab, download_tab, map_tab, chatbot_tab = st.tabs([
1195
+ "🗒️ " + t("full_itinerary"),
1196
+ "💼 " + t("details"),
1197
+ "💾 " + t("download_share"),
1198
+ "🗺️ 지도 및 시각화",
1199
+ "🤖 챗봇 인터페이스"
1200
+ ])
1201
+
1202
+ # 일정 탭
1203
+ with itinerary_tab:
1204
+ st.text_area("Your Itinerary", st.session_state.generated_itinerary, height=600)
1205
+
1206
+ # 상세 정보 탭
1207
+ with details_tab:
1208
+ agent_tabs = st.tabs(["🌎 Destination", "🏨 Accommodation", "🚗 Transportation", "🎭 Activities", "🍽️ Dining"])
1209
+ with agent_tabs[0]:
1210
+ st.markdown("### 🌎 Destination Research")
1211
+ st.markdown(st.session_state.results["destination_info"])
1212
+ with agent_tabs[1]:
1213
+ st.markdown("### 🏨 Accommodation Options")
1214
+ st.markdown(st.session_state.results["accommodation_info"])
1215
+ with agent_tabs[2]:
1216
+ st.markdown("### 🚗 Transportation Plan")
1217
+ st.markdown(st.session_state.results["transportation_info"])
1218
+ with agent_tabs[3]:
1219
+ st.markdown("### 🎭 Recommended Activities")
1220
+ st.markdown(st.session_state.results["activities_info"])
1221
+ with agent_tabs[4]:
1222
+ st.markdown("### 🍽️ Dining Recommendations")
1223
+ st.markdown(st.session_state.results["dining_info"])
1224
+
1225
+ # 다운로드 및 공유 탭
1226
+ with download_tab:
1227
+ col1, col2 = st.columns([2, 1])
1228
+ with col1:
1229
+ st.markdown("### " + t("save_itinerary"))
1230
+ st.markdown("Download your personalized travel plan to access it offline or share with your travel companions.")
1231
+ st.markdown("""
1232
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px; margin-top: 20px;">
1233
+ <h4 style="margin-top: 0;">""" + t("your_itinerary_file") + """</h4>
1234
+ <p style="font-size: 0.9rem; color: #6c757d;">""" + t("text_format") + """</p>
1235
+ """, unsafe_allow_html=True)
1236
+ st.markdown("<div style='margin: 10px 0;'>" + get_download_link(st.session_state.generated_itinerary, st.session_state.filename) + "</div>", unsafe_allow_html=True)
1237
+ st.markdown("</div>", unsafe_allow_html=True)
1238
+ st.markdown("### " + t("share_itinerary"))
1239
+ st.markdown("*Coming soon: Email your itinerary or share via social media.*")
1240
+ with col2:
1241
+ st.markdown("### " + t("save_for_mobile"))
1242
+ st.markdown("*Coming soon: QR code for easy access on your phone*")
1243
+
1244
+ # 지도 및 시각화 탭
1245
+ with map_tab:
1246
+ st.markdown("### 목적지 지도")
1247
+ # session_state에서 목적지 값을 가져옵니다. (없을 경우 기본값 "Paris")
1248
+ dest = st.session_state.get("destination", "Paris")
1249
+
1250
+ # 지오코딩을 통해 위도, 경도 구하기
1251
+ try:
1252
+ geolocator = Nominatim(user_agent="travel_app")
1253
+ location = geolocator.geocode(dest)
1254
+ if location:
1255
+ lat, lon = location.latitude, location.longitude
1256
+ else:
1257
+ st.error("입력하신 목적지를 찾을 수 없습니다. 기본 좌표를 사용합니다.")
1258
+ lat, lon = 48.8566, 2.3522 # Paris 기본값
1259
+ except Exception as e:
1260
+ st.error("지오코딩 오류 발생: " + str(e))
1261
+ lat, lon = 48.8566, 2.3522
1262
+
1263
+ # 지도 데이터 생성 (필요시 목적지 주변 명소 데이터를 동적으로 생성 가능)
1264
+ map_data = pd.DataFrame({
1265
+ "lat": [lat],
1266
+ "lon": [lon],
1267
+ "name": [dest]
1268
+ })
1269
+ st.map(map_data)
1270
+
1271
+ st.markdown("#### Pydeck을 활용한 인터랙티브 지도 예시")
1272
+ layer = pdk.Layer(
1273
+ "ScatterplotLayer",
1274
+ data=map_data,
1275
+ get_position='[lon, lat]',
1276
+ get_color='[200, 30, 0, 160]',
1277
+ get_radius=200,
1278
+ )
1279
+ view_state = pdk.ViewState(
1280
+ latitude=lat,
1281
+ longitude=lon,
1282
+ zoom=12,
1283
+ pitch=50,
1284
+ )
1285
+ deck_chart = pdk.Deck(layers=[layer], initial_view_state=view_state)
1286
+ st.pydeck_chart(deck_chart)
1287
+
1288
+ # 챗봇 인터페이스 탭 (Clear 버튼 제거됨)
1289
+ with chatbot_tab:
1290
+ st.markdown("### AI 챗봇 인터페이스")
1291
+
1292
+ # 대화 기록을 세션 상태에 저장 (메시지, 발신자, 타임스탬프)
1293
+ if "chat_history" not in st.session_state:
1294
+ st.session_state.chat_history = []
1295
+
1296
+ # 사용자 입력창 및 전송 버튼
1297
+ user_message = st.text_input("메시지를 입력하세요:", key="chat_input")
1298
+ if st.button("전송", key="send_button"):
1299
+ if user_message:
1300
+ # 제미나이 기반 챗봇 응답: run_task()를 활용하여 chatbot_task에 질의
1301
+ response = run_task(chatbot_task, user_message)
1302
+ st.session_state.chat_history.append({
1303
+ "speaker": "사용자",
1304
+ "message": user_message,
1305
+ "time": datetime.now()
1306
+ })
1307
+ st.session_state.chat_history.append({
1308
+ "speaker": "AI",
1309
+ "message": response,
1310
+ "time": datetime.now()
1311
+ })
1312
+
1313
+ # 대화 기록 출력 (타임스탬프 포함, 스크롤 가능한 영역)
1314
+ st.markdown("<div style='max-height:400px; overflow-y:auto; padding:10px; border:1px solid #eaeaea; border-radius:6px;'>", unsafe_allow_html=True)
1315
+ for chat in st.session_state.chat_history:
1316
+ time_str = chat["time"].strftime("%H:%M:%S")
1317
+ st.markdown(f"**{chat['speaker']}** ({time_str}): {chat['message']}")
1318
+ st.markdown("</div>", unsafe_allow_html=True)
1319
+
1320
+ st.markdown("""
1321
+ <div style="margin-top: 50px; text-align: center; padding: 20px; color: #6c757d; font-size: 0.8rem;">
1322
+ <p>""" + t("built_with") + """</p>
1323
+ <p style="margin-top: 10px;">Created by TechMatrix Solvers for <a href="https://www.hackbyte.in/" target="_blank">IIITDMJ HackByte3.0</a> (April 2025)</p>
1324
+ <div style="display: flex; justify-content: center; gap: 15px; margin-top: 10px;">
1325
+ <a href="https://www.linkedin.com/in/abhay-gupta-197b17264/" target="_blank" style="color: #0077B5;">Abhay</a>
1326
+ <a href="https://www.linkedin.com/in/jay-kumar-jk/" target="_blank" style="color: #0077B5;">Jay</a>
1327
+ <a href="https://www.linkedin.com/in/kripanshu-gupta-a66349261/" target="_blank" style="color: #0077B5;">Kripanshu</a>
1328
+ <a href="https://www.linkedin.com/in/aditi-soni-259813285/" target="_blank" style="color: #0077B5;">Aditi</a>
1329
+ </div>
1330
+ </div>
1331
+ """, unsafe_allow_html=True)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ google-generativeai==0.5.2
2
+ langchain-google-genai==1.0.2
3
+ streamlit==1.32.0
4
+ langchain==0.1.11
5
+ geopy
travel.py ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ # travel.py - AI Agent System for Travel Planning
3
+ # ----------------------------------------------
4
+ #
5
+ # This module implements a sophisticated multi-agent system for travel planning
6
+ # with a specific focus on Indian travel destinations and experiences.
7
+ #
8
+ # ARCHITECTURE:
9
+ # The system uses a collection of specialized AI agents, each responsible for
10
+ # a different aspect of travel planning:
11
+ #
12
+ # 1. Destination Research Agent - Researches destinations based on user preferences
13
+ # 2. Accommodation Agent - Suggests suitable accommodations meeting budget and preference constraints
14
+ # 3. Transportation Agent - Plans optimal transportation between locations
15
+ # 4. Activities Agent - Curates personalized activities based on interests
16
+ # 5. Dining Agent - Recommends dining experiences showcasing local cuisine
17
+ # 6. Itinerary Agent - Compiles all recommendations into a cohesive day-by-day plan
18
+ # 7. Chatbot Agent - Provides conversational responses to travel queries
19
+ #
20
+ # Each agent is powered by Google's Generative AI (Gemini) and is specialized through
21
+ # careful system prompting that defines its role, goals, and expected outputs.
22
+ #
23
+ # WORKFLOW:
24
+ # 1. User submits travel preferences
25
+ # 2. Each agent processes the information in sequence
26
+ # 3. Final itinerary is compiled from all agent outputs
27
+ # 4. Results are presented to the user as a complete travel plan
28
+ #
29
+ # PRIMARY FUNCTIONS:
30
+ # - run_task(): Core function to execute a specific agent task
31
+ # - generate_travel_itinerary(): Orchestrates the full planning process
32
+ # - save_itinerary_to_file(): Saves the generated itinerary for the user
33
+ #
34
+ # Created by TechMatrix Solvers for IIITDMJ HackByte3.0
35
+ """
36
+
37
+ import os
38
+ import json
39
+ import logging
40
+ from datetime import datetime, timedelta
41
+ from langchain_google_genai import ChatGoogleGenerativeAI
42
+ from langchain.schema import SystemMessage, HumanMessage
43
+
44
+ # Setup logging configuration
45
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
46
+
47
+ # -------------------------------------------------------------------------------
48
+ # Agent and Task Classes with Type Hints and Docstrings
49
+ # -------------------------------------------------------------------------------
50
+ class Agent:
51
+ def __init__(self, role: str, goal: str, backstory: str, personality: str = "", llm=None) -> None:
52
+ """
53
+ Initialize an Agent with role, goal, backstory, personality, and assigned LLM.
54
+ """
55
+ self.role = role
56
+ self.goal = goal
57
+ self.backstory = backstory
58
+ self.personality = personality
59
+ self.tools = [] # Initialize with empty list for future tool integrations
60
+ self.llm = llm
61
+
62
+ class Task:
63
+ def __init__(self, description: str, agent: Agent, expected_output: str, context=None) -> None:
64
+ """
65
+ Initialize a Task with its description, the responsible agent, expected output, and optional context.
66
+ """
67
+ self.description = description
68
+ self.agent = agent
69
+ self.expected_output = expected_output
70
+ self.context = context or []
71
+
72
+ # -------------------------------------------------------------------------------
73
+ # Initialize LLM
74
+ # -------------------------------------------------------------------------------
75
+ google_api_key = os.getenv("GEMINI_API_KEY") # Use Google API key from environment variables
76
+ if not google_api_key:
77
+ logging.error("GEMINI_API_KEY is not set in the environment variables.")
78
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", google_api_key=google_api_key)
79
+
80
+ # -------------------------------------------------------------------------------
81
+ # Define Travel Agents
82
+ # -------------------------------------------------------------------------------
83
+ destination_research_agent = Agent(
84
+ role="Destination Research Agent",
85
+ goal=(
86
+ "Research and provide comprehensive information about the destination including popular attractions, "
87
+ "local culture, weather patterns, best times to visit, and local transportation options with special focus on Indian context."
88
+ ),
89
+ backstory=(
90
+ "An experienced travel researcher with extensive knowledge of Indian destinations. "
91
+ "I specialize in uncovering both popular attractions and hidden gems that match travelers' interests."
92
+ ),
93
+ personality="Curious, detail-oriented, and knowledgeable about Indian cultures and travel trends.",
94
+ llm=llm,
95
+ )
96
+
97
+ accommodation_agent = Agent(
98
+ role="Accommodation Agent",
99
+ goal="Find and recommend suitable accommodations based on the traveler's preferences, budget, and location requirements with focus on Indian hospitality options.",
100
+ backstory="A hospitality expert who understands different types of accommodations in India and can match travelers with their ideal places to stay.",
101
+ personality="Attentive, resourceful, and focused on comfort and value.",
102
+ llm=llm,
103
+ )
104
+
105
+ transportation_agent = Agent(
106
+ role="Transportation Agent",
107
+ goal="Plan efficient transportation between the origin, destination, and all points of interest in the itinerary using Indian transportation networks.",
108
+ backstory="A logistics specialist with knowledge of India's transportation systems, from flights to local transit options including trains, buses, and auto-rickshaws.",
109
+ personality="Efficient, practical, and detail-oriented.",
110
+ llm=llm,
111
+ )
112
+
113
+ activities_agent = Agent(
114
+ role="Activities & Attractions Agent",
115
+ goal="Curate personalized activities and attractions that align with the traveler's interests, preferences, and time constraints in Indian destinations.",
116
+ backstory="An enthusiastic explorer who has experienced diverse activities across India and knows how to match experiences to individual preferences.",
117
+ personality="Enthusiastic, creative, and personable.",
118
+ llm=llm,
119
+ )
120
+
121
+ dining_agent = Agent(
122
+ role="Dining & Culinary Agent",
123
+ goal="Recommend dining experiences that showcase local Indian cuisine while accommodating dietary preferences and budget considerations.",
124
+ backstory="A culinary expert with knowledge of India's diverse food scenes and an appreciation for authentic local dining experiences.",
125
+ personality="Passionate about food, culturally aware, and attentive to preferences.",
126
+ llm=llm,
127
+ )
128
+
129
+ itinerary_agent = Agent(
130
+ role="Itinerary Integration Agent",
131
+ goal="Compile all recommendations into a cohesive, day-by-day itinerary that optimizes time, minimizes travel fatigue, and maximizes enjoyment for travel in India.",
132
+ backstory="A master travel planner who understands how to balance activities, rest, and logistics to create the perfect Indian travel experience.",
133
+ personality="Organized, balanced, and practical.",
134
+ llm=llm,
135
+ )
136
+
137
+ # -------------------------------------------------------------------------------
138
+ # Define Chatbot Agent and Task for Interactive Conversation
139
+ # -------------------------------------------------------------------------------
140
+ chatbot_agent = Agent(
141
+ role="Chatbot Agent",
142
+ goal="Engage in interactive conversation to answer travel-related queries about India.",
143
+ backstory="A conversational AI assistant who provides instant, accurate travel information and recommendations for Indian destinations.",
144
+ personality="Friendly, conversational, and knowledgeable about travel in India.",
145
+ llm=llm,
146
+ )
147
+
148
+ chatbot_task = Task(
149
+ description="Provide a conversational and detailed response to travel-related queries about Indian destinations.",
150
+ agent=chatbot_agent,
151
+ expected_output="A friendly, helpful response to the user's query about travel in India."
152
+ )
153
+
154
+ # -------------------------------------------------------------------------------
155
+ # Define Other Travel Tasks
156
+ # -------------------------------------------------------------------------------
157
+ destination_research_task = Task(
158
+ description="""Research {destination} thoroughly, considering the traveler's interests in {preferences}.
159
+
160
+ Efficient research parameters:
161
+ - Prioritize research in these critical categories:
162
+ * Top attractions that match specific {preferences} (not generic lists)
163
+ * Local transportation systems with cost-efficiency analysis
164
+ * Neighborhood breakdown with accommodation recommendations by budget tier
165
+ * Seasonal considerations for the specific travel dates
166
+ * Safety assessment with specific areas to embrace or avoid
167
+ * Cultural norms that impact visitor experience (dress codes, tipping, etiquette)
168
+
169
+ - Apply efficiency filters:
170
+ * Focus exclusively on verified information from official tourism boards, recent travel guides, and reliable local sources
171
+ * Analyze recent visitor reviews (< 6 months old) to identify changing conditions
172
+ * Evaluate price-to-experience value for attractions instead of just popularity
173
+ * Identify logistical clusters where multiple interests can be satisfied efficiently
174
+ * Research off-peak times for popular attractions to minimize waiting
175
+ * Evaluate digital tools (apps, passes, reservation systems) that streamline the visit
176
+
177
+ - Create practical knowledge matrices:
178
+ * Transportation method comparison (cost vs. time vs. convenience)
179
+ * Weather impact on specific activities
180
+ * Budget allocation recommendations based on preference priorities
181
+ * Time-saving opportunity identification""",
182
+ agent=destination_research_agent,
183
+ expected_output="""Targeted destination brief containing:
184
+ 1. Executive summary highlighting the 5 most relevant aspects based on {preferences}
185
+ 2. Neighborhood analysis with accommodation recommendations mapped to specific interests
186
+ 3. Transportation efficiency guide with cost/convenience matrix
187
+ 4. Cultural briefing focusing only on need-to-know information that impacts daily activities
188
+ 5. Seasonal advantages and challenges specific to travel dates
189
+ 6. Digital resource toolkit (essential apps, websites, reservation systems)
190
+ 7. Budget optimization strategies with price ranges for key experiences
191
+ 8. Safety and health quick-reference including emergency contacts
192
+ 9. Logistics efficiency map showing optimal activity clustering
193
+ 10. Local insider advantage recommendations that save time or money
194
+
195
+ Format should prioritize scannable information with bullet points, comparison tables, and decision matrices rather than lengthy prose."""
196
+ )
197
+
198
+ accommodation_task = Task(
199
+ description="Find suitable accommodations in {destination} based on a {budget} budget and preferences for {preferences}. Focus on Indian accommodations including heritage hotels, homestays, and modern options.",
200
+ agent=accommodation_agent,
201
+ expected_output="List of recommended accommodations with details on location, amenities, price range, and availability."
202
+ )
203
+
204
+ transportation_task = Task(
205
+ description="Plan transportation from {origin} to {destination} and local transportation options during the stay. Include Indian railways, local buses, metro systems, and ride-hailing services where available.",
206
+ agent=transportation_agent,
207
+ expected_output="Transportation plan including flights/routes to the destination and recommendations for getting around locally."
208
+ )
209
+
210
+ activities_task = Task(
211
+ description="""Suggest activities and attractions in {destination} that align with interests in {preferences}.
212
+
213
+ Detailed requirements:
214
+ - Categorize activities into: Cultural Experiences, Outdoor Adventures, Culinary Experiences,
215
+ Entertainment & Nightlife, Family-Friendly Activities, and Local Hidden Gems
216
+ - For each activity, include:
217
+ * Detailed description with historical/cultural context where relevant
218
+ * Precise location with neighborhood information
219
+ * Operating hours with seasonal variations noted
220
+ * Pricing information with different ticket options/packages
221
+ * Accessibility considerations for travelers with mobility limitations
222
+ * Recommended duration for the activity (minimum and ideal time)
223
+ * Best time of day/week/year to visit
224
+ * Crowd levels by season
225
+ * Photography opportunities and restrictions
226
+ * Required reservations or booking windows
227
+ - Include a mix of iconic must-see attractions and off-the-beaten-path experiences
228
+ - Consider weather patterns in {destination} during travel period
229
+ - Analyze the {preferences} to match specific personality types and interest levels
230
+ - Include at least 2-3 rainy day alternatives for outdoor activities
231
+ - Provide local transportation options to reach each attraction
232
+ - Note authentic local experiences that provide cultural immersion
233
+ - Flag any activities requiring special equipment, permits, or physical fitness levels""",
234
+ agent=activities_agent,
235
+ expected_output="""Comprehensive curated list of activities and attractions with:
236
+ 1. Clear categorization by type (cultural, outdoor, culinary, entertainment, family-friendly, hidden gems)
237
+ 2. Essential details for planning (location, timing, cost, requirements)
238
+ 3. Special recommendations based on {preferences} with personalized suggestions
239
+ 4. Weather contingency options
240
+ 5. Insider tips for maximizing experience value
241
+ 6. Logistical guidance for efficient navigation between attractions"""
242
+ )
243
+
244
+ dining_task = Task(
245
+ description="""Recommend dining options in {destination} that showcase local cuisine while accommodating {preferences} with special attention to authentic Indian culinary experiences.
246
+
247
+ For each recommended dining establishment, provide:
248
+ - Name, location, and cuisine type
249
+ - Signature dishes and culinary specialties
250
+ - Price range and value assessment
251
+ - Ambiance description and dress code if applicable
252
+ - Operating hours and reservation policies
253
+ - Local popularity vs. tourist popularity
254
+ - Special dietary accommodations (vegetarian, vegan, gluten-free, etc.)
255
+ - Authentic cultural dining experiences that provide insight into local life""",
256
+ agent=dining_agent,
257
+ expected_output="""Curated dining guide for {destination} with:
258
+ 1. Top restaurant recommendations categorized by meal type, cuisine, and price point
259
+ 2. Must-try local dishes specific to the region
260
+ 3. Unique dining experiences that reflect local culture
261
+ 4. Budget-friendly options with exceptional value
262
+ 5. Strategic meal timing to complement activity schedule
263
+ 6. Advance reservation requirements where applicable
264
+ 7. Special dietary consideration options
265
+ 8. Hidden gems frequented by locals"""
266
+ )
267
+
268
+ itinerary_task = Task(
269
+ description="""Create a day-by-day itinerary for a {duration}-day trip to {destination} based on all previous research and recommendations. Focus on authentic experiences that reflect true Indian culture and heritage.
270
+
271
+ The itinerary should:
272
+ - Balance activities with rest and travel time
273
+ - Group attractions by geographic proximity to minimize transit time
274
+ - Consider opening hours, best times to visit, and seasonal factors
275
+ - Include meal recommendations that complement the day's activities
276
+ - Provide contingency options for weather or unexpected closures
277
+ - Balance must-see attractions with authentic local experiences
278
+ - Account for {preferences} in prioritizing experiences
279
+ - Include precise timing, transportation methods, and logistical details
280
+ - Optimize for the traveler's interests while maintaining a realistic pace
281
+ - Incorporate free time for exploration and spontaneity""",
282
+ agent=itinerary_agent,
283
+ expected_output="""Complete day-by-day itinerary containing:
284
+ 1. Daily schedule with timing, locations, and activities
285
+ 2. Transportation details between all points
286
+ 3. Meal recommendations with timing and cuisine type
287
+ 4. Activity duration estimates with buffer time included
288
+ 5. Alternative options for flexibility
289
+ 6. Booking/reservation requirements with deadlines
290
+ 7. Cost estimates for budgeting purposes
291
+ 8. Local cultural insights for each experience
292
+ 9. Strategic planning notes (best photo spots, crowd avoidance, etc.)
293
+ 10. Evening entertainment options
294
+
295
+ Format should be scannable with clear headings, timing, and logistical details for easy reference during travel."""
296
+ )
297
+
298
+ # -------------------------------------------------------------------------------
299
+ # Helper Function to Run a Task with Full Agent & Task Information
300
+ # -------------------------------------------------------------------------------
301
+ def run_task(task: Task, input_text: str) -> str:
302
+ """
303
+ Executes the given task using the associated agent's LLM and returns the response content.
304
+ """
305
+ try:
306
+ if not isinstance(task, Task):
307
+ raise ValueError(f"Expected 'task' to be an instance of Task, got {type(task)}")
308
+ if not hasattr(task, 'agent') or not isinstance(task.agent, Agent):
309
+ raise ValueError("Task must have a valid 'agent' attribute of type Agent.")
310
+
311
+ system_input = (
312
+ f"Agent Details:\n"
313
+ f"Role: {task.agent.role}\n"
314
+ f"Goal: {task.agent.goal}\n"
315
+ f"Backstory: {task.agent.backstory}\n"
316
+ f"Personality: {task.agent.personality}\n"
317
+ )
318
+ task_input = (
319
+ f"Task Details:\n"
320
+ f"Task Description: {task.description}\n"
321
+ f"Expected Output: {task.expected_output}\n"
322
+ f"Input for Task:\n{input_text}\n"
323
+ )
324
+ messages = [
325
+ SystemMessage(content=system_input),
326
+ HumanMessage(content=task_input)
327
+ ]
328
+ response = task.agent.llm.invoke(messages)
329
+ if not response or not response.content:
330
+ raise ValueError("Empty response from LLM.")
331
+ return response.content
332
+ except Exception as e:
333
+ logging.error(f"Error in task '{task.agent.role}': {e}")
334
+ return f"Error in {task.agent.role}: {e}"
335
+
336
+ # -------------------------------------------------------------------------------
337
+ # User Input Functions
338
+ # -------------------------------------------------------------------------------
339
+ def get_user_input() -> dict:
340
+ """
341
+ Collects user input for travel itinerary generation.
342
+ """
343
+ print("\n=== Travel Itinerary Generator ===\n")
344
+ origin = input("Enter your origin: ")
345
+ destination = input("Enter your destination: ")
346
+ duration = input("Enter duration in days: ")
347
+
348
+ current_date = datetime.now()
349
+ start_date = current_date + timedelta(days=7)
350
+ end_date = start_date + timedelta(days=int(duration))
351
+
352
+ preferences = input("Enter your preferences (comma separated): ")
353
+ budget = input("Enter your budget: ")
354
+
355
+ return {
356
+ "origin": origin,
357
+ "destination": destination,
358
+ "duration": duration,
359
+ "start_date": start_date.strftime("%Y-%m-%d"),
360
+ "end_date": end_date.strftime("%Y-%m-%d"),
361
+ "preferences": preferences,
362
+ "budget": budget
363
+ }
364
+
365
+ # -------------------------------------------------------------------------------
366
+ # Main Function to Generate Travel Itinerary
367
+ # -------------------------------------------------------------------------------
368
+ def generate_travel_itinerary(user_input: dict) -> str:
369
+ """
370
+ Generates a personalized travel itinerary by sequentially running defined tasks.
371
+ """
372
+ print("\nGenerating your personalized travel itinerary...\n")
373
+
374
+ # Create input context using f-string formatting
375
+ input_context = (
376
+ f"Travel Request Details:\n"
377
+ f"Origin: {user_input['origin']}\n"
378
+ f"Destination: {user_input['destination']}\n"
379
+ f"Duration: {user_input['duration']} days\n"
380
+ f"Budget Level: {user_input['budget']}\n"
381
+ f"Preferences/Interests: {user_input['preferences']}\n"
382
+ f"Special Requirements: {user_input['special_requirements']}\n"
383
+ )
384
+
385
+ # Step 1: Destination Research
386
+ print("Researching your destination...")
387
+ destination_info = run_task(destination_research_task, input_context)
388
+ print("✓ Destination research completed")
389
+
390
+ # Step 2: Accommodation Recommendations
391
+ print("Finding ideal accommodations...")
392
+ accommodation_info = run_task(accommodation_task, input_context)
393
+ print("✓ Accommodation recommendations completed")
394
+
395
+ # Step 3: Transportation Planning
396
+ print("Planning transportation...")
397
+ transportation_info = run_task(transportation_task, input_context)
398
+ print("✓ Transportation planning completed")
399
+
400
+ # Step 4: Activities & Attractions
401
+ print("Curating activities and attractions...")
402
+ activities_info = run_task(activities_task, input_context)
403
+ print("✓ Activities and attractions curated")
404
+
405
+ # Step 5: Dining Recommendations
406
+ print("Finding dining experiences...")
407
+ dining_info = run_task(dining_task, input_context)
408
+ print("✓ Dining recommendations completed")
409
+
410
+ # Step 6: Create Day-by-Day Itinerary
411
+ print("Creating your day-by-day itinerary...")
412
+ combined_info = (
413
+ input_context + "\n"
414
+ "Destination Information:\n" + destination_info + "\n"
415
+ "Accommodation Options:\n" + accommodation_info + "\n"
416
+ "Transportation Plan:\n" + transportation_info + "\n"
417
+ "Recommended Activities:\n" + activities_info + "\n"
418
+ "Dining Recommendations:\n" + dining_info + "\n"
419
+ )
420
+ itinerary = run_task(itinerary_task, combined_info)
421
+ print("✓ Itinerary creation completed")
422
+ print("✓ Itinerary generation completed")
423
+
424
+ return itinerary
425
+
426
+ # -------------------------------------------------------------------------------
427
+ # Save Itinerary to File
428
+ # -------------------------------------------------------------------------------
429
+ def save_itinerary_to_file(itinerary: str, user_input: dict, output_dir: str = None) -> str:
430
+ """
431
+ Saves the generated itinerary to a text file and returns the filepath.
432
+ """
433
+ date_str = datetime.now().strftime("%Y-%m-%d")
434
+ filename = f"India_Travel_Itinerary_{user_input['destination']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
435
+
436
+ if output_dir:
437
+ if not os.path.exists(output_dir):
438
+ try:
439
+ os.makedirs(output_dir)
440
+ logging.info(f"Created output directory: {output_dir}")
441
+ except Exception as e:
442
+ logging.error(f"Error creating directory {output_dir}: {e}")
443
+ return ""
444
+ filepath = os.path.join(output_dir, filename)
445
+ else:
446
+ filepath = filename
447
+
448
+ try:
449
+ with open(filepath, "w", encoding="utf-8") as f:
450
+ f.write(itinerary)
451
+ logging.info(f"Your itinerary has been saved as: {filepath}")
452
+ return filepath
453
+ except Exception as e:
454
+ logging.error(f"Error saving itinerary: {e}")
455
+ return ""
456
+
457
+ # -------------------------------------------------------------------------------
458
+ # Main Function
459
+ # -------------------------------------------------------------------------------
460
+ def main() -> None:
461
+ """
462
+ Main entry point for the travel itinerary generator application.
463
+ """
464
+ print("Welcome to the India Travel Planner! Let's create your perfect itinerary.")
465
+ user_input = get_user_input()
466
+
467
+ print("\nGenerating your personalized travel itinerary...\n")
468
+ itinerary = generate_travel_itinerary(user_input)
469
+
470
+ print("\n" + "=" * 50)
471
+ print("Your travel itinerary is ready!")
472
+ print("=" * 50 + "\n")
473
+
474
+ print(itinerary)
475
+
476
+ output_file = save_itinerary_to_file(itinerary, user_input)
477
+ print(f"\nYour itinerary has been saved to: {output_file}")
478
+
479
+ if __name__ == "__main__":
480
+ main()