Spaces:
Sleeping
Sleeping
| """ | |
| 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( | |
| """ | |
| --- | |
| <div style="text-align: center; color: #666;"> | |
| <small>Powered by CrewAI β’ Built with Gradio β’ Secure API handling β’ Browser-based location detection</small> | |
| </div> | |
| """ | |
| ) | |
| # Launch the interface | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| debug=True) | |