Spaces:
Sleeping
Sleeping
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") | |
def login_huggingface(token): | |
login(token=token) | |
login_huggingface(token=os.getenv("TOKEN")) | |
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) | |
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] | |
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!") | |
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.") | |
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 | |
def tabs(): | |
return st.tabs(['COOK','SAVED']) | |
if not mode: | |
cook, saved = tabs() | |
with cook: | |
cooking() | |
with saved: | |
rsaved() | |
else: | |
mart() |