AgentX-Travel / app-backup.py
openfree's picture
Create app-backup.py
00b4149 verified
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)