openfree commited on
Commit
00b4149
·
verified ·
1 Parent(s): b0a15f7

Create app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +1234 -0
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)