import gradio as gr import os import dotenv import json import re from pathlib import Path from recommendation_system import MovieRecommender # Disable tokenizer warnings os.environ["TOKENIZERS_PARALLELISM"] = "false" # Load environment variables dotenv.load_dotenv() # Check if OpenAI API key is set if "OPENAI_API_KEY" not in os.environ: print("Warning: OPENAI_API_KEY not found in environment variables.") print("Please set it in a .env file or directly in your environment.") # Check for TMDB API key if "TMDB_API_KEY" not in os.environ: print("Warning: TMDB_API_KEY not found in environment variables.") print("Please set it in a .env file to enable movie posters.") # Initialize the movie recommender recommender = MovieRecommender() # Default user (mocked for demo) DEFAULT_USER_ID = "demo_user" PREFERENCES_FILE = "data/user_preferences.json" # --- FAVORITES LOGIC --- def load_preferences(): if Path(PREFERENCES_FILE).exists(): with open(PREFERENCES_FILE, "r") as f: prefs = json.load(f) else: prefs = {} # Ensure "favorites" key exists if DEFAULT_USER_ID not in prefs: prefs[DEFAULT_USER_ID] = {"favorites": []} elif "favorites" not in prefs[DEFAULT_USER_ID]: prefs[DEFAULT_USER_ID]["favorites"] = [] return prefs def save_preferences(prefs): with open(PREFERENCES_FILE, "w") as f: json.dump(prefs, f, indent=2) def save_favorite_movie(movie_title): prefs = load_preferences() if movie_title not in prefs[DEFAULT_USER_ID]["favorites"]: prefs[DEFAULT_USER_ID]["favorites"].append(movie_title) save_preferences(prefs) return f"✅ '{movie_title}' saved to favorites!" return f"â„šī¸ '{movie_title}' is already in favorites." def delete_favorite_movie(movie_title): prefs = load_preferences() if movie_title in prefs[DEFAULT_USER_ID]["favorites"]: prefs[DEFAULT_USER_ID]["favorites"].remove(movie_title) save_preferences(prefs) return f"đŸ—‘ī¸ '{movie_title}' removed from favorites." return f"âš ī¸ '{movie_title}' not found in favorites." def list_favorite_movies(): prefs = load_preferences() if prefs[DEFAULT_USER_ID].get("favorites"): return "\n".join(f"- {m}" for m in prefs[DEFAULT_USER_ID]["favorites"]) return "You have no favorite movies yet." # --- PROCESS RESPONSE WITH POSTERS --- def process_response(response): """Process the chatbot response to extract movie information and poster URLs""" # Extract poster URLs poster_pattern = r'\[POSTER_URL: (.*?)\]' poster_urls = re.findall(poster_pattern, response) # If no posters found, return original text if not poster_urls: return response, "" # Split the response by poster tags parts = re.split(r'\[POSTER_URL: .*?\]', response) # Clean text response (remove poster tags) clean_response = "".join(parts) # Create HTML output with posters html_output = '
' # Find movie titles movie_titles = [] all_text = response # Extract movie titles by finding patterns in the text # Look for patterns like "Title: Movie Name" or "Movie Name (Year)" title_pattern = r'Title:\s*(.*?)(?:\n|$)' year_pattern = r'(\b[A-Z][^()\n]*?)\s*\(\d{4}\)' title_matches = re.findall(title_pattern, all_text) year_matches = re.findall(year_pattern, all_text) # Combine found titles, prioritizing explicit "Title:" format potential_titles = title_matches + year_matches # If we found fewer titles than posters, use a more aggressive approach if len(potential_titles) < len(poster_urls): # Try to extract titles from the text chunks between poster URLs # This looks for the first line or something that looks like a title for i, part in enumerate(parts): if i < len(poster_urls): # Make sure we don't go beyond available posters lines = part.strip().split('\n') if lines: # Get the first non-empty line as a potential title for line in lines: line = line.strip() if line: # Skip lines that look like descriptions if len(line) < 100 and not line.startswith(("- ", "â€ĸ ")): potential_titles.append(line) break # Fill in missing titles if needed while len(potential_titles) < len(poster_urls): potential_titles.append(f"Movie {len(potential_titles) + 1}") # Process each poster for i in range(len(poster_urls)): title = "Movie" # Default title # Use the extracted title if available if i < len(potential_titles): title = potential_titles[i] html_output += f"""
{title}
{title}
""" html_output += '
' return clean_response, html_output # --- CHATBOT REPLY --- def respond(message: str, history: list) -> tuple: # Get text response from recommender full_response = recommender.get_response(DEFAULT_USER_ID, message) # Process response to create HTML with posters text_response, html_posters = process_response(full_response) return text_response, html_posters # --- GRADIO INTERFACE --- with gr.Blocks(theme=gr.themes.Soft(), js="() => { document.documentElement.classList.add('dark') }") as demo: gr.HTML("""

MovieMind đŸŽŦ

Ask for movie recommendations and manage your favorite movies.

""") chatbot = gr.Chatbot(height=400) movie_posters = gr.HTML(label="Movie Posters") with gr.Row(): msg = gr.Textbox( placeholder="Ask for movie recommendations...", show_label=False, container=False, scale=12 # Increased scale for wider input box ) send_btn = gr.Button("→", elem_id="send-btn", scale=1) clear = gr.Button("đŸ—‘ī¸", elem_id="trash-btn", scale=1) # Changed to trash icon with gr.Row(): movie_input = gr.Textbox(label="Movie title") save_btn = gr.Button("Save to Favorites") delete_btn = gr.Button("Delete from Favorites") view_btn = gr.Button("View Favorites") output = gr.Textbox(label="Favorite Movies List", lines=6) def user(user_message, history): return "", history + [[user_message, None]] def bot(history): text_response, html_posters = respond(history[-1][0], history) history[-1][1] = text_response return history, html_posters msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then( bot, chatbot, [chatbot, movie_posters] ) # Add the send button functionality - same as submitting the text input send_btn.click(user, [msg, chatbot], [msg, chatbot], queue=False).then( bot, chatbot, [chatbot, movie_posters] ) clear.click(lambda: ([], ""), None, [chatbot, movie_posters], queue=False) save_btn.click(fn=save_favorite_movie, inputs=movie_input, outputs=output) delete_btn.click(fn=delete_favorite_movie, inputs=movie_input, outputs=output) view_btn.click(fn=list_favorite_movies, outputs=output) # Add CSS to style the buttons and inputs gr.HTML(""" """) if __name__ == "__main__": demo.launch(share=True)