import gradio as gr
from mcp_server import handle_tool_call
import re
import json
# --- Load Shelf Life Data ---
with open("spoilage_data.json", "r") as f:
shelf_life_data = json.load(f)
# --- Backend Logic Wrapping ---
def run_all_tools(text):
parsed = handle_tool_call("parse_ingredients", {"text": text})
spoilage = handle_tool_call("predict_spoilage", {"items": parsed})
carbon = handle_tool_call("estimate_carbon", {"items": parsed})
items_with_risk = [
{**item, "risk": spoilage[i]} for i, item in enumerate(parsed)
]
meal_plan = handle_tool_call(
"generate_meal_plan",
{"items": items_with_risk, "spoilage": spoilage, "carbon": carbon, "parsed": parsed}
)
print(meal_plan)
return parsed, spoilage, carbon, meal_plan
# --- Output Formatters ---
def format_items_table(meal_plan_json):
if isinstance(meal_plan_json, str):
try:
meal_plan_json = json.loads(meal_plan_json)
except json.JSONDecodeError:
pass
ingredient_data = []
if isinstance(meal_plan_json, list):
for block in meal_plan_json:
if isinstance(block, dict):
for val in block.values():
if isinstance(val, str):
match = re.search(r"```json\n(.*?)```", val, re.DOTALL)
if not match:
match = re.search(r"```(.*?)```", val, re.DOTALL)
if match:
try:
extracted_json = json.loads(match.group(1))
if isinstance(extracted_json, dict):
ingredient_data = extracted_json.get("ingredientDetails", [])
elif isinstance(extracted_json, list):
ingredient_data = extracted_json
break
except json.JSONDecodeError:
continue
if not ingredient_data:
return "
No ingredient data available in meal plan.
"
html = """
| Ingredient |
Expiry / Shelf Life |
Spoilage Risk |
Carbon Footprint (kg CO₂e/kg) |
"""
for item in ingredient_data:
name = item.get("Ingredient", "Unknown").title()
expiry_display = item.get("Expiry / Shelf Life", "N/A")
risk = item.get("Spoilage Risk", "Unknown").capitalize()
carbon_fp_str = str(item.get("Carbon Footprint", "N/A")).replace(" kg CO₂e/kg", "").strip()
days_match = re.search(r"(\d+)", expiry_display)
if days_match:
days = int(days_match.group(1))
if days <= 2:
expiry_class = "expiry-short"
icon = "⏰"
elif days <= 5:
expiry_class = "expiry-medium"
icon = "📆"
else:
expiry_class = "expiry-long"
icon = "✅"
else:
expiry_class = ""
icon = "❓"
expiry_html = f'{icon} {expiry_display}'
risk_color = {"High": "red", "Medium": "orange", "Low": "green"}.get(risk, "gray")
try:
carbon_value = float(carbon_fp_str)
intensity = "🌍" * int(min(carbon_value / 0.5, 5))
carbon_html = f"{carbon_value:.2f} {intensity}"
except ValueError:
carbon_html = "🌫️ N/A"
html += f"""
| {name} |
{expiry_html} |
{risk} |
{carbon_html} |
"""
html += "
"
return html
def format_meal_output(meal_plan_json):
import uuid
if isinstance(meal_plan_json, str):
try:
meal_plan_json = json.loads(meal_plan_json)
except json.JSONDecodeError:
return "Invalid meal plan format.
"
combined_text = ""
for block in meal_plan_json:
for val in block.values():
if isinstance(val, str):
combined_text += "\n" + val
sections = {
"b": {"title": "🌍 Carbon Footprint Overview", "content": ""},
"c": {"title": "📋 Suggested Meals", "content": ""},
"d": {"title": "🔁 Alternative Ingredients", "content": ""},
"e": {"title": "🧠 Reducing Waste & Emissions", "content": ""}
}
matches = re.findall(r"### \((b|c|d|e)\) (.+?)\n(.*?)(?=\n### \(|\Z)", combined_text, re.DOTALL)
for code, heading, content in matches:
if code in sections:
sections[code]["content"] = content.strip()
# Dark-mode accordion style
style = """
"""
html_parts = []
for sec in ["b", "c", "d", "e"]:
content = sections[sec]["content"]
if not content:
continue
content_html = content.replace("\n", "
")
html_parts.append(f"""
{sections[sec]["title"]}
{content_html}
""")
return style + "\n".join(html_parts)
def combined_output_func(text, api_key):
status_text = "⏳ Processing..."
parsed = handle_tool_call("parse_ingredients", {"text": text})
spoilage = handle_tool_call("predict_spoilage", {"items": parsed})
carbon = handle_tool_call("estimate_carbon", {"items": parsed})
items_with_risk = [
{**item, "risk": spoilage[i]} for i, item in enumerate(parsed)
]
meal_plan = handle_tool_call(
"generate_meal_plan",
{
"items": items_with_risk,
"spoilage": spoilage,
"carbon": carbon,
"parsed": parsed,
"api_key": api_key
}
)
# Check for connection error inside 'recipes'
if (
isinstance(meal_plan, list) and
len(meal_plan) >= 2 and
isinstance(meal_plan[1], dict) and
"recipes" in meal_plan[1] and
meal_plan[1]["recipes"] == ['Error generating recipes: Connection error.']
):
status_text = "❌ Connection error: Please input correct API key"
error_html = """
⚠️ Connection error: Please input a correct Nebius API key and try again.
"""
return status_text, error_html
else:
status_text = "✅ Success! EcoChef has generated your meal plan and sustainability report."
success_banner = """
🌱 Success! Your meal plan and carbon footprint report have been generated.
"""
if isinstance(meal_plan, str):
try:
meal_plan = json.loads(meal_plan)
except json.JSONDecodeError:
meal_plan = []
table_html = format_items_table(meal_plan)
meal_html = format_meal_output(meal_plan)
full_html = f"""
{success_banner}
{table_html}
{meal_html}
"""
return status_text, full_html
# Continue as usual if no error
if isinstance(meal_plan, str):
try:
meal_plan = json.loads(meal_plan)
except json.JSONDecodeError:
meal_plan = []
status_text = "✅ Done!"
table_html = format_items_table(meal_plan)
meal_html = format_meal_output(meal_plan)
full_html = f"""
{table_html}
{meal_html}
"""
return status_text, full_html
# --- Gradio UI ---
with gr.Blocks(title="EcoChef Dashboard", theme=gr.themes.Base(primary_hue="green", secondary_hue="blue")) as demo:
gr.Markdown("# 🥗 **EcoChef — Eco-Friendly AI Meal Planner with Food Carbon Insights**")
gr.Markdown("> _Reduce food waste. Eat smarter and Save the planet._")
with gr.Row():
with gr.Column(scale=1):
user_input = gr.Textbox(
lines=4,
placeholder="e.g. 2 bananas, LOBSTER, 6 eggs, rice, lentils",
value="Bananas 2, Lobsters 2, rice 1kg, lentils 500gm, yogurt expiring on 12th june, pasta expiring on 20th june",
label="🧊 What's in your fridge or pantry?"
)
gr.Markdown("_Note: **You may add expiry date of a product as: expiring on**_")
nebius_key_input = gr.Textbox(
placeholder="Paste your Nebius AI API key here...",
label="🔐 Enter Nebius AI API Key",
type="password"
)
submit_btn = gr.Button("✨ Analyze Ingredients")
status = gr.Markdown("⏳", visible=False)
gr.Markdown(" Carbon footprint data taken from the SU-EATABLE LIFE dataset.")
gr.Markdown(" 🤖 Created for Gradio Agents & MCP Hackathon 2025")
with gr.Column(scale=2):
combined_output = gr.HTML(label="Ingredients & Meal Plan")
submit_btn.click(
combined_output_func,
inputs=[user_input, nebius_key_input],
outputs=[status, combined_output]
)
if __name__ == "__main__":
demo.launch(mcp_server=True)