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)