import gradio as gr import json import os import pandas as pd import re import glob from typing import Dict, List, Any from main3 import run_rag_tool_router, execute_diet_tool from diet_tool import generate_diet_plan, get_diet_recommendations, calculate_nutrition_info # Ensure outputs directory exists os.makedirs("outputs", exist_ok=True) # Global chat history chat_history = [] def format_json_to_table(json_data: Any, title: str = "") -> str: """Convert JSON data to a formatted table string""" if not json_data: return "" try: if isinstance(json_data, str): json_data = json.loads(json_data) if isinstance(json_data, dict): return format_dict_to_table(json_data, title) elif isinstance(json_data, list): return format_list_to_table(json_data, title) else: return str(json_data) except: return str(json_data) def format_dict_to_table(data: dict, title: str = "") -> str: """Format dictionary as a table""" if not data: return "" result = f"## {title}\n\n" if title else "" # Check if it's a simple key-value dict if all(not isinstance(v, (dict, list)) for v in data.values()): result += "| Key | Value |\n" result += "|-----|-------|\n" for key, value in data.items(): result += f"| **{key}** | {value} |\n" else: # Complex nested structure for key, value in data.items(): if isinstance(value, dict): result += f"### {key}\n" result += format_dict_to_table(value) elif isinstance(value, list): result += f"### {key}\n" result += format_list_to_table(value) else: result += f"**{key}**: {value}\n" return result def format_list_to_table(data: list, title: str = "") -> str: """Format list as a table""" if not data: return "" result = f"## {title}\n\n" if title else "" if not data: return result # Check if all items are dictionaries with similar keys if all(isinstance(item, dict) for item in data): # Get all unique keys all_keys = set() for item in data: all_keys.update(item.keys()) if all_keys: # Create table header result += "| " + " | ".join(all_keys) + " |\n" result += "|" + "|".join(["---"] * len(all_keys)) + "|\n" # Add rows for item in data: row = [] for key in all_keys: value = item.get(key, "") if isinstance(value, (dict, list)): value = json.dumps(value, ensure_ascii=False)[:50] + "..." row.append(str(value)) result += "| " + " | ".join(row) + " |\n" else: result += "Empty list\n" else: # Simple list for i, item in enumerate(data): result += f"{i+1}. {item}\n" return result def format_diet_plan_result(result: dict) -> str: """Format diet plan result specifically""" if not result or "error" in result: return f"❌ Error: {result.get('error', 'Unknown error')}" formatted = "## 🍽️ Diet Plan\n\n" if "plan_type" in result: formatted += f"**Plan Type**: {result['plan_type']}\n\n" if "plan" in result: plan = result["plan"] # Daily totals if "daily_totals" in plan: totals = plan["daily_totals"] formatted += "### 📊 Daily Nutrition Summary\n\n" formatted += "| Nutrient | Amount |\n" formatted += "|----------|--------|\n" formatted += f"| **Calories** | {totals.get('calories', 0)} kcal |\n" formatted += f"| **Protein** | {totals.get('protein', 0)} g |\n" formatted += f"| **Carbs** | {totals.get('carbs', 0)} g |\n" formatted += f"| **Fat** | {totals.get('fat', 0)} g |\n" formatted += f"| **Fiber** | {totals.get('fiber', 0)} g |\n\n" # Meals if "meals" in plan: formatted += "### 🍽️ Meals\n\n" for meal in plan["meals"]: formatted += f"#### {meal.get('meal_name', 'Meal')} ({meal.get('time_range', '')})\n\n" # Meal nutrition if "nutrition" in meal: nutrition = meal["nutrition"] formatted += "**Nutrition**: " formatted += f"{nutrition.get('calories', 0)} kcal, " formatted += f"{nutrition.get('protein', 0)}g protein, " formatted += f"{nutrition.get('carbs', 0)}g carbs, " formatted += f"{nutrition.get('fat', 0)}g fat\n\n" # Foods if "foods" in meal: formatted += "**Foods**:\n" for food in meal["foods"]: formatted += f"- **{food.get('name', 'Unknown')}** " formatted += f"({food.get('portion_grams', 0)}g) - " formatted += f"{food.get('calories', 0)} kcal\n" formatted += "\n" elif "plans" in result: # Weekly plan plans = result["plans"] formatted += f"### 📅 Weekly Plan ({len(plans)} days)\n\n" for i, plan in enumerate(plans): # Show all days formatted += f"#### Day {i+1} - {plan.get('date', '')}\n" if "daily_totals" in plan: totals = plan["daily_totals"] formatted += f"**Total**: {totals.get('calories', 0)} kcal, " formatted += f"{totals.get('protein', 0)}g protein\n\n" return formatted def format_recommendations_result(result: dict) -> str: """Format recommendations result specifically""" if not result or "error" in result: return f"❌ Error: {result.get('error', 'Unknown error')}" formatted = "## 📊 Diet Recommendations\n\n" # Basic metrics if "bmr" in result: formatted += f"**BMR (Basal Metabolic Rate)**: {result['bmr']} kcal/day\n" if "tdee" in result: formatted += f"**TDEE (Total Daily Energy Expenditure)**: {result['tdee']} kcal/day\n" if "target_calories" in result: formatted += f"**Target Calories**: {result['target_calories']} kcal/day\n\n" # Macro ratios if "macro_ratios" in result: ratios = result["macro_ratios"] formatted += "### 🎯 Macro Ratios\n\n" formatted += "| Macronutrient | Percentage |\n" formatted += "|---------------|------------|\n" for macro, ratio in ratios.items(): formatted += f"| **{macro.title()}** | {ratio} |\n" formatted += "\n" # Macro grams if "macro_grams" in result: grams = result["macro_grams"] formatted += "### ⚖️ Daily Macro Targets (grams)\n\n" formatted += "| Macronutrient | Grams |\n" formatted += "|---------------|-------|\n" for macro, grams_val in grams.items(): formatted += f"| **{macro.title()}** | {grams_val} g |\n" formatted += "\n" # Recommendations if "recommendations" in result: recs = result["recommendations"] formatted += "### 💡 Recommendations\n\n" for key, value in recs.items(): formatted += f"**{key.replace('_', ' ').title()}**: {value}\n" return formatted def format_nutrition_result(result: dict) -> str: """Format nutrition calculation result specifically""" if not result or "error" in result: return f"❌ Error: {result.get('error', 'Unknown error')}" formatted = "## 🧮 Nutrition Information\n\n" if "food_name" in result: formatted += f"**Food**: {result['food_name']}\n" if "portion_grams" in result: formatted += f"**Portion**: {result['portion_grams']} grams\n\n" # Basic nutrition formatted += "### 📊 Nutritional Values\n\n" formatted += "| Nutrient | Amount |\n" formatted += "|----------|--------|\n" nutrients = ["calories", "protein", "carbs", "fat", "fiber"] for nutrient in nutrients: if nutrient in result: unit = "kcal" if nutrient == "calories" else "g" formatted += f"| **{nutrient.title()}** | {result[nutrient]} {unit} |\n" # Vitamins and minerals if "vitamins" in result and result["vitamins"]: formatted += "\n### 🥕 Vitamins\n\n" if isinstance(result["vitamins"], list): for vitamin in result["vitamins"]: formatted += f"- {vitamin}\n" else: formatted += str(result["vitamins"]) if "minerals" in result and result["minerals"]: formatted += "\n### ⚡ Minerals\n\n" if isinstance(result["minerals"], list): for mineral in result["minerals"]: formatted += f"- {mineral}\n" else: formatted += str(result["minerals"]) return formatted def load_latest_json_output() -> str: """Load and format the latest JSON output file""" outputs_dir = "outputs" if not os.path.exists(outputs_dir): return "❌ No outputs directory found" # Get all JSON files in outputs directory json_files = glob.glob(os.path.join(outputs_dir, "*.json")) if not json_files: return "❌ No JSON files found in outputs directory" # Sort by modification time and get the latest latest_file = max(json_files, key=os.path.getmtime) try: with open(latest_file, 'r', encoding='utf-8') as f: data = json.load(f) # Extract filename for title filename = os.path.basename(latest_file) # Format based on file type if "diet_plan" in filename: if "weekly" in filename: return format_weekly_diet_plan(data, filename) else: return format_daily_diet_plan(data, filename) elif "recommendations" in filename: return format_recommendations_result(data, filename) elif "nutrition" in filename: return format_nutrition_result(data, filename) else: return format_json_data_to_table(data, f"Latest Output: {filename}") except Exception as e: return f"❌ Error reading {latest_file}: {str(e)}" def format_weekly_diet_plan(data: dict, filename: str) -> str: """Format weekly diet plan from JSON file""" if not data or "result" not in data: return f"❌ Invalid data in {filename}" result = data["result"] if "plans" not in result: return f"❌ No plans found in {filename}" plans = result["plans"] formatted = f"## 📅 Weekly Diet Plan ({len(plans)} days)\n\n" formatted += f"**File**: {filename}\n\n" # Summary table for all days formatted += "### 📊 Weekly Summary\n\n" formatted += "| Day | Date | Calories | Protein | Carbs | Fat | Fiber |\n" formatted += "|-----|------|----------|---------|-------|-----|-------|\n" for i, plan in enumerate(plans): totals = plan.get("daily_totals", {}) date = plan.get("date", f"Day {i+1}") formatted += f"| {i+1} | {date} | {totals.get('calories', 0)} | {totals.get('protein', 0)}g | {totals.get('carbs', 0)}g | {totals.get('fat', 0)}g | {totals.get('fiber', 0)}g |\n" # Detailed view for all days formatted += "\n### 🍽️ Detailed Meals (All Days)\n\n" for i, plan in enumerate(plans): date = plan.get("date", f"Day {i+1}") formatted += f"#### Day {i+1} - {date}\n\n" meals = plan.get("meals", []) for meal in meals: meal_name = meal.get("meal_name", "Meal") time_range = meal.get("time_range", "") nutrition = meal.get("nutrition", {}) formatted += f"**{meal_name}** ({time_range})\n" formatted += f"- Calories: {nutrition.get('calories', 0)} | Protein: {nutrition.get('protein', 0)}g | Carbs: {nutrition.get('carbs', 0)}g | Fat: {nutrition.get('fat', 0)}g\n" # Foods foods = meal.get("foods", []) if foods: formatted += " - Foods: " food_names = [f.get("name", "Unknown") for f in foods[:3]] # Show first 3 foods formatted += ", ".join(food_names) if len(foods) > 3: formatted += f" (+{len(foods)-3} more)" formatted += "\n" formatted += "\n" return formatted def format_daily_diet_plan(data: dict, filename: str) -> str: """Format daily diet plan from JSON file""" if not data or "result" not in data: return f"❌ Invalid data in {filename}" result = data["result"] if "plan" not in result: return f"❌ No plan found in {filename}" plan = result["plan"] formatted = f"## 🍽️ Daily Diet Plan\n\n" formatted += f"**File**: {filename}\n\n" # Daily totals if "daily_totals" in plan: totals = plan["daily_totals"] formatted += "### 📊 Daily Nutrition Summary\n\n" formatted += "| Nutrient | Amount |\n" formatted += "|----------|--------|\n" formatted += f"| **Calories** | {totals.get('calories', 0)} kcal |\n" formatted += f"| **Protein** | {totals.get('protein', 0)} g |\n" formatted += f"| **Carbs** | {totals.get('carbs', 0)} g |\n" formatted += f"| **Fat** | {totals.get('fat', 0)} g |\n" formatted += f"| **Fiber** | {totals.get('fiber', 0)} g |\n\n" # Meals if "meals" in plan: formatted += "### 🍽️ Meals\n\n" for meal in plan["meals"]: meal_name = meal.get("meal_name", "Meal") time_range = meal.get("time_range", "") nutrition = meal.get("nutrition", {}) formatted += f"#### {meal_name} ({time_range})\n\n" formatted += f"**Nutrition**: {nutrition.get('calories', 0)} kcal, {nutrition.get('protein', 0)}g protein, {nutrition.get('carbs', 0)}g carbs, {nutrition.get('fat', 0)}g fat\n\n" # Foods foods = meal.get("foods", []) if foods: formatted += "**Foods**:\n" for food in foods: name = food.get("name", "Unknown") portion = food.get("portion_grams", 0) calories = food.get("calories", 0) formatted += f"- **{name}** ({portion}g) - {calories} kcal\n" formatted += "\n" return formatted def list_all_json_outputs() -> str: """List all JSON output files with their details""" outputs_dir = "outputs" if not os.path.exists(outputs_dir): return "❌ No outputs directory found" json_files = glob.glob(os.path.join(outputs_dir, "*.json")) if not json_files: return "❌ No JSON files found in outputs directory" # Sort by modification time (newest first) json_files.sort(key=os.path.getmtime, reverse=True) formatted = "## 📁 Available JSON Outputs\n\n" formatted += "| File | Type | Size | Modified |\n" formatted += "|------|------|------|----------|\n" for file_path in json_files: filename = os.path.basename(file_path) file_type = "Unknown" if "diet_plan_weekly" in filename: file_type = "Weekly Diet Plan" elif "diet_plan_daily" in filename: file_type = "Daily Diet Plan" elif "recommendations" in filename: file_type = "Recommendations" elif "nutrition" in filename: file_type = "Nutrition Info" file_size = os.path.getsize(file_path) mod_time = os.path.getmtime(file_path) mod_date = pd.Timestamp.fromtimestamp(mod_time).strftime("%Y-%m-%d %H:%M") formatted += f"| {filename} | {file_type} | {file_size:,} bytes | {mod_date} |\n" return formatted def extract_and_format_json_in_text(text: str) -> str: """Extract JSON objects from text and format them as tables""" if not text: return text formatted_text = text # First, try to find JSON in code blocks code_block_pattern = r'```(?:json)?\s*(\{.*?\})\s*```' matches = list(re.finditer(code_block_pattern, formatted_text, re.DOTALL | re.IGNORECASE)) for match in reversed(matches): json_str = match.group(1).strip() try: json_data = json.loads(json_str) table = format_json_data_to_table(json_data) formatted_text = formatted_text[:match.start()] + table + formatted_text[match.end():] except: continue # Then, try to find standalone JSON objects # Look for patterns that start with { and end with } json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}' matches = list(re.finditer(json_pattern, formatted_text, re.DOTALL)) for match in reversed(matches): json_str = match.group(0).strip() # Skip if it's already been processed or is too short if len(json_str) < 10 or '|' in json_str: continue try: json_data = json.loads(json_str) # Only format if it looks like structured data if isinstance(json_data, (dict, list)) and len(str(json_data)) > 20: table = format_json_data_to_table(json_data) formatted_text = formatted_text[:match.start()] + table + formatted_text[match.end():] except: continue return formatted_text def format_json_data_to_table(json_data: Any) -> str: """Format JSON data to a table string""" if isinstance(json_data, dict): return format_dict_to_table(json_data, "Data") elif isinstance(json_data, list): return format_list_to_table(json_data, "Data") else: return str(json_data) def process_user_input(user_input: str, history: List) -> tuple: """Process user input through RAG system""" global chat_history if not user_input.strip(): return history, "" # Convert Gradio history format to our format rag_history = [] for msg in history: if isinstance(msg, dict): rag_history.append(msg) else: # Handle old format user_msg, bot_msg = msg rag_history.append({"role": "user", "content": user_msg}) rag_history.append({"role": "assistant", "content": bot_msg}) # Process through RAG system result = run_rag_tool_router(user_input, rag_history) # Update our chat history chat_history.append({"role": "user", "content": user_input}) # Handle different response types if isinstance(result, dict) and "final" in result: response = result["final"] # Format any JSON in the response formatted_response = extract_and_format_json_in_text(response) chat_history.append({"role": "assistant", "content": formatted_response}) history.append({"role": "user", "content": user_input}) history.append({"role": "assistant", "content": formatted_response}) return history, "" elif isinstance(result, dict) and "action" in result: action = result["action"] args = result.get("args", {}) # Show the action being taken action_response = f"**Executing**: {action}\n\n**Arguments**:\n```json\n{json.dumps(args, indent=2)}\n```" # Execute diet tools if action in ["generate_diet_plan", "get_diet_recommendations", "calculate_nutrition_info", "expand_diet_database"]: diet_result = execute_diet_tool(action, args) if diet_result.get("success"): final_response = f"✅ **{diet_result.get('message', 'Operation completed successfully')}**\n\n" if "result" in diet_result: # Format the result based on action type if action == "generate_diet_plan": formatted_result = format_diet_plan_result(diet_result['result']) elif action == "get_diet_recommendations": formatted_result = format_recommendations_result(diet_result['result']) elif action == "calculate_nutrition_info": formatted_result = format_nutrition_result(diet_result['result']) else: formatted_result = format_json_to_table(diet_result['result'], "Result") final_response += formatted_result else: final_response = f"❌ **Error**: {diet_result.get('error', 'Unknown error occurred')}" # Add to history history.append({"role": "user", "content": user_input}) history.append({"role": "assistant", "content": action_response + "\n\n" + final_response}) chat_history.append({"role": "assistant", "content": final_response}) else: # For other tools, show placeholder final_response = "🔄 This tool is not yet implemented in the web interface." history.append({"role": "user", "content": user_input}) history.append({"role": "assistant", "content": action_response + "\n\n" + final_response}) chat_history.append({"role": "assistant", "content": final_response}) return history, "" else: # Handle errors or other responses error_response = f"❌ **Error**: {json.dumps(result, indent=2, ensure_ascii=False)}" chat_history.append({"role": "assistant", "content": error_response}) history.append({"role": "user", "content": user_input}) history.append({"role": "assistant", "content": error_response}) return history, "" def create_diet_plan_interface(age: int, sex: str, height_cm: float, weight_kg: float, activity_level: str, goal: str, plan_type: str) -> str: """Create diet plan using the interface""" user_profile = { "age": age, "sex": sex, "height_cm": height_cm, "weight_kg": weight_kg, "activity_level": activity_level, "goal": goal, "dietary_restrictions": [], "allergies": [], "preferences": [] } try: result = generate_diet_plan(user_profile, plan_type) return format_diet_plan_result(result) except Exception as e: return f"❌ Error: {str(e)}" def get_recommendations_interface(age: int, sex: str, height_cm: float, weight_kg: float, activity_level: str, goal: str) -> str: """Get diet recommendations using the interface""" user_profile = { "age": age, "sex": sex, "height_cm": height_cm, "weight_kg": weight_kg, "activity_level": activity_level, "goal": goal, "dietary_restrictions": [], "allergies": [], "preferences": [] } try: result = get_diet_recommendations(user_profile) return format_recommendations_result(result) except Exception as e: return f"❌ Error: {str(e)}" def calculate_nutrition_interface(food_name: str, portion_grams: float) -> str: """Calculate nutrition info using the interface""" try: result = calculate_nutrition_info(food_name, portion_grams) return format_nutrition_result(result) except Exception as e: return f"❌ Error: {str(e)}" # Create Gradio interface with gr.Blocks(title="RAG Diet Assistant") as app: gr.Markdown("# 🍽️ RAG Diet Assistant") gr.Markdown("Your intelligent nutrition planning assistant powered by RAG (Retrieval-Augmented Generation)") with gr.Tabs(): # Chat Interface Tab with gr.Tab("💬 Chat Assistant"): gr.Markdown("Ask me anything about diet planning, nutrition, or health!") chatbot = gr.Chatbot( label="Conversation", height=500, type="messages", render_markdown=True ) with gr.Row(): msg = gr.Textbox( label="Your message", placeholder="Ask me to create a diet plan, calculate nutrition, or get recommendations...", lines=2, scale=4 ) send_btn = gr.Button("Send", variant="primary", scale=1) # Action buttons with gr.Row(): clear_btn = gr.Button("Clear Chat", variant="secondary") load_latest_btn = gr.Button("📄 Load Latest JSON", variant="secondary") list_outputs_btn = gr.Button("📁 List All Outputs", variant="secondary") # Event handlers msg.submit(process_user_input, [msg, chatbot], [chatbot, msg]) send_btn.click(process_user_input, [msg, chatbot], [chatbot, msg]) clear_btn.click(lambda: ([], ""), outputs=[chatbot, msg]) load_latest_btn.click(lambda: ([{"role": "assistant", "content": load_latest_json_output()}], ""), outputs=[chatbot, msg]) list_outputs_btn.click(lambda: ([{"role": "assistant", "content": list_all_json_outputs()}], ""), outputs=[chatbot, msg]) # Diet Plan Creator Tab with gr.Tab("📋 Diet Plan Creator"): gr.Markdown("Create personalized diet plans based on your profile") with gr.Row(): with gr.Column(): gr.Markdown("### Personal Information") age = gr.Number(label="Age", value=30, minimum=15, maximum=100) sex = gr.Dropdown(label="Gender", choices=["male", "female"], value="male") height_cm = gr.Number(label="Height (cm)", value=175, minimum=100, maximum=250) weight_kg = gr.Number(label="Weight (kg)", value=75, minimum=30, maximum=300) gr.Markdown("### Activity & Goals") activity_level = gr.Dropdown( label="Activity Level", choices=["sedentary", "light", "moderate", "active", "very_active"], value="moderate" ) goal = gr.Dropdown( label="Goal", choices=["weight_loss", "muscle_gain", "maintenance", "keto", "mediterranean"], value="weight_loss" ) plan_type = gr.Dropdown( label="Plan Type", choices=["daily", "weekly"], value="daily" ) create_plan_btn = gr.Button("Create Diet Plan", variant="primary") with gr.Column(): plan_output = gr.Textbox( label="Diet Plan", lines=20, max_lines=30 ) create_plan_btn.click( create_diet_plan_interface, inputs=[age, sex, height_cm, weight_kg, activity_level, goal, plan_type], outputs=plan_output ) # Recommendations Tab with gr.Tab("📊 Diet Recommendations"): gr.Markdown("Get personalized nutrition recommendations") with gr.Row(): with gr.Column(): gr.Markdown("### Personal Information") rec_age = gr.Number(label="Age", value=30, minimum=15, maximum=100) rec_sex = gr.Dropdown(label="Gender", choices=["male", "female"], value="male") rec_height_cm = gr.Number(label="Height (cm)", value=175, minimum=100, maximum=250) rec_weight_kg = gr.Number(label="Weight (kg)", value=75, minimum=30, maximum=300) gr.Markdown("### Activity & Goals") rec_activity_level = gr.Dropdown( label="Activity Level", choices=["sedentary", "light", "moderate", "active", "very_active"], value="moderate" ) rec_goal = gr.Dropdown( label="Goal", choices=["weight_loss", "muscle_gain", "maintenance", "keto", "mediterranean"], value="weight_loss" ) get_rec_btn = gr.Button("Get Recommendations", variant="primary") with gr.Column(): rec_output = gr.Textbox( label="Recommendations", lines=15, max_lines=20 ) get_rec_btn.click( get_recommendations_interface, inputs=[rec_age, rec_sex, rec_height_cm, rec_weight_kg, rec_activity_level, rec_goal], outputs=rec_output ) # Nutrition Calculator Tab with gr.Tab("🧮 Nutrition Calculator"): gr.Markdown("Calculate nutritional values for specific foods") with gr.Row(): with gr.Column(): food_name = gr.Textbox( label="Food Name", placeholder="e.g., Chicken Breast, Apple, Brown Rice...", value="Chicken Breast" ) portion_grams = gr.Number( label="Portion (grams)", value=150, minimum=1, maximum=1000 ) calc_nutrition_btn = gr.Button("Calculate Nutrition", variant="primary") with gr.Column(): nutrition_output = gr.Textbox( label="Nutritional Information", lines=15, max_lines=20 ) calc_nutrition_btn.click( calculate_nutrition_interface, inputs=[food_name, portion_grams], outputs=nutrition_output ) def create_gradio_interface(): """Create and return the Gradio interface for Hugging Face Spaces""" return app if __name__ == "__main__": # For local development app.launch( server_name="0.0.0.0", server_port=7860, share=False )