File size: 6,558 Bytes
b21a8be
 
d525585
c3e0374
dcde4b8
9d7c9f4
 
 
f26b898
9d7c9f4
d5ebf09
fa6fe88
697474f
9d7c9f4
 
1696771
9d7c9f4
 
 
 
697474f
dcde4b8
c3e0374
697474f
 
1388552
be25f7d
1a07610
 
245a7c8
1a07610
 
9d7c9f4
db0c9ca
 
 
 
 
fc83c21
 
 
db0c9ca
8094330
 
db0c9ca
dcde4b8
db0c9ca
 
 
 
 
 
 
 
 
3379056
be25f7d
97eefa1
ac3441a
 
dcde4b8
4c90401
dcde4b8
ac3441a
 
 
4c90401
be25f7d
ac3441a
4c90401
dcde4b8
 
be25f7d
ac3441a
be25f7d
4c90401
 
 
 
 
 
 
 
 
 
 
 
6a40614
95325ed
 
0c5120f
ff65a9d
 
 
4c90401
 
 
 
10c7691
ff65a9d
2a5d6eb
ff65a9d
4c90401
10c7691
2a5d6eb
c4c42ba
2a5d6eb
36957f3
4c90401
36957f3
c4c42ba
9d7c9f4
 
bcd25c4
97eefa1
fa1b6d5
1696771
bcd25c4
9d7c9f4
dcde4b8
 
245a7c8
 
8094330
36957f3
95325ed
 
 
f0e5ff8
95325ed
 
38349fe
a499d88
ac3441a
10c7691
 
 
 
 
4c90401
 
 
 
 
 
 
 
 
36957f3
4c90401
10c7691
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import streamlit as st
import pandas as pd
import chromadb
from sentence_transformers import SentenceTransformer
from transformers import pipeline, AutoModelForQuestionAnswering, AutoTokenizer
from PIL import Image
from io import BytesIO
import requests

# --- 1. Load Recipes Dataset ---
@st.cache_data
def load_recipes():
    try:
        recipes_df = pd.read_csv("recipes.csv")
        recipes_df = recipes_df.rename(columns={"recipe_name": "title", "directions": "instructions"})
        recipes_df = recipes_df[['title', 'ingredients', 'instructions', 'img_src']]
        recipes_df.fillna("", inplace=True)
        recipes_df["ingredients"] = recipes_df["ingredients"].str.lower().str.replace(r'[^\w\s]', '', regex=True)
        recipes_df["combined_text"] = recipes_df["title"] + " " + recipes_df["ingredients"]
        return recipes_df
    except Exception as e:
        st.error(f"โš  Error loading recipes: {e}")
        return pd.DataFrame()

recipes_df = load_recipes()

# --- 2. Load SentenceTransformer Model ---
@st.cache_resource
def load_embedding_model():
    return SentenceTransformer("all-mpnet-base-v2")

embedding_model = load_embedding_model()

# --- 3. Initialize ChromaDB ---
chroma_client = chromadb.PersistentClient(path="./chroma_db")
collection = chroma_client.get_or_create_collection(name="recipe_collection")

# --- 4. Generate & Store Embeddings ---
def get_sentence_transformer_embeddings(text):
    return embedding_model.encode(text).tolist()

try:
    existing_data = collection.get()
    existing_ids = set(existing_data["ids"]) if existing_data and "ids" in existing_data else set()
except Exception as e:
    st.error(f"โš  ChromaDB Error: {e}")
    existing_ids = set()

for index, row in recipes_df.iterrows():
    recipe_id = str(index)
    if recipe_id in existing_ids:
        continue
    embedding = get_sentence_transformer_embeddings(row["combined_text"])
    if embedding:
        collection.add(embeddings=[embedding], documents=[row["combined_text"]], ids=[recipe_id])

# --- 5. Retrieve Similar Recipes ---
def retrieve_recipes(query, top_k=3):
    query_embedding = get_sentence_transformer_embeddings(query)
    results = collection.query(query_embeddings=[query_embedding], n_results=top_k)
    
    if results and "ids" in results and results["ids"]:
        recipe_indices = [int(id) for id in results["ids"][0] if id.isdigit()]
        return recipes_df.iloc[recipe_indices] if recipe_indices else None
    return None

