import gradio as gr import pandas as pd import numpy as np import pickle import os import zipfile import re from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity # --- 1. Automatic ZIP Extraction --- zip_filename = 'Dishes_Images_Zip_Final.zip' if os.path.exists(zip_filename): try: with zipfile.ZipFile(zip_filename, 'r') as zip_ref: zip_ref.extractall('.') except Exception as e: print(f"Extraction error: {e}") # --- 2. Load Data and Models --- model = SentenceTransformer('all-MiniLM-L6-v2') df = pd.read_csv('bitewise_clean_dataset.csv') with open('BiteWise_Dish_Embeddings.pkl', 'rb') as f: dish_embeddings = pickle.load(f) with open('BiteWise_User_Embeddings.pkl', 'rb') as f: user_embeddings = pickle.load(f) min_len = min(len(df), len(dish_embeddings), len(user_embeddings)) df = df.iloc[:min_len].copy() dish_embeddings = dish_embeddings[:min_len] user_embeddings = user_embeddings[:min_len] # Create a master list of all available image paths once at startup ALL_IMAGE_PATHS = [] for root, _, files in os.walk("."): for f in files: if f.lower().endswith(('.jpg', '.jpeg', '.png')): ALL_IMAGE_PATHS.append(os.path.join(root, f)) def find_any_matching_image(dish_name): """Robust image search logic based on keyword matching.""" name_clean = str(dish_name).lower() keywords = [kw for kw in re.findall(r'\w+', name_clean) if len(kw) > 2] if not keywords: return None # Try 1: Exact keyword match for p in ALL_IMAGE_PATHS: f_name = os.path.basename(p).lower() if all(kw in f_name for kw in keywords): return p # Try 2: Partial match if len(keywords) >= 2: for p in ALL_IMAGE_PATHS: f_name = os.path.basename(p).lower() if keywords[0] in f_name and keywords[1] in f_name: return p # Try 3: Primary keyword match primary_kw = max(keywords, key=len) for p in ALL_IMAGE_PATHS: if primary_kw in os.path.basename(p).lower(): return p return None def get_hybrid_recommendations(user_query, user_name, city, hobbies, style, alpha=0.65): if not user_query or not user_name: return [None, "### ⚠️ Identity required."] * 3 user_profile_text = f"{user_name} from {city} loves {hobbies} and has a {style} style." # NEW: Create embeddings for both query and persona query_emb = model.encode([user_query]) profile_emb = model.encode([user_profile_text]) # Calculate similarities (The Hybrid Logic) content_sim = cosine_similarity(query_emb, dish_embeddings).flatten() user_sim = cosine_similarity(profile_emb, user_embeddings).flatten() # Weighting: 65% Craving (Content) + 35% Persona (Demographics) raw_scores = (alpha * content_sim) + (1 - alpha) * user_sim # Normalizing scores for display (74%-99%) min_s, max_s = raw_scores.min(), raw_scores.max() scaled_scores = 0.74 + (raw_scores - min_s) * (0.25) / (max_s - min_s) if max_s > min_s else raw_scores df['final_score'] = scaled_scores unique_df = df.sort_values(by='final_score', ascending=False).drop_duplicates(subset=['dish_name']) top_results = unique_df.head(3) output = [] for _, row in top_results.iterrows(): image_path = find_any_matching_image(row['dish_name']) # NEW: Generate the 'Culinary Twin' profile from the dataset metadata # This provides transparency on WHY this dish was recommended twin_info = f"A {row['user_age']} year old from {row['user_origin']}, with a {row['user_fashion_style']} style." res_text = f"""

{row['dish_name'].upper()}

{row['restaurant_name']} — {row['food_vibe']}

👤 Recommended by your Culinary Twin:
{twin_info}

MATCH SCORE: {row['final_score']:.2%} | ⭐ {row['rating']}


The Vision: {row['visual_description']}

The Experience: {row['taste_review']}

""" output.extend([image_path, res_text]) return output def log_journal_entry(dish, restaurant, rating, experience): return f"### ✅ Entry for '{dish}' logged in your Journal." # --- 3. CUSTOM BOUTIQUE CSS --- custom_css = """ .gradio-container {background-color: #f4f1ea !important; color: #4a3f35 !important;} .tabs {border: none !important;} .tab-nav {border-bottom: 1px solid #dcd7c9 !important; justify-content: center !important; gap: 40px !important;} .tab-nav button {font-family: 'Georgia', serif !important; color: #8c7b6c !important; background: transparent !important; border: none !important; font-size: 1.1em !important;} .tab-nav button.selected {color: #5d4037 !important; border-bottom: 2px solid #5d4037 !important; font-weight: bold !important;} input, textarea, .dropdown {background-color: #fdfdfb !important; border: 1px solid #dcd7c9 !important; border-radius: 0px !important; color: #4a3f35 !important;} input:focus, textarea:focus {border-color: #5d4037 !important; outline: none !important; box-shadow: 0 0 0 1px #5d4037 !important;} .primary {background-color: #5d4037 !important; color: #f4f1ea !important; border-radius: 0px !important; font-family: 'Georgia', serif !important; text-transform: uppercase; letter-spacing: 3px;} .gr-image {border: 8px solid #fff !important; box-shadow: 0 10px 25px rgba(0,0,0,0.08) !important;} footer {display: none !important;} """ with gr.Blocks(css=custom_css) as demo: gr.Markdown("

BITEWISE

") gr.Markdown("

— A Curation of Taste and Sentiment —

") with gr.Tabs(): with gr.TabItem("I. THE PERSONA"): with gr.Row(): u_name = gr.Textbox(label="Full Name") u_city = gr.Dropdown(choices=sorted(df['user_origin'].unique().tolist()), label="City") with gr.Row(): u_hobbies = gr.Dropdown(choices=sorted(df['user_hobbies'].unique().tolist()), label="Pursuit") u_style = gr.Dropdown(choices=sorted(df['user_fashion_style'].unique().tolist()), label="Aesthetic") save_btn = gr.Button("Establish Identity", variant="primary") save_msg = gr.Markdown() save_btn.click(lambda x: f"

Welcome, {x}. Identity stored.

", inputs=u_name, outputs=save_msg) with gr.TabItem("II. THE DISCOVERY"): u_query = gr.Textbox(label="WHAT DO YOU CRAVE?", placeholder="Describe a flavor...") search_btn = gr.Button("Search Archive", variant="primary") with gr.Row(): with gr.Column(): im1 = gr.Image(show_label=False, height=400); tx1 = gr.HTML() with gr.Column(): im2 = gr.Image(show_label=False, height=400); tx2 = gr.HTML() with gr.Column(): im3 = gr.Image(show_label=False, height=400); tx3 = gr.HTML() search_btn.click(get_hybrid_recommendations, inputs=[u_query, u_name, u_city, u_hobbies, u_style], outputs=[im1, tx1, im2, tx2, im3, tx3]) with gr.TabItem("III. THE JOURNAL"): gr.Markdown("

SHARE YOUR CULINARY REFLECTIONS

") with gr.Row(): j_dish = gr.Textbox(label="Dish Visited") j_rest = gr.Textbox(label="Restaurant") j_rating = gr.Slider(minimum=1, maximum=5, step=0.5, label="Sentiment Rating") j_exp = gr.TextArea(label="Personal Reflection", placeholder="Describe the soul of the dish...") submit_btn = gr.Button("Commit Entry", variant="primary") j_msg = gr.Markdown() submit_btn.click(log_journal_entry, inputs=[j_dish, j_rest, j_rating, j_exp], outputs=j_msg) if __name__ == "__main__": demo.launch()