import spacy.cli spacy.cli.download("en_core_web_sm") import os import gradio as gr from groq import Groq from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter import tempfile import re from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.pagesizes import letter import spacy from geopy.geocoders import Nominatim import folium import time import traceback from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont # Load API key GR_API_KEY = os.getenv("GR_API_KEY") client = Groq(api_key=GR_API_KEY) MOODS = [ "Adventure", "Relaxing", "Romantic", "Cultural & Heritage", "Foodie & Culinary", "Shopping & Urban", "Beach & Island", "Any" ] MOOD_IMAGES = { "Adventure": "images/adventure.png", "Relaxing": "images/relaxing.png", "Romantic": "images/romantic.png", "Cultural & Heritage": "images/cultural.png", "Foodie & Culinary": "images/foodie.png", "Shopping & Urban": "images/shopping.png", "Beach & Island": "images/beach.png", "Any": "images/default.png" } nlp = spacy.load("en_core_web_sm") def clean_place_name(name): return re.sub(r'^(the|a|an)\s+', '', name, flags=re.IGNORECASE).strip() def extract_places_and_map(itinerary_text, city_context=None): pattern = r"(visit|explore|trip to|at|go to|see)\s+([A-Z][A-Za-zÀ-ÖØ-öø-ÿ'’\- ]+)" matches = re.findall(pattern, itinerary_text, re.IGNORECASE) rule_places = [m[1].strip() for m in matches] doc = nlp(itinerary_text) ner_places = [ent.text for ent in doc.ents if ent.label_ in ["GPE", "LOC", "FAC"]] places = list(set(rule_places + ner_places)) geolocator = Nominatim(user_agent="feelaway_app") locations = [] for place in places: try: query = f"{place}, {city_context}" if city_context else place loc = geolocator.geocode(query, language='en', timeout=10) if loc: locations.append((place, loc.latitude, loc.longitude)) except Exception: continue if not locations: return "
No locations found for mapping.
" m = folium.Map(location=[locations[0][1], locations[0][2]], zoom_start=10) for name, lat, lon in locations: folium.Marker([lat, lon], popup=name).add_to(m) return m._repr_html_() def generate_itinerary(mood, location, budget, days): mood_text = "any mood" if mood == "Any" else mood budget_text = "any budget" if budget == "Any" else budget location_text = location if location.strip() != "" else "any destination" # 🧠 AI prompt prompt = ( f"Create a detailed {days}-day travel itinerary for {location_text}. " f"The traveler’s mood is {mood_text}, with a {budget_text} budget. " f"Include daily activities, key landmarks, and meal or cultural suggestions. " f"Use Markdown formatting with **bold headings** for days and time slots." ) try: print(f"🔍 Calling Groq API for itinerary: {location_text}") # Generate itinerary response = client.chat.completions.create( messages=[ {"role": "system", "content": "You are an expert travel planner."}, {"role": "user", "content": prompt} ], model="openai/gpt-oss-20b", temperature=0.8, max_tokens=2800, top_p=1 ) itinerary_text = response.choices[0].message.content # Get general season/weather info season_text = get_best_season(location_text) # 📍 Map and image image_path = os.path.abspath(MOOD_IMAGES.get(mood, MOOD_IMAGES["Any"])) map_html = extract_places_and_map(itinerary_text, city_context=location_text if location_text.lower() != "any destination" else None) # 🧾 Combine output combined_output = f"### {season_text}\n\n{itinerary_text}" return combined_output, image_path, map_html except Exception as e: print("⚠️ Error while generating itinerary:\n", e) return "🌤️ Unable to generate itinerary at this time.", None, None def get_best_season(location): location_clean = location.strip() if not location_clean or location_clean.lower() == "any destination": return "🌤️ Best season info is available for specific locations only." prompt = ( f"You are a travel expert. Suggest the best season or months to visit {location_clean}, " f"considering weather, tourist crowds, and local events. Keep it 1-2 sentences." ) try: response = client.chat.completions.create( messages=[ {"role": "system", "content": "You are a travel expert."}, {"role": "user", "content": prompt} ], model="openai/gpt-oss-20b", temperature=0.7, max_tokens=250, top_p=1 ) season_text = ( getattr(response.choices[0].message, "content", "") or getattr(response.choices[0], "text", "") ).strip() if not season_text: # fallback if model returns nothing return SEASON_FALLBACK.get(location_clean, "🌤️ Season info not available right now.") return f"🌤️ {season_text}" except Exception as e: print("⚠️ Error while getting season:\n", e) return SEASON_FALLBACK.get(location_clean, "🌤️ Unable to determine the best season right now.") def generate_pdf(itinerary_text): if not itinerary_text.strip(): return None, gr.update(visible=False) # Unique file name for each download temp_dir = tempfile.gettempdir() temp_filename = f"feelaway_itinerary_{int(time.time())}.pdf" temp_path = os.path.join(temp_dir, temp_filename) # Create a clean PDF doc = SimpleDocTemplate(temp_path, pagesize=letter, rightMargin=40, leftMargin=40, topMargin=40, bottomMargin=40) pdfmetrics.registerFont(TTFont('DejaVu', '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')) styles = getSampleStyleSheet() style = styles["Normal"] style.fontName = "DejaVu" style.fontSize = 13 style.leading = 18 # Convert markdown bold to HTML bold html_text = re.sub(r'\*\*(.+?)\*\*', r'\1', itinerary_text) # ✅ Fix invalid