Spaces:
Sleeping
Sleeping
| import math | |
| from dataclasses import dataclass | |
| from datetime import date, datetime, timedelta | |
| from typing import Dict, List, Tuple, Optional | |
| import gradio as gr | |
| from dateutil.parser import parse as parse_dt | |
| # Optional LLM (nice-to-have) | |
| LLM_AVAILABLE = True | |
| try: | |
| from transformers import pipeline | |
| except Exception: | |
| LLM_AVAILABLE = False | |
| # -------------------------- | |
| # Data + Helpers | |
| # -------------------------- | |
| VIBES = [ | |
| "Relax & Chill", | |
| "Adventure", | |
| "Food & Cafes", | |
| "Culture & Museums", | |
| "Nature & Scenic", | |
| "Nightlife", | |
| "Budget Backpacking", | |
| "Luxury", | |
| ] | |
| TRAVEL_STYLE_TEMPLATES = { | |
| "Relax & Chill": [ | |
| "Late breakfast at a local cafe", | |
| "Leisurely walk + scenic viewpoint", | |
| "Spa / beach / slow afternoon", | |
| "Sunset spot", | |
| "Early dinner + light dessert" | |
| ], | |
| "Adventure": [ | |
| "Early start + main adventure activity", | |
| "Local lunch (high-energy)", | |
| "Second activity (short hike / water sport)", | |
| "Rest + recovery", | |
| "Dinner near your stay" | |
| ], | |
| "Food & Cafes": [ | |
| "Famous breakfast spot", | |
| "Street food crawl / market", | |
| "Specialty cafe break", | |
| "Food experience (cooking class / tasting)", | |
| "Dinner at a highly rated local place" | |
| ], | |
| "Culture & Museums": [ | |
| "Main museum/heritage site", | |
| "Neighborhood walk (old town)", | |
| "Local lunch near cultural area", | |
| "Second museum/gallery/temple", | |
| "Evening performance / calm dinner" | |
| ], | |
| "Nature & Scenic": [ | |
| "Sunrise viewpoint / park", | |
| "Nature trail / lake / botanical garden", | |
| "Local lunch", | |
| "Scenic drive / cable car / waterfall", | |
| "Sunset + dinner" | |
| ], | |
| "Nightlife": [ | |
| "Late start brunch", | |
| "Light sightseeing", | |
| "Rest", | |
| "Evening bar/club district", | |
| "Late-night food" | |
| ], | |
| "Budget Backpacking": [ | |
| "Free walking tour / public park", | |
| "Local cheap eats", | |
| "Museum on discount day / free attractions", | |
| "Sunset viewpoint", | |
| "Affordable dinner" | |
| ], | |
| "Luxury": [ | |
| "Brunch + premium experience", | |
| "Private tour / signature attraction", | |
| "Fine dining lunch", | |
| "High-end shopping / spa", | |
| "Chef’s tasting / rooftop dinner" | |
| ], | |
| } | |
| PACKING_BASE = { | |
| "Essentials": [ | |
| "ID/passport, tickets, hotel confirmation", | |
| "Phone + charger + power bank", | |
| "Cards + some cash", | |
| "Reusable water bottle", | |
| "Basic medicines (painkiller, ORS, band-aids)", | |
| ], | |
| "Clothing": [ | |
| "Comfortable outfits (mix & match)", | |
| "Light jacket / hoodie", | |
| "Sleepwear, innerwear", | |
| "Comfortable walking shoes", | |
| ], | |
| "Extras": [ | |
| "Sunglasses / cap", | |
| "Small daypack", | |
| "Toiletries kit", | |
| ] | |
| } | |
| FOOD_SUGGESTIONS = { | |
| "India": [ | |
| "Try a local thali / meals-on-a-plate place", | |
| "Street food (only busy stalls + bottled water)", | |
| "Regional breakfast specialty (poha/idli/paratha etc.)", | |
| "One ‘famous’ dessert spot (jalebi, kulfi, etc.)" | |
| ], | |
| "Generic": [ | |
| "Find a local market/food hall for cheap + variety", | |
| "Try one signature dish of the region", | |
| "Do one fancy meal, keep others simple", | |
| "Carry snacks to avoid impulse spending" | |
| ] | |
| } | |
| def parse_start_date(s: str) -> date: | |
| if not s or not s.strip(): | |
| return date.today() | |
| try: | |
| return parse_dt(s).date() | |
| except Exception: | |
| return date.today() | |
| def money(x: float) -> str: | |
| return f"{x:,.0f}" if abs(x) >= 100 else f"{x:,.2f}" | |
| class TripInputs: | |
| destination: str | |
| days: int | |
| start_date: date | |
| budget: float | |
| vibe: str | |
| people: int | |
| pace: str # Slow / Balanced / Packed | |
| food_pref: str # Veg/Non-veg/Any | |
| country_hint: str # India/Other | |
| # -------------------------- | |
| # Optional LLM (for nicer phrasing) | |
| # -------------------------- | |
| from functools import lru_cache | |
| def load_llm(): | |
| return pipeline("text2text-generation", model="google/flan-t5-base", device=-1) | |
| def maybe_polish_with_llm(text: str, use_llm: bool) -> str: | |
| if not use_llm or not LLM_AVAILABLE: | |
| return text | |
| try: | |
| gen = load_llm() | |
| prompt = ( | |
| "Rewrite this travel plan more clearly with headings and bullet points. " | |
| "Keep all details and don't add new places. Text:\n\n" + text | |
| ) | |
| out = gen(prompt, max_new_tokens=350, do_sample=False)[0]["generated_text"] | |
| return out.strip() if out else text | |
| except Exception: | |
| return text | |
| # -------------------------- | |
| # Core Planner (deterministic + fast) | |
| # -------------------------- | |
| def budget_breakdown(total: float, vibe: str, pace: str) -> Dict[str, float]: | |
| """ | |
| Simple envelope budget. Tune these as you like. | |
| """ | |
| # Defaults | |
| stay = 0.35 | |
| food = 0.25 | |
| local = 0.15 | |
| activities = 0.15 | |
| buffer = 0.10 | |
| if vibe in ["Luxury"]: | |
| stay, food, activities = 0.45, 0.22, 0.18 | |
| local, buffer = 0.10, 0.05 | |
| elif vibe in ["Budget Backpacking"]: | |
| stay, food, local = 0.25, 0.25, 0.20 | |
| activities, buffer = 0.15, 0.15 | |
| elif vibe in ["Nightlife"]: | |
| food, activities = 0.22, 0.22 | |
| stay, local, buffer = 0.33, 0.13, 0.10 | |
| if pace == "Packed": | |
| activities += 0.05 | |
| buffer -= 0.05 | |
| elif pace == "Slow": | |
| local += 0.03 | |
| buffer += 0.02 | |
| activities -= 0.05 | |
| # normalize | |
| parts = {"Stay": stay, "Food": food, "Local Transport": local, "Activities": activities, "Buffer": buffer} | |
| s = sum(parts.values()) | |
| for k in parts: | |
| parts[k] = (parts[k] / s) * total | |
| return parts | |
| def day_plan_blocks(vibe: str, pace: str) -> List[str]: | |
| blocks = TRAVEL_STYLE_TEMPLATES.get(vibe, TRAVEL_STYLE_TEMPLATES["Relax & Chill"]).copy() | |
| if pace == "Packed": | |
| # add one extra micro-block | |
| blocks.insert(2, "Quick extra stop (photo spot / short local experience)") | |
| elif pace == "Slow": | |
| # remove one block and add rest | |
| if len(blocks) > 4: | |
| blocks.pop(2) | |
| blocks.insert(3, "Rest / free time") | |
| return blocks | |
| def packing_list(days: int, vibe: str, food_pref: str) -> str: | |
| clothing_count = max(2, min(days, 7)) # suggest rotating laundry | |
| lines = [] | |
| lines.append("### Packing list\n") | |
| for section, items in PACKING_BASE.items(): | |
| lines.append(f"**{section}**") | |
| for it in items: | |
| lines.append(f"- {it}") | |
| lines.append("") | |
| lines.append("**Trip-specific**") | |
| lines.append(f"- Tops: ~{clothing_count}, Bottoms: ~{max(2, clothing_count//2)} (repeatable)") | |
| lines.append("- Socks/innerwear: enough for 4–5 days + quick wash plan") | |
| if vibe in ["Adventure", "Nature & Scenic"]: | |
| lines.append("- Light rain jacket / poncho") | |
| lines.append("- Sunscreen + insect repellent") | |
| lines.append("- Small first-aid kit upgrade (crepe bandage, antiseptic)") | |
| if vibe in ["Nightlife"]: | |
| lines.append("- One nice outfit + comfortable going-out shoes") | |
| if food_pref.lower().startswith("veg"): | |
| lines.append("- Dry snacks you trust (protein bars / nuts) for backup") | |
| lines.append("") | |
| return "\n".join(lines) | |
| def food_suggestions(country_hint: str, food_pref: str, vibe: str) -> str: | |
| tips = FOOD_SUGGESTIONS["India"] if country_hint == "India" else FOOD_SUGGESTIONS["Generic"] | |
| lines = ["### Food suggestions\n"] | |
| if food_pref.lower().startswith("veg"): | |
| lines.append("- Filter: pure-veg / veg-friendly places; check reviews for cross-contamination if needed.") | |
| elif food_pref.lower().startswith("non"): | |
| lines.append("- Try regional non-veg specialties, but keep one simple meal/day to manage budget.") | |
| else: | |
| lines.append("- Mix local specialties + simple meals to keep spending stable.") | |
| if vibe == "Food & Cafes": | |
| lines.append("- Do one food crawl day: 4–6 small items instead of 1 big expensive meal.") | |
| lines.append("- Save cafe-hopping for afternoons; mornings are better for popular breakfast spots.") | |
| for t in tips: | |
| lines.append(f"- {t}") | |
| lines.append("") | |
| return "\n".join(lines) | |
| def build_itinerary(inp: TripInputs) -> str: | |
| per_day = inp.budget / max(inp.days, 1) | |
| env = budget_breakdown(inp.budget, inp.vibe, inp.pace) | |
| lines = [] | |
| lines.append(f"# ✈️ Travel Itinerary Bot\n") | |
| lines.append(f"**Destination:** {inp.destination}") | |
| lines.append(f"**Days:** {inp.days} | **Start:** {inp.start_date.isoformat()} | **People:** {inp.people}") | |
| lines.append(f"**Vibe:** {inp.vibe} | **Pace:** {inp.pace} | **Food preference:** {inp.food_pref}") | |
| lines.append(f"**Total budget:** {money(inp.budget)} | **Budget/day (avg):** {money(per_day)}") | |
| lines.append("") | |
| lines.append("## Budget breakdown (envelopes)") | |
| for k, v in env.items(): | |
| lines.append(f"- **{k}**: {money(v)}") | |
| lines.append("") | |
| blocks = day_plan_blocks(inp.vibe, inp.pace) | |
| lines.append("## Day-by-day plan") | |
| for d in range(inp.days): | |
| day_date = inp.start_date + timedelta(days=d) | |
| lines.append(f"### Day {d+1} — {day_date.strftime('%a, %d %b %Y')}") | |
| lines.append(f"- Morning: {blocks[0]}") | |
| lines.append(f"- Late morning: {blocks[1]}") | |
| lines.append(f"- Afternoon: {blocks[2]}") | |
| lines.append(f"- Evening: {blocks[3]}") | |
| lines.append(f"- Night: {blocks[4]}") | |
| lines.append(f"- **Spend target today:** ~{money(per_day)} (adjust using envelopes above)") | |
| lines.append("") | |
| lines.append(packing_list(inp.days, inp.vibe, inp.food_pref)) | |
| lines.append(food_suggestions(inp.country_hint, inp.food_pref, inp.vibe)) | |
| lines.append("## Quick checklist") | |
| lines.append("- Book stays near transit / main area to save time & money.") | |
| lines.append("- Keep 10% buffer for surprises (cabs, tickets, snacks).") | |
| lines.append("- If a day goes over budget: compensate next day by choosing free attractions + simpler meals.") | |
| lines.append("") | |
| lines.append("> Note: This is a planning assistant. Always verify opening hours, local rules, and safety advisories.") | |
| return "\n".join(lines) | |
| # -------------------------- | |
| # Gradio App | |
| # -------------------------- | |
| def generate_plan(destination, days, start_date_str, budget, vibe, people, pace, food_pref, country_hint, use_llm): | |
| days = int(days) | |
| people = int(people) | |
| budget = float(budget) | |
| start = parse_start_date(start_date_str) | |
| inp = TripInputs( | |
| destination=destination.strip() or "Your destination", | |
| days=max(1, min(days, 30)), | |
| start_date=start, | |
| budget=max(0.0, budget), | |
| vibe=vibe, | |
| people=max(1, min(people, 12)), | |
| pace=pace, | |
| food_pref=food_pref, | |
| country_hint=country_hint, | |
| ) | |
| raw = build_itinerary(inp) | |
| final = maybe_polish_with_llm(raw, use_llm=use_llm) | |
| return final | |
| with gr.Blocks(title="Travel Itinerary Bot") as demo: | |
| gr.Markdown( | |
| "# 🧳 Travel Itinerary Bot\n" | |
| "Enter **destination + days + budget + vibe** → get a **day-by-day itinerary**, **packing list**, and **food suggestions**.\n" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| destination = gr.Textbox(label="Destination", placeholder="Goa / Manali / Jaipur / Bangkok ...", value="Goa") | |
| days = gr.Slider(1, 14, value=5, step=1, label="Number of days") | |
| start_date_str = gr.Textbox(label="Start date (optional)", placeholder="2025-12-20 or 'next Monday'", value="") | |
| budget = gr.Number(label="Total budget", value=20000) | |
| vibe = gr.Dropdown(VIBES, value="Relax & Chill", label="Vibe") | |
| pace = gr.Radio(["Slow", "Balanced", "Packed"], value="Balanced", label="Pace") | |
| people = gr.Slider(1, 6, value=1, step=1, label="People") | |
| food_pref = gr.Radio(["Veg", "Non-veg", "Any"], value="Any", label="Food preference") | |
| country_hint = gr.Radio(["India", "Other"], value="India", label="Country hint (for food tips)") | |
| use_llm = gr.Checkbox(label="Use FLAN-T5 to polish text (optional, slower)", value=False) | |
| btn = gr.Button("Generate itinerary", variant="primary") | |
| with gr.Column(scale=2): | |
| output = gr.Markdown() | |
| btn.click( | |
| generate_plan, | |
| inputs=[destination, days, start_date_str, budget, vibe, people, pace, food_pref, country_hint, use_llm], | |
| outputs=[output], | |
| ) | |
| demo.launch(share=True) | |