""" Entry point for the Tourism Recommendation System Gradio app. This file should be named 'app.py' for Hugging Face Spaces deployment. Modified to use proper browser-based location detection. """ import gradio as gr import json import warnings import tempfile import os from crew import tourismAgent warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd") def process_location(location_input): """ Process location input to handle both coordinate and descriptive formats. """ if not location_input or location_input.strip() == "": return None location_input = location_input.strip() # Check if location is in lat,lng format if ',' in location_input: parts = location_input.split(',') if len(parts) == 2: try: lat = float(parts[0].strip()) lng = float(parts[1].strip()) return { "latitude": lat, "longitude": lng, "source": "manual" } except ValueError: pass # Treat as descriptive location return { "description": location_input, "source": "manual" } def parse_browser_location(location_data): """ Parse location data received from browser geolocation API. Expected format: "lat,lng,accuracy" or JSON string with coordinates """ if not location_data or location_data.strip() == "" or location_data == "null": return None try: # Handle error messages if location_data.startswith('ERROR:'): print(f"Browser location error: {location_data}") return None # Try to parse as JSON first (in case of more detailed data) if location_data.startswith('{'): data = json.loads(location_data) return { "latitude": data.get('latitude'), "longitude": data.get('longitude'), "accuracy": data.get('accuracy'), "source": "browser" } else: # Parse as simple "lat,lng,accuracy" format parts = location_data.split(',') if len(parts) >= 2: lat = float(parts[0].strip()) lng = float(parts[1].strip()) accuracy = float(parts[2].strip()) if len(parts) > 2 else None return { "latitude": lat, "longitude": lng, "accuracy": accuracy, "source": "browser" } except (json.JSONDecodeError, ValueError, IndexError) as e: print(f"Error parsing browser location: {e}") return None return None def validate_api_key(api_key): """ Validate if the provided API key has the correct format. """ if not api_key or api_key.strip() == "": return False, "API key is required" # Basic Anthropic API key format validation api_key = api_key.strip() if not api_key.startswith("sk-ant-"): return False, "Invalid API key format. Anthropic API keys should start with 'sk-ant-'" if len(api_key) < 50: return False, "API key appears to be too short" return True, "API key format is valid" def get_tourism_recommendations(interests, location_input, browser_location, api_key): """ Get tourism recommendations based on user interests and location. """ try: # Validate API key first is_valid, validation_message = validate_api_key(api_key) if not is_valid: return f"āŒ API Key Error: {validation_message}\n\nPlease enter a valid Anthropic API key to use this service." # Validate inputs if not interests or interests.strip() == "": return "āŒ Please provide your interests or preferences for places to visit." # Process location - prioritize browser location over manual input location = None location_info = "" # First try browser location if browser_location and browser_location.strip(): browser_loc = parse_browser_location(browser_location) if browser_loc: location = browser_loc accuracy_info = f" (±{int(browser_loc.get('accuracy', 0))}m)" if browser_loc.get('accuracy') else "" location_info = f"šŸ“ Your location: {browser_loc['latitude']:.4f}, {browser_loc['longitude']:.4f}{accuracy_info}" # Fallback to manual location input if browser location failed if not location and location_input: manual_loc = process_location(location_input) if manual_loc: location = manual_loc if 'description' in manual_loc: location_info = f"šŸ“ Using location: {manual_loc['description']}" else: location_info = f"šŸ“ Using coordinates: {manual_loc['latitude']:.4f}, {manual_loc['longitude']:.4f}" # If no location provided if not location: location_info = "šŸ“ No specific location provided - using general recommendations" # Prepare inputs for the CrewAI inputs = { "interests": interests.strip(), "location": location, } # Initialize tourism agent with the provided API key agent = tourismAgent(api_key=api_key.strip()) # Run the tourism agent result = agent.crew().kickoff(inputs=inputs) # Add location info to the result result_with_location = f"{location_info}\n\n{result}" return result_with_location except Exception as e: error_msg = f"An error occurred while getting recommendations: {str(e)}" return f"āŒ {error_msg}" # Create the Gradio interface with gr.Blocks( title="šŸŒ Tourism Recommendation System", theme=gr.themes.Soft(), css=""" .gradio-container { max-width: 800px !important; margin: 0 auto !important; } .header { text-align: center; margin-bottom: 30px; } .location-section { background-color: #e8f4f8; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #17a2b8; } .api-key-section { background-color: #fff3cd; padding: 15px; border-radius: 8px; margin: 15px 0; border-left: 4px solid #ffc107; } .location-button { margin: 10px 0; } .status-success { color: #28a745; background-color: #d4edda; border: 1px solid #c3e6cb; padding: 10px; border-radius: 5px; margin: 5px 0; } .status-error { color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 10px; border-radius: 5px; margin: 5px 0; } """ ) as demo: # Header gr.Markdown( """ # šŸŒ Tourism Recommendation System Get personalized travel recommendations based on your interests and location! Simply describe what kind of places you'd like to visit, and optionally provide your location. """, elem_classes=["header"] ) with gr.Row(): with gr.Column(scale=1): # API Key section gr.Markdown("## šŸ”‘ API Configuration") with gr.Group(): api_key_input = gr.Textbox( label="Anthropic API Key", placeholder="Enter your Anthropic API key (sk-ant-...)", type="password", info="Your API key is required to use Claude. Get one from: https://console.anthropic.com/" ) # Input section gr.Markdown("## šŸ“ Your Preferences") interests_input = gr.Textbox( label="What kind of places interest you?", placeholder="e.g., historic places, museums, beaches, adventure spots, local cuisine, art galleries...", lines=3, max_lines=5 ) # Location options gr.Markdown("## šŸ“ Location Settings") with gr.Group(elem_classes=["location-section"]): gr.Markdown( """ **šŸŽÆ For Best Recommendations:** Use your browser location for accurate, location-based suggestions. Your browser will ask for permission to access your location. This data stays private and is only used for recommendations. """ ) # Browser location button and status with gr.Row(): get_location_btn = gr.Button( "šŸ“ Get My Current Location", variant="secondary", elem_classes=["location-button"] ) # Hidden field to store browser location data browser_location = gr.Textbox( label="Browser Location Data", visible=False, interactive=False, value="" ) location_status = gr.Markdown( "Click 'Get My Current Location' to detect your position using your device's GPS/location services.", elem_classes=["status-info"] ) gr.Markdown("**Alternative:** Manually specify your location below:") location_input = gr.Textbox( label="Manual Location Entry", placeholder="e.g., 'New York, NY' or '40.7128,-74.0060' (latitude,longitude)", info="Enter city name or coordinates. Browser location takes priority if both are provided." ) # Submit button submit_btn = gr.Button( "šŸ” Get Recommendations", variant="primary", size="lg" ) with gr.Column(scale=1): # Output section gr.Markdown("## šŸŽÆ Recommendations") output = gr.Textbox( label="Your Personalized Recommendations", placeholder="Your recommendations will appear here...", lines=20, max_lines=30, show_copy_button=True ) # JavaScript function to get location - this runs in the user's browser, not on the server get_location_btn.click( fn=None, inputs=[], outputs=[browser_location, location_status], js=""" () => { return new Promise((resolve) => { if (!navigator.geolocation) { resolve(["", "āŒ **Location Error:** Geolocation is not supported by your browser."]); return; } // Show loading status immediately const loadingStatus = "šŸ”„ **Getting your location...** Please allow location access when prompted."; navigator.geolocation.getCurrentPosition( function(position) { const lat = position.coords.latitude; const lng = position.coords.longitude; const accuracy = Math.round(position.coords.accuracy); // Format: lat,lng,accuracy for easy parsing const locationData = `${lat},${lng},${accuracy}`; const statusMsg = `āœ… **Location detected successfully!**\\nšŸ“ Coordinates: ${lat.toFixed(4)}, ${lng.toFixed(4)}\\nšŸŽÆ Accuracy: ±${accuracy} meters`; resolve([locationData, statusMsg]); }, function(error) { let errorMsg = "āŒ **Location Error:** "; switch(error.code) { case error.PERMISSION_DENIED: errorMsg += "You denied location access. Please enable location permissions in your browser settings and try again."; break; case error.POSITION_UNAVAILABLE: errorMsg += "Location information is unavailable. Please check your GPS/location services."; break; case error.TIMEOUT: errorMsg += "Location request timed out. Please try again."; break; default: errorMsg += "An unknown error occurred while getting your location."; break; } resolve(["", errorMsg]); }, { enableHighAccuracy: true, timeout: 15000, // 15 seconds maximumAge: 300000 // 5 minutes cache } ); }); } """ ) # Submit button click submit_btn.click( get_tourism_recommendations, inputs=[interests_input, location_input, browser_location, api_key_input], outputs=[output] ) # Enter key support interests_input.submit( get_tourism_recommendations, inputs=[interests_input, location_input, browser_location, api_key_input], outputs=[output] ) # Footer gr.Markdown( """ ---
Powered by CrewAI • Built with Gradio • Secure API handling • Browser-based location detection
""" ) # Launch the interface if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7860, share=False, debug=True)