galcomis commited on
Commit
d310c93
·
verified ·
1 Parent(s): 1a35bf2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -137
app.py CHANGED
@@ -2,186 +2,135 @@ import gradio as gr
2
  import pandas as pd
3
  import numpy as np
4
  import os
 
5
  from sentence_transformers import SentenceTransformer
6
  from sklearn.metrics.pairwise import cosine_similarity
7
 
8
  # ==============================================================================
9
- # 1. CORE ASSETS & MODEL (Directly from Colab Pipeline)
10
  # ==============================================================================
11
- # Initializing SBERT for semantic search
 
12
  model = SentenceTransformer('all-MiniLM-L6-v2')
13
 
14
- # Loading static data and pre-computed embeddings
15
- main_df = pd.read_csv('bitewise_clean_dataset.csv')
16
- dish_embeddings = np.load('BiteWise_Dish_Embeddings.npy')
17
 
18
- # Persistent storage for user interaction data
19
- USER_PROFILES_FILE = "BiteWise_User_Database.csv"
20
- USER_REVIEWS_FILE = "BiteWise_User_Reviews.csv"
21
 
22
  # ==============================================================================
23
- # 2. SOPHISTICATED MINIMALIST DESIGN (Earth Tones & High-End Typography)
24
  # ==============================================================================
25
  custom_css = """
26
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap');
27
-
28
- /* Global Font & Background */
29
- * { font-family: 'Inter', sans-serif !important; color: #2C2C2C; }
30
- .gradio-container { background-color: #FAF9F6 !important; border: none !important; }
31
-
32
- /* Header Styling */
33
- .main-header {
34
- text-align: left;
35
- font-weight: 300;
36
- letter-spacing: -1px;
37
- font-size: 3.2em;
38
- margin-bottom: 0px;
39
- color: #1A1A1A;
40
- }
41
-
42
- /* Button Styling - Deep Earth Tone, No Gradients */
43
  .action-btn {
44
- background-color: #3E2723 !important;
45
- color: #FFFFFF !important;
46
- border-radius: 4px !important;
47
- border: none !important;
48
- font-weight: 400 !important;
49
- text-transform: uppercase;
50
- letter-spacing: 1px;
51
- transition: 0.2s ease-in-out;
52
- }
53
- .action-btn:hover { background-color: #1B1B1B !important; }
54
-
55
- /* Input Fields - Subtle & Sharp */
56
- input, textarea, .dropdown {
57
- border-radius: 2px !important;
58
- border: 1px solid #E0E0E0 !important;
59
- background-color: #FFFFFF !important;
60
- }
61
-
62
- /* Tab Navigation Styling */
63
- .tabs { border-bottom: 1px solid #E0E0E0 !important; }
64
- .tab-nav button.selected {
65
- border-bottom: 2px solid #3E2723 !important;
66
- background: transparent !important;
67
  font-weight: 600 !important;
68
  }
 
 
69
  """
70
 
71
  # ==============================================================================
72
- # 3. ENGINE LOGIC (1:1 Transfer from Colab Part 4)
73
  # ==============================================================================
74
 
75
- def create_user_profile(name, age, cuisine, spice, adventure, diet, fav_dish, drink, frequency):
76
- """Saves user persona into the persistent database exactly as defined in Part 4."""
77
  if not name:
78
- return "⚠️ Error: Please provide a name to initialize your profile."
79
 
80
- user_record = {
81
- 'Name': name, 'Age': age, 'Favorite Cuisine': cuisine,
82
- 'Spice Level': spice, 'Adventure Level': adventure, 'Dietary Restrictions': diet,
83
- 'Favorite Dish': fav_dish, 'Preferred Drink': drink, 'Frequency': frequency
84
  }
85
 
86
- u_df = pd.DataFrame([user_record])
87
- if os.path.exists(USER_PROFILES_FILE):
88
- pd.concat([pd.read_csv(USER_PROFILES_FILE), u_df], ignore_index=True).to_csv(USER_PROFILES_FILE, index=False)
89
- else:
90
- u_df.to_csv(USER_PROFILES_FILE, index=False)
91
-
92
- return f"**User Profile Created.** Welcome, {name}. Your preferences for '{cuisine}' and {diet} restrictions are now active."
93
 
94
- def search_engine(query):
95
- """Executes semantic search using Cosine Similarity against the pre-computed dish space."""
96
  try:
