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 = """ """ 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""" """ html += "
Ingredient Expiry / Shelf Life Spoilage Risk Carbon Footprint (kg CO₂e/kg)
{name} {expiry_html} {risk} {carbon_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)