Spaces:
Paused
Paused
| # app.py | |
| import gradio as gr | |
| import requests | |
| import os | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| # Configurazione API | |
| API_KEYS = { | |
| "GOOGLE_PLACES": "AIzaSyAZpcC5nIdI-S-3Y28_giY4ZQiGDQ1ialY", | |
| "UNSPLASH_ACCESS": "OqNHNAQYxq5VKPciSZpdKCvr2_wlKAvOegC2SR4N57M", | |
| "OPENWEATHER": "a78fd47d30bd59926106c97c2f7a6fe2" | |
| } | |
| CSS = """ | |
| body { font-family: 'Arial', sans-serif; background: #f5f7fa; margin: 0; } | |
| .travel-container { max-width: 1200px; margin: 0 auto; padding: 20px; } | |
| .header { text-align: center; color: #2d3436; margin-bottom: 30px; } | |
| .section { background: white; padding: 20px; border-radius: 15px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } | |
| .card { border: 1px solid #e0e0e0; padding: 15px; margin: 10px 0; border-radius: 8px; transition: transform 0.2s; } | |
| .card:hover { transform: translateY(-2px); } | |
| .gallery { display: flex; flex-wrap: wrap; gap: 10px; margin: 15px 0; } | |
| .gallery img { width: 30%; flex-grow: 1; border-radius: 8px; height: 200px; object-fit: cover; } | |
| .weather-widget { background: #e3f2fd !important; padding: 15px !important; } | |
| .price-tag { color: #2e7d32; font-weight: bold; } | |
| .rating { color: #f9a825; } | |
| .error { color: #d32f2f; background: #ffebee; padding: 15px; border-radius: 8px; } | |
| .loading { color: #1976d2; padding: 20px; text-align: center; } | |
| .card-content { display: flex; flex-direction: column; gap: 8px; } | |
| .details { display: flex; gap: 15px; margin: 10px 0; } | |
| .status { color: #1976d2; font-weight: 500; } | |
| .website-link { color: #1565c0 !important; text-decoration: none; } | |
| .website-link:hover { text-decoration: underline; } | |
| """ | |
| def get_coordinates(city): | |
| try: | |
| url = "https://maps.googleapis.com/maps/api/geocode/json" | |
| params = { | |
| "key": API_KEYS["GOOGLE_PLACES"], | |
| "address": city, | |
| "language": "it" | |
| } | |
| response = requests.get(url, params=params, timeout=10) | |
| data = response.json() | |
| if data['results']: | |
| location = data['results'][0]['geometry']['location'] | |
| return (location['lat'], location['lng']) | |
| return (None, None) | |
| except Exception as e: | |
| print(f"Errore geocoding: {str(e)}") | |
| return (None, None) | |
| def fetch_places(city, category): | |
| try: | |
| lat, lng = get_coordinates(city) | |
| if not lat or not lng: | |
| return [] | |
| place_types = { | |
| "mangiare": "restaurant", | |
| "visitare": "tourist_attraction", | |
| "dormire": "lodging" | |
| } | |
| url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" | |
| params = { | |
| "key": API_KEYS["GOOGLE_PLACES"], | |
| "location": f"{lat},{lng}", | |
| "radius": 10000, | |
| "type": place_types[category], | |
| "language": "it", | |
| "rankby": "prominence" | |
| } | |
| response = requests.get(url, params=params, timeout=15) | |
| response.raise_for_status() | |
| results = response.json().get("results", [])[:8] | |
| places = [] | |
| for place in results: | |
| details_url = "https://maps.googleapis.com/maps/api/place/details/json" | |
| details_params = { | |
| "key": API_KEYS["GOOGLE_PLACES"], | |
| "place_id": place['place_id'], | |
| "fields": "name,vicinity,rating,price_level,types,website,opening_hours,formatted_phone_number", | |
| "language": "it" | |
| } | |
| details_response = requests.get(details_url, params=details_params) | |
| details = details_response.json().get('result', {}) | |
| price_level = "€" * details.get("price_level", 1) if details.get("price_level") else "💸 Variabile" | |
| rating = details.get("rating", "⭐ Non disponibile") | |
| opening_status = "🕒 Aperto ora" if details.get('opening_hours', {}).get('open_now') else "🕒 Chiuso" | |
| places.append({ | |
| "name": details.get("name", "Nome non disponibile"), | |
| "address": details.get("vicinity", "Indirizzo non disponibile"), | |
| "rating": f"{rating}/5" if isinstance(rating, (int, float)) else rating, | |
| "price": price_level, | |
| "types": ", ".join([t.replace("_", " ").title() for t in details.get("types", [])]), | |
| "opening": opening_status, | |
| "website": details.get("website", "#"), | |
| "phone": details.get("formatted_phone_number", "📞 Non disponibile") | |
| }) | |
| return places | |
| except Exception as e: | |
| print(f"Errore fetch_places: {str(e)}") | |
| return [] | |
| def get_city_photos(city): | |
| try: | |
| url = "https://api.unsplash.com/search/photos" | |
| headers = {"Authorization": f"Client-ID {API_KEYS['UNSPLASH_ACCESS']}"} | |
| params = {"query": f"{city} city", "per_page": 3} | |
| response = requests.get(url, headers=headers, params=params, timeout=10) | |
| response.raise_for_status() | |
| photos = [img['urls']['regular'] for img in response.json()['results']] | |
| return f""" | |
| <div class='section'> | |
| <h3>📸 Foto di {city.capitalize()}</h3> | |
| <div class='gallery'> | |
| {"".join([f"<img src='{photo}'>" for photo in photos])} | |
| </div> | |
| </div> | |
| """ | |
| except Exception as e: | |
| print(f"Errore Unsplash: {str(e)}") | |
| return "" | |
| def get_weather(city): | |
| try: | |
| url = "http://api.openweathermap.org/data/2.5/weather" | |
| params = { | |
| "q": city, | |
| "appid": API_KEYS["OPENWEATHER"], | |
| "units": "metric", | |
| "lang": "it" | |
| } | |
| response = requests.get(url, params=params, timeout=10) | |
| response.raise_for_status() | |
| data = response.json() | |
| return f""" | |
| <div class='section weather-widget'> | |
| <h3>⛅ Meteo Attuale</h3> | |
| <p>🌡️ Temperatura: {data['main']['temp']}°C</p> | |
| <p>☁️ Condizioni: {data['weather'][0]['description'].capitalize()}</p> | |
| <p>💧 Umidità: {data['main']['humidity']}%</p> | |
| <p>🌬️ Vento: {data['wind']['speed']} m/s</p> | |
| </div> | |
| """ | |
| except Exception as e: | |
| print(f"Errore meteo: {str(e)}") | |
| return "" | |
| def generate_info_section(title, icon, items): | |
| if not items: | |
| return f""" | |
| <div class='section'> | |
| <h2>{icon} {title}</h2> | |
| <div class='card error'> | |
| <p>🔍 Nessun risultato trovato. Prova con:</p> | |
| <ul> | |
| <li>Un nome città più specifico</li> | |
| <li>Verifica la correttezza del nome</li> | |
| <li>Prova una città più grande</li> | |
| </ul> | |
| </div> | |
| </div> | |
| """ | |
| cards = [] | |
| for item in items: | |
| cards.append(f""" | |
| <div class='card'> | |
| <h3>{item['name']}</h3> | |
| <div class='card-content'> | |
| <p>📍 {item['address']}</p> | |
| <p class='status'>{item['opening']}</p> | |
| <div class='details'> | |
| <p class='rating'>{item['rating']}</p> | |
| <p class='price-tag'>{item['price']}</p> | |
| </div> | |
| <p>🏷️ {item['types']}</p> | |
| <p>{item['phone']}</p> | |
| {f"<a href='{item['website']}' target='_blank' class='website-link'>🌐 Sito Web</a>" if item['website'] != "#" else ""} | |
| </div> | |
| </div> | |
| """) | |
| return f""" | |
| <div class='section'> | |
| <h2>{icon} {title}</h2> | |
| {"".join(cards)} | |
| </div> | |
| """ | |
| def get_city_info(city): | |
| try: | |
| if not city.strip(): | |
| return "<div class='error'>🚨 Inserisci il nome di una città</div>" | |
| yield """ | |
| <div class='loading'> | |
| <h3>🔎 Ricerca informazioni in corso...</h3> | |
| <p>Stiamo analizzando più di 15 fonti diverse</p> | |
| <p>⏱️ Tempo stimato: 5-10 secondi</p> | |
| </div> | |
| """ | |
| weather_html = get_weather(city) | |
| photos_html = get_city_photos(city) | |
| places_to_eat = fetch_places(city, "mangiare") | |
| places_to_visit = fetch_places(city, "visitare") | |
| places_to_stay = fetch_places(city, "dormire") | |
| content = f""" | |
| <div class='travel-container'> | |
| <h1 class='header'>🌍 {city.capitalize()}</h1> | |
| {weather_html} | |
| {photos_html} | |
| {generate_info_section("Dove Mangiare", "🍽️", places_to_eat)} | |
| {generate_info_section("Cosa Visitare", "🏛️", places_to_visit)} | |
| {generate_info_section("Dove Dormire", "🛏️", places_to_stay)} | |
| </div> | |
| """ | |
| yield content | |
| except Exception as e: | |
| print(f"Errore: {str(e)}") | |
| yield f""" | |
| <div class='error'> | |
| <h3>❌ Errore grave</h3> | |
| <p>Motivo: {str(e)}</p> | |
| <p>Prova a:</p> | |
| <ul> | |
| <li>Controllare la connessione internet</li> | |
| <li>Riprova tra qualche minuto</li> | |
| <li>Contatta il supporto se il problema persiste</li> | |
| </ul> | |
| </div> | |
| """ | |
| with gr.Blocks(css=CSS) as app: | |
| gr.Markdown("# 🧳 Travel Assistant Pro") | |
| with gr.Row(): | |
| city_input = gr.Textbox( | |
| label="Inserisci la città da esplorare", | |
| placeholder="Es: Roma, Tokyo, Barcellona...", | |
| scale=4 | |
| ) | |
| search_btn = gr.Button("Avvia Ricerca", variant="primary", scale=1) | |
| info_output = gr.HTML() | |
| search_btn.click( | |
| fn=get_city_info, | |
| inputs=city_input, | |
| outputs=info_output | |
| ) | |
| if __name__ == "__main__": | |
| app.launch() |