# --- 6. Load LLM Model ---
@st.cache_resource
def load_llm_model():
    tokenizer = AutoTokenizer.from_pretrained("deepset/roberta-base-squad2")
    model = AutoModelForQuestionAnswering.from_pretrained("deepset/roberta-base-squad2")
    return pipeline("question-answering", model=model, tokenizer=tokenizer)

llm_model = load_llm_model()

# --- 7. Load Classifier ---
@st.cache_resource
def load_classifier():
    return pipeline("zero-shot-classification", model="facebook/bart-large-mnli")

classifier = load_classifier()

# --- 8. Query Classification ---
def is_food_related(query):
    labels = ["Food & Cooking", "General Knowledge"]
    result = classifier(query, candidate_labels=labels, multi_label=False)
    return result["labels"][0] == "Food & Cooking"

def answer_question(query, context=""):
    greetings = ["hi", "hello", "hii", "hey", "greetings", "how are you", "what's up", "how's it going"]
    query_cleaned = query.lower().strip()
    
    if query_cleaned in greetings:
        return "Hello! I'm here to assist with recipes and food-related questions. ๐Ÿฝ๏ธ What would you like to know?"
    
    if not is_food_related(query_cleaned):
        return "I specialize in food-related topics! ๐Ÿฝ๏ธ Please ask me about recipes, cooking methods, or ingredients."
    
    # Retrieve related recipes only if the query is about food
    related_recipes = retrieve_recipes(query, top_k=1)
    if related_recipes is None or related_recipes.empty:
        return "I specialize in recipes! ๐Ÿฝ๏ธ Feel free to ask me about ingredients, cooking methods, or meal ideas. ๐Ÿ˜Š"
    
    # Use the most relevant recipe's instructions for answer generation
    context = related_recipes.iloc[0]['instructions']
    response = llm_model(question=query, context=context)
    answer = response.get("answer", "").strip()
    
    return answer if answer else "I'm not sure, but I can help with recipes! What would you like to cook?"

# --- 9. Display Image Function ---
def display_image(image_url, recipe_name):
    try:
        if not isinstance(image_url, str) or not image_url.startswith("http"):
            raise ValueError("Invalid or missing image URL")
        response = requests.get(image_url, timeout=5)
        response.raise_for_status()
        image = Image.open(BytesIO(response.content))
        st.image(image, caption=recipe_name, use_container_width=True)
    except requests.exceptions.RequestException as e:
        st.warning(f"โš  Image fetch error: {e}")
        placeholder_url = "https://via.placeholder.com/300?text=No+Image"
        st.image(placeholder_url, caption=recipe_name, use_container_width=True)

# --- 10. Streamlit UI ---
st.title("๐Ÿฝ๏ธ AI Recipe & Q&A Assistant")

user_query = st.text_input("Enter your question or recipe search query:", "", key="main_query_input")

if "retrieved_recipes" not in st.session_state:
    st.session_state["retrieved_recipes"] = None

if st.button("Ask AI"):
    if user_query:
        if is_food_related(user_query):  # First check if it's a food-related query
            response = answer_question(user_query)
            st.subheader("๐Ÿค– AI Answer:")
            st.write(response)

            retrieved_recipes = retrieve_recipes(user_query)
            if retrieved_recipes is not None and not retrieved_recipes.empty:
                st.session_state["retrieved_recipes"] = retrieved_recipes
                st.subheader("๐Ÿด Found Recipes:")
                for _, recipe in retrieved_recipes.iterrows():
                    st.markdown(f"### {recipe['title']}")
                    st.write(f"**Ingredients:** {recipe['ingredients']}")
                    st.write(f"**Instructions:** {recipe['instructions']}")
                    display_image(recipe.get('img_src', ''), recipe['title'])
            else:
                st.warning("โš ๏ธ No relevant recipes found.")
        else:
            # If query is not food-related, return only general response (No recipe retrieval)
            st.subheader("๐Ÿค– AI Answer:")
            st.write(answer_question(user_query))