import streamlit as st from PIL import Image from model.classifier import get_model, predict from model.search_script import search_for_recipes import streamlit.components.v1 as components import time import base64 from utils.layout import render_layout @st.cache_resource def load_model(): return get_model() def classification_and_recommendation_page(): st.markdown("## 🖼️ Task A: Image Classification + 🍽️ Recipe Recommendation") st.markdown("""
Upload one or more food images. This module classifies each image into Onion, Pear, Strawberry, or Tomato using EfficientNet-B0, and then recommends recipes based on the combined classification results.
""", unsafe_allow_html=True) model = load_model() # --- Upload and classify --- uploaded_files = st.file_uploader("📤 Upload images (JPG/PNG)", type=["jpg", "jpeg", "png"], accept_multiple_files=True) if "uploaded_images" not in st.session_state: st.session_state.uploaded_images = [] if "image_tags" not in st.session_state: st.session_state.image_tags = {} if uploaded_files: for img_file in uploaded_files: if img_file.name not in [img.name for img in st.session_state.uploaded_images]: img = Image.open(img_file).convert("RGB") label, _ = predict(img, model) st.session_state.uploaded_images.append(img_file) st.session_state.image_tags[img_file.name] = label # --- Show grid of classified images --- if st.session_state.uploaded_images: html = """
""" for img in st.session_state.uploaded_images: label = st.session_state.image_tags.get(img.name, "unknown") img_b64 = base64.b64encode(img.getvalue()).decode() html += f"""
{label.upper()}
{img.name}
""" html += "
" grid_rows = ((len(st.session_state.uploaded_images) - 1) // 5 + 1) components.html(html, height=200 * grid_rows + 20, scrolling=True) # --- Recipe Search --- st.markdown("---") st.markdown("## 🔍 Recipe Recommendation") if 'search_system' not in st.session_state: with st.spinner("Initializing recipe search system"): st.session_state.search_system = search_for_recipes() search_system = st.session_state.search_system if not search_system.is_ready: st.error("System not ready. Please check data files and try again.") return unique_tags = list(set(st.session_state.image_tags.values())) default_query = " ".join(unique_tags) query = st.text_input( "Search for recipes:", value=default_query, placeholder="e.g., 'onion tomato pasta', 'strawberry dessert', etc." ) col1, col2 = st.columns(2) with col1: num_results = st.slider("Number of results", 1, 15, 5) with col2: min_rating = st.slider("Minimum rating", 1.0, 5.0, 3.0, 0.1) if st.button("🔍 Search Recipes") and query: with st.spinner(f"Searching for '{query}'..."): results = search_system.search_recipes(query, num_results, min_rating) if results: st.markdown(f"### Top {len(results)} recipe recommendations for: *'{query}'*") st.markdown("
", unsafe_allow_html=True) for i, recipe in enumerate(results, 1): steps_html = "".join([f"
  • {step.strip().capitalize()}
  • " for step in recipe.get("steps", [])]) description = recipe.get("description", "").strip().capitalize() html_code = f"""
    {i}. {recipe['name']}
    {recipe['minutes']} min   |   {recipe['n_steps']} steps   |   {recipe['avg_rating']:.1f}/5.0 ({recipe['num_ratings']} ratings)
    Match Score: {recipe['similarity_score']:.1%} (query match)
    Tags:
    {" ".join([f"{tag}" for tag in recipe['tags']])}
    Ingredients:
    {', '.join(recipe['ingredients'][:8])}{'...' if len(recipe['ingredients']) > 8 else ''}
    {f"
    Description:
    {description}
    " if description else ""} {f"
    Steps:
      {steps_html}
    " if steps_html else ""}
    """ components.html(html_code, height=340, scrolling=True) else: st.warning(f"No recipes found for '{query}' with a minimum rating of {min_rating}/5.0.") render_layout(classification_and_recommendation_page)