Merge pull request #257 from BartugKaan/feature/travel-agent-ics-export

feat(travel_agent): Add iCalendar (.ics) export functionality to planners
This commit is contained in:
Shubham Saboo
2025-07-31 22:04:20 -05:00
committed by GitHub
4 changed files with 206 additions and 30 deletions

View File

@@ -5,6 +5,7 @@ This Streamlit app is an AI-powered travel Agent that generates personalized tra
- Research and discover exciting travel destinations, activities, and accommodations
- Customize your itinerary based on the number of days you want to travel
- Utilize the power of GPT-4o to generate intelligent and personalized travel plans
- Download your itinerary as a calendar (.ics) file to import into Google Calendar, Apple Calendar, or other calendar apps
### How to get Started?
@@ -32,8 +33,29 @@ pip install -r requirements.txt
streamlit run travel_agent.py
```
For local LLM usage (with Ollama):
```bash
streamlit run local_travel_agent.py
```
### How it Works?
The AI Travel Agent has two main components:
- Researcher: Responsible for generating search terms based on the user's destination and travel duration, and searching the web for relevant activities and accommodations using SerpAPI.
- Planner: Takes the research results and user preferences to generate a personalized draft itinerary that includes suggested activiti
- **Researcher:** Responsible for generating search terms based on the user's destination and travel duration, and searching the web for relevant activities and accommodations using SerpAPI.
- **Planner:** Takes the research results and user preferences to generate a personalized draft itinerary that includes suggested activities, dining options, and accommodations.
### Using the Calendar Download Feature
After generating your travel itinerary:
1. Click the "Download Itinerary as Calendar (.ics)" button that appears next to the "Generate Itinerary" button
2. Save the .ics file to your computer
3. Import the file into your preferred calendar application (Google Calendar, Apple Calendar, Outlook, etc.)
4. Each day of your itinerary will appear as an all-day event in your calendar
5. The complete details for each day's activities are included in the event description
This feature makes it easy to keep track of your travel plans and have your itinerary available on all your devices, even offline.
### Local vs Cloud Version
- **travel_agent.py**: Uses OpenAI's GPT-4o for high-quality itineraries (requires OpenAI API key)
- **local_travel_agent.py**: Uses Ollama for local LLM inference without sending data to external APIs (requires Ollama to be installed and running)

View File

@@ -2,12 +2,70 @@ from textwrap import dedent
from agno.agent import Agent
from agno.tools.serpapi import SerpApiTools
import streamlit as st
import re
from agno.models.ollama import Ollama
from icalendar import Calendar, Event
from datetime import datetime, timedelta
def generate_ics_content(plan_text:str, start_date: datetime = None) -> bytes:
"""
Generate an ICS calendar file from a travel itinerary text.
Args:
plan_text: The travel itinerary text
start_date: Optional start date for the itinerary (defaults to today)
Returns:
bytes: The ICS file content as bytes
"""
cal = Calendar()
cal.add('prodid','-//AI Travel Planner//github.com//' )
cal.add('version', '2.0')
if start_date is None:
start_date = datetime.today()
# Split the plan into days
day_pattern = re.compile(r'Day (\d+)[:\s]+(.*?)(?=Day \d+|$)', re.DOTALL)
days = day_pattern.findall(plan_text)
if not days: # If no day pattern found, create a single all-day event with the entire content
event = Event()
event.add('summary', "Travel Itinerary")
event.add('description', plan_text)
event.add('dtstart', start_date.date())
event.add('dtend', start_date.date())
event.add("dtstamp", datetime.now())
cal.add_component(event)
else:
# Process each day
for day_num, day_content in days:
day_num = int(day_num)
current_date = start_date + timedelta(days=day_num - 1)
# Create a single event for the entire day
event = Event()
event.add('summary', f"Day {day_num} Itinerary")
event.add('description', day_content.strip())
# Make it an all-day event
event.add('dtstart', current_date.date())
event.add('dtend', current_date.date())
event.add("dtstamp", datetime.now())
cal.add_component(event)
return cal.to_ical()
# Set up the Streamlit app
st.title("AI Travel Planner using Llama-3.2 ✈️")
st.title("AI Travel Planner using Llama-3.2 ")
st.caption("Plan your next adventure with AI Travel Planner by researching and planning a personalized itinerary on autopilot using local Llama-3")
# Initialize session state to store the generated itinerary
if 'itinerary' not in st.session_state:
st.session_state.itinerary = None
# Get SerpAPI key from the user
serp_api_key = st.text_input("Enter Serp API Key for Search functionality", type="password")
@@ -15,7 +73,7 @@ if serp_api_key:
researcher = Agent(
name="Researcher",
role="Searches for travel destinations, activities, and accommodations based on user preferences",
model=Ollama(id="llama3.2", max_tokens=1024),
model=Ollama(id="llama3.2"),
description=dedent(
"""\
You are a world-class travel researcher. Given a travel destination and the number of days the user wants to travel for,
@@ -35,7 +93,7 @@ if serp_api_key:
planner = Agent(
name="Planner",
role="Generates a draft itinerary based on user preferences and research results",
model=Ollama(id="llama3.2", max_tokens=1024),
model=Ollama(id="llama3.2"),
description=dedent(
"""\
You are a senior travel planner. Given a travel destination, the number of days the user wants to travel for, and a list of research results,
@@ -57,8 +115,27 @@ if serp_api_key:
destination = st.text_input("Where do you want to go?")
num_days = st.number_input("How many days do you want to travel for?", min_value=1, max_value=30, value=7)
if st.button("Generate Itinerary"):
with st.spinner("Processing..."):
# Get the response from the assistant
response = planner.run(f"{destination} for {num_days} days", stream=False)
st.write(response.content)
col1, col2 = st.columns(2)
with col1:
if st.button("Generate Itinerary"):
with st.spinner("Processing..."):
# Get the response from the assistant
response = planner.run(f"{destination} for {num_days} days", stream=False)
# Store the response in session state
st.session_state.itinerary = response.content
st.write(response.content)
# Only show download button if there's an itinerary
with col2:
if st.session_state.itinerary:
# Generate the ICS file
ics_content = generate_ics_content(st.session_state.itinerary)
# Provide the file for download
st.download_button(
label="Download Itinerary as Calendar (.ics)",
data=ics_content,
file_name="travel_itinerary.ics",
mime="text/calendar"
)

View File

@@ -1,4 +1,5 @@
streamlit
agno
openai
google-search-results
google-search-results
icalendar

View File

@@ -2,12 +2,69 @@ from textwrap import dedent
from agno.agent import Agent
from agno.tools.serpapi import SerpApiTools
import streamlit as st
import re
from agno.models.openai import OpenAIChat
from icalendar import Calendar, Event
from datetime import datetime, timedelta
def generate_ics_content(plan_text:str, start_date: datetime = None) -> bytes:
"""
Generate an ICS calendar file from a travel itinerary text.
Args:
plan_text: The travel itinerary text
start_date: Optional start date for the itinerary (defaults to today)
Returns:
bytes: The ICS file content as bytes
"""
cal = Calendar()
cal.add('prodid','-//AI Travel Planner//github.com//' )
cal.add('version', '2.0')
if start_date is None:
start_date = datetime.today()
# Split the plan into days
day_pattern = re.compile(r'Day (\d+)[:\s]+(.*?)(?=Day \d+|$)', re.DOTALL)
days = day_pattern.findall(plan_text)
if not days: # If no day pattern found, create a single all-day event with the entire content
event = Event()
event.add('summary', "Travel Itinerary")
event.add('description', plan_text)
event.add('dtstart', start_date.date())
event.add('dtend', start_date.date())
event.add("dtstamp", datetime.now())
cal.add_component(event)
else:
# Process each day
for day_num, day_content in days:
day_num = int(day_num)
current_date = start_date + timedelta(days=day_num - 1)
# Create a single event for the entire day
event = Event()
event.add('summary', f"Day {day_num} Itinerary")
event.add('description', day_content.strip())
# Make it an all-day event
event.add('dtstart', current_date.date())
event.add('dtend', current_date.date())
event.add("dtstamp", datetime.now())
cal.add_component(event)
return cal.to_ical()
# Set up the Streamlit app
st.title("AI Travel Planner ✈️")
st.title("AI Travel Planner ")
st.caption("Plan your next adventure with AI Travel Planner by researching and planning a personalized itinerary on autopilot using GPT-4o")
# Initialize session state to store the generated itinerary
if 'itinerary' not in st.session_state:
st.session_state.itinerary = None
# Get OpenAI API key from user
openai_api_key = st.text_input("Enter OpenAI API Key to access GPT-4o", type="password")
@@ -60,22 +117,41 @@ if openai_api_key and serp_api_key:
destination = st.text_input("Where do you want to go?")
num_days = st.number_input("How many days do you want to travel for?", min_value=1, max_value=30, value=7)
if st.button("Generate Itinerary"):
with st.spinner("Researching your destination..."):
# First get research results
research_results = researcher.run(f"Research {destination} for a {num_days} day trip", stream=False)
col1, col2 = st.columns(2)
with col1:
if st.button("Generate Itinerary"):
with st.spinner("Researching your destination..."):
# First get research results
research_results = researcher.run(f"Research {destination} for a {num_days} day trip", stream=False)
# Show research progress
st.write(" Research completed")
with st.spinner("Creating your personalized itinerary..."):
# Pass research results to planner
prompt = f"""
Destination: {destination}
Duration: {num_days} days
Research Results: {research_results.content}
Please create a detailed itinerary based on this research.
"""
response = planner.run(prompt, stream=False)
# Store the response in session state
st.session_state.itinerary = response.content
st.write(response.content)
# Only show download button if there's an itinerary
with col2:
if st.session_state.itinerary:
# Generate the ICS file
ics_content = generate_ics_content(st.session_state.itinerary)
# Show research progress
st.write("✓ Research completed")
with st.spinner("Creating your personalized itinerary..."):
# Pass research results to planner
prompt = f"""
Destination: {destination}
Duration: {num_days} days
Research Results: {research_results.content}
Please create a detailed itinerary based on this research.
"""
response = planner.run(prompt, stream=False)
st.write(response.content)
# Provide the file for download
st.download_button(
label="Download Itinerary as Calendar (.ics)",
data=ics_content,
file_name="travel_itinerary.ics",
mime="text/calendar"
)