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()