Spaces:
Sleeping
Sleeping
DevGruGold (Joe Lee) commited on
Commit ·
6d2dfdc
0
Parent(s):
rebuild: updated SDK versions
Browse files- README.md +13 -0
- app.py +164 -0
- requirements.txt +2 -0
README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MakeMeDinner
|
| 3 |
+
emoji: 🍳
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: yellow
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
sdk_version: 1.40.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
Multimodal AI cooking assistant for AMD Developer Hackathon.
|
app.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MakeMeDinner — Vision + LLM Cooking Assistant Demo
|
| 3 |
+
Streamlit app for HuggingFace Spaces
|
| 4 |
+
"""
|
| 5 |
+
import streamlit as st
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import List
|
| 8 |
+
import random
|
| 9 |
+
|
| 10 |
+
st.set_page_config(page_title="MakeMeDinner AI", page_icon="🍳", layout="wide")
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class Ingredient:
|
| 14 |
+
name: str
|
| 15 |
+
confidence: float
|
| 16 |
+
tag: str
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class Recipe:
|
| 20 |
+
title: str
|
| 21 |
+
match: int
|
| 22 |
+
time: str
|
| 23 |
+
calories: int
|
| 24 |
+
tags: List[str]
|
| 25 |
+
desc: str
|
| 26 |
+
steps: List[str]
|
| 27 |
+
ingredients: List[str]
|
| 28 |
+
missing: List[str]
|
| 29 |
+
|
| 30 |
+
RECIPE_DB = [
|
| 31 |
+
Recipe("Veggie Omelette", 96, "15 min", 320,
|
| 32 |
+
["vegetarian", "quick"],
|
| 33 |
+
"Fluffy eggs loaded with fresh veggies.",
|
| 34 |
+
["Whisk 3 eggs with salt+pepper", "Sauté diced veggies 3 min", "Pour eggs, add spinach+cheese", "Fold, cook 2 min each side"],
|
| 35 |
+
["eggs", "tomatoes", "onion", "cheese", "spinach"],
|
| 36 |
+
["olive oil", "salt"]),
|
| 37 |
+
Recipe("Shakshuka", 91, "25 min", 280,
|
| 38 |
+
["halal", "gluten-free"],
|
| 39 |
+
"Poached eggs in rich spiced tomato sauce.",
|
| 40 |
+
["Sauté onion and pepper 5 min", "Add tomatoes, cumin, paprika", "Simmer 10 min", "Crack eggs into wells", "Cover 5-7 min"],
|
| 41 |
+
["eggs", "tomatoes", "onion", "bell pepper"],
|
| 42 |
+
["cumin", "paprika", "bread"]),
|
| 43 |
+
Recipe("Spinach Frittata", 89, "20 min", 380,
|
| 44 |
+
["vegetarian", "quick"],
|
| 45 |
+
"Oven-baked egg dish — perfect for brunch.",
|
| 46 |
+
["Preheat broiler", "Sauté veggies in pan", "Pour 4 beaten eggs", "Stovetop 4 min, broil 2 min"],
|
| 47 |
+
["eggs", "tomatoes", "onion", "spinach", "cheese"],
|
| 48 |
+
[]),
|
| 49 |
+
Recipe("Tomato Bruschetta", 78, "10 min", 180,
|
| 50 |
+
["vegan", "quick"],
|
| 51 |
+
"Fresh diced tomatoes on toasted bread.",
|
| 52 |
+
["Dice tomatoes and onion", "Mince garlic + olive oil", "Combine, add salt+pepper", "Spoon onto toasted bread"],
|
| 53 |
+
["tomatoes", "onion", "garlic", "olive oil"],
|
| 54 |
+
["bread", "basil"]),
|
| 55 |
+
Recipe("Stuffed Peppers", 72, "35 min", 420,
|
| 56 |
+
["gluten-free"],
|
| 57 |
+
"Bell peppers stuffed with egg scramble.",
|
| 58 |
+
["Halve peppers, remove seeds", "Scramble eggs with veggies", "Stuff peppers, top with cheese", "Bake 375°F 20 min"],
|
| 59 |
+
["bell pepper", "eggs", "tomatoes", "onion", "spinach", "cheese"],
|
| 60 |
+
[]),
|
| 61 |
+
Recipe("Spinach Salad", 85, "5 min", 150,
|
| 62 |
+
["vegan", "keto", "quick"],
|
| 63 |
+
"Raw crunchy salad perfect as a side.",
|
| 64 |
+
["Chop tomatoes into wedges", "Tear spinach", "Toss with olive oil, salt, pepper"],
|
| 65 |
+
["tomatoes", "spinach", "onion", "olive oil"],
|
| 66 |
+
[]),
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
+
# CSS
|
| 70 |
+
st.markdown("""
|
| 71 |
+
<style>
|
| 72 |
+
.main-header { text-align:center; padding:2rem 1rem; }
|
| 73 |
+
.main-header h1 { font-size:2.5rem; background: linear-gradient(135deg,#ff6b35,#f7931e); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
|
| 74 |
+
.ingredient-pill { display:inline-block; background:rgba(255,107,53,0.15); border:1px solid rgba(255,107,53,0.3); color:#ff6b35; padding:4px 12px; border-radius:8px; margin:2px; font-size:0.85rem; }
|
| 75 |
+
.recipe-card { background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.06); border-radius:16px; padding:1.25rem; margin-bottom:1rem; }
|
| 76 |
+
.recipe-title { font-size:1.1rem; font-weight:700; color:#fff; }
|
| 77 |
+
.match-badge { background:rgba(0,200,83,0.15); color:#00c853; padding:2px 10px; border-radius:6px; font-size:0.75rem; font-weight:700; }
|
| 78 |
+
</style>
|
| 79 |
+
""", unsafe_allow_html=True)
|
| 80 |
+
|
| 81 |
+
st.markdown('<div class="main-header"><h1>🍳 MakeMeDinner AI</h1><p style="color:#888;">Scan ingredients, get recipes. Powered by multimodal AI.</p></div>', unsafe_allow_html=True)
|
| 82 |
+
|
| 83 |
+
col1, col2 = st.columns([1,1.2])
|
| 84 |
+
|
| 85 |
+
with col1:
|
| 86 |
+
st.subheader("📷 Scan Your Ingredients")
|
| 87 |
+
uploaded = st.file_uploader("Drop or snap a photo", type=["jpg","jpeg","png"])
|
| 88 |
+
st.caption("In production: camera capture + CLIP/SigLIP vision model")
|
| 89 |
+
|
| 90 |
+
if uploaded:
|
| 91 |
+
st.image(uploaded, use_container_width=True)
|
| 92 |
+
|
| 93 |
+
if st.button("🔍 Analyze Ingredients", type="primary") or uploaded:
|
| 94 |
+
with st.spinner("Analyzing with vision AI..."):
|
| 95 |
+
import time; time.sleep(1.5)
|
| 96 |
+
|
| 97 |
+
detected = [
|
| 98 |
+
Ingredient("Eggs", 0.97, "dairy-free"),
|
| 99 |
+
Ingredient("Tomatoes", 0.94, "vegan"),
|
| 100 |
+
Ingredient("Onion", 0.91, "vegan"),
|
| 101 |
+
Ingredient("Cheese", 0.88, "halal"),
|
| 102 |
+
Ingredient("Spinach", 0.85, "vegan"),
|
| 103 |
+
Ingredient("Bell Pepper", 0.72, "vegan"),
|
| 104 |
+
Ingredient("Garlic", 0.68, "vegan"),
|
| 105 |
+
]
|
| 106 |
+
st.session_state["pantry"] = detected
|
| 107 |
+
|
| 108 |
+
with col2:
|
| 109 |
+
if "pantry" in st.session_state:
|
| 110 |
+
st.subheader("Detected Ingredients")
|
| 111 |
+
html = ""
|
| 112 |
+
for i in st.session_state["pantry"]:
|
| 113 |
+
html += f'<span class="ingredient-pill">{i.name} <span style="opacity:0.7;">{i.confidence*100:.0f}%</span></span>'
|
| 114 |
+
st.markdown(html, unsafe_allow_html=True)
|
| 115 |
+
|
| 116 |
+
filters = ["all", "vegan", "keto", "gluten-free", "halal", "quick"]
|
| 117 |
+
active = st.segmented_control("Filter", filters, default="all")
|
| 118 |
+
|
| 119 |
+
matched = [r for r in RECIPE_DB if active=="all" or active in r.tags or (active=="quick" and "quick" in r.tags)]
|
| 120 |
+
|
| 121 |
+
st.markdown(f"""<div style="margin:1.5rem 0 0.5rem;">
|
| 122 |
+
<h3>🥘 {len(matched)} Recipe Ideas</h3>
|
| 123 |
+
<p style="color:#888; font-size:0.85rem;">Ranked by ingredient match and your filters</p>
|
| 124 |
+
</div>""", unsafe_allow_html=True)
|
| 125 |
+
|
| 126 |
+
for r in matched:
|
| 127 |
+
with st.container():
|
| 128 |
+
st.markdown(f"""<div class="recipe-card">
|
| 129 |
+
<div style="display:flex; justify-content:space-between; align-items:start;">
|
| 130 |
+
<div class="recipe-title">{r.title}</div>
|
| 131 |
+
<div class="match-badge">{r.match}% match</div>
|
| 132 |
+
</div>
|
| 133 |
+
<div style="color:#888; font-size:0.8rem; margin:0.5rem 0;">⏱ {r.time} · 🔥 {r.calories} cal · 🥗 {len(r.ingredients)} items</div>
|
| 134 |
+
<div style="color:#ccc; font-size:0.9rem;">{r.desc}</div>
|
| 135 |
+
</div>
|
| 136 |
+
""", unsafe_allow_html=True)
|
| 137 |
+
|
| 138 |
+
if st.button(f"📖 View Steps", key=r.title):
|
| 139 |
+
st.session_state["selected_recipe"] = r
|
| 140 |
+
|
| 141 |
+
# Selected recipe steps
|
| 142 |
+
if "selected_recipe" in st.session_state:
|
| 143 |
+
r = st.session_state["selected_recipe"]
|
| 144 |
+
st.markdown(f"### {r.title} — Steps")
|
| 145 |
+
for i, step in enumerate(r.steps, 1):
|
| 146 |
+
st.markdown(f"<div style='padding:0.5rem 0; border-bottom:1px solid rgba(255,255,255,0.05);'><b>{i}.</b> {step}</div>", unsafe_allow_html=True)
|
| 147 |
+
|
| 148 |
+
if r.missing:
|
| 149 |
+
st.markdown(f"#### 🛒 Missing: {', '.join(r.missing)}")
|
| 150 |
+
|
| 151 |
+
if st.button("🔊 Read Aloud"):
|
| 152 |
+
# Using browser TTS via HTML audio element injection
|
| 153 |
+
text = r.title + ". " + ". ".join(r.steps)
|
| 154 |
+
st.markdown(f"""
|
| 155 |
+
<script>
|
| 156 |
+
const u = new SpeechSynthesisUtterance("{text}"); u.rate=0.9; window.speechSynthesis.speak(u);
|
| 157 |
+
</script>
|
| 158 |
+
""", unsafe_allow_html=True)
|
| 159 |
+
st.toast("Reading steps aloud...")
|
| 160 |
+
|
| 161 |
+
st.markdown("---")
|
| 162 |
+
st.caption("MakeMeDinner — AMD Developer Hackathon · Vision & Multimodal AI Track · OSS")
|
| 163 |
+
|
| 164 |
+
# v2 rebuild trigger
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.28
|
| 2 |
+
Pillow>=10.0.0
|