travelscoutix / 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)