import os
import asyncio
import requests
import urllib.parse
import gradio as gr
import traceback
from typing import Optional, Dict, Any, AsyncGenerator
# Import the core components from the original script
from main import RAGSystem, geocoding, calculate_additional_transport_times
class ProfessionalTransportationPlanner:
def __init__(self):
# Global route storage
self.global_route_data = {
"origin": None,
"destination": None,
"routes": {}
}
# Comprehensive transport mode details
self.transport_modes = {
"car": {
"name": "Car",
"icon": "π",
"avg_speed": 60, # km/h
"cost_per_km": 0.15, # Estimated fuel and maintenance
"environmental_impact": "High",
"best_for": ["Flexibility", "Long distances", "Group travel"]
},
"bike": {
"name": "Bicycle",
"icon": "π²",
"avg_speed": 15, # km/h
"cost_per_km": 0.01, # Minimal maintenance
"environmental_impact": "Very Low",
"best_for": ["Short distances", "Exercise", "Urban areas"]
},
"foot": {
"name": "Walking",
"icon": "πΆ",
"avg_speed": 5, # km/h
"cost_per_km": 0,
"environmental_impact": "Zero",
"best_for": ["Short distances", "Exploration", "Health"]
},
"bus": {
"name": "Public Bus",
"icon": "π",
"avg_speed": 25, # km/h
"cost_per_km": 0.10, # Public transit fare
"environmental_impact": "Low",
"best_for": ["Budget travel", "Urban commuting", "No parking hassles"]
},
"airplane": {
"name": "Airplane",
"icon": "βοΈ",
"avg_speed": 800, # km/h
"cost_per_km": 0.50, # Varies widely
"environmental_impact": "High",
"best_for": ["Long distances", "International travel", "Time-sensitive trips"]
}
}
# Initialize RAG system
self.rag_system = RAGSystem()
def geocode_location(self, location: str) -> Dict[str, Any]:
"""Advanced geocoding with comprehensive details"""
try:
key = os.getenv("TRACE") # GraphHopper API key
status, lat, lng, formatted_loc = geocoding(location, key)
if status != 200 or lat == "null" or lng == "null":
return {
"status": "error",
"message": f"Could not geocode location: {location}",
"details": "Possible reasons: Incomplete address, typo, or unsupported location"
}
return {
"status": "success",
"location": formatted_loc,
"latitude": lat,
"longitude": lng,
}
except Exception as e:
return {
"status": "error",
"message": f"Geocoding error: {str(e)}",
"details": "Unable to process location. Please check the address and try again."
}
def plan_route(self, origin: str, destination: str) -> str:
"""Comprehensive route planning with detailed insights"""
try:
# Geocode locations
orig_data = self.geocode_location(origin)
dest_data = self.geocode_location(destination)
if orig_data["status"] == "error" or dest_data["status"] == "error":
return f"""
π¨ Route Planning Error
Origin: {orig_data.get('message', 'Unknown origin error')}
Destination: {dest_data.get('message', 'Unknown destination error')}
Please verify your locations and try again.
"""
# Reset global route data
self.global_route_data = {
"origin": orig_data["location"],
"destination": dest_data["location"],
"routes": {}
}
# Supported transport modes
transport_modes = ["car", "bike", "foot", "bus", "airplane"]
# HTML for route summary
route_summary = f"""
πΊοΈ Route Analysis
From: {orig_data['location']}
To: {dest_data['location']}
π¦ Transport Mode Comparison
| Mode |
Duration |
Distance |
Cost Est. |
Environmental Impact |
"""
# Route URL and key
route_url = "https://graphhopper.com/api/1/route?"
key = os.getenv("TRACE")
# Process each transport mode
for vehicle in transport_modes:
# Construct URL for API-supported modes
if vehicle in ["car", "bike", "foot"]:
op = f"&point={orig_data['latitude']}%2C{orig_data['longitude']}"
dp = f"&point={dest_data['latitude']}%2C{dest_data['longitude']}"
paths_url = route_url + urllib.parse.urlencode({"key": key, "vehicle": vehicle}) + op + dp
paths_response = requests.get(paths_url)
if paths_response.status_code == 200:
paths_data = paths_response.json()
if "paths" in paths_data and len(paths_data["paths"]) > 0:
path = paths_data["paths"][0]
miles = path["distance"] / 1000 / 1.61
km = path["distance"] / 1000
sec = int(path["time"] / 1000 % 60)
min = int(path["time"] / 1000 / 60 % 60)
hr = int(path["time"] / 1000 / 60 / 60)
# Estimated cost calculation
mode_details = self.transport_modes.get(vehicle, {})
est_cost = km * mode_details.get('cost_per_km', 0)
route_summary += f"""
|
{mode_details.get('icon', '')}
{mode_details.get('name', vehicle.capitalize())}
|
{hr:02d}:{min:02d}:{sec:02d} |
{km:.1f} km |
${est_cost:.2f} |
{mode_details.get('environmental_impact', 'Unknown')} |
"""
# Store route data
self.global_route_data["routes"][vehicle] = {
"duration": f"{hr:02d}:{min:02d}:{sec:02d}",
"distance_km": km,
"distance_miles": miles,
"estimated_cost": est_cost
}
# Handle estimated modes (bus, airplane)
else:
# Estimate based on car route
base_car_distance = 100 # Default km
duration_minutes = calculate_additional_transport_times(base_car_distance, vehicle)
hr, min_remainder = divmod(duration_minutes, 60)
min, sec = divmod(min_remainder * 60, 60)
miles = base_car_distance / 1.61
# Estimated cost calculation
mode_details = self.transport_modes.get(vehicle, {})
est_cost = base_car_distance * mode_details.get('cost_per_km', 0)
route_summary += f"""
|
{mode_details.get('icon', '')}
{mode_details.get('name', vehicle.capitalize())}
(Estimated)
|
{int(hr):02d}:{int(min):02d}:{int(sec):02d} |
{base_car_distance:.1f} km |
${est_cost:.2f} |
{mode_details.get('environmental_impact', 'Unknown')} |
"""
# Store estimated route data
self.global_route_data["routes"][vehicle] = {
"duration": f"{int(hr):02d}:{int(min):02d}:{int(sec):02d}",
"distance_km": base_car_distance,
"distance_miles": miles,
"estimated_cost": est_cost,
"estimated": True
}
route_summary += """
"""
return route_summary
except Exception as e:
error_trace = traceback.format_exc()
print(f"Comprehensive route planning error: {error_trace}")
return f"""
π¨ Unexpected Error
An unexpected error occurred during route planning.
Error Details: {str(e)}
Please try again or contact support.
"""
async def stream_route_query(self, query: str, transport_preference: Optional[str] = None) -> AsyncGenerator[str, None]:
"""Streaming AI-powered route query with enhanced context"""
try:
# Check if routes have been planned
if not self.global_route_data.get("origin") or not self.global_route_data.get("destination"):
yield """
β οΈ Query Limitation
Please plan a route first before asking about it.
Use the 'Route Planning' tab to create a route, then ask questions.
"""
return
# Enhance query with route context
context = f"Route from {self.global_route_data['origin']} to {self.global_route_data['destination']}"
full_query = f"{query} ({context})"
# Streaming response simulation (replace with actual streaming implementation)
# Here we'll use the original query method but simulate streaming
response = self.rag_system.query(full_query, transport_preference)
# Start the response
yield f"""
π€ AI Route Assistant
"""
# Simulate streaming by yielding chunks of text
words = response["answer"].split()
current_chunk = ""
for word in words:
current_chunk += word + " "
# Yield chunks to simulate streaming
if len(current_chunk.split()) % 5 == 0:
yield f"{current_chunk}"
await asyncio.sleep(0.1) # Small delay to simulate streaming
# Yield any remaining text
if current_chunk:
yield f"{current_chunk}"
# Close the response div
yield """
π‘ Tip: This response is AI-generated based on available route information.
"""
except Exception as e:
error_trace = traceback.format_exc()
print(f"Route query error: {error_trace}")
yield f"""
π¨ Query Error
Unable to process your query.
Error Details: {str(e)}
"""
def create_gradio_interface():
"""Create a professional Gradio interface with enhanced styling"""
# Comprehensive CSS for professional design
css = """
/* Global Styling */
.gradio-container {
font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
background-color: #0f1429;
color: #e0e0e0;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* Typography */
h1, h2, h3 {
color: #ffffff;
font-weight: 600;
margin-bottom: 15px;
}
/* Input Styling */
.gradio-container .form .input-text {
background-color: #1a2138;
border: 1px solid #2c3e50;
color: #ffffff;
padding: 10px;
border-radius: 6px;
transition: all 0.3s ease;
}
.gradio-container .form .input-text:focus {
border-color: #3498db;
box-shadow: 0 0 10px rgba(52, 152, 219, 0.3);
}
/* Button Styling */
.gradio-container .button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
transition: all 0.3s ease;
}
.gradio-container .button:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
/* Card Styling */
.route-info-card,
.route-comparison-card,
.ai-response-card,
.route-error-card,
.route-warning-card {
background-color: #1a2138;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.route-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
.route-table th {
background-color: #2c3e50;
color: #ecf0f1;
padding: 12px;
text-align: left;
border-bottom: 2px solid #34495e;
}
.route-table td {
padding: 12px;
border-bottom: 1px solid #2c3e50;
color: #bdc3c7;
}
.route-table tr:hover {
background-color: #2c3e50;
transition: background-color 0.3s ease;
}
/* AI Response Styling */
.ai-response-content {
background-color: #252b42;
padding: 15px;
border-radius: 8px;
line-height: 1.6;
color: #e0e0e0;
}
.ai-response-tip {
margin-top: 15px;
font-size: 0.9em;
color: #7f8c8d;
font-style: italic;
}
/* Error and Warning Cards */
.route-error-card {
border-left: 5px solid #e74c3c;
}
.route-warning-card {
border-left: 5px solid #f39c12;
}
/* Responsive Design */
@media (max-width: 768px) {
.gradio-container {
padding: 10px;
}
.route-table {
font-size: 0.9em;
}
.route-table th, .route-table td {
padding: 8px;
}
}
"""
# Initialize the planner
planner = ProfessionalTransportationPlanner()
# Create Gradio interface
with gr.Blocks(css=css, title="Professional Transportation Planner", theme=gr.themes.Soft()) as demo:
# Header with professional branding
gr.Markdown("""
# π Professional Transportation Planner
## Intelligent Route Analysis & AI-Powered Navigation
""")
# Main interface layout with enhanced tabs
with gr.Tabs() as tabs:
# Route Planner Tab
with gr.Tab("Route Planner", id="route-planner"):
# Input section with improved layout
with gr.Row():
origin_input = gr.Textbox(
label="Origin Location",
placeholder="Enter precise starting point (e.g., 123 Main St, New York, NY)",
info="π Provide a detailed, specific location for best results",
scale=1
)
destination_input = gr.Textbox(
label="Destination Location",
placeholder="Enter precise destination (e.g., 456 Elm St, Boston, MA)",
info="π Enter a complete, accurate address",
scale=1
)
# Advanced options row
with gr.Row():
# Plan Route Button with enhanced styling
plan_route_btn = gr.Button(
"π Analyze Route",
variant="primary"
)
# Results section
route_output = gr.HTML(label="πΊοΈ Detailed Route Analysis")
# AI Insights Tab
with gr.Tab("Route Insights", id="route-insights"):
# Query input with context-aware placeholder
query_input = gr.Textbox(
label="Ask AI Route Assistant",
placeholder="What specific insights do you need about your planned route?",
info="π‘ Ask questions about transportation, local info, or route details"
)
# Transport mode preference with icons
transport_pref = gr.Dropdown(
["car", "bike", "foot", "bus", "airplane"],
label="π Preferred Transport Mode (Optional)",
info="Narrow down AI insights to a specific mode of transport"
)
# Insights Button with professional styling
query_btn = gr.Button(
"π€ Get AI Insights",
variant="primary"
)
# Streaming AI Response Output
query_output = gr.HTML(label="π§ AI Route Insights")
# Event Handlers with async support
plan_route_btn.click(
fn=planner.plan_route,
inputs=[origin_input, destination_input],
outputs=route_output
)
query_btn.click(
fn=planner.stream_route_query,
inputs=[query_input, transport_pref],
outputs=query_output,
api_name="route_insights"
)
return demo
def main():
# Create and launch the interface with additional configurations
demo = create_gradio_interface()
demo.launch(
server_name="0.0.0.0",
server_port=8000,
share=True,
debug=True # Enable debug mode for development
)
if __name__ == "__main__":
main()