recipe-gen / app.py
OmPrakashSingh1704's picture
Update app.py
22b9a53 verified
import random
import streamlit as st
import re, secrets, os, ast,json
import pandas as pd
from huggingface_hub import login, InferenceClient
import pickle
from sklearn.metrics.pairwise import cosine_similarity
import datetime
import wordcloud
import matplotlib.pyplot as plt
from pygwalker.api.streamlit import StreamlitRenderer
import plotly.express as px
st.set_page_config(layout="wide")
@st.cache_resource
def login_huggingface(token):
login(token=token)
login_huggingface(token=os.getenv("TOKEN"))
@st.cache_data
def load_pickle(file_path):
with open(file_path, 'rb') as file:
return pickle.load(file)
cv = load_pickle('cv.pkl')
vectors = load_pickle('vectors.pkl')
items_dict = pd.DataFrame.from_dict(load_pickle('items_dict.pkl'))
mode = st.toggle(label="MART")
if 'ind' not in st.session_state:
st.session_state.ind = []
def spinner(txt):return st.spinner(txt)
def columns(n):
return st.columns(n)
def text_area(txt,value,placeholder):
return st.text_area(txt,value=value,placeholder=placeholder)
def number_input(txt,min_value,max_value,value,step):return st.number_input(txt,min_value=min_value,max_value=max_value,value=value,step=step)
def button(txt,on_click,type,disabled=False,use_container_width=False,kwargs=None):
return st.button(label=txt,on_click=on_click,disabled=disabled,type=type,use_container_width=use_container_width,kwargs=kwargs)
def container(border,height):
return st.container(border=border,height=height)
def selectbox(txt,options,key=None):return st.selectbox(txt,options=options,key=key)
def expander(txt):return st.expander(txt)
@st.cache_resource(show_spinner=False)
def pygwalerapp(df):
return StreamlitRenderer(df)
def preprocess_text(text):
# Remove non-alphabet characters and extra spaces
text = re.sub(r'[^a-zA-Z\s]', '', text)
text = re.sub(r'\s+', ' ', text).strip()
return text.lower()
def get_recommendations(user_description, count_vectorizer, count_matrix):
user_description = preprocess_text(user_description)
user_vector = count_vectorizer.transform([user_description])
cosine_similarities = cosine_similarity(user_vector, count_matrix).flatten()
similar_indices = cosine_similarities.argsort()[::-1]
return similar_indices[:5]
@st.fragment
def show_recipe(recipe):
with spinner("HANG TIGHT, RECIPE INCOMING..."):
name_and_dis = f'# {recipe["name"]}\n\n'
name_and_dis += f'{recipe["description"]}\n\n'
ingredients = '## Ingredients:\n'
instructions = '\n## Instructions:\n'
for instruction in recipe["instructions"]:
instructions += f"{instruction['step_number']}. {instruction['instruction']}\n"
st.write(name_and_dis)
col01, col02 = columns(2)
with col01:
cont = container(border=True, height=500)
with cont:
st.write(ingredients)
for j, i in enumerate(recipe["ingredients"]):
ind = get_recommendations(i['name'], cv, vectors)
st.session_state.ind.append(ind[:5].tolist())
selectbox(i['name'],
options=items_dict.iloc[ind][
"PRODUCT_NAME"].values,
key=f"selectbox_{j}_{i['name']}{random.random() * 100}")
with col02:
cont = container(border=True, height=500)
with cont:st.write(instructions)
# @st.fragment
def cooking():
client = InferenceClient("mistralai/Mixtral-8x7B-Instruct-v0.1")
if 'recipe' not in st.session_state:
st.session_state.recipe = None
if 'recipe_saved' not in st.session_state:
st.session_state.recipe_saved = None
if 'user_direction' not in st.session_state:
st.session_state.user_direction = None
if 'serving_size' not in st.session_state:
st.session_state.serving_size = 2
if 'selected_difficulty' not in st.session_state:
st.session_state.selected_difficulty = "Quick & Easy"
if 'exclusions' not in st.session_state:
st.session_state.exclusions = None
def create_detailed_prompt(user_direction, exclusions, serving_size, difficulty):
if difficulty == "Quick & Easy":
prompt = (
f"Provide a 'Quick and Easy' recipe for {user_direction} that excludes {exclusions} and has a serving size of {serving_size}. "
f"It should require as few ingredients as possible and should be ready in as little time as possible. "
f"The steps should be simple, and the ingredients should be commonly found in a household pantry. "
f"Provide a detailed ingredient list and step-by-step guide that explains the instructions to prepare in detail."
)
elif difficulty == "Intermediate":
prompt = (
f"Provide a classic recipe for {user_direction} that excludes {exclusions} and has a serving size of {serving_size}. "
f"The recipe should offer a bit of a cooking challenge but should not require professional skills. "
f"The recipe should feature traditional ingredients and techniques that are authentic to its cuisine. "
f"Provide a detailed ingredient list and step-by-step guide that explains the instructions to prepare in detail."
)
elif difficulty == "Professional":
prompt = (
f"Provide a advanced recipe for {user_direction} that excludes {exclusions} and has a serving size of {serving_size}. "
f"The recipe should push the boundaries of culinary arts, integrating unique ingredients, advanced cooking techniques, and innovative presentations. "
f"The recipe should be able to be served at a high-end restaurant or would impress at a gourmet food competition. "
f"Provide a detailed ingredient list and step-by-step guide that explains the instructions to prepare in detail."
)
return prompt
def generate_recipe(user_inputs):
with spinner('Building the perfect recipe...'):
prompt = create_detailed_prompt(user_inputs['user_direction'], user_inputs['exclusions'],
user_inputs['serving_size'], user_inputs['difficulty'])
functions = [
{
"name": "provide_recipe",
"description": "Provides a detailed recipe strictly adhering to the user input/specifications, especially ingredient exclusions and the recipe difficulty",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "A creative name for the recipe"
},
"description": {
"type": "string",
"description": "a brief one-sentence description of the provided recipe"
},
"ingredients": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Quantity and name of the ingredient"
}
}
}
},
"instructions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"step_number": {
"type": "number",
"description": "The sequence number of this step"
},
"instruction": {
"type": "string",
"description": "Detailed description of what to do in this step"
}
}
}
}
},
"required": [
"name",
"description",
"ingredients",
"instructions"
],
},
}
]
generate_kwargs = dict(
temperature=0.9,
max_new_tokens=10000,
top_p=0.9,
repetition_penalty=1.0,
do_sample=True,
)
prompt += f"\nPlease format the output in JSON. The JSON should include fields for 'name', 'description', 'ingredients', and 'instructions', with each field structured as described below.\n\n{json.dumps(functions)}"
response = client.text_generation(prompt, **generate_kwargs)
st.session_state.recipe = response
st.session_state.recipe_saved = False
def clear_inputs():
st.session_state.user_direction = None
st.session_state.exclusions = None
st.session_state.serving_size = 2
st.session_state.selected_difficulty = "Quick & Easy"
st.session_state.recipe = None
st.title("Let's get cooking")
col1, col2 = columns(2)
with col1:
st.session_state.user_direction = text_area(
"What do you want to cook? Describe anything - a dish, cuisine, event, or vibe.",
value=st.session_state.user_direction,
placeholder="quick snack, asian style bowl with either noodles or rice, something italian",
)
with col2:
st.session_state.serving_size = number_input(
"How many servings would you like to cook?",
min_value=1,
max_value=100,
value=st.session_state.serving_size,
step=1
)
difficulty_dictionary = {
"Quick & Easy": {
"description": "Easy recipes with straightforward instructions. Ideal for beginners or those seeking quick and simple cooking.",
},
"Intermediate": {
"description": "Recipes with some intricate steps that invite a little challenge. Perfect for regular cooks wanting to expand their repertoire with new ingredients and techniques.",
},
"Professional": {
"description": "Complex recipes that demand a high level of skill and precision. Suited for seasoned cooks aspiring to professional-level sophistication and creativity.",
}
}
st.session_state.selected_difficulty = st.radio(
"Choose a difficulty level for your recipe.",
[
list(difficulty_dictionary.keys())[0],
list(difficulty_dictionary.keys())[1],
list(difficulty_dictionary.keys())[2]
],
captions=[
difficulty_dictionary["Quick & Easy"]["description"],
difficulty_dictionary["Intermediate"]["description"],
difficulty_dictionary["Professional"]["description"]
],
index=list(difficulty_dictionary).index(st.session_state.selected_difficulty)
)
st.session_state.exclusions = text_area(
"Any ingredients you want to exclude?",
value=st.session_state.exclusions,
placeholder="gluten, dairy, nuts, cilantro",
)
fancy_exclusions = ""
if st.session_state.selected_difficulty == "Professional":
exclude_fancy = st.checkbox(
"Exclude cliche professional ingredients? (gold leaf, truffle, edible flowers, microgreens)",
value=True)
if exclude_fancy:
fancy_exclusions = "gold leaf, truffle, edible flowers, microgreens, gold dust"
user_inputs = {
"user_direction": st.session_state.user_direction,
"exclusions": f"{st.session_state.exclusions}, {fancy_exclusions}".strip(", "),
"serving_size": st.session_state.serving_size,
"difficulty": st.session_state.selected_difficulty
}
button_cols_submit = st.columns([1, 1, 4])
with button_cols_submit[0]:
button(txt='Submit', on_click=generate_recipe, kwargs=dict(user_inputs=user_inputs),
type="primary",
use_container_width=True)
with button_cols_submit[1]:
button(txt='Reset', on_click=clear_inputs, type="secondary", use_container_width=True)
with button_cols_submit[2]:
st.empty()
def create_safe_filename(recipe_name):
# format and generate random URL-safe text string
safe_name = recipe_name.lower()
safe_name = safe_name.replace(" ", "_")
safe_name = re.sub(r"[^a-zA-Z0-9_]", "", safe_name)
safe_name = (safe_name[:50]) if len(safe_name) > 50 else safe_name
unique_token = secrets.token_hex(8)
safe_filename = f"{unique_token}_{safe_name}"
return safe_filename
def save_recipe():
with st.spinner('WAIT SAVING YOUR DISH...'):
filename = create_safe_filename(recipe["name"])
os.makedirs('data', exist_ok=True)
with open(f'./data/{filename}.pkl', 'wb') as f:
pickle.dump(recipe, f)
st.session_state.recipe_saved = True
if st.session_state.recipe is not None:
st.divider()
print(st.session_state.recipe)
recipe = json.loads(st.session_state.recipe)
if not st.session_state.recipe_saved:
show_recipe(recipe)
recipe['timestamp'] = str(datetime.datetime.now())
if st.session_state.recipe_saved == True:
disable_button = True
else:
disable_button = False
button_cols_save = st.columns([1, 1, 4])
with button_cols_save[0]:
button("Save Recipe", on_click=save_recipe, disabled=disable_button, type="primary")
with button_cols_save[1]:
st.empty()
with button_cols_save[2]:
st.empty()
if st.session_state.recipe_saved == True:
st.success("Recipe Saved!")
@st.fragment
def rsaved():
st.title("Saved Recipes")
def load_saved_recipes_from_pickle(directory_path):
os.makedirs('data', exist_ok=True)
recipes = []
# Iterate through all files in the directory
for filename in os.listdir(directory_path):
if filename.endswith('.pkl'):
file_path = os.path.join(directory_path, filename)
with open(file_path, 'rb') as file:
recipe = pickle.load(file)
recipes.append(recipe)
return recipes
# get all saved files
directory_path = 'data'
recipes = load_saved_recipes_from_pickle(directory_path)
# print(recipes)
cols = st.columns([4, 1])
with cols[1]:
user_sort = st.selectbox("Sort", ('Recent', 'Oldest', 'A-Z', 'Z-A', 'Random'))
if user_sort == 'Recent':
recipes.sort(key=lambda x: x['timestamp'], reverse=True)
elif user_sort == 'Oldest':
recipes.sort(key=lambda x: x['timestamp'])
elif user_sort == 'A-Z':
recipes.sort(key=lambda x: x['name'])
elif user_sort == 'Z-A':
recipes.sort(key=lambda x: x['name'], reverse=True)
elif user_sort == 'Random':
recipes.sort(key=lambda x: x['file'])
with cols[0]:
user_search = selectbox("Search Recipes", [""] + [recipe['name'] for recipe in recipes])
st.write("") # just some space
if user_search != "":
st.divider()
filtered_recipes = [recipe for recipe in recipes if recipe['name'] == user_search]
if filtered_recipes:
show_recipe(filtered_recipes[0])
else:
st.write("No recipe found.")
@st.fragment
def mart():
st.markdown("# :eyes: PEOPLE SEARCHING FOR...")
print(st.session_state.ind)
flattened_indices = list(set(index for sublist in st.session_state.ind for index in sublist))
df = items_dict.iloc[flattened_indices]
if st.session_state.ind != []:
with spinner("LET'S SEE, WHAT PEOPLE WANT..."):
cont1 = container(border=True, height=500)
with cont1:
wc = wordcloud.WordCloud(width=1000, height=320, background_color='white').generate(
' '.join(df['CATEGORY']))
# Display word cloud
fig, ax = plt.subplots()
ax.imshow(wc, interpolation='bilinear')
ax.axis('off')
st.pyplot(fig, use_container_width=True)
with container(border=True, height=500):
col1, col2 = columns(2)
with col1:
st.markdown('## DEMAND AS PER CATEGORY')
with plt.style.context('Solarize_Light2'):
fig0 = px.pie(df, names='CATEGORY', hole=0.5)
fig0.update_traces(text=df['CATEGORY'], textposition='outside')
st.plotly_chart(fig0, use_container_width=True)
with col2:
st.markdown('## DEMAND AS PER AVAILABILITY')
with plt.style.context('Solarize_Light2'):
fig1 = px.pie(df, names='AVAILABILITY', hole=0.5)
fig1.update_traces(text=df['AVAILABILITY'], textposition='outside')
st.plotly_chart(fig1, use_container_width=True)
with container(border=True, height=620):
st.markdown('## :deciduous_tree: Hierarchical view of demand using TreeMap')
fig4 = px.treemap(df, path=['CATEGORY', 'AVAILABILITY','PRODUCT'],color='AVAILABILITY')
fig4.update_layout(height=550)
st.plotly_chart(fig4, use_container_width=True,scrolling=False)
with container(border=True, height=500):
fig = px.bar(df,
y='CATEGORY',
x='BREADCRUMBS',
color='BRAND',
# title='Gross Sales of Every Segment Country-wise',
barmode='relative',
title="CATEGORY Vs. BREADCRUMBS"
)
st.plotly_chart(fig, use_container_width=True)
# st.markdown("## :desktop_computer: DO YOUR OWN RESEARCH...")
with expander("## :desktop_computer: LOOKING FOR SOMETHING ELSE..."):
pygwalerapp(df).explorer()
# Initialize the inference client for the Mixtral model
@st.fragment
def tabs():
return st.tabs(['COOK','SAVED'])
if not mode:
cook, saved = tabs()
with cook:
cooking()
with saved:
rsaved()
else:
mart()