Spaces:
Running
Running
import streamlit as st | |
import numpy as np | |
import pandas as pd | |
import re | |
import json | |
from openai import OpenAI | |
import secrets | |
from datetime import datetime | |
client = OpenAI( | |
api_key = st.secrets["open_ai_key"] | |
) | |
# state management | |
if 'gpt_response' not in st.session_state: | |
st.session_state.gpt_response = 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 | |
if 'admin' not in st.session_state: | |
st.session_state.admin = False | |
# functions | |
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 st.spinner('Building the perfect recipe...'): | |
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" | |
], | |
}, | |
} | |
] | |
prompt = create_detailed_prompt(user_inputs['user_direction'], user_inputs['exclusions'], user_inputs['serving_size'], user_inputs['difficulty']) | |
messages = [{"role": "user", "content": prompt}] | |
st.session_state.gpt_response = client.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=messages, | |
temperature=0.75, | |
top_p=0.75, | |
functions=functions, | |
function_call={"name":"provide_recipe"}, # auto is default, but we'll be explicit | |
) | |
st.session_state.recipe_saved = False | |
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(): | |
filename = create_safe_filename(recipe["name"]) | |
with open(f'/data/{filename}.json', 'w') as f: | |
json.dump(recipe, f, indent=4) | |
st.session_state.recipe_saved = True | |
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" | |
# app | |
st.title("Let's get cooking") | |
st.session_state.user_direction = st.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", | |
) | |
st.session_state.serving_size = st.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 = st.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) | |
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}", | |
"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]: | |
st.button(label='Submit', on_click=generate_recipe, kwargs=dict(user_inputs=user_inputs), type="primary", use_container_width=True) | |
with button_cols_submit[1]: | |
st.button(label='Reset', on_click=clear_inputs, type="secondary", use_container_width=True) | |
with button_cols_submit[2]: | |
st.empty() | |
if st.session_state.gpt_response is not None: | |
st.divider() | |
recipe = json.loads(st.session_state.gpt_response.choices[0].message.function_call.arguments) | |
recipe_md = '' | |
recipe_md += f'# {recipe["name"]} \n\n' | |
recipe_md += f'{recipe["description"]} \n\n' | |
recipe_md += f'## Ingredients: \n' | |
for ingredient in recipe['ingredients']: | |
recipe_md += f"- {ingredient['name']} \n" | |
recipe_md += f'\n## Instructions:\n' | |
for instruction in recipe['instructions']: | |
recipe_md += f"{instruction['step_number']}. {instruction['instruction']} \n" | |
recipe['md'] = recipe_md | |
recipe['timestamp'] = str(datetime.now()) | |
st.markdown(recipe_md) | |
st.write("") | |
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]: | |
st.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!") |