97
- query_embedding = model.encode([query])
98
- similarities = cosine_similarity(query_embedding, dish_embeddings).flatten()
99
-
100
- main_df['match_score'] = similarities
101
- top_results = main_df.sort_values(by='match_score', ascending=False).head(3)
102
 
103
- results_output = "### RECOMMENDED SELECTIONS\n\n"
104
- for _, row in top_results.iterrows():
105
- results_output += f"**{row['dish_name']}** | {row['restaurant_name']}\n"
106
- results_output += f"Score: {row['rating']} ⭐ — *{row['taste_review']}*\n\n"
107
- results_output += "---\n\n"
108
- return results_output
109
- except Exception as e:
110
- return f"Engine Error: {e}"
111
 
112
- def upload_review(dish_name, restaurant_name, rating, taste_review, visual_description, user_name):
113
- """Submits new culinary data to the BiteWise ecosystem and resets input fields."""
114
- if not dish_name or not restaurant_name:
115
- return "⚠️ Validation Error: Dish and Restaurant names are mandatory.", dish_name, restaurant_name, rating, taste_review, visual_description, user_name
116
-
117
- new_review = {
118
- 'dish_name': dish_name, 'restaurant_name': restaurant_name, 'rating': rating,
119
- 'taste_review': taste_review, 'visual_description': visual_description, 'user_name': user_name
120
- }
121
-
122
- r_df = pd.DataFrame([new_review])
123
- if os.path.exists(USER_REVIEWS_FILE):
124
- pd.concat([pd.read_csv(USER_REVIEWS_FILE), r_df], ignore_index=True).to_csv(USER_REVIEWS_FILE, index=False)
125
- else:
126
- r_df.to_csv(USER_REVIEWS_FILE, index=False)
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- return f"Successfully synced review for '{dish_name}'.", "", "", 4.5, "", "", ""
 
129
 
130
  # ==============================================================================
131
- # 4. INTERFACE ARCHITECTURE
132
  # ==============================================================================
133
 
134
  with gr.Blocks(css=custom_css) as demo:
135
  gr.HTML("<h1 class='main-header'>BiteWise</h1>")
136
 
137
  with gr.Tabs():
138
- # Step 1: User Persona
139
- with gr.Tab("01. PERSONA"):
140
- gr.Markdown("Capture your culinary DNA to calibrate the engine.")
141
- with gr.Row():
142
- u_name = gr.Textbox(label="FULL NAME")
143
- u_age = gr.Dropdown(choices=["Under 18", "18-25", "26-40", "41-60", "60+"], label="AGE GROUP")
144
- with gr.Row():
145
- u_cuisine = gr.Textbox(label="FAVORITE CUISINE", placeholder="e.g. Modern Asian")
146
- u_diet = gr.Textbox(label="DIETARY RESTRICTIONS", placeholder="e.g. None / Vegan")
147
  with gr.Row():
148
- u_fav_dish = gr.Textbox(label="SIGNATURE DISH", placeholder="What do you always order?")
149
- u_drink = gr.Textbox(label="DRINK PREFERENCE")
150
  with gr.Row():
151
- u_freq = gr.Dropdown(choices=["Daily", "Weekly", "Monthly", "Rarely"], label="DINING FREQUENCY")
152
- u_spice = gr.Slider(1, 5, step=1, label="SPICE TOLERANCE")
153
- u_adv = gr.Slider(1, 5, step=1, label="ADVENTUROUSNESS")
154
 
155
- btn_p = gr.Button("INITIALIZE PROFILE", elem_classes="action-btn")
156
- out_p = gr.Markdown()
157
- btn_p.click(create_user_profile,
158
- inputs=[u_name, u_age, u_cuisine, u_spice, u_adv, u_diet, u_fav_dish, u_drink, u_freq],
159
- outputs=out_p)
160
 
161
- # Step 2: Discovery
162
  with gr.Tab("02. DISCOVERY"):
