import gradio as gr import json import os import requests import sys from gradio.themes.utils import colors # === Environment Setup === GROQ_API_KEY = os.getenv("GROQ_API_KEY") if not GROQ_API_KEY: print("⚠️ GROQ_API_KEY not set. Set it in Hugging Face Secrets.") GROQ_API_KEY = "gsk-fake-for-testing" # Fallback for local testing # === File Definitions for Persistence === HISTORY_FILE_TEMPLATE = "chat_history_{}.json" USER_PROFILE_FILE = "user_profiles.json" # --- Personalized History Management --- def get_history_filename(user_id): return HISTORY_FILE_TEMPLATE.format(user_id) def save_history(history, user_id): filename = get_history_filename(user_id) with open(filename, 'w', encoding='utf-8') as f: json.dump(history, f, ensure_ascii=False, indent=2) def load_history(history, user_id): filename = get_history_filename(user_id) if os.path.exists(filename): with open(filename, 'r', encoding='utf-8') as f: return json.load(f) return [] def clear_history_file(user_id): if not isinstance(user_id, str): # Ensure user_id is a valid string print(f"❌ Invalid user_id format: {user_id}") user_id = "anonymous" # Fallback to prevent file errors filename = get_history_filename(user_id) # Generates correct file name print(f"πŸ—‘ Clearing chat history for {user_id}, file: {filename}") try: with open(filename, 'w', encoding='utf-8') as f: json.dump([], f, ensure_ascii=False, indent=2) except Exception as e: print(f"❌ Failed to clear chat history: {e}") # === GROQ API Config === GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions" MODEL_NAME = "llama3-8b-8192" def query_groq(message, history): if not GROQ_API_KEY or not GROQ_API_KEY.startswith("gsk_"): print("🚫 GROQ_API_KEY is missing or invalid.", file=sys.stderr) return "GROQ_API_KEY is missing or invalid. Please check Hugging Face Secrets." headers = { "Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json" } payload = { "model": MODEL_NAME, "messages": history, "temperature": 0.7 } try: response = requests.post(GROQ_API_URL, headers=headers, json=payload) if response.status_code == 200: return response.json()["choices"][0]["message"]["content"].strip() else: return f"❌ GROQ API Error {response.status_code}: {response.text}" except Exception as e: return f"⚠️ Failed to call GROQ API: {str(e)}" # === Chatbot Response Function === def respond_chat(message, history, user_id, profile): if history is None or not history: history = load_history(user_id, profile) # Load user-specific past history user_profile = update_user_profile(user_id, message) system_message = { "role": "system", "content": f"You are a friendly travel assistant named Sky. User profile: {user_profile}" } # Include conversation history in API request messages_for_api = history + [system_message, {"role": "user", "content": message}] response_text = query_groq(message, messages_for_api) history.append({"role": "user", "content": message}) history.append({"role": "assistant", "content": response_text}) save_history(history, user_id) # Save chat history after each interaction return history, history # Persist chat history in Gradio UI # === User Profile Management === def save_user_profile(user_id, profile_data): profiles = load_user_profiles() profiles[user_id] = profile_data with open(USER_PROFILE_FILE, 'w', encoding='utf-8') as f: json.dump(profiles, f, ensure_ascii=False, indent=2) def load_user_profiles(): if os.path.exists(USER_PROFILE_FILE): with open(USER_PROFILE_FILE, 'r', encoding='utf-8') as f: return json.load(f) return {} def update_user_profile(user_id, user_msg): profiles = load_user_profiles() profile = profiles.get(user_id, { "name": user_id, "preferences": {}, "past_trips": [], "travel_style": "Unknown" }) keywords = ["beach", "mountain", "budget", "luxury", "adventure", "relaxation"] for word in keywords: if word in user_msg.lower(): profile["preferences"][word] = True save_user_profile(user_id, profile) return profile # === Function to Update Preferences from Sidebar === def update_preferences(budget, language, destination, current_location, days, traveling_alone, diet): user_id = "anonymous" profiles = load_user_profiles() profile = profiles.get(user_id, { "name": user_id, "preferences": {}, "past_trips": [], "travel_style": "Unknown" }) profile["budget"] = budget profile["language"] = language profile["destination"] = destination profile["travel_days"] = days profile["traveling_alone"] = traveling_alone profile["current_location"] = current_location profile["dietary_restrictions"] = diet save_user_profile(user_id, profile) return f"Travel preferences updated for user {user_id}." # === Function to Clear Chat History === def clear_chat_history(user_id): print(f"πŸ—‘ Clearing chat history for user: {user_id}") # Debugging clear_history_file(user_id) # Delete chat history file return [], [] # Reset displayed chatbot conversation # Function to generate a receipt for budget tab def generate_receipt(expenses): categories = { "Food": 0, "Transportation": 0, "Accommodation": 0, "Other": 0 } total_cost = 0 for item in expenses: category = item.get("category", "Other") amount = item.get("amount", 0) categories[category] += amount total_cost += amount receipt = "**Receipt:**\n\n" for category, amount in categories.items(): receipt += f"{category}: ${amount}\n" receipt += f"\n**Total Cost:** ${total_cost}\n" return receipt # Function to generate an itinerary based on number of days and solo travel def create_itinerary(days, traveling_alone): itinerary = "**Your Trip Itinerary:**\n\n" for day in range(1, days + 1): if traveling_alone == "Yes": itinerary += f"Day {day}: Explore local attractions, enjoy a quiet meal, and relax.\n" else: itinerary += f"Day {day}: Group tour, shared meals, and adventure activities.\n" return itinerary #Theme stuff travelwise_purple = colors.Color( name="travelwise_purple", c50="#f4f0f8", c100="#e3d9ee", c200="#c7b3dc", c300="#ab8dca", c400="#8f67b8", c500="#947EB0", # main color c600="#7e5f97", c700="#5e476f", c800="#3f2f47", c900="#1f1820", c950="#0f0c10" ) light_neutral = colors.Color( name="light_neutral", c50="#ffffff", c100="#f9f9f9", c200="#f3f3f3", c300="#eaeaea", c400="#d9d9d9", c500="#c0c0c0", c600="#a7a7a7", c700="#8e8e8e", c800="#757575", c900="#5c5c5c", c950="#3c3c3c" ) my_theme = gr.themes.Soft( primary_hue= travelwise_purple, neutral_hue= light_neutral, # optional ).set( body_background_fill="#f1eeccff", button_secondary_background_fill="#ABA8A6", button_primary_text_color="#004643" ) def generate_receipt(expenses): categories = { "Food": 0, "Transportation": 0, "Accommodation": 0, "Other": 0 } total_cost = 0 for item in expenses: category = item.get("category", "Other") amount = item.get("amount", 0) # If a category is not predefined, accumulate it under 'Other' if category not in categories: categories["Other"] += amount else: categories[category] += amount total_cost += amount receipt = "**Receipt:**\n\n" for cat, amt in categories.items(): receipt += f"{cat}: ${amt}\n" receipt += f"\n**Total Cost:** ${total_cost}\n" return receipt def parse_expenses(user_input): """ Parses a string of expenses in the format: "Food - 20, Transportation - 15, Accommodation - 100" into a list of dictionaries required by generate_receipt. """ expenses = [] if not user_input.strip(): return "⚠️ Please enter some expenses." entries = user_input.split(",") for entry in entries: try: category, amount = entry.strip().split("-") expenses.append({ "category": category.strip().title(), "amount": float(amount.strip()) }) except ValueError: return ("⚠️ Please ensure each expense is entered in the format: " "Category - Amount (e.g., Food - 20, Transportation - 15)") return generate_receipt(expenses) # Gradio UI # === Gradio UI Setup === # === Gradio UI Setup === with gr.Blocks(theme=my_theme) as demo: chat_history = gr.State([]) # Maintain chat history across interactions # πŸ”Ή Add `user_id_input` to store user identity across sessions πŸ”Ή user_id_input = gr.Textbox(value="anonymous", interactive=False, visible=False) with gr.Tabs(): with gr.TabItem("Chatbot"): with gr.Row(): with gr.Column(scale=2): gr.Image( value="logo.png", width=400, show_label=False, show_download_button=False, container=False, show_share_button=False, interactive=False ) gr.Markdown("Plan your perfect trip with ease! Travelwise helps you customize itineraries, explore budgeting options, and get personalized tipsβ€”all based on your travel style and preferences. Whether you're going solo or in a group, relaxing or adventuring, the bot adapts to your needs in real time.") with gr.Row(): with gr.Column(scale=2): gr.Markdown("## Chatbot") chatbot = gr.Chatbot(type="messages") # Display chat conversation chat_input = gr.Textbox(label="Your Message") # User input field chat_button = gr.Button("Send Message") # Send message button clear_history_btn = gr.Button("Clear Chat History") # Reset conversation button with gr.Column(scale=1): gr.Markdown("### ✈️ Travel Preferences") budget = gr.Textbox(label="πŸ’° Budget") language = gr.Dropdown( label="🌐 Language", choices=["English", "Korean", "Japanese", "French", "Spanish", "Chinese", "Italian"] ) destination = gr.Textbox(label="πŸ—ΊοΈ Travel Location") current_location = gr.Textbox(label="πŸ“ Current Location") days = gr.Slider(minimum=1, maximum=200, step=1, label="πŸ“† Days Traveling") traveling_alone = gr.Dropdown(label="Traveling Alone?", choices=["Yes", "No"], value="No") diet = gr.Textbox(label="πŸ₯— Dietary Restrictions") pref_button = gr.Button("Update Preferences") pref_confirmation = gr.Textbox(label="Preference Update Confirmation") with gr.TabItem("Budget"): gr.Markdown("### Enter your expenses in the format: `Category - Amount`, separated by commas.") expense_input = gr.Textbox( placeholder="Food - 20, Transportation - 15, Accommodation - 100", label="Expenses" ) generate_button = gr.Button("Generate Receipt") receipt_output = gr.Markdown() generate_button.click( fn=parse_expenses, inputs=[expense_input], outputs=[receipt_output] ) with gr.TabItem("Itinerary"): days_input = gr.Slider(1, 14, step=1, label="Number of Days") solo_input = gr.Dropdown(["Yes", "No"], label="Traveling Alone?") generate_itinerary_button = gr.Button("Generate Itinerary") itinerary_output = gr.Markdown() generate_itinerary_button.click( fn=create_itinerary, inputs=[days_input, solo_input], outputs=[itinerary_output] ) # πŸ”Ή Fix: Ensure user ID is passed correctly πŸ”Ή chat_button.click( fn=respond_chat, inputs=[chat_input, chat_history, user_id_input], # βœ… Ensure correct user ID is used outputs=[chatbot, chat_history] ) # πŸ”Ή Fix: Correctly clear history by passing `user_id_input` πŸ”Ή clear_history_btn.click( fn=clear_chat_history, inputs=[user_id_input], # βœ… Pass stored user ID correctly outputs=[chatbot, chat_history] ) # testing with token # Bind the sidebar button for updating travel preferences. pref_button.click( fn=update_preferences, inputs=[budget, language, destination, current_location, days, traveling_alone, diet], outputs=pref_confirmation ) demo.launch(debug=True)