Spaces:
Running
Running
import streamlit as st | |
import os | |
import json | |
from datetime import datetime, timedelta | |
import base64 | |
import pandas as pd | |
import pydeck as pdk | |
from travel import ( | |
destination_research_task, accommodation_task, transportation_task, | |
activities_task, dining_task, itinerary_task, chatbot_task, | |
run_task | |
) | |
# st.set_page_config()๋ ๋ค๋ฅธ Streamlit ํจ์๋ณด๋ค ๊ฐ์ฅ ๋จผ์ ์คํ๋์ด์ผ ํฉ๋๋ค. | |
st.set_page_config( | |
page_title="Your AI Agent for Travelling", | |
page_icon="โ๏ธ", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# ------------------------------------------ | |
# ๋ค๊ตญ์ด ์ง์์ ์ํ ๋ฒ์ญ ์ฌ์ ๋ฐ ํฌํผ ํจ์ | |
# ------------------------------------------ | |
translations = { | |
"en": { | |
"page_title": "Your AI Agent for Travelling", | |
"header": "Your AI Agent for Travelling", | |
"create_itinerary": "Create Your Itinerary", | |
"trip_details": "Trip Details", | |
"origin": "Origin", | |
"destination": "Destination", | |
"travel_dates": "Travel Dates", | |
"duration": "Duration (days)", | |
"preferences": "Preferences", | |
"additional_preferences": "Additional Preferences", | |
"interests": "Interests", | |
"special_requirements": "Special Requirements", | |
"submit": "๐ Create My Personal Travel Itinerary", | |
"request_details": "Your Travel Request", | |
"from": "From", | |
"when": "When", | |
"budget": "Budget", | |
"travel_style": "Travel Style", | |
"live_agent_outputs": "Live Agent Outputs", | |
"full_itinerary": "Full Itinerary", | |
"details": "Details", | |
"download_share": "Download & Share", | |
"save_itinerary": "Save Your Itinerary", | |
"plan_another_trip": "๐ Plan Another Trip", | |
"about": "About", | |
"how_it_works": "How it works", | |
"travel_agents": "Travel Agents", | |
"share_itinerary": "Share Your Itinerary", | |
"save_for_mobile": "Save for Mobile", | |
"built_with": "Built with โค๏ธ for you", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "Your Travel Itinerary is Ready! ๐", | |
"personalized_experience": "We've created a personalized travel experience just for you. Explore your itinerary below.", | |
"agent_activity": "Agent Activity", | |
"error_origin_destination": "Please enter both origin and destination.", | |
"your_itinerary_file": "Your Itinerary File", | |
"text_format": "Text format - Can be opened in any text editor" | |
}, | |
"ko": { | |
"page_title": "๋น์ ์ ์ฌํ์ ์ํ AI ์์ด์ ํธ", | |
"header": "๋น์ ์ ์ฌํ์ ์ํ AI ์์ด์ ํธ", | |
"create_itinerary": "์ฌํ ์ผ์ ์์ฑ", | |
"trip_details": "์ฌํ ์ธ๋ถ ์ ๋ณด", | |
"origin": "์ถ๋ฐ์ง", | |
"destination": "๋ชฉ์ ์ง", | |
"travel_dates": "์ฌํ ๋ ์ง", | |
"duration": "๊ธฐ๊ฐ (์ผ์)", | |
"preferences": "์ ํธ์ฌํญ", | |
"additional_preferences": "์ถ๊ฐ ์ ํธ์ฌํญ", | |
"interests": "๊ด์ฌ์ฌ", | |
"special_requirements": "ํน๋ณ ์๊ตฌ์ฌํญ", | |
"submit": "๐ ๋๋ง์ ์ฌํ ์ผ์ ์์ฑ", | |
"request_details": "์ฌํ ์์ฒญ ์ ๋ณด", | |
"from": "์ถ๋ฐ์ง", | |
"when": "์ฌํ ๊ธฐ๊ฐ", | |
"budget": "์์ฐ", | |
"travel_style": "์ฌํ ์คํ์ผ", | |
"live_agent_outputs": "์ค์๊ฐ ์์ด์ ํธ ๊ฒฐ๊ณผ", | |
"full_itinerary": "์ ์ฒด ์ผ์ ", | |
"details": "์ธ๋ถ์ฌํญ", | |
"download_share": "๋ค์ด๋ก๋ ๋ฐ ๊ณต์ ", | |
"save_itinerary": "์ผ์ ์ ์ฅ", | |
"plan_another_trip": "๐ ๋ค๋ฅธ ์ฌํ ๊ณํ", | |
"about": "์๊ฐ", | |
"how_it_works": "์๋ ๋ฐฉ์", | |
"travel_agents": "์ฌํ ์์ด์ ํธ", | |
"share_itinerary": "์ผ์ ๊ณต์ ", | |
"save_for_mobile": "๋ชจ๋ฐ์ผ ์ ์ฅ", | |
"built_with": "๋น์ ์ ์ํด โค๏ธ ๋ง๋ค์ด์ก์ต๋๋ค", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "์ฌํ ์ผ์ ์ด ์ค๋น๋์์ต๋๋ค! ๐", | |
"personalized_experience": "๋น์ ๋ง์ ์ํ ๋ง์ถคํ ์ฌํ ๊ฒฝํ์ด ๋ง๋ค์ด์ก์ต๋๋ค. ์๋์์ ์ผ์ ์ ํ์ธํ์ธ์.", | |
"agent_activity": "์์ด์ ํธ ํ๋", | |
"error_origin_destination": "์ถ๋ฐ์ง์ ๋ชฉ์ ์ง๋ฅผ ๋ชจ๋ ์ ๋ ฅํ์ธ์.", | |
"your_itinerary_file": "๋น์ ์ ์ฌํ ์ผ์ ํ์ผ", | |
"text_format": "ํ ์คํธ ํ์ - ๋ชจ๋ ํ ์คํธ ํธ์ง๊ธฐ์์ ์ด ์ ์์ต๋๋ค." | |
}, | |
"ja": { | |
"page_title": "ใใชใใฎๆ ่กใฎใใใฎAIใจใผใธใงใณใ", | |
"header": "ใใชใใฎๆ ่กใฎใใใฎAIใจใผใธใงใณใ", | |
"create_itinerary": "ๆ ่กใใฉใณไฝๆ", | |
"trip_details": "ๆ ่ก่ฉณ็ดฐ", | |
"origin": "ๅบ็บๅฐ", | |
"destination": "็ฎ็ๅฐ", | |
"travel_dates": "ๆ ่กๆฅ็จ", | |
"duration": "ๆ้๏ผๆฅๆฐ๏ผ", | |
"preferences": "ๅฅฝใฟ", | |
"additional_preferences": "่ฟฝๅ ใฎๅฅฝใฟ", | |
"interests": "่ๅณ", | |
"special_requirements": "็นๅฅใช่ฆไปถ", | |
"submit": "๐ ็งใฎใใใฎๆ ่กใใฉใณไฝๆ", | |
"request_details": "ๆ ่กใชใฏใจในใ", | |
"from": "ๅบ็บๅฐ", | |
"when": "ๆ ่กๆ้", | |
"budget": "ไบ็ฎ", | |
"travel_style": "ๆ ่กในใฟใคใซ", | |
"live_agent_outputs": "ใชใขใซใฟใคใ ใจใผใธใงใณใๅบๅ", | |
"full_itinerary": "ๅ จ่ก็จ", | |
"details": "่ฉณ็ดฐ", | |
"download_share": "ใใฆใณใญใผใใจๅ ฑๆ", | |
"save_itinerary": "ๆ ่กใใฉใณใไฟๅญ", | |
"plan_another_trip": "๐ ไปใฎๆ ่กใ่จ็ป", | |
"about": "ๆฆ่ฆ", | |
"how_it_works": "ไฝฟใๆน", | |
"travel_agents": "ๆ ่กใจใผใธใงใณใ", | |
"share_itinerary": "ๆ ่กใใฉใณใๅ ฑๆ", | |
"save_for_mobile": "ใขใใคใซไฟๅญ", | |
"built_with": "ๆใ่พผใใฆไฝใใใพใใ", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "ๆ ่กใใฉใณใฎๆบๅใใงใใพใใ๏ผ ๐", | |
"personalized_experience": "ใใชใใฎใใใซใใผใฝใใฉใคใบใใใๆ ่กไฝ้จใไฝๆใใพใใใไธใฎใใฉใณใใ่ฆงใใ ใใใ", | |
"agent_activity": "ใจใผใธใงใณใใขใฏใใฃใใใฃ", | |
"error_origin_destination": "ๅบ็บๅฐใจ็ฎ็ๅฐใฎไธกๆนใๅ ฅๅใใฆใใ ใใใ", | |
"your_itinerary_file": "ใใชใใฎๆ ่กใใฉใณใใกใคใซ", | |
"text_format": "ใใญในใๅฝขๅผ - ไปปๆใฎใใญในใใจใใฃใฟใง้ใใพใใ" | |
}, | |
"zh": { | |
"page_title": "ๆจ็ๆ ่ก AI ไปฃ็", | |
"header": "ๆจ็ๆ ่ก AI ไปฃ็", | |
"create_itinerary": "ๅๅปบๆจ็่ก็จ", | |
"trip_details": "ๆ ่ก่ฏฆๆ ", | |
"origin": "ๅบๅๅฐ", | |
"destination": "็ฎ็ๅฐ", | |
"travel_dates": "ๆ ่กๆฅๆ", | |
"duration": "ๅคฉๆฐ", | |
"preferences": "ๅๅฅฝ", | |
"additional_preferences": "ๅ ถไปๅๅฅฝ", | |
"interests": "ๅ ด่ถฃ", | |
"special_requirements": "็นๆฎ้ๆฑ", | |
"submit": "๐ ๅๅปบๆ็ไธชๆงๅ่ก็จ", | |
"request_details": "ๆจ็ๆ ่ก่ฏทๆฑ", | |
"from": "ๅบๅๅฐ", | |
"when": "ๆ ่กๆถ้ด", | |
"budget": "้ข็ฎ", | |
"travel_style": "ๆ ่ก้ฃๆ ผ", | |
"live_agent_outputs": "ๅฎๆถไปฃ็่พๅบ", | |
"full_itinerary": "ๅฎๆด่ก็จ", | |
"details": "่ฏฆๆ ", | |
"download_share": "ไธ่ฝฝไธๅไบซ", | |
"save_itinerary": "ไฟๅญ่ก็จ", | |
"plan_another_trip": "๐ ่ฎกๅๅฆไธ่ถๆ ่ก", | |
"about": "ๅ ณไบ", | |
"how_it_works": "ๅทฅไฝๅ็", | |
"travel_agents": "ๆ ่กไปฃ็", | |
"share_itinerary": "ๅไบซ่ก็จ", | |
"save_for_mobile": "ไฟๅญๅฐๆๆบ", | |
"built_with": "็จโค๏ธไธบๆจๅถไฝ", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "ๆจ็ๆ ่ก่ก็จๅทฒๅๅคๅฐฑ็ปช๏ผ ๐", | |
"personalized_experience": "ๆไปฌๅทฒไธบๆจๅๅปบไบไธชๆงๅ็ๆ ่กไฝ้ช๏ผ่ฏทๅจไธๆนๆฅ็ๆจ็่ก็จใ", | |
"agent_activity": "ไปฃ็ๆดปๅจ", | |
"error_origin_destination": "่ฏท่พๅ ฅๅบๅๅฐๅ็ฎ็ๅฐใ", | |
"your_itinerary_file": "ๆจ็่ก็จๆไปถ", | |
"text_format": "ๆๆฌๆ ผๅผ - ๅฏๅจไปปไฝๆๆฌ็ผ่พๅจไธญๆๅผใ" | |
}, | |
"es": { | |
"page_title": " Tu Agente de IA para Viajar", | |
"header": " Tu Agente de IA para Viajar", | |
"create_itinerary": "Crea Tu Itinerario", | |
"trip_details": "Detalles del Viaje", | |
"origin": "Origen", | |
"destination": "Destino", | |
"travel_dates": "Fechas del Viaje", | |
"duration": "Duraciรณn (dรญas)", | |
"preferences": "Preferencias", | |
"additional_preferences": "Preferencias Adicionales", | |
"interests": "Intereses", | |
"special_requirements": "Requisitos Especiales", | |
"submit": "๐ Crea Mi Itinerario Personalizado", | |
"request_details": "Tu Solicitud de Viaje", | |
"from": "Desde", | |
"when": "Cuรกndo", | |
"budget": "Presupuesto", | |
"travel_style": "Estilo de Viaje", | |
"live_agent_outputs": "Salidas en Vivo del Agente", | |
"full_itinerary": "Itinerario Completo", | |
"details": "Detalles", | |
"download_share": "Descargar y Compartir", | |
"save_itinerary": "Guardar Itinerario", | |
"plan_another_trip": "๐ Planear Otro Viaje", | |
"about": "Acerca de", | |
"how_it_works": "Cรณmo Funciona", | |
"travel_agents": "Agentes de Viaje", | |
"share_itinerary": "Compartir Itinerario", | |
"save_for_mobile": "Guardar para Mรณvil", | |
"built_with": "Hecho con โค๏ธ para ti", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "ยกTu itinerario de viaje estรก listo! ๐", | |
"personalized_experience": "Hemos creado una experiencia de viaje personalizada solo para ti. Explora tu itinerario a continuaciรณn.", | |
"agent_activity": "Actividad del Agente", | |
"error_origin_destination": "Por favor, ingresa tanto el origen como el destino.", | |
"your_itinerary_file": "Tu Archivo de Itinerario", | |
"text_format": "Formato de texto - Se puede abrir en cualquier editor de texto." | |
}, | |
"fr": { | |
"page_title": " Votre Agent IA pour Voyager", | |
"header": " Votre Agent IA pour Voyager", | |
"create_itinerary": "Crรฉez Votre Itinรฉraire", | |
"trip_details": "Dรฉtails du Voyage", | |
"origin": "Origine", | |
"destination": "Destination", | |
"travel_dates": "Dates du Voyage", | |
"duration": "Durรฉe (jours)", | |
"preferences": "Prรฉfรฉrences", | |
"additional_preferences": "Prรฉfรฉrences Supplรฉmentaires", | |
"interests": "Centres d'intรฉrรชt", | |
"special_requirements": "Exigences Spรฉciales", | |
"submit": "๐ Crรฉez Mon Itinรฉraire Personnalisรฉ", | |
"request_details": "Votre Demande de Voyage", | |
"from": "De", | |
"when": "Quand", | |
"budget": "Budget", | |
"travel_style": "Style de Voyage", | |
"live_agent_outputs": "Rรฉsultats en Direct de l'Agent", | |
"full_itinerary": "Itinรฉraire Complet", | |
"details": "Dรฉtails", | |
"download_share": "Tรฉlรฉcharger et Partager", | |
"save_itinerary": "Enregistrer l'Itinรฉraire", | |
"plan_another_trip": "๐ Planifier un Autre Voyage", | |
"about": "ร Propos", | |
"how_it_works": "Fonctionnement", | |
"travel_agents": "Agents de Voyage", | |
"share_itinerary": "Partager l'Itinรฉraire", | |
"save_for_mobile": "Enregistrer pour Mobile", | |
"built_with": "Conรงu avec โค๏ธ pour vous", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "Votre itinรฉraire de voyage est prรชt ! ๐", | |
"personalized_experience": "Nous avons crรฉรฉ une expรฉrience de voyage personnalisรฉe rien que pour vous. Dรฉcouvrez votre itinรฉraire ci-dessous.", | |
"agent_activity": "Activitรฉ de l'Agent", | |
"error_origin_destination": "Veuillez saisir ร la fois le lieu de dรฉpart et la destination.", | |
"your_itinerary_file": "Votre Fichier d'Itinรฉraire", | |
"text_format": "Format texte - Peut รชtre ouvert dans n'importe quel รฉditeur de texte." | |
}, | |
"de": { | |
"page_title": "Ihr KI-Reiseassistent", | |
"header": " Ihr KI-Reiseassistent", | |
"create_itinerary": "Erstellen Sie Ihre Reiseroute", | |
"trip_details": "Reisedetails", | |
"origin": "Abfahrtsort", | |
"destination": "Zielort", | |
"travel_dates": "Reisedaten", | |
"duration": "Dauer (Tage)", | |
"preferences": "Vorlieben", | |
"additional_preferences": "Zusรคtzliche Vorlieben", | |
"interests": "Interessen", | |
"special_requirements": "Besondere Anforderungen", | |
"submit": "๐ Erstellen Sie meine personalisierte Reiseroute", | |
"request_details": "Ihre Reiseanfrage", | |
"from": "Von", | |
"when": "Wann", | |
"budget": "Budget", | |
"travel_style": "Reisestil", | |
"live_agent_outputs": "Live Agent Ausgaben", | |
"full_itinerary": "Komplette Reiseroute", | |
"details": "Details", | |
"download_share": "Herunterladen & Teilen", | |
"save_itinerary": "Reiseroute speichern", | |
"plan_another_trip": "๐ Plane eine weitere Reise", | |
"about": "รber", | |
"how_it_works": "Wie es funktioniert", | |
"travel_agents": "Reiseassistenten", | |
"share_itinerary": "Reiseroute teilen", | |
"save_for_mobile": "Fรผr Mobilgerรคte speichern", | |
"built_with": "Mit โค๏ธ fรผr Sie gebaut", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "Ihre Reiseroute ist fertig! ๐", | |
"personalized_experience": "Wir haben eine personalisierte Reiseerfahrung nur fรผr Sie erstellt. Entdecken Sie Ihre Reiseroute unten.", | |
"agent_activity": "Agentenaktivitรคt", | |
"error_origin_destination": "Bitte geben Sie sowohl den Abfahrtsort als auch das Ziel ein.", | |
"your_itinerary_file": "Ihre Reise-Datei", | |
"text_format": "Textformat โ Kann in jedem Texteditor geรถffnet werden." | |
}, | |
"ar": { | |
"page_title": " ูููู ุงูุณูุฑ ุงูุฐูู ุงูุฎุงุต ุจู", | |
"header": " ูููู ุงูุณูุฑ ุงูุฐูู ุงูุฎุงุต ุจู", | |
"create_itinerary": "ุฅูุดุงุก ุฎุท ุณูุฑ ุงูุฑุญูุฉ", | |
"trip_details": "ุชูุงุตูู ุงูุฑุญูุฉ", | |
"origin": "ุงูู ุบุงุฏุฑุฉ ู ู", | |
"destination": "ุงููุฌูุฉ", | |
"travel_dates": "ุชูุงุฑูุฎ ุงูุณูุฑ", | |
"duration": "ุงูู ุฏุฉ (ุจุงูุฃูุงู )", | |
"preferences": "ุงูุชูุถููุงุช", | |
"additional_preferences": "ุชูุถููุงุช ุฅุถุงููุฉ", | |
"interests": "ุงูุงูุชู ุงู ุงุช", | |
"special_requirements": "ุงูู ุชุทูุจุงุช ุงูุฎุงุตุฉ", | |
"submit": "๐ ุฅูุดุงุก ุฎุท ุณูุฑ ุงูุฑุญูุฉ ุงูุดุฎุตู", | |
"request_details": "ุทูุจ ุงูุณูุฑ ุงูุฎุงุต ุจู", | |
"from": "ู ู", | |
"when": "ู ุชู", | |
"budget": "ุงูู ูุฒุงููุฉ", | |
"travel_style": "ุฃุณููุจ ุงูุณูุฑ", | |
"live_agent_outputs": "ู ุฎุฑุฌุงุช ุงููููู ุงูู ุจุงุดุฑุฉ", | |
"full_itinerary": "ุฎุท ุณูุฑ ุงูุฑุญูุฉ ุงููุงู ู", | |
"details": "ุงูุชูุงุตูู", | |
"download_share": "ุชูุฒูู ูู ุดุงุฑูุฉ", | |
"save_itinerary": "ุญูุธ ุฎุท ุณูุฑ ุงูุฑุญูุฉ", | |
"plan_another_trip": "๐ ุฎุทุท ูุฑุญูุฉ ุฃุฎุฑู", | |
"about": "ุญูู", | |
"how_it_works": "ููู ูุนู ู", | |
"travel_agents": "ูููุงุก ุงูุณูุฑ", | |
"share_itinerary": "ุดุงุฑู ุฎุท ุณูุฑ ุงูุฑุญูุฉ", | |
"save_for_mobile": "ุญูุธ ูููุงุชู ุงูู ุญู ูู", | |
"built_with": "ู ุตููุน ุจุญุจ ู ู ุฃุฌูู", | |
# ์ถ๋ ฅ ๊ด๋ จ ์ถ๊ฐ ํ ์คํธ | |
"itinerary_ready": "ุชู ุชุฌููุฒ ุฎุท ุณูุฑ ุฑุญูุชู! ๐", | |
"personalized_experience": "ููุฏ ุฃูุดุฃูุง ุชุฌุฑุจุฉ ุณูุฑ ู ุฎุตุตุฉ ูู. ุงุณุชุนุฑุถ ุฎุท ุณูุฑ ุฑุญูุชู ุฃุฏูุงู.", | |
"agent_activity": "ูุดุงุท ุงููููู", | |
"error_origin_destination": "ูุฑุฌู ุฅุฏุฎุงู ููุทุฉ ุงูุงูุทูุงู ูุงููุฌูุฉ.", | |
"your_itinerary_file": "ู ูู ุฎุท ุณูุฑ ุฑุญูุชู", | |
"text_format": "ุชูุณูู ูุตู - ูู ูู ูุชุญู ูู ุฃู ู ุญุฑุฑ ูุตูุต." | |
} | |
} | |
def t(key): | |
lang = st.session_state.get("selected_language", "en") | |
return translations[lang].get(key, key) | |
# --------------------------- | |
# ์ธ์ ์ด๊ธฐํ | |
# --------------------------- | |
if 'selected_language' not in st.session_state: | |
st.session_state.selected_language = "en" # ๊ธฐ๋ณธ์ ์์ด | |
# ------------------------------------------ | |
# ์ฌ์ด๋๋ฐ์ ์ธ์ด ์ ํ ์์ ฏ ์ถ๊ฐ | |
# ------------------------------------------ | |
with st.sidebar: | |
language = st.selectbox( | |
"Language / ์ธ์ด / ่จ่ช / ่ฏญ่จ / Idioma / Langue / Sprache / ุงููุบุฉ", | |
["English", "ํ๊ตญ์ด", "ๆฅๆฌ่ช", "ไธญๆ", "Espaรฑol", "Franรงais", "Deutsch", "ุงูุนุฑุจูุฉ"] | |
) | |
lang_map = { | |
"English": "en", | |
"ํ๊ตญ์ด": "ko", | |
"ๆฅๆฌ่ช": "ja", | |
"ไธญๆ": "zh", | |
"Espaรฑol": "es", | |
"Franรงais": "fr", | |
"Deutsch": "de", | |
"ุงูุนุฑุจูุฉ": "ar" | |
} | |
st.session_state.selected_language = lang_map.get(language, "en") | |
# ------------------------------------------ | |
# ์ดํ Streamlit UI ์ฝ๋ ์์ | |
# ------------------------------------------ | |
# Modern CSS with refined color scheme and sleek animations | |
st.markdown(""" | |
<style> | |
/* Sleek Color Palette */ | |
:root { | |
--primary: #3a86ff; | |
--primary-light: #4895ef; | |
--primary-dark: #2667ff; | |
--secondary: #4cc9f0; | |
--accent: #4361ee; | |
--background: #f8f9fa; | |
--card-bg: #ffffff; | |
--text: #212529; | |
--text-light: #6c757d; | |
--text-muted: #adb5bd; | |
--border: #e9ecef; | |
--success: #2ecc71; | |
--warning: #f39c12; | |
--info: #3498db; | |
} | |
/* Refined Animations */ | |
@keyframes smoothFadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
@keyframes slideInRight { | |
from { opacity: 0; transform: translateX(20px); } | |
to { opacity: 1; transform: translateX(0); } | |
} | |
.animate-in { | |
animation: smoothFadeIn 0.5s cubic-bezier(0.215, 0.61, 0.355, 1); | |
} | |
.slide-in { | |
animation: slideInRight 0.5s cubic-bezier(0.215, 0.61, 0.355, 1); | |
} | |
/* Sleek Header Styles */ | |
.main-header { | |
font-size: 2.5rem; | |
color: var(--primary-dark); | |
text-align: center; | |
margin-bottom: 0.8rem; | |
font-weight: 700; | |
letter-spacing: -0.5px; | |
} | |
.sub-header { | |
font-size: 1.4rem; | |
color: var(--accent); | |
font-weight: 600; | |
margin-top: 1.8rem; | |
margin-bottom: 0.8rem; | |
border-bottom: 1px solid var(--border); | |
padding-bottom: 0.4rem; | |
} | |
/* Sleek Card Styles */ | |
.modern-card { | |
background-color: var(--card-bg); | |
border-radius: 10px; | |
padding: 1.2rem; | |
margin-bottom: 1.2rem; | |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); | |
transition: all 0.25s ease; | |
border: 1px solid var(--border); | |
} | |
.modern-card:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); | |
} | |
/* Refined Form Styles */ | |
.stTextInput > div > div > input, | |
.stDateInput > div > div > input, | |
.stTextArea > div > div > textarea { | |
border-radius: 6px; | |
border: 1px solid var(--border); | |
padding: 10px 12px; | |
font-size: 14px; | |
transition: all 0.2s ease; | |
box-shadow: none; | |
} | |
.stTextInput > div > div > input:focus, | |
.stDateInput > div > div > input:focus, | |
.stTextArea > div > div > textarea:focus { | |
border: 1px solid var(--primary); | |
box-shadow: 0 0 0 1px rgba(58, 134, 255, 0.15); | |
} | |
/* Sleek Button Styles */ | |
.stButton > button { | |
background-color: var(--primary); | |
color: white; | |
font-weight: 500; | |
padding: 0.5rem 1.2rem; | |
border-radius: 6px; | |
border: none; | |
transition: all 0.2s ease; | |
font-size: 14px; | |
letter-spacing: 0.3px; | |
} | |
.stButton > button:hover { | |
background-color: var(--primary-dark); | |
transform: translateY(-1px); | |
box-shadow: 0 3px 8px rgba(58, 134, 255, 0.25); | |
} | |
/* Sleek Tab Styles */ | |
.stTabs [data-baseweb="tab-list"] { | |
gap: 2px; | |
background-color: var(--background); | |
border-radius: 8px; | |
padding: 2px; | |
} | |
.stTabs [data-baseweb="tab"] { | |
border-radius: 6px; | |
padding: 8px 16px; | |
font-size: 14px; | |
font-weight: 500; | |
} | |
.stTabs [aria-selected="true"] { | |
background-color: var(--primary); | |
color: white !important; | |
} | |
/* Progress Bar Styles */ | |
.stProgress > div > div > div > div { | |
background-color: var(--primary); | |
} | |
/* Progress Styles */ | |
.progress-container { | |
margin: 1.2rem 0; | |
background-color: var(--background); | |
border-radius: 8px; | |
padding: 0.8rem; | |
border: 1px solid var(--border); | |
} | |
.step-complete { | |
color: #4CAF50; | |
font-weight: 600; | |
} | |
.step-pending { | |
color: #9E9E9E; | |
} | |
.step-active { | |
color: var(--primary); | |
font-weight: 600; | |
} | |
/* Agent Output */ | |
.agent-output { | |
background-color: #f8f9fa; | |
border-left: 5px solid var(--primary); | |
padding: 1.2rem; | |
margin: 1rem 0; | |
border-radius: 10px; | |
max-height: 400px; | |
overflow-y: auto; | |
} | |
/* Footer */ | |
.footer { | |
text-align: center; | |
margin-top: 3rem; | |
color: var(--text-light); | |
font-size: 0.9rem; | |
padding: 1rem; | |
border-top: 1px solid #eaeaea; | |
} | |
/* Agent Log */ | |
.agent-log { | |
background-color: #F5F5F5; | |
border-left: 3px solid var(--primary); | |
padding: 0.5rem; | |
margin-bottom: 0.5rem; | |
font-family: monospace; | |
border-radius: 4px; | |
} | |
/* Info and Success Boxes */ | |
.info-box { | |
background-color: var(--primary-light); | |
color: white; | |
padding: 1rem; | |
border-radius: 0.5rem; | |
margin-bottom: 1rem; | |
} | |
.success-box { | |
background-color: #E8F5E9; | |
padding: 1rem; | |
border-radius: 0.5rem; | |
margin-bottom: 1rem; | |
border-left: 5px solid #4CAF50; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Helper function to download HTML file | |
def get_download_link(text_content, filename): | |
b64 = base64.b64encode(text_content.encode()).decode() | |
href = f'<a class="download-link" href="data:text/plain;base64,{b64}" download="{filename}"><i>๐ฅ</i> {t("save_itinerary")}</a>' | |
return href | |
# Updated helper function to display modern progress with a single UI element | |
def display_modern_progress(current_step, total_steps=6): | |
if 'progress_steps' not in st.session_state: | |
st.session_state.progress_steps = { | |
0: {'status': 'pending', 'name': t("trip_details")}, | |
1: {'status': 'pending', 'name': t("about")}, | |
2: {'status': 'pending', 'name': t("travel_style")}, | |
3: {'status': 'pending', 'name': t("live_agent_outputs")}, | |
4: {'status': 'pending', 'name': t("download_share")}, | |
5: {'status': 'pending', 'name': t("full_itinerary")} | |
} | |
for i in range(total_steps): | |
if i < current_step: | |
st.session_state.progress_steps[i]['status'] = 'complete' | |
elif i == current_step: | |
st.session_state.progress_steps[i]['status'] = 'active' | |
else: | |
st.session_state.progress_steps[i]['status'] = 'pending' | |
progress_percentage = (current_step / total_steps) * 100 | |
st.progress(progress_percentage / 100) | |
st.markdown(""" | |
<style> | |
.compact-progress { | |
background: white; | |
border-radius: 10px; | |
padding: 15px; | |
box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
margin-bottom: 20px; | |
} | |
.progress-title { | |
font-size: 16px; | |
font-weight: bold; | |
margin-bottom: 15px; | |
color: #333; | |
border-bottom: 1px solid #eee; | |
padding-bottom: 10px; | |
} | |
.step-grid { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 10px; | |
} | |
.step-item { | |
display: flex; | |
align-items: center; | |
padding: 8px 10px; | |
border-radius: 6px; | |
background: #f8f9fa; | |
box-shadow: 0 1px 3px rgba(0,0,0,0.05); | |
} | |
.step-item.complete { | |
border-left: 3px solid #4CAF50; | |
background: #f1f8e9; | |
} | |
.step-item.active { | |
border-left: 3px solid #2196F3; | |
background: #e3f2fd; | |
font-weight: bold; | |
} | |
.step-item.pending { | |
border-left: 3px solid #9e9e9e; | |
opacity: 0.7; | |
} | |
.step-icon { | |
margin-right: 8px; | |
font-size: 14px; | |
} | |
.step-text { | |
font-size: 13px; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
</style> | |
<div class="compact-progress"> | |
""", unsafe_allow_html=True) | |
st.markdown('<div class="step-grid">', unsafe_allow_html=True) | |
for i, step_info in st.session_state.progress_steps.items(): | |
status = step_info['status'] | |
name = step_info['name'] | |
if status == 'complete': | |
icon = "โ " | |
status_class = "complete" | |
elif status == 'active': | |
icon = "๐" | |
status_class = "active" | |
else: | |
icon = "โญ" | |
status_class = "pending" | |
st.markdown(f""" | |
<div class="step-item {status_class}"> | |
<span class="step-icon">{icon}</span> | |
<span class="step-text">{name}</span> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown('</div></div>', unsafe_allow_html=True) | |
return progress_percentage | |
def update_step_status(step_index, status): | |
if 'progress_steps' in st.session_state and step_index in st.session_state.progress_steps: | |
st.session_state.progress_steps[step_index]['status'] = status | |
def run_task_with_logs(task, input_text, log_container, output_container, results_key=None): | |
log_message = f"๐ค Starting {task.agent.role}..." | |
st.session_state.log_messages.append(log_message) | |
with log_container: | |
st.markdown("### " + t("agent_activity")) | |
for msg in st.session_state.log_messages: | |
st.markdown(msg) | |
result = run_task(task, input_text) | |
if results_key: | |
st.session_state.results[results_key] = result | |
log_message = f"โ {task.agent.role} completed!" | |
st.session_state.log_messages.append(log_message) | |
with log_container: | |
st.markdown("### " + t("agent_activity")) | |
for msg in st.session_state.log_messages: | |
st.markdown(msg) | |
with output_container: | |
st.markdown(f"### {task.agent.role} Output") | |
st.markdown("<div class='agent-output'>" + result + "</div>", unsafe_allow_html=True) | |
return result | |
# ------------------------------------------ | |
# Session state ์ด๊ธฐํ | |
# ------------------------------------------ | |
if 'generated_itinerary' not in st.session_state: | |
st.session_state.generated_itinerary = None | |
if 'generation_complete' not in st.session_state: | |
st.session_state.generation_complete = False | |
if 'current_step' not in st.session_state: | |
st.session_state.current_step = 0 | |
if 'results' not in st.session_state: | |
st.session_state.results = { | |
"destination_info": "", | |
"accommodation_info": "", | |
"transportation_info": "", | |
"activities_info": "", | |
"dining_info": "", | |
"itinerary": "", | |
"final_itinerary": "" | |
} | |
if 'log_messages' not in st.session_state: | |
st.session_state.log_messages = [] | |
if 'current_output' not in st.session_state: | |
st.session_state.current_output = None | |
if 'form_submitted' not in st.session_state: | |
st.session_state.form_submitted = False | |
# Modern animated header | |
st.markdown(f""" | |
<div class="animate-in" style="text-align: center;"> | |
<div style="margin-bottom: 20px;"> | |
<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));"> | |
</div> | |
<h1 class="main-header">{t("header")}</h1> | |
<p style="font-size: 1.2rem; color: #6c757d; margin-bottom: 25px;"> | |
โจ Create your personalized AI-powered travel itinerary in minutes! โจ | |
</p> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown('<hr style="height:3px;border:none;background-color:#f0f0f0;margin-bottom:25px;">', unsafe_allow_html=True) | |
with st.sidebar: | |
st.markdown(""" | |
<div style="text-align: center; padding: 20px 0; margin-bottom: 20px; border-bottom: 1px solid #eaeaea;"> | |
<img src="https://img.icons8.com/fluency/96/travel-card.png" width="80" style="margin-bottom: 15px;"> | |
<h3 style="margin-bottom: 5px; color: #4361ee;">Your AI Agent for Travelling</h3> | |
<p style="color: #6c757d; font-size: 0.9rem;">AI-Powered Travel Planning</p> | |
</div> | |
""", unsafe_allow_html=True) | |
st.markdown('<div class="modern-card">', unsafe_allow_html=True) | |
st.markdown("### ๐ " + t("about")) | |
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!") | |
st.markdown('</div>', unsafe_allow_html=True) | |
st.markdown('<div class="modern-card">', unsafe_allow_html=True) | |
st.markdown("### ๐ " + t("how_it_works")) | |
st.markdown(""" | |
<ol style="padding-left: 25px;"> | |
<li><b>๐๏ธ Enter</b> your travel details</li> | |
<li><b>๐ง AI analysis</b> of your preferences</li> | |
<li><b>๐ Generate</b> comprehensive itinerary</li> | |
<li><b>๐ฅ Download</b> and enjoy your trip!</li> | |
</ol> | |
""", unsafe_allow_html=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
st.markdown('<div class="modern-card">', unsafe_allow_html=True) | |
st.markdown("### ๐ค Travel Agents") | |
agents = [ | |
("๐ญ Research Specialist", "Finds the best destinations based on your preferences"), | |
("๐จ Accommodation Expert", "Suggests suitable hotels and stays"), | |
("๐ Transportation Planner", "Plans efficient travel routes"), | |
("๐ฏ Activities Curator", "Recommends activities tailored to your interests"), | |
("๐ฝ๏ธ Dining Connoisseur", "Finds the best dining experiences"), | |
("๐ Itinerary Creator", "Puts everything together in a daily plan") | |
] | |
for name, desc in agents: | |
st.markdown("**" + name + "**") | |
st.markdown("<small>" + desc + "</small>", unsafe_allow_html=True) | |
st.markdown('</div>', unsafe_allow_html=True) | |
if not st.session_state.generation_complete: | |
st.markdown('<div class="modern-card animate-in">', unsafe_allow_html=True) | |
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) | |
st.markdown(""" | |
<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> | |
""", unsafe_allow_html=True) | |
with st.form("travel_form"): | |
col1, col2 = st.columns(2) | |
with col1: | |
st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px; margin-bottom: 12px;">Trip Details</p>', unsafe_allow_html=True) | |
origin = st.text_input(t("origin"), placeholder="e.g., New York, USA") | |
destination = st.text_input(t("destination"), placeholder="e.g., Paris, France") | |
st.markdown('<p style="margin-bottom: 5px; font-size: 14px;">Travel Dates</p>', unsafe_allow_html=True) | |
start_date = st.date_input("Start Date", min_value=datetime.now(), label_visibility="collapsed") | |
duration = st.slider(t("duration"), min_value=1, max_value=30, value=7) | |
end_date = start_date + timedelta(days=duration-1) | |
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) | |
with col2: | |
st.markdown('<p style="font-weight: 500; color: var(--primary); font-size: 14px; margin-bottom: 12px;">Preferences</p>', unsafe_allow_html=True) | |
travelers = st.number_input("Travelers", min_value=1, max_value=15, value=2) | |
budget_options = ["Budget", "Moderate", "Luxury"] | |
budget = st.selectbox("Budget", budget_options, help="Budget: Economy options | Moderate: Mid-range | Luxury: High-end experiences") | |
travel_style = st.multiselect("๐ Travel Style", options=["Culture", "Adventure", "Relaxation", "Food & Dining", "Nature", "Shopping", "Nightlife", "Family-friendly"], default=["Culture", "Food & Dining"]) | |
with st.expander("Additional Preferences", expanded=False): | |
preferences = st.text_area("Interests", placeholder="History museums, local cuisine, hiking, art...") | |
special_requirements = st.text_area("Special Requirements", placeholder="Dietary restrictions, accessibility needs...") | |
submit_button = st.form_submit_button(t("submit")) | |
st.markdown('</div>', unsafe_allow_html=True) | |
if submit_button: | |
if not origin or not destination: | |
st.error(t("error_origin_destination")) | |
else: | |
st.session_state.form_submitted = True | |
user_input = { | |
"origin": origin, | |
"destination": destination, | |
"duration": str(duration), | |
"travel_dates": f"{start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}", | |
"travelers": str(travelers), | |
"budget": budget.lower(), | |
"travel_style": ", ".join(travel_style), | |
"preferences": preferences, | |
"special_requirements": special_requirements | |
} | |
# ๊ธฐ์กด์ ์ฌํ ์์ฒญ ํ๋กฌํํธ | |
input_context = f"""Travel Request Details: | |
Origin: {user_input['origin']} | |
Destination: {user_input['destination']} | |
Duration: {user_input['duration']} days | |
Travel Dates: {user_input['travel_dates']} | |
Travelers: {user_input['travelers']} | |
Budget Level: {user_input['budget']} | |
Travel Style: {user_input['travel_style']} | |
Preferences/Interests: {user_input['preferences']} | |
Special Requirements: {user_input['special_requirements']} | |
""" | |
# LLM์ ์ ๋ฌํ ํ๋กฌํํธ์ ์ธ์ด ์ง์๋ฌธ ์ถ๊ฐ | |
llm_language_instructions = { | |
"en": "Please output the response in English.", | |
"ko": "ํ๊ตญ์ด๋ก ์ถ๋ ฅํด ์ฃผ์ธ์.", | |
"ja": "ๆฅๆฌ่ชใงๅบๅใใฆใใ ใใใ", | |
"zh": "่ฏท็จไธญๆ่พๅบใ", | |
"es": "Por favor, responda en espaรฑol.", | |
"fr": "Veuillez rรฉpondre en franรงais.", | |
"de": "Bitte antworten Sie auf Deutsch.", | |
"ar": "ูุฑุฌู ุงูุฑุฏ ุจุงููุบุฉ ุงูุนุฑุจูุฉ." | |
} | |
selected_lang = st.session_state.get("selected_language", "en") | |
language_instruction = llm_language_instructions.get(selected_lang, "Please output the response in English.") | |
modified_input_context = language_instruction + "\n" + input_context | |
st.markdown(""" | |
<div class="sleek-processing-container"> | |
<div class="pulse-container"> | |
<div class="pulse-ring"></div> | |
<div class="pulse-core"></div> | |
</div> | |
</div> | |
<style> | |
.sleek-processing-container { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
padding: 20px 0; | |
} | |
.pulse-container { | |
position: relative; | |
width: 50px; | |
height: 50px; | |
} | |
.pulse-core { | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
transform: translate(-50%, -50%); | |
width: 12px; | |
height: 12px; | |
background-color: #4361ee; | |
border-radius: 50%; | |
box-shadow: 0 0 8px rgba(67, 97, 238, 0.6); | |
} | |
.pulse-ring { | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
border: 2px solid #4361ee; | |
border-radius: 50%; | |
animation: pulse 1.5s ease-out infinite; | |
opacity: 0; | |
} | |
@keyframes pulse { | |
0% { transform: scale(0.1); opacity: 0; } | |
50% { opacity: 0.5; } | |
100% { transform: scale(1); opacity: 0; } | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
st.markdown('<div class="modern-card">', unsafe_allow_html=True) | |
progress_tab, logs_tab, details_tab = st.tabs(["๐ Progress", "๐ Live Activity", "๐ " + t("request_details")]) | |
with details_tab: | |
st.markdown("#### " + t("request_details")) | |
st.markdown("**" + t("destination") + ":** " + user_input['destination']) | |
st.markdown("**" + t("from") + ":** " + user_input['origin']) | |
st.markdown("**" + t("when") + ":** " + user_input['travel_dates'] + " (" + user_input['duration'] + " days)") | |
st.markdown("**" + t("budget") + ":** " + user_input['budget'].title()) | |
st.markdown("**" + t("travel_style") + ":** " + user_input['travel_style']) | |
if user_input['preferences']: | |
st.markdown("**Interests:** " + user_input['preferences']) | |
if user_input['special_requirements']: | |
st.markdown("**Special Requirements:** " + user_input['special_requirements']) | |
with progress_tab: | |
if 'progress_placeholder' not in st.session_state: | |
st.session_state.progress_placeholder = st.empty() | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(0) | |
with logs_tab: | |
log_container = st.container() | |
st.session_state.log_messages = [] | |
st.markdown('</div>', unsafe_allow_html=True) | |
output_container = st.container() | |
with output_container: | |
st.markdown('<div class="modern-card">', unsafe_allow_html=True) | |
st.markdown("### ๐ " + t("live_agent_outputs")) | |
st.info("Our AI agents will show their work here as they create your itinerary") | |
st.markdown('</div>', unsafe_allow_html=True) | |
st.session_state.current_step = 0 | |
update_step_status(0, 'active') | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
destination_info = run_task_with_logs( | |
destination_research_task, | |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']), | |
log_container, | |
output_container, | |
"destination_info" | |
) | |
update_step_status(0, 'complete') | |
st.session_state.current_step = 1 | |
update_step_status(1, 'active') | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
accommodation_info = run_task_with_logs( | |
accommodation_task, | |
modified_input_context.format(destination=user_input['destination'], budget=user_input['budget'], preferences=user_input['preferences']), | |
log_container, | |
output_container, | |
"accommodation_info" | |
) | |
update_step_status(1, 'complete') | |
st.session_state.current_step = 2 | |
update_step_status(2, 'active') | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
transportation_info = run_task_with_logs( | |
transportation_task, | |
modified_input_context.format(origin=user_input['origin'], destination=user_input['destination']), | |
log_container, | |
output_container, | |
"transportation_info" | |
) | |
update_step_status(2, 'complete') | |
st.session_state.current_step = 3 | |
update_step_status(3, 'active') | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
activities_info = run_task_with_logs( | |
activities_task, | |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']), | |
log_container, | |
output_container, | |
"activities_info" | |
) | |
update_step_status(3, 'complete') | |
st.session_state.current_step = 4 | |
update_step_status(4, 'active') | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
dining_info = run_task_with_logs( | |
dining_task, | |
modified_input_context.format(destination=user_input['destination'], preferences=user_input['preferences']), | |
log_container, | |
output_container, | |
"dining_info" | |
) | |
update_step_status(4, 'complete') | |
st.session_state.current_step = 5 | |
update_step_status(5, 'active') | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
combined_info = f"""{input_context} | |
Destination Information: | |
{destination_info} | |
Accommodation Options: | |
{accommodation_info} | |
Transportation Plan: | |
{transportation_info} | |
Recommended Activities: | |
{activities_info} | |
Dining Recommendations: | |
{dining_info} | |
""" | |
itinerary = run_task_with_logs( | |
itinerary_task, | |
combined_info.format(duration=user_input['duration'], origin=user_input['origin'], destination=user_input['destination']), | |
log_container, | |
output_container, | |
"itinerary" | |
) | |
update_step_status(5, 'complete') | |
st.session_state.current_step = 6 | |
with st.session_state.progress_placeholder.container(): | |
display_modern_progress(st.session_state.current_step) | |
st.session_state.generated_itinerary = itinerary | |
st.session_state.generation_complete = True | |
date_str = datetime.now().strftime("%Y-%m-%d") | |
st.session_state.filename = f"{user_input['destination'].replace(' ', '_')}_{date_str}_itinerary.txt" | |
if st.session_state.generation_complete: | |
st.markdown(""" | |
<div class="modern-card animate-in"> | |
<div style="display: flex; justify-content: center; margin-bottom: 20px;"> | |
<div class="success-animation"> | |
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"> | |
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" /> | |
<path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8" /> | |
</svg> | |
</div> | |
</div> | |
<h2 style="text-align: center; color: #4361ee;">""" + t("itinerary_ready") + """</h2> | |
<p style="text-align: center; color: #6c757d; margin-bottom: 20px;">""" + t("personalized_experience") + """</p> | |
</div> | |
<style> | |
.success-animation { | |
width: 100px; | |
height: 100px; | |
position: relative; | |
} | |
.checkmark { | |
width: 100px; | |
height: 100px; | |
border-radius: 50%; | |
display: block; | |
stroke-width: 2; | |
stroke: #4361ee; | |
stroke-miterlimit: 10; | |
box-shadow: 0 0 20px rgba(67, 97, 238, 0.3); | |
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both; | |
} | |
.checkmark__circle { | |
stroke-dasharray: 166; | |
stroke-dashoffset: 166; | |
stroke-width: 2; | |
stroke-miterlimit: 10; | |
stroke: #4361ee; | |
fill: none; | |
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards; | |
} | |
.checkmark__check { | |
transform-origin: 50% 50%; | |
stroke-dasharray: 48; | |
stroke-dashoffset: 48; | |
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards; | |
} | |
@keyframes stroke { | |
100% { stroke-dashoffset: 0; } | |
} | |
@keyframes scale { | |
0%, 100% { transform: none; } | |
50% { transform: scale3d(1.1, 1.1, 1); } | |
} | |
@keyframes fill { | |
100% { box-shadow: 0 0 20px rgba(67, 97, 238, 0.3); } | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# ์ถ๊ฐ๋ ํญ: ์ ์ฒด ์ผ์ , ์์ธ ์ ๋ณด, ๋ค์ด๋ก๋/๊ณต์ , ์ง๋ ๋ฐ ์๊ฐํ, AI ์ฑ๋ด ์ธํฐํ์ด์ค | |
itinerary_tab, details_tab, download_tab, map_tab, chatbot_tab = st.tabs([ | |
"๐๏ธ " + t("full_itinerary"), | |
"๐ผ " + t("details"), | |
"๐พ " + t("download_share"), | |
"๐บ๏ธ ์ง๋ ๋ฐ ์๊ฐํ", | |
"๐ค ์ฑ๋ด ์ธํฐํ์ด์ค" | |
]) | |
# ์ผ์ ํญ | |
with itinerary_tab: | |
st.text_area("Your Itinerary", st.session_state.generated_itinerary, height=600) | |
# ์์ธ ์ ๋ณด ํญ | |
with details_tab: | |
agent_tabs = st.tabs(["๐ Destination", "๐จ Accommodation", "๐ Transportation", "๐ญ Activities", "๐ฝ๏ธ Dining"]) | |
with agent_tabs[0]: | |
st.markdown("### ๐ Destination Research") | |
st.markdown(st.session_state.results["destination_info"]) | |
with agent_tabs[1]: | |
st.markdown("### ๐จ Accommodation Options") | |
st.markdown(st.session_state.results["accommodation_info"]) | |
with agent_tabs[2]: | |
st.markdown("### ๐ Transportation Plan") | |
st.markdown(st.session_state.results["transportation_info"]) | |
with agent_tabs[3]: | |
st.markdown("### ๐ญ Recommended Activities") | |
st.markdown(st.session_state.results["activities_info"]) | |
with agent_tabs[4]: | |
st.markdown("### ๐ฝ๏ธ Dining Recommendations") | |
st.markdown(st.session_state.results["dining_info"]) | |
# ๋ค์ด๋ก๋ ๋ฐ ๊ณต์ ํญ | |
with download_tab: | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
st.markdown("### " + t("save_itinerary")) | |
st.markdown("Download your personalized travel plan to access it offline or share with your travel companions.") | |
st.markdown(""" | |
<div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px; margin-top: 20px;"> | |
<h4 style="margin-top: 0;">""" + t("your_itinerary_file") + """</h4> | |
<p style="font-size: 0.9rem; color: #6c757d;">""" + t("text_format") + """</p> | |
""", unsafe_allow_html=True) | |
st.markdown("<div style='margin: 10px 0;'>" + get_download_link(st.session_state.generated_itinerary, st.session_state.filename) + "</div>", unsafe_allow_html=True) | |
st.markdown("</div>", unsafe_allow_html=True) | |
st.markdown("### " + t("share_itinerary")) | |
st.markdown("*Coming soon: Email your itinerary or share via social media.*") | |
with col2: | |
st.markdown("### " + t("save_for_mobile")) | |
st.markdown("*Coming soon: QR code for easy access on your phone*") | |
# ์ธํฐ๋ํฐ๋ธ ์ง๋ ๋ฐ ์๊ฐํ ํญ | |
with map_tab: | |
st.markdown("### ๋ชฉ์ ์ง ์ง๋") | |
# ์์: ๋ชฉ์ ์ง ์ฃผ๋ณ์ ์ฃผ์ ๋ช ์ ์ขํ ๋ฐ์ดํฐ (์ค์ API๋ DB๋ฅผ ํตํด ๋์ ์ผ๋ก ๊ฐ์ ธ์ฌ ์ ์์) | |
map_data = pd.DataFrame({ | |
"lat": [48.8584, 48.8606, 48.8529], | |
"lon": [2.2945, 2.3376, 2.3500], | |
"name": ["Eiffel Tower", "Louvre Museum", "Notre Dame"] | |
}) | |
# ๊ธฐ๋ณธ ์ง๋ ์ถ๋ ฅ (st.map) | |
st.map(map_data) | |
st.markdown("#### Pydeck์ ํ์ฉํ ์ธํฐ๋ํฐ๋ธ ์ง๋ ์์") | |
layer = pdk.Layer( | |
"ScatterplotLayer", | |
data=map_data, | |
get_position='[lon, lat]', | |
get_color='[200, 30, 0, 160]', | |
get_radius=200, | |
) | |
view_state = pdk.ViewState( | |
latitude=48.8566, | |
longitude=2.3522, | |
zoom=12, | |
pitch=50, | |
) | |
deck_chart = pdk.Deck(layers=[layer], initial_view_state=view_state) | |
st.pydeck_chart(deck_chart) | |
# AI ์ฑ๋ด ์ธํฐํ์ด์ค ํญ (์ ๋ฏธ๋์ด ์ ์ฉ) | |
with chatbot_tab: | |
st.markdown("### AI ์ฑ๋ด ์ธํฐํ์ด์ค") | |
# ๋ํ ๊ธฐ๋ก์ ์ธ์ ์ํ์ ์ ์ฅ (๋ฉ์์ง, ๋ฐ์ ์, ํ์์คํฌํ) | |
if "chat_history" not in st.session_state: | |
st.session_state.chat_history = [] | |
# ์ฌ์ฉ์ ์ ๋ ฅ์ฐฝ ๋ฐ ์ ์ก ๋ฒํผ | |
user_message = st.text_input("๋ฉ์์ง๋ฅผ ์ ๋ ฅํ์ธ์:", key="chat_input") | |
if st.button("์ ์ก", key="send_button"): | |
if user_message: | |
# ์ ๋ฏธ๋์ด ๊ธฐ๋ฐ ์ฑ๋ด ์๋ต: run_task()๋ฅผ ํ์ฉํ์ฌ chatbot_task์ ์ง์ | |
response = run_task(chatbot_task, user_message) | |
st.session_state.chat_history.append({ | |
"speaker": "์ฌ์ฉ์", | |
"message": user_message, | |
"time": datetime.now() | |
}) | |
st.session_state.chat_history.append({ | |
"speaker": "AI", | |
"message": response, | |
"time": datetime.now() | |
}) | |
# ๋ํ ๊ธฐ๋ก ์ถ๋ ฅ (ํ์์คํฌํ ํฌํจ, ์คํฌ๋กค ๊ฐ๋ฅํ ์์ญ) | |
st.markdown("<div style='max-height:400px; overflow-y:auto; padding:10px; border:1px solid #eaeaea; border-radius:6px;'>", unsafe_allow_html=True) | |
for chat in st.session_state.chat_history: | |
time_str = chat["time"].strftime("%H:%M:%S") | |
st.markdown(f"**{chat['speaker']}** ({time_str}): {chat['message']}") | |
st.markdown("</div>", unsafe_allow_html=True) | |
st.markdown(""" | |
<div style="margin-top: 50px; text-align: center; padding: 20px; color: #6c757d; font-size: 0.8rem;"> | |
<p>""" + t("built_with") + """</p> | |
</div> | |
""", unsafe_allow_html=True) | |