CookBookAI / app.py
Liori25's picture
Update app.py
2a75430 verified
raw
history blame
20 kB
import gradio as gr
import pandas as pd
import pickle
import numpy as np
import os
import random
import base64
from huggingface_hub import InferenceClient
from sklearn.metrics.pairwise import cosine_similarity
from IO_pipeline import RecipeDigitalizerPipeline
# ==========================================
# 1. SETUP & DATA LOADING
# ==========================================
hf_token = os.getenv("HF_TOKEN")
API_MODEL = "BAAI/bge-small-en-v1.5"
client = InferenceClient(token=hf_token) if hf_token else None
print("โณ Loading Data...")
try:
df_recipes = pd.read_csv('RecipeData_10K.csv')
with open('recipe_embeddings.pkl', 'rb') as f:
data = pickle.load(f)
if isinstance(data, dict):
stored_embeddings = np.array(data['embeddings'])
elif isinstance(data, pd.DataFrame):
target_col = next((c for c in ['embedding', 'embeddings', 'vectors'] if c in data.columns), None)
stored_embeddings = np.vstack(data[target_col].values) if target_col else data
else:
stored_embeddings = data
print("โœ… Data Loaded!")
except Exception as e:
print(f"โŒ Error loading data: {e}")
df_recipes = pd.DataFrame({'Title': [], 'Raw_Output': []})
stored_embeddings = None
# ==========================================
# 2. HELPER: IMAGE TO BASE64
# ==========================================
def image_to_base64(image_path):
if not os.path.exists(image_path):
return ""
with open(image_path, "rb") as img_file:
return base64.b64encode(img_file.read()).decode('utf-8')
# Load local images if they exist, otherwise use placeholders
try:
logo_b64 = image_to_base64("logo.jpg")
profile_b64 = image_to_base64("232px-Tv_the_muppet_show_bein_green.jpg")
process_b64 = image_to_base64("preview of process.jpg")
except:
logo_b64 = ""
profile_b64 = ""
process_b64 = ""
# ==========================================
# 3. BACKEND LOGIC
# ==========================================
def get_embedding_via_api(text):
if not client: raise ValueError("HF_TOKEN missing")
response = client.feature_extraction(text, model=API_MODEL)
return np.array(response)
def find_similar_recipes_list(query_text):
if stored_embeddings is None: return ["Database error."] * 3
query_vec = get_embedding_via_api("Represent this recipe for retrieving similar dishes: " + query_text)
if len(query_vec.shape) == 1: query_vec = query_vec.reshape(1, -1)
scores = cosine_similarity(query_vec, stored_embeddings)[0]
top_indices = scores.argsort()[-3:][::-1]
results_list = []
for idx in top_indices:
score = scores[idx]
row = df_recipes.iloc[idx]
title = row['Title']
desc = str(row['Raw_Output'])
score_display = f"{score:.0%}"
# HTML styled as a "Suggested Post"
card_content = (
f"<div class='suggested-header'>Suggested for you ยท {score_display} Match</div>"
f"### ๐Ÿฅ˜ {title}\n"
f"<div class='sim-scroll'>{desc}</div>"
)
results_list.append(card_content)
while len(results_list) < 3:
results_list.append("")
return results_list
def format_recipe(json_data):
if "error" in json_data: return f"Error: {json_data['error']}", ""
title = json_data.get("title", "Unknown")
ing = "\n".join([f"- {x}" for x in json_data.get("ingredients", [])])
inst = "\n".join([f"{i+1}. {x}" for i, x in enumerate(json_data.get("instructions", []))])
text = f"๐Ÿฝ๏ธ {title}\n\n๐Ÿ›’ INGREDIENTS:\n{ing}\n\n๐Ÿณ INSTRUCTIONS:\n{inst}"
return text, f"{title} {ing} {inst}"
def ui_update_pipeline(image_path):
if not hf_token:
return "Error: HF_TOKEN missing", "", gr.update(), gr.update(), "", gr.update(), ""
try:
os.environ["HF_TOKEN"] = hf_token
digitizer = RecipeDigitalizerPipeline()
json_res = digitizer.run_pipeline(image_path)
readable, query = format_recipe(json_res)
if query:
sim_list = find_similar_recipes_list(query)
else:
sim_list = ["No query generated.", "", ""]
return (readable, sim_list[0], gr.update(visible=True), gr.update(visible=True), sim_list[1], gr.update(visible=True), sim_list[2])
except Exception as e:
return f"Error: {e}", "Error", gr.update(), gr.update(), "", gr.update(), ""
# ==========================================
# 4. FACEBOOK UI THEME & CSS
# ==========================================
theme = gr.themes.Default(
primary_hue="blue",
radius_size="sm",
font=['Helvetica', 'Arial', 'sans-serif']
)
facebook_css = """
body { background-color: #f0f2f5; margin: 0; padding: 0; }
.gradio-container { background-color: #f0f2f5 !important; max-width: 100% !important; margin: 0 !important; padding: 0 !important; }
footer { display: none !important; }
/* 1. STICKY NAVBAR */
.fb-navbar {
background: white;
height: 56px;
padding: 0 16px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 999;
justify-content: space-between;
}
.fb-logo {
color: #1877F2;
font-size: 28px;
font-weight: bold;
letter-spacing: -0.5px;
margin-right: 20px;
}
.fb-search {
background: #f0f2f5;
border-radius: 50px;
padding: 10px 20px;
color: #65676B;
width: 250px;
display: flex;
align-items: center;
}
/* 2. LAYOUT COLUMNS */
.main-layout { display: flex; justify-content: center; padding-top: 20px; gap: 20px; }
.col-left { width: 300px; display: none; } /* Hidden on mobile */
.col-feed { width: 600px; max-width: 100%; }
.col-right { width: 300px; display: none; }
/* 3. CARD STYLING */
.fb-card {
background: white;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-bottom: 15px;
overflow: hidden;
padding: 12px 16px;
}
/* 4. NAVIGATION BUTTONS */
.nav-btn {
text-align: left !important;
justify-content: flex-start !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
color: #050505 !important;
font-weight: 600 !important;
font-size: 15px !important;
padding: 10px 8px !important;
border-radius: 8px !important;
margin-bottom: 5px !important;
}
.nav-btn:hover { background-color: #e4e6eb !important; }
.nav-btn.selected { background-color: #e7f3ff !important; color: #1877f2 !important; }
/* 5. POST HEADER */
.post-header { display: flex; align-items: center; margin-bottom: 12px; }
.user-avatar { width: 40px; height: 40px; border-radius: 50%; background: #ddd; margin-right: 10px; object-fit: cover; }
.post-info { display: flex; flex-direction: column; }
.post-author { font-weight: 600; color: #050505; font-size: 15px; }
.post-meta { font-size: 13px; color: #65676B; }
/* 6. POST ACTIONS */
.post-actions {
border-top: 1px solid #ced0d4;
margin-top: 10px;
padding-top: 5px;
display: flex;
justify-content: space-around;
}
.action-btn { background: transparent !important; color: #65676B !important; box-shadow: none !important; }
.action-btn:hover { background: #f2f2f2 !important; }
/* 7. CONTACTS */
.contact-row {
display: flex; align-items: center; padding: 8px; border-radius: 8px; cursor: pointer;
}
.contact-row:hover { background-color: #e4e6eb; }
.contact-name { font-weight: 500; font-size: 14px; margin-left: 10px; color: #050505; }
/* UTILS */
.sim-scroll { height: 200px; overflow-y: auto; font-size: 13px; color: #050505; }
.suggested-header { font-size: 12px; font-weight: bold; color: #65676B; margin-bottom: 5px; }
/* Media Queries */
@media (min-width: 1100px) { .col-left, .col-right { display: block; } }
"""
# ==========================================
# 5. LAYOUT CONSTRUCTION
# ==========================================
with gr.Blocks(title="Legacy Kitchen", css=facebook_css, theme=theme) as demo:
# --- 1. FACEBOOK NAVBAR ---
gr.HTML(f"""
<div class="fb-navbar">
<div style="display:flex; align-items:center;">
<div class="fb-logo">facebook</div>
<div class="fb-search">๐Ÿ” &nbsp; Search Legacy Kitchen</div>
</div>
<div style="display:flex; gap:10px;">
<div style="width:40px;height:40px;background:#e4e6eb;border-radius:50%;display:flex;align-items:center;justify-content:center;">โž•</div>
<div style="width:40px;height:40px;background:#e4e6eb;border-radius:50%;display:flex;align-items:center;justify-content:center;">๐Ÿ’ฌ</div>
<img src="data:image/jpeg;base64,{profile_b64}" style="width:40px; height:40px; border-radius:50%; object-fit:cover;">
</div>
</div>
""")
# --- 2. MAIN 3-COLUMN LAYOUT ---
with gr.Row(elem_classes=["main-layout"]):
# === LEFT COLUMN (Sidebar Navigation) ===
with gr.Column(elem_classes=["col-left"]):
gr.HTML(f"""
<div class="contact-row">
<img src="data:image/jpeg;base64,{profile_b64}" style="width:36px; height:36px; border-radius:50%;">
<div class="contact-name">Welcome User</div>
</div>
""")
nav_digital = gr.Button("โœจ AI Digitizer (Create)", elem_classes=["nav-btn", "selected"])
nav_feed = gr.Button("๐Ÿ“ฐ News Feed", elem_classes=["nav-btn"])
nav_saved = gr.Button("๐Ÿ”– Saved Recipes", elem_classes=["nav-btn"])
nav_about = gr.Button("โ„น๏ธ About Project", elem_classes=["nav-btn"])
gr.HTML("<hr style='border:0; border-top:1px solid #ced0d4; margin: 10px 0;'>")
gr.Markdown("### Your Shortcuts")
gr.Markdown("๐Ÿฅ˜ Culinary Arts Group\n\n๐Ÿฅง Grandmother's Secrets\n\n๐Ÿฅ— Healthy Eating")
# === CENTER COLUMN (The Feed & App Logic) ===
with gr.Column(elem_classes=["col-feed"]):
# --- VIEW 1: AI DIGITIZER (Styled as "Create Post") ---
with gr.Group(visible=True) as digitalizer_view:
# "Create Post" Card
with gr.Group(elem_classes=["fb-card"]):
gr.Markdown("### Create Post")
gr.HTML("<hr style='border:0; border-top:1px solid #e4e6eb; margin: 10px 0;'>")
with gr.Row():
input_img = gr.Image(type="filepath", label="Add to your post", height=250, container=True)
gr.HTML("<div style='margin-top:10px; font-size:14px; color:#65676B;'>Add to your post: ๐ŸŸข Photo/Video ๐Ÿ‘ค Tag People ๐Ÿ“ Location</div>")
magic_btn = gr.Button("Post", variant="primary") # Blue button
# Examples
gr.Examples(
examples=[["quick_tries_images/applecrisp.jpg"], ["quick_tries_images/meatballs recipe.jpg"]],
inputs=input_img, label="Quick Try (Click to load image)"
)
# Output ("The Resulting Post")
with gr.Group(elem_classes=["fb-card"]):
gr.HTML("""
<div class="post-header">
<div class="user-avatar" style="background:#1877F2;"></div>
<div class="post-info">
<div class="post-author">Legacy Kitchen AI</div>
<div class="post-meta">Just now ยท ๐ŸŒ</div>
</div>
</div>
""")
out_text = gr.Textbox(label="Transcription Result", placeholder="Your digitized recipe text will appear here...", lines=10, show_label=False)
with gr.Row(elem_classes=["post-actions"]):
gr.Button("๐Ÿ‘ Like", elem_classes=["action-btn"], size="sm")
gr.Button("๐Ÿ’ฌ Comment", elem_classes=["action-btn"], size="sm")
gr.Button("โ†ช Share", elem_classes=["action-btn"], size="sm")
# Similar Recipes (Styled as "Suggested Posts")
gr.Markdown("### Suggested for you")
# Sim Result 1
with gr.Group(elem_classes=["fb-card"], visible=False) as c1_box:
sim1 = gr.Markdown("Similar Recipe 1")
with gr.Row(elem_classes=["post-actions"], visible=False) as c1_btns:
gr.Button("Save", elem_classes=["action-btn"], size="sm")
# Sim Result 2
with gr.Group(elem_classes=["fb-card"], visible=False) as c2_box:
sim2 = gr.Markdown("Similar Recipe 2")
with gr.Row(elem_classes=["post-actions"]):
gr.Button("Save", elem_classes=["action-btn"], size="sm")
# Sim Result 3
with gr.Group(elem_classes=["fb-card"], visible=False) as c3_box:
sim3 = gr.Markdown("Similar Recipe 3")
with gr.Row(elem_classes=["post-actions"]):
gr.Button("Save", elem_classes=["action-btn"], size="sm")
magic_btn.click(ui_update_pipeline, input_img, [out_text, sim1, c1_btns, c2_box, sim2, c3_box, sim3])
# --- VIEW 2: FEED ---
with gr.Column(visible=False) as feed_view:
if not df_recipes.empty:
feed_samples = df_recipes.sample(10)
for index, row in feed_samples.iterrows():
user_name = random.choice(["Grandma Rose", "Chef Mike", "Sarah J."])
emoji = random.choice(["๐Ÿฅ˜", "๐Ÿฅ—", "๐Ÿฐ", "๐ŸŒฎ"])
post_time = random.choice(["2h", "3h", "6h", "Yesterday"])
with gr.Group(elem_classes=["fb-card"]):
# Custom HTML Header for the card
gr.HTML(f"""
<div class="post-header">
<div class="user-avatar" style="background:#e4e6eb; display:flex; align-items:center; justify-content:center; font-size:20px;">{emoji}</div>
<div class="post-info">
<div class="post-author">{user_name}</div>
<div class="post-meta">{post_time} ยท ๐ŸŒ</div>
</div>
</div>
""")
gr.Markdown(f"**{row['Title']}**")
gr.Markdown(f"{str(row['Raw_Output'])[:300]}... <span style='color:#1877F2; cursor:pointer;'>See more</span>")
# Action Bar
with gr.Row(elem_classes=["post-actions"]):
gr.Button("๐Ÿ‘ Like", elem_classes=["action-btn"], size="sm")
gr.Button("๐Ÿ’ฌ Comment", elem_classes=["action-btn"], size="sm")
gr.Button("โ†ช Share", elem_classes=["action-btn"], size="sm")
else:
gr.Markdown("โš ๏ธ Database is empty.")
# --- VIEW 3: SAVED RECIPES ---
with gr.Column(visible=False) as saved_view:
gr.Markdown("### ๐Ÿ”– Saved Items")
if not df_recipes.empty:
saved_batch = df_recipes.head(20)
for index, row in saved_batch.iterrows():
with gr.Group(elem_classes=["fb-card"]):
with gr.Row():
gr.HTML("<div style='width:80px; height:80px; background:#eee; border-radius:8px;'></div>")
with gr.Column():
gr.Markdown(f"**{row['Title']}**")
gr.HTML("<span style='color:#65676B; font-size:12px;'>Saved from News Feed</span>")
gr.Button("View Collection", size="sm", variant="secondary")
# --- VIEW 4: ABOUT ---
with gr.Group(visible=False) as about_view:
with gr.Group(elem_classes=["fb-card"]):
gr.Markdown("# About Legacy Kitchen")
gr.Markdown("Developed by **Shahar Firshtman** and **Lior Feinstein**.")
gr.HTML(f"""
<div style="margin-top: 20px;">
<img src="data:image/jpeg;base64,{process_b64}" style="width: 100%; border-radius: 8px;">
</div>
""")
# === RIGHT COLUMN (Contacts / Sponsored) ===
with gr.Column(elem_classes=["col-right"]):
gr.Markdown("### Sponsored")
with gr.Group(elem_classes=["fb-card"]):
gr.Markdown("**Culinary School**\nJoin 10,000+ students learning to cook today!")
gr.Image("https://picsum.photos/300/150", show_label=False, interactive=False, height=150)
gr.HTML("<hr style='border:0; border-top:1px solid #ced0d4; margin: 10px 0;'>")
gr.Markdown("### Contacts")
# Fake contacts list
contacts = ["Elon Musk", "Gordon Ramsay", "Jamie Oliver", "Martha Stewart"]
for contact in contacts:
gr.HTML(f"""
<div class="contact-row">
<div style="position:relative;">
<img src="https://ui-avatars.com/api/?name={contact}&background=random" style="width:36px; height:36px; border-radius:50%;">
<div style="position:absolute; bottom:0; right:0; width:10px; height:10px; background:#31a24c; border-radius:50%; border:2px solid white;"></div>
</div>
<div class="contact-name">{contact}</div>
</div>
""")
# ==========================================
# 6. JAVASCRIPT NAVIGATION LOGIC
# ==========================================
def go_digi():
return (
gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
gr.update(elem_classes=["nav-btn", "selected"]), gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn"])
)
def go_feed():
return (
gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn", "selected"]), gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn"])
)
def go_saved():
return (
gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),
gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn", "selected"]), gr.update(elem_classes=["nav-btn"])
)
def go_about():
return (
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn"]), gr.update(elem_classes=["nav-btn", "selected"])
)
outputs_ui = [digitalizer_view, feed_view, saved_view, about_view, nav_digital, nav_feed, nav_saved, nav_about]
nav_digital.click(go_digi, None, outputs_ui)
nav_feed.click(go_feed, None, outputs_ui)
nav_saved.click(go_saved, None, outputs_ui)
nav_about.click(go_about, None, outputs_ui)
if __name__ == "__main__":
demo.launch()