import os
import json
import time
import requests
import pandas as pd
import streamlit as st
from datetime import date, timedelta
# -----------------------------
# Setup: API Keys from env vars
# -----------------------------
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY", "")
# -----------------------------
# Google Places helpers
# -----------------------------
PLACES_TEXT_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/textsearch/json"
def places_text_search(query, key, max_results=20):
"""
Simple wrapper for Google Places 'Text Search' API.
We page through results (up to ~60) but cap by max_results.
"""
if not key:
return []
params = {
"query": query,
"key": key,
}
out = []
next_page_token = None
while True:
if next_page_token:
params["pagetoken"] = next_page_token
# Google requires short delay before using next_page_token
time.sleep(2)
r = requests.get(PLACES_TEXT_SEARCH_URL, params=params, timeout=30)
if r.status_code != 200:
break
data = r.json()
out.extend(data.get("results", []))
next_page_token = data.get("next_page_token")
if not next_page_token or len(out) >= max_results:
break
return out[:max_results]
def fetch_hotels(destination: str, max_results=20):
"""
Fetch hotels around destination. Uses price_level (0-4) and rating when available.
"""
results = places_text_search(f"hotels in {destination}", GOOGLE_MAPS_API_KEY, max_results=max_results)
rows = []
for r in results:
rows.append({
"name": r.get("name"),
"rating": r.get("rating"),
"reviews": r.get("user_ratings_total"),
"price_level(0-4)": r.get("price_level"),
"address": r.get("formatted_address"),
"lat": r.get("geometry", {}).get("location", {}).get("lat"),
"lng": r.get("geometry", {}).get("location", {}).get("lng")
})
df = pd.DataFrame(rows)
# sort: rating desc, then reviews desc
if not df.empty:
df = df.sort_values(by=["rating", "reviews"], ascending=[False, False], na_position="last")
return df
def fetch_attractions(destination: str, max_results=20):
"""
Fetch attractions/POIs.
"""
query = f"top attractions in {destination}"
results = places_text_search(query, GOOGLE_MAPS_API_KEY, max_results=max_results)
rows = []
for r in results:
rows.append({
"name": r.get("name"),
"category": ", ".join(r.get("types", [])),
"rating": r.get("rating"),
"reviews": r.get("user_ratings_total"),
"address": r.get("formatted_address"),
"lat": r.get("geometry", {}).get("location", {}).get("lat"),
"lng": r.get("geometry", {}).get("location", {}).get("lng")
})
df = pd.DataFrame(rows)
if not df.empty:
df = df.sort_values(by=["rating", "reviews"], ascending=[False, False], na_position="last")
return df
# -----------------------------
# Budget helper
# -----------------------------
def simple_budget_breakdown(total_budget, days, travelers=1):
"""
Very coarse split of a trip budget (adjust as needed).
Returns per-trip & per-day guide.
"""
total_budget = float(total_budget)
days = max(1, int(days))
travelers = max(1, int(travelers))
# Example split: 45% stay, 30% food, 15% local transport, 10% activities
stay = total_budget * 0.45
food = total_budget * 0.30
transport = total_budget * 0.15
activities = total_budget * 0.10
per_day = {
"Stay": round(stay / days, 2),
"Food": round(food / days, 2),
"Local Transport": round(transport / days, 2),
"Activities": round(activities / days, 2),
"Total/day": round(total_budget / days, 2)
}
per_person = round(total_budget / travelers, 2)
return per_day, per_person
# -----------------------------
# OpenAI itinerary helper
# -----------------------------
def generate_itinerary_with_openai(destination, start_date, days, interests, budget_hint, travelers):
"""
Calls OpenAI Chat Completions to generate an itinerary.
Expects OPENAI_API_KEY in env. Uses the Chat Completions HTTP endpoint.
"""
if not OPENAI_API_KEY:
return "OpenAI API key not set. Please set OPENAI_API_KEY in your environment."
# Build system & user prompts
sys_prompt = (
"You are a helpful travel planner. Create practical, walkable day-by-day itineraries with morning, afternoon, and evening blocks, "
"add short logistic hints, and keep it realistic for the location. Keep each day concise and bulleted."
)
user_prompt = (
f"Destination: {destination}\n"
f"Start date: {start_date}\n"
f"Trip length: {days} days\n"
f"Travelers: {travelers}\n"
f"Interests: {', '.join(interests) if interests else 'general sightseeing'}\n"
f"Budget guidance: {budget_hint}\n\n"
"Please produce:\n"
"1) A short overview paragraph (tone: friendly & practical)\n"
"2) Day-by-day plan, each day with 3 bullet sections: Morning / Afternoon / Evening\n"
"3) A compact list of neighborhood/area suggestions for dining\n"
"4) 6 packing tips relevant to weather & activities\n"
)
# Minimal direct HTTP call to OpenAI Chat Completions (compatible with current API).
url = "https://api.openai.com/v1/chat/completions"
headers = {
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": "gpt-4o-mini", # pick a chat-capable model available to your account
"messages": [
{"role": "system", "content": sys_prompt},
{"role": "user", "content": user_prompt}
],
"temperature": 0.7,
"max_tokens": 1200
}
try:
resp = requests.post(url, headers=headers, data=json.dumps(payload), timeout=60)
resp.raise_for_status()
data = resp.json()
return data["choices"][0]["message"]["content"].strip()
except Exception as e:
return f"Failed to generate itinerary: {e}"
# -----------------------------
# Streamlit UI
# -----------------------------
st.set_page_config(page_title="Virtual AI Travel Planner", page_icon="🧭", layout="wide")
st.title("🧭 Virtual AI Travel Planner")
st.caption("Enter your destination & budget → get hotels, attractions, and an AI-built itinerary.\n\n*Educational demo. Always verify details before booking.*")
with st.sidebar:
st.header("🔑 API Keys")
st.write("Set these as environment variables before running:\n- `OPENAI_API_KEY`\n- `GOOGLE_MAPS_API_KEY`")
st.write("Detected:")
st.code(f"OPENAI_API_KEY set: {bool(OPENAI_API_KEY)}\nGOOGLE_MAPS_API_KEY set: {bool(GOOGLE_MAPS_API_KEY)}")
col1, col2, col3 = st.columns([1.2,1,1])
with col1:
destination = st.text_input("Destination (city/country)", placeholder="e.g., Tokyo, Japan")
start = st.date_input("Start date", value=date.today() + timedelta(days=14))
days = st.number_input("Trip length (days)", min_value=1, max_value=21, value=5, step=1)
with col2:
budget = st.number_input("Total budget (your currency)", min_value=0.0, value=1000.0, step=100.0, help="Rough trip budget total")
travelers = st.number_input("Travelers", min_value=1, value=2, step=1)
interests = st.multiselect("Interests", [
"Food", "Museums", "Nature", "Architecture", "Shopping", "Nightlife", "Adventure", "History", "Beaches", "Hiking"
], default=["Food", "Museums"])
with col3:
max_hotels = st.slider("Max hotels to fetch", 5, 40, 15)
max_attractions = st.slider("Max attractions to fetch", 5, 40, 20)
run = st.button("✨ Plan my trip")
if run:
if not destination.strip():
st.error("Please enter a destination.")
st.stop()
# Budget breakdown
per_day, per_person = simple_budget_breakdown(budget, days, travelers)
st.subheader("💸 Budget Guide")
c1, c2 = st.columns(2)
with c1:
st.write("**Per-day guide** (rough):")
st.table(pd.DataFrame([per_day]))
with c2:
st.metric("Per-person total", f"{per_person:.2f}")
# Google results
st.subheader("🏨 Hotels (Google Places)")
hotels_df = fetch_hotels(destination, max_results=max_hotels)
if hotels_df.empty:
st.info("No hotel data (check your Google API key & billing).")
else:
st.dataframe(hotels_df, use_container_width=True)
st.subheader("📍 Attractions / Things to do (Google Places)")
attractions_df = fetch_attractions(destination, max_results=max_attractions)
if attractions_df.empty:
st.info("No attractions data (check your Google API key & billing).")
else:
st.dataframe(attractions_df, use_container_width=True)
# AI itinerary
st.subheader("🧠 AI Itinerary")
budget_hint = f"Total budget approx {budget} for {travelers} travelers over {days} days. Per-day guide: {per_day}."
itinerary = generate_itinerary_with_openai(destination, start, days, interests, budget_hint, travelers)
st.write(itinerary)
# Optional: CSV downloads
st.download_button(
"⬇️ Download hotels CSV",
data=hotels_df.to_csv(index=False).encode("utf-8"),
file_name=f"{destination.replace(' ','_').lower()}_hotels.csv",
mime="text/csv"
)
st.download_button(
"⬇️ Download attractions CSV",
data=attractions_df.to_csv(index=False).encode("utf-8"),
file_name=f"{destination.replace(' ','_').lower()}_attractions.csv",
mime="text/csv"
)
st.success("Done! Scroll up to view tables and itinerary. 🌍✈️")
else:
st.info("Fill the form and click **Plan my trip**.")
No comments:
Post a Comment