Tour_Generator_GA / streamlit_ui.py
GaetanoParente's picture
first commit
639f871
"""
streamlit_ui.py β€” Streamlit UI for Tour Generator.
Allows uploading POIs and selecting profile to generate tours.
"""
import streamlit as st
import requests
import json
import os
import pandas as pd
from typing import Optional
from pathlib import Path
from huggingface_hub import CommitScheduler
DATASET_REPO_ID = "NextGenTech/tour-generator-logs"
LOG_DIR = Path("data")
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOG_DIR / "tour_results.jsonl"
scheduler = CommitScheduler(
repo_id=DATASET_REPO_ID,
repo_type="dataset",
folder_path=LOG_DIR,
path_in_repo="logs",
every=15
)
st.title("πŸ—ΊοΈ Tour Generator")
st.markdown("Upload Points of Interest and select a user profile to generate an optimized tour using genetic algorithms.")
# Get available profiles
try:
response = requests.get("http://localhost:8000/profiles")
if response.status_code == 200:
available_profiles = response.json()["profiles"]
else:
available_profiles = ["cultural_walker", "foodie_transit", "family_mixed", "art_lover_car"]
except:
available_profiles = ["cultural_walker", "foodie_transit", "family_mixed", "art_lover_car"]
st.header("πŸ“ Upload Points of Interest (POIs)")
pois_file = st.file_uploader(
"Upload POIs as CSV or JSON file",
type=['csv', 'json'],
help="CSV columns: id,name,lat,lon,score,visit_duration,time_window_open,time_window_close,category,tags\nJSON: list of POI objects"
)
st.header("πŸ‘€ Select User Profile")
profile_option = st.radio("Profile Type", ["Predefined", "Custom"])
profile_name: Optional[str] = None
profile_data: Optional[dict] = None
if profile_option == "Predefined":
profile_name = st.selectbox("Choose a predefined profile", available_profiles)
if profile_name:
try:
response = requests.get(f"http://localhost:8000/profiles/{profile_name}")
if response.status_code == 200:
profile_data = response.json()
st.subheader(f"Profile Details: {profile_name}")
st.json(profile_data)
except:
st.warning("Could not load profile details")
else:
profile_file = st.file_uploader(
"Upload custom profile JSON",
type=['json'],
help="JSON object with TouristProfile fields"
)
if profile_file:
try:
profile_data = json.load(profile_file)
st.json(profile_data)
except:
st.error("Invalid JSON file")
st.header("βš™οΈ Tour Parameters")
col1, col2 = st.columns(2)
with col1:
budget = st.number_input("Time Budget (minutes)", value=480, min_value=60, help="Total available time for the tour")
start_lat = st.number_input("Start Latitude", value=41.9028, format="%.4f")
with col2:
start_time = st.number_input("Start Time (minutes from midnight)", value=540, min_value=0, max_value=1439, help="e.g., 540 = 9:00 AM")
start_lon = st.number_input("Start Longitude", value=12.4964, format="%.4f")
if st.button("πŸš€ Generate Tour", type="primary"):
if not pois_file:
st.error("Please upload a POIs file")
st.stop()
if profile_option == "Custom" and not profile_data:
st.error("Please upload a custom profile JSON")
st.stop()
# Prepare request
files = {'pois_file': pois_file}
data = {
'budget': budget,
'start_time': start_time,
'start_lat': start_lat,
'start_lon': start_lon,
}
if profile_name:
data['profile_name'] = profile_name
elif profile_data:
data['profile_json'] = json.dumps(profile_data)
with st.spinner("Generating tour... This may take a few moments."):
try:
response = requests.post("http://localhost:8000/generate_tour", files=files, data=data, timeout=60)
if response.status_code == 200:
result = response.json()
# Log the result
log_entry = {
"timestamp": str(pd.Timestamp.now()),
"profile": profile_name if profile_name else "custom",
"budget": budget,
"start_time": start_time,
"start_lat": start_lat,
"start_lon": start_lon,
"result": result
}
with scheduler.lock:
with LOG_FILE.open("a", encoding="utf-8") as f:
json.dump(log_entry, f, ensure_ascii=False)
f.write("\n")
st.success("βœ… Tour generated successfully!")
# Display summary
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Score", f"{result['total_score']:.2f}")
with col2:
st.metric("Total Distance", f"{result['total_distance']:.1f} km")
with col3:
st.metric("Total Time", f"{result['total_time']} min")
st.write(f"**Feasible:** {'βœ… Yes' if result['is_feasible'] else '❌ No'}")
# Display stops
st.header("πŸ“‹ Tour Itinerary")
if result['stops']:
stops_df = pd.DataFrame(result['stops'])
stops_df['arrival_time'] = stops_df['arrival'].apply(lambda x: f"{x//60:02d}:{x%60:02d}")
stops_df['departure_time'] = stops_df['departure'].apply(lambda x: f"{x//60:02d}:{x%60:02d}")
stops_df['wait_min'] = stops_df['wait']
st.dataframe(
stops_df[['poi_name', 'arrival_time', 'departure_time', 'wait_min', 'travel_distance_km', 'travel_time_min']],
width="stretch"
)
else:
st.info("No stops in the generated tour")
else:
st.error(f"Error: {response.status_code} - {response.text}")
except requests.exceptions.RequestException as e:
st.error(f"Failed to connect to the API. Make sure the FastAPI server is running on localhost:8000\nError: {str(e)}")
st.markdown("---")
st.markdown("**Note:** Make sure to start the FastAPI server first: `python app.py` or `uvicorn app:app --reload`")