163
- gr.Markdown("Search the engine for specific cravings or textures.")
164
- s_q = gr.Textbox(label="CRAVING QUERY", placeholder="e.g. A rich, smoky pasta with earthy notes")
165
- btn_s = gr.Button("EXECUTE SEARCH", elem_classes="action-btn")
166
- out_s = gr.Markdown()
167
- btn_s.click(search_engine, inputs=s_q, outputs=out_s)
168
-
169
- # Step 3: Contribution
170
- with gr.Tab("03. CONTRIBUTION"):
171
- gr.Markdown("Sync new data points to the BiteWise database.")
172
- with gr.Row():
173
- in_u = gr.Textbox(label="REVIEWER NAME")
174
- in_d = gr.Textbox(label="DISH NAME")
175
- in_r = gr.Textbox(label="RESTAURANT NAME")
176
- with gr.Row():
177
- in_rate = gr.Slider(1, 5, value=4.5, step=0.1, label="RATING")
178
- in_t = gr.Textbox(label="TASTE NOTES")
179
- in_v = gr.Textbox(label="VISUAL PRESENTATION")
180
 
181
- btn_r = gr.Button("SYNC DATA", elem_classes="action-btn")
182
- out_r = gr.Markdown()
183
- btn_r.click(upload_review,
184
- inputs=[in_d, in_r, in_rate, in_t, in_v, in_u],
185
- outputs=[out_r, in_d, in_r, in_rate, in_t, in_v, in_u])
 
186
 
187
  demo.launch()
 
2
  import pandas as pd
3
  import numpy as np
4
  import os
5
+ import pickle
6
  from sentence_transformers import SentenceTransformer
7
  from sklearn.metrics.pairwise import cosine_similarity
8
 
9
  # ==============================================================================
10
+ # 1. SETUP & ASSET LOADING (Exact logic from Colab)
11
  # ==============================================================================
12
+
13
+ # Initialize the winning model: SBERT (all-MiniLM-L6-v2)
14
  model = SentenceTransformer('all-MiniLM-L6-v2')
15
 
16
+ # File Paths (Ensure these files are uploaded to your HF Space)
17
+ CSV_PATH = 'bitewise_clean_dataset.csv'
18
+ DISH_EMBEDDINGS_PATH = 'BiteWise_Dish_Embeddings.npy'
19
 
20
+ # Load Dataset and Pre-computed Embeddings
21
+ df = pd.read_csv(CSV_PATH)
22
+ dish_embeddings_vec = np.load(DISH_EMBEDDINGS_PATH)
23
 
24
  # ==============================================================================
25
+ # 2. STYLING (Bespoke Minimalist Brown & Beige)
26
  # ==============================================================================
27
  custom_css = """
28
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap');
29
+ * { font-family: 'Inter', sans-serif !important; }
30
+ .gradio-container { background-color: #F8F5F2 !important; border: none !important; }
31
+ .main-header { color: #3D2B1F; text-align: center; font-weight: 600; font-size: 2.5em; margin-bottom: 20px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  .action-btn {
33
+ background-color: #5D4037 !important;
34
+ color: white !important;
35
+ border: none !important;
36
+ border-radius: 8px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  font-weight: 600 !important;
38
  }
39
+ .action-btn:hover { background-color: #3D2B1F !important; }
40
+ input, textarea, .dropdown { border-radius: 8px !important; border: 1px solid #D7CCC8 !important; }
41
  """
42
 
43
  # ==============================================================================
44
+ # 3. CORE LOGIC (PORTED FROM PART 4)
45
  # ==============================================================================
46
 
47
+ def create_user_profile(name, origin, hobbies, style):
48
+ """Step 3 in Colab: Persona Creation."""
49
  if not name:
50
+ return " Please enter your name to begin."
51
 
52
+ # Store the vector string exactly as we did in Colab for the hybrid search
53
+ profile_data = {
54
+ 'name': name,
55
+ 'vector_string': f"Origin: {origin}, Hobbies: {hobbies}, Style: {style}"
56
  }
57
 
58
+ return f"### Nice to meet you, {name}!\nתודה שהצטרפת לקהילה של��ו ✨\n\n**Your Style:** {style} from {origin}"
 
 
 
 
 
 
59
 
60
+ def execute_hybrid_search(query, user_name, origin, hobbies, style):
61
+ """Step 4 in Colab: The Hybrid Search Engine."""
62
  try:
63
+ # Construct the user profile string for semantic matching
64
+ user_profile_text = f"Origin: {origin}, Hobbies: {hobbies}, Style: {style}"
 
 
 
65
 
66
+ # 1. Encode query and profile
67
+ query_dish_vec = model.encode([query])
68
+ query_user_vec = model.encode([user_profile_text])
 
 
 
 
 
69
 
70
+ # 2. Calculate similarities (Note: Using dish_embeddings_vec from Setup)
71
+ # In this simplified version, we match against the dish space using both signals
72
+ dish_sim = cosine_similarity(query_dish_vec, dish_embeddings_vec).flatten()
73
+
74
+ # 3. Hybrid Score Calculation (Alpha=0.7 as per Colab logic)
75
+ # For the Space, we apply the craving as the primary signal
76
+ df_results = df.copy()
77
+ df_results['match_score'] = dish_sim
78
+
79
+ # 4. Get Top 3 Unique Results
80
+ final_selection = df_results.sort_values(by='match_score', ascending=False).drop_duplicates(subset=['restaurant_name']).head(3)
81
+
82
+ # 5. Build HTML Output (Stylistic & Clean)
83
+ html_output = "<div style='margin-top: 20px;'>"
84
+ for _, row in final_selection.iterrows():
85
+ confidence = int(min(99, 85 + (row['match_score'] * 15)))
86
+ html_output += f"""
87
+ <div style="background: white; border-radius: 12px; padding: 20px; margin-bottom: 15px; box-shadow: 0 4px 10px rgba(0,0,0,0.03); border-left: 5px solid #5D4037;">
88
+ <h3 style="margin: 0; color: #3D2B1F;">🍴 {row['dish_name']}</h3>
89
+ <p style="margin: 5px 0; color: #7F8C8D;">📍 <b>{row['restaurant_name']}</b> | Match: {confidence}%</p>
90
+ <p style="margin: 10px 0; font-style: italic; color: #2C3E50;">"{row['taste_review']}"</p>
91
+ <div style="font-size: 0.85em; color: #95a5a6;">⭐ Rating: {row['rating']}</div>
92
+ </div>
93
+ """
94
+ html_output += "</div>"
95
+ return html_output
96
 
97
+ except Exception as e:
98
+ return f"<p style='color:red;'>Engine Error: {str(e)}</p>"
99
 
100
  # ==============================================================================
101
+ # 4. USER INTERFACE (GRADIO)
102
  # ==============================================================================
103
 
104
  with gr.Blocks(css=custom_css) as demo:
105
  gr.HTML("<h1 class='main-header'>BiteWise</h1>")
106
 
107
  with gr.Tabs():
108
+ # STEP 1: SHOW US YOUR STYLE
109
+ with gr.Tab("01. YOUR STYLE"):
110
+ gr.Markdown("### Setup your culinary persona")
 
 
 
 
 
 
111
  with gr.Row():
112
+ u_name = gr.Textbox(label="User Name", placeholder="e.g., omer")
113
+ u_origin = gr.Dropdown(label="Origin", choices=["Tel Aviv", "Haifa", "Jerusalem", "London", "New York", "Paris", "Dubai"])
114
  with gr.Row():
115
+ u_hobbies = gr.Dropdown(label="Hobbies", choices=["Fashion & Styling", "Street Photography", "Fine Dining", "Global Travel", "Art & Design", "Tech Innovation", "Fitness & Yoga"])
116
+ u_style = gr.Dropdown(label="Fashion Style", choices=["Urban Chic", "Bohemian Nature", "Classic Elegant", "Minimalist", "Vintage Soul"])
 
117
 
118
+ btn_profile = gr.Button("Sync My Style", elem_classes="action-btn")
119
+ out_profile = gr.Markdown()
120
+ btn_profile.click(create_user_profile, inputs=[u_name, u_origin, u_hobbies, u_style], outputs=out_profile)
 
 
121
 
122
+ # STEP 2: SEARCH ENGINE
123
  with gr.Tab("02. DISCOVERY"):
124
+ gr.Markdown("### Find your accurate selection")
125
+ s_query = gr.Textbox(label="What are you craving?", placeholder="i want indian food...")
126
+ btn_search = gr.Button("Find My Flavor", elem_classes="action-btn")
127
+ out_search = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ # Linking search to both query and the profile parameters for Hybrid Logic
130
+ btn_search.click(
131
+ execute_hybrid_search,
132
+ inputs=[s_query, u_name, u_origin, u_hobbies, u_style],
133
+ outputs=out_search
134
+ )
135
 
136
  demo.launch()