help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • Blog
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
recipe-assistant-bot
/
app
RSK World
recipe-assistant-bot
Recipe Assistant Bot - Python + Flask + AI Chatbot + Recipe Suggestions + Cooking Tips + Meal Planning
app
  • __pycache__
  • tests
  • __init__.py756 B
  • advanced_features.py19.6 KB
  • auth.py8.8 KB
  • chatbot.py55.4 KB
  • cooking_assistant.py27.9 KB
  • image_recognition.py15.3 KB
  • rating_review.py16.4 KB
  • routes.py79.9 KB
  • shopping_nutrition.py22.4 KB
  • smart_kitchen.py19.7 KB
  • voice_assistant.py13.4 KB
QUICKSTART.mdEquationSolver.storyboardREADME.mdtranscripts.txtAIMathSolver.storyboard.gitignorecooking_assistant.pyroutes.pyMain.storyboardUnitConverter.storyboardSettings.storyboardvoice_assistant.py
README.md
Raw Download

README.md

# Recipe Assistant Bot

![Recipe Assistant Bot](https://img.shields.io/badge/Version-1.0.0-blue.svg)
![Python](https://img.shields.io/badge/Python-3.8+-green.svg)
![Flask](https://img.shields.io/badge/Flask-2.3.3-red.svg)
![License](https://img.shields.io/badge/License-Educational-yellow.svg)

**Author:** RSK World (https://rskworld.in)
**Founder:** Molla Samser
**Designer & Tester:** Rima Khatun
**Contact:** help@rskworld.in, +91 93305 39277
**Year:** 2026

## 🍳 Description

Recipe Assistant Bot is an AI-powered cooking chatbot that provides recipe suggestions, ingredient substitutions, cooking tips, and meal planning assistance. Perfect for food apps and cooking platforms, this beginner-friendly project demonstrates the integration of Python web development with natural language processing.

## ✨ Features

- 🍽️ **Recipe Suggestions** - Get personalized recipe recommendations
- 🔄 **Ingredient Substitutions** - Find alternatives for missing ingredients
- 💡 **Cooking Tips** - Learn professional cooking techniques
- 📋 **Meal Planning** - Plan your meals efficiently
- 🥗 **Dietary Restrictions** - Support for vegetarian, vegan, gluten-free, keto, and more
- 🌐 **Web Interface** - Modern, responsive chat interface
- 📱 **Mobile Friendly** - Works on all devices

## 🛠️ Technologies Used

- **Backend:** Python 3.8+, Flask 2.3.3
- **Frontend:** HTML5, CSS3, JavaScript, Bootstrap 5
- **APIs:** Recipe APIs, OpenAI API (optional)
- **Database:** SQLite (default), configurable for other databases
- **Additional:** Flask-CORS, Requests, Python-dotenv

## 📋 Requirements

- Python 3.8 or higher
- pip (Python package manager)
- Git (for cloning)

## 🚀 Installation

1. **Clone the repository:**
```bash
git clone <repository-url>
cd recipe-assistant-bot
```

2. **Create a virtual environment:**
```bash
python -m venv venv

# On Windows
venv\Scripts\activate

# On macOS/Linux
source venv/bin/activate
```

3. **Install dependencies:**
```bash
pip install -r requirements.txt
```

4. **Set up environment variables:**
```bash
cp .env.example .env
# Edit .env file with your configuration
```

5. **Run the application:**
```bash
python run.py
```

6. **Open your browser and navigate to:**
```
http://127.0.0.1:5000
```

## 📁 Project Structure

```
recipe-assistant-bot/
├── app/
│ ├── __init__.py # Flask app factory
│ ├── routes.py # Application routes
│ └── chatbot.py # Core chatbot logic
├── static/
│ ├── css/
│ │ └── style.css # Custom styles
│ ├── js/
│ │ └── chat.js # Frontend JavaScript
│ └── images/ # Static images
├── templates/
│ └── index.html # Main HTML template
├── config.py # Configuration settings
├── run.py # Application runner
├── requirements.txt # Python dependencies
├── .env.example # Environment variables example
└── README.md # This file
```

## 🔧 Configuration

### Environment Variables

Create a `.env` file based on `.env.example`:

```bash
# Flask Configuration
FLASK_APP=run.py
FLASK_ENV=development
SECRET_KEY=your-secret-key-here

# API Keys (Optional)
SPOONACULAR_API_KEY=your-spoonacular-api-key
OPENAI_API_KEY=your-openai-api-key

# Server Configuration
HOST=127.0.0.1
PORT=5000
```

### Optional API Integration

For enhanced functionality, you can integrate with:

1. **Spoonacular API** (for real recipe data):
- Sign up at https://spoonacular.com/food-api
- Add your API key to `.env`

2. **OpenAI API** (for advanced AI responses):
- Sign up at https://openai.com/
- Add your API key to `.env`

## 📖 Usage

### Basic Chat

1. Open the web interface
2. Type your cooking-related question in the chat
3. Press Enter or click Send
4. Receive instant recipe suggestions, substitutions, or tips

### Example Queries

- "Give me a chicken recipe"
- "What can I substitute for eggs?"
- "Give me some baking tips"
- "Vegetarian pasta recipes"
- "How do I cook rice perfectly?"

### Quick Suggestions

Use the quick suggestion buttons for common queries:
- Chicken Recipe
- Egg Substitutes
- Baking Tips
- Vegetarian Options

## 🧪 Testing

Run the test suite:

```bash
# Install test dependencies
pip install pytest pytest-flask

# Run tests
pytest

# Run with coverage
pytest --cov=app
```

## 📚 API Endpoints

### Chat API
- **POST** `/api/chat` - Send message to chatbot
- **GET** `/api/recipes` - Get recipe suggestions
- **POST** `/api/substitutions` - Get ingredient substitutions
- **GET** `/api/tips` - Get cooking tips

### Request Examples

```javascript
// Chat endpoint
POST /api/chat
{
"message": "Give me a chicken recipe"
}

// Substitutions endpoint
POST /api/substitutions
{
"ingredient": "eggs"
}
```

## 🔒 Security Features

- CORS protection
- Input validation and sanitization
- Rate limiting (configurable)
- Secure session handling
- Environment variable configuration

## 🌟 Customization

### Adding New Recipes

Edit `app/chatbot.py` and add recipes to the `_load_recipes()` method:

```python
{
'name': 'Your Recipe Name',
'ingredients': ['ingredient1', 'ingredient2'],
'instructions': 'Step-by-step instructions',
'prep_time': '30 minutes',
'difficulty': 'easy',
'dietary': ['vegetarian']
}
```

### Adding New Substitutions

Edit the `_load_substitutions()` method:

```python
'your_ingredient': ['substitution1', 'substitution2', 'substitution3']
```

## 🚀 Deployment

### Production Deployment

1. **Set production environment:**
```bash
export FLASK_ENV=production
export FLASK_CONFIG=production
```

2. **Use a production server:**
```bash
gunicorn -w 4 -b 0.0.0.0:5000 run:app
```

3. **Set up reverse proxy (nginx recommended)**

### Docker Deployment

```dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "run:app"]
```

## 🤝 Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 📄 License

This project is created for educational purposes by RSK World. Content used for educational purposes only.

## 📞 Support

- **Website:** https://rskworld.in
- **Email:** help@rskworld.in
- **Phone:** +91 93305 39277
- **Address:** Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India - 713147

## 🙏 Acknowledgments

- **Founder:** Molla Samser
- **Designer & Tester:** Rima Khatun
- **RSK World** - Free Programming Resources & Source Code

---

**© 2026 RSK World. All rights reserved.**
*Educational Purpose Project*
.gitignore
Raw Download
Find: Go to:
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Virtual Environment
venv/
ENV/
env/
.venv

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store

# Environment variables
.env
.env.local

# Flask
instance/
.webassets-cache

# Logs
*.log
logs/
*.log.*

# Database
*.db
*.sqlite
*.sqlite3

# Uploads
uploads/
static/uploads/

# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# Temporary files
*.tmp
*.bak
*.swp

# OS
Thumbs.db
.DS_Store
80 lines•716 B
text
app/cooking_assistant.py
Raw Download
Find: Go to:
"""
Recipe Assistant Bot - AI Cooking Assistant
Author: RSK World (https://rskworld.in)
Founder: Molla Samser
Designer & Tester: Rima Khatun
Contact: help@rskworld.in, +91 93305 39277
Year: 2026
"""

import json
import time
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, asdict
from datetime import datetime, timedelta
from enum import Enum

class CookingStepType(Enum):
    """Types of cooking steps."""
    PREPARATION = "preparation"
    COOKING = "cooking"
    SEASONING = "seasoning"
    PLATING = "plating"
    RESTING = "resting"
    CLEANUP = "cleanup"

class DifficultyLevel(Enum):
    """Cooking difficulty levels."""
    BEGINNER = "beginner"
    INTERMEDIATE = "intermediate"
    ADVANCED = "advanced"
    EXPERT = "expert"

@dataclass
class CookingStep:
    """Individual cooking step."""
    id: str
    step_number: int
    title: str
    description: str
    step_type: CookingStepType
    duration_minutes: int
    temperature: Optional[float]
    equipment_needed: List[str]
    ingredients_needed: List[str]
    tips: List[str]
    warnings: List[str]
    completion_criteria: List[str]
    
    def to_dict(self) -> Dict:
        """Convert to dictionary."""
        result = asdict(self)
        result['step_type'] = self.step_type.value
        return result

@dataclass
class CookingSession:
    """Active cooking session with AI guidance."""
    id: str
    recipe_name: str
    user_id: str
    current_step: int
    total_steps: int
    start_time: str
    estimated_completion_time: str
    difficulty_level: DifficultyLevel
    skill_adjustments: Dict[str, Any]
    progress_percentage: float
    paused: bool
    completed_steps: List[str]
    notes: List[str]
    
    def to_dict(self) -> Dict:
        """Convert to dictionary."""
        result = asdict(self)
        result['difficulty_level'] = self.difficulty_level.value
        return result

class AICookingAssistant:
    """AI-powered cooking assistant with step-by-step guidance."""
    
    def __init__(self):
        """Initialize AI cooking assistant."""
        self.active_sessions = {}  # session_id -> CookingSession
        self.recipe_steps = {}  # recipe_name -> List[CookingStep]
        self.cooking_tips = self._load_cooking_tips()
        self.technique_guides = self._load_technique_guides()
        self.troubleshooting_guide = self._load_troubleshooting_guide()
        
        # Load detailed recipe steps
        self._load_recipe_steps()
    
    def _load_cooking_tips(self) -> Dict[str, List[str]]:
        """Load cooking tips by category."""
        return {
            'knife_skills': [
                "Keep your knives sharp for safer, more efficient cutting",
                "Use the proper knife for each task - chef's knife for general work, paring knife for detail work",
                "Practice the claw grip to protect your fingers while cutting",
                "Cut on a stable surface and keep your cutting board from sliding"
            ],
            'temperature_control': [
                "Let meat come to room temperature before cooking for even results",
                "Preheat your pan properly to prevent sticking",
                "Use a thermometer to ensure accurate cooking temperatures",
                "Allow cooked meat to rest before cutting to retain juices"
            ],
            'seasoning': [
                "Season in layers - salt at each stage of cooking",
                "Taste and adjust seasoning throughout the cooking process",
                "Use fresh herbs at the end to preserve their flavor",
                "Remember that salt enhances flavor, other spices add character"
            ],
            'timing': [
                "Read the entire recipe before starting to plan your timing",
                "Prep all ingredients before you begin cooking (mise en place)",
                "Clean as you go to maintain an organized workspace",
                "Set timers for each cooking step to prevent overcooking"
            ],
            'safety': [
                "Always keep a fire extinguisher in the kitchen",
                "Turn pot handles inward to prevent accidental spills",
                "Use dry oven mitts to handle hot pans",
                "Never leave cooking food unattended"
            ]
        }
    
    def _load_technique_guides(self) -> Dict[str, Dict]:
        """Load detailed cooking technique guides."""
        return {
            'sautéing': {
                'description': 'Cooking quickly in a small amount of fat over relatively high heat',
                'key_points': [
                    'Use a wide, shallow pan for best results',
                    'Don\'t overcrowd the pan - cook in batches if needed',
                    'Keep the food moving to ensure even cooking',
                    'Listen for the sizzle to know the pan is hot enough'
                ],
                'common_mistakes': [
                    'Pan not hot enough - food steams instead of browns',
                    'Overcrowding the pan lowers the temperature',
                    'Adding cold food to hot oil causes splattering'
                ]
            },
            'braising': {
                'description': 'Cooking food slowly in liquid after browning',
                'key_points': [
                    'Brown the meat first for better flavor',
                    'Use enough liquid to come about halfway up the food',
                    'Keep the liquid at a gentle simmer, not boiling',
                    'Cover the pot to retain moisture'
                ],
                'common_mistakes': [
                    'Not browning the meat first',
                    'Using too much liquid',
                    'Cooking at too high temperature'
                ]
            },
            'roasting': {
                'description': 'Cooking with dry heat in an oven',
                'key_points': [
                    'Preheat the oven thoroughly',
                    'Use a roasting rack for air circulation',
                    'Rotate the pan halfway through cooking',
                    'Let meat rest before carving'
                ],
                'common_mistakes': [
                    'Oven not fully preheated',
                    'Overcrowding the pan',
                    'Not using a meat thermometer'
                ]
            }
        }
    
    def _load_troubleshooting_guide(self) -> Dict[str, Dict]:
        """Load troubleshooting guide for common cooking problems."""
        return {
            'meat_tough': {
                'symptoms': ['Meat is hard to chew', 'Dry texture', 'Difficult to cut'],
                'causes': ['Overcooked', 'Wrong cut of meat', 'Not enough fat', 'Cooked at too high temperature'],
                'solutions': [
                    'Use a meat thermometer to avoid overcooking',
                    'Choose appropriate cuts for your cooking method',
                    'Consider marinating tougher cuts',
                    'Cook at lower temperatures for longer periods'
                ]
            },
            'sauce_broken': {
                'symptoms': ['Oil separated', 'Curled appearance', 'Grainy texture'],
                'causes': ['Temperature too high', 'Added ingredients too quickly', 'Too much fat', 'Not enough emulsifier'],
                'solutions': [
                    'Remove from heat and whisk vigorously',
                    'Add a small amount of mustard or egg yolk',
                    'Gradually add liquid while whisking',
                    'Keep sauce warm but not hot'
                ]
            },
            'vegetables_mushy': {
                'symptoms': ['Soft texture', 'Loss of color', 'Mushy consistency'],
                'causes': ['Overcooked', 'Cooked too long', 'Too much water', 'Cut too small'],
                'solutions': [
                    'Cook vegetables briefly (blanch or steam)',
                    'Use ice bath to stop cooking process',
                    'Cut vegetables uniformly for even cooking',
                    'Don\'t overcrowd the pan'
                ]
            }
        }
    
    def _load_recipe_steps(self):
        """Load detailed steps for common recipes."""
        self.recipe_steps = {
            'Spaghetti Carbonara': [
                CookingStep(
                    id="step_1",
                    step_number=1,
                    title="Prepare Ingredients",
                    description="Gather and prepare all ingredients. Cut pancetta into small cubes, grate Parmesan cheese, and crack eggs into a bowl.",
                    step_type=CookingStepType.PREPARATION,
                    duration_minutes=10,
                    temperature=None,
                    equipment_needed=["cutting board", "knife", "grater", "mixing bowl"],
                    ingredients_needed=["spaghetti", "eggs", "pancetta", "parmesan", "black pepper"],
                    tips=["Use room temperature eggs for better mixing", "Grate cheese finely for smooth sauce"],
                    warnings=["Keep eggs separate until ready to use"],
                    completion_criteria=["All ingredients prepped and ready", "Water boiling for pasta"]
                ),
                CookingStep(
                    id="step_2",
                    step_number=2,
                    title="Cook Pasta",
                    description="Add spaghetti to boiling salted water and cook according to package directions until al dente.",
                    step_type=CookingStepType.COOKING,
                    duration_minutes=8,
                    temperature=212,
                    equipment_needed=["large pot", "colander"],
                    ingredients_needed=["spaghetti", "salt"],
                    tips=["Save pasta water for sauce", "Stir occasionally to prevent sticking"],
                    warnings=["Water will be hot - handle carefully"],
                    completion_criteria=["Pasta is al dente", "Pasta water reserved"]
                ),
                CookingStep(
                    id="step_3",
                    step_number=3,
                    title="Cook Pancetta",
                    description="Cook pancetta in a large skillet until crisp and golden brown.",
                    step_type=CookingStepType.COOKING,
                    duration_minutes=5,
                    temperature=350,
                    equipment_needed=["large skillet", "spatula"],
                    ingredients_needed=["pancetta"],
                    tips=["Don't overcrowd the pan", "Render fat slowly for best flavor"],
                    warnings=["Fat will be hot", "Don't burn the pancetta"],
                    completion_criteria=["Pancetta is crispy", "Fat is rendered"]
                ),
                CookingStep(
                    id="step_4",
                    step_number=4,
                    title="Mix Sauce",
                    description="Beat eggs with grated Parmesan and black pepper. Add hot pasta and toss quickly.",
                    step_type=CookingStepType.SEASONING,
                    duration_minutes=2,
                    temperature=None,
                    equipment_needed=["mixing bowl", "whisk", "tongs"],
                    ingredients_needed=["eggs", "parmesan", "black pepper", "cooked pasta"],
                    tips=["Work quickly to avoid scrambling eggs", "Use pasta water to adjust consistency"],
                    warnings=["Eggs should not be fully cooked", "Remove from heat if needed"],
                    completion_criteria=["Sauce is creamy", "Pasta is well coated"]
                )
            ],
            'Chicken Stir Fry': [
                CookingStep(
                    id="step_1",
                    step_number=1,
                    title="Prepare Ingredients",
                    description="Cut chicken into bite-sized pieces and chop vegetables. Mix sauce ingredients.",
                    step_type=CookingStepType.PREPARATION,
                    duration_minutes=15,
                    temperature=None,
                    equipment_needed=["cutting board", "knife", "mixing bowls"],
                    ingredients_needed=["chicken", "vegetables", "soy sauce", "garlic", "ginger"],
                    tips=["Cut vegetables uniformly for even cooking", "Pat chicken dry for better browning"],
                    warnings=["Keep raw chicken separate from vegetables"],
                    completion_criteria=["All ingredients prepped", "Sauce mixed and ready"]
                ),
                CookingStep(
                    id="step_2",
                    step_number=2,
                    title="Cook Chicken",
                    description="Heat oil in wok or large skillet and cook chicken until golden and cooked through.",
                    step_type=CookingStepType.COOKING,
                    duration_minutes=6,
                    temperature=375,
                    equipment_needed=["wok or skillet", "spatula"],
                    ingredients_needed=["chicken", "oil"],
                    tips=["Don't overcrowd the pan", "Cook in batches if needed"],
                    warnings=["Oil will be very hot", "Chicken must reach 165°F internal temperature"],
                    completion_criteria=["Chicken is golden brown", "Internal temperature is 165°F"]
                ),
                CookingStep(
                    id="step_3",
                    step_number=3,
                    title="Stir Fry Vegetables",
                    description="Add vegetables to hot wok and stir-fry until crisp-tender.",
                    step_type=CookingStepType.COOKING,
                    duration_minutes=4,
                    temperature=400,
                    equipment_needed=["wok", "spatula"],
                    ingredients_needed=["vegetables", "garlic", "ginger"],
                    tips=["Keep vegetables moving", "Add harder vegetables first"],
                    warnings=["Vegetables cook quickly", "Don't overcook"],
                    completion_criteria=["Vegetables are crisp-tender", "Aromatics are fragrant"]
                ),
                CookingStep(
                    id="step_4",
                    step_number=4,
                    title="Combine and Sauce",
                    description="Return chicken to wok, add sauce, and toss until everything is coated.",
                    step_type=CookingStepType.SEASONING,
                    duration_minutes=2,
                    temperature=None,
                    equipment_needed=["wok", "spatula"],
                    ingredients_needed=["cooked chicken", "cooked vegetables", "sauce"],
                    tips=["Toss quickly to combine", "Taste and adjust seasoning"],
                    warnings=["Sauce will thicken quickly", "Be careful of steam"],
                    completion_criteria=["Everything is well coated", "Sauce is thickened"]
                )
            ]
        }
    
    def start_cooking_session(self, recipe_name: str, user_id: str, 
                            skill_level: DifficultyLevel = DifficultyLevel.BEGINNER,
                            dietary_preferences: List[str] = None) -> Dict:
        """Start a new cooking session with AI guidance."""
        if recipe_name not in self.recipe_steps:
            return {'error': 'Recipe steps not available'}
        
        session_id = f"cooking_session_{len(self.active_sessions) + 1}"
        steps = self.recipe_steps[recipe_name]
        
        # Calculate total time and difficulty adjustments
        total_time = sum(step.duration_minutes for step in steps)
        skill_adjustments = self._calculate_skill_adjustments(skill_level, steps)
        
        session = CookingSession(
            id=session_id,
            recipe_name=recipe_name,
            user_id=user_id,
            current_step=1,
            total_steps=len(steps),
            start_time=datetime.now().isoformat(),
            estimated_completion_time=(
                datetime.now() + timedelta(minutes=total_time)
            ).isoformat(),
            difficulty_level=skill_level,
            skill_adjustments=skill_adjustments,
            progress_percentage=0.0,
            paused=False,
            completed_steps=[],
            notes=[]
        )
        
        self.active_sessions[session_id] = session
        
        return {
            'session_id': session_id,
            'current_step': self.get_current_step(session_id),
            'total_steps': len(steps),
            'estimated_time': total_time,
            'skill_adjustments': skill_adjustments,
            'message': f'Cooking session started for {recipe_name}'
        }
    
    def get_current_step(self, session_id: str) -> Optional[CookingStep]:
        """Get current cooking step for a session."""
        if session_id not in self.active_sessions:
            return None
        
        session = self.active_sessions[session_id]
        recipe_name = session.recipe_name
        steps = self.recipe_steps[recipe_name]
        
        if session.current_step <= len(steps):
            return steps[session.current_step - 1]
        
        return None
    
    def advance_to_next_step(self, session_id: str, notes: str = None) -> Dict:
        """Advance to the next cooking step."""
        if session_id not in self.active_sessions:
            return {'error': 'Session not found'}
        
        session = self.active_sessions[session_id]
        current_step = self.get_current_step(session_id)
        
        if not current_step:
            return {'error': 'No more steps available'}
        
        # Mark current step as completed
        session.completed_steps.append(current_step.id)
        session.current_step += 1
        
        # Update progress
        session.progress_percentage = (session.current_step - 1) / session.total_steps * 100
        
        # Add notes if provided
        if notes:
            session.notes.append({
                'step': current_step.id,
                'note': notes,
                'timestamp': datetime.now().isoformat()
            })
        
        next_step = self.get_current_step(session_id)
        
        if next_step:
            return {
                'session_id': session_id,
                'previous_step': current_step.to_dict(),
                'current_step': next_step.to_dict(),
                'progress': session.progress_percentage,
                'message': f'Moved to step {session.current_step}: {next_step.title}'
            }
        else:
            # Cooking completed
            session.progress_percentage = 100.0
            return {
                'session_id': session_id,
                'completed': True,
                'final_step': current_step.to_dict(),
                'total_time': self._calculate_session_time(session),
                'message': 'Cooking session completed successfully!'
            }
    
    def get_step_guidance(self, session_id: str, guidance_type: str = 'detailed') -> Dict:
        """Get detailed guidance for current step."""
        if session_id not in self.active_sessions:
            return {'error': 'Session not found'}
        
        current_step = self.get_current_step(session_id)
        if not current_step:
            return {'error': 'No current step'}
        
        guidance = {
            'step': current_step.to_dict(),
            'basic_tips': current_step.tips,
            'warnings': current_step.warnings
        }
        
        if guidance_type == 'detailed':
            # Add technique-specific guidance
            technique_tips = self._get_technique_tips(current_step)
            guidance['technique_guide'] = technique_tips
            
            # Add skill-level adjustments
            session = self.active_sessions[session_id]
            skill_guidance = self._get_skill_level_guidance(
                current_step, session.difficulty_level
            )
            guidance['skill_guidance'] = skill_guidance
        
        elif guidance_type == 'troubleshooting':
            # Add troubleshooting tips
            troubleshooting = self._get_troubleshooting_tips(current_step)
            guidance['troubleshooting'] = troubleshooting
        
        return guidance
    
    def pause_session(self, session_id: str, reason: str = None) -> Dict:
        """Pause a cooking session."""
        if session_id not in self.active_sessions:
            return {'error': 'Session not found'}
        
        session = self.active_sessions[session_id]
        session.paused = True
        
        return {
            'session_id': session_id,
            'paused': True,
            'reason': reason,
            'message': 'Cooking session paused'
        }
    
    def resume_session(self, session_id: str) -> Dict:
        """Resume a paused cooking session."""
        if session_id not in self.active_sessions:
            return {'error': 'Session not found'}
        
        session = self.active_sessions[session_id]
        session.paused = False
        
        current_step = self.get_current_step(session_id)
        
        return {
            'session_id': session_id,
            'resumed': True,
            'current_step': current_step.to_dict() if current_step else None,
            'message': 'Cooking session resumed'
        }
    
    def _calculate_skill_adjustments(self, skill_level: DifficultyLevel, 
                                 steps: List[CookingStep]) -> Dict[str, Any]:
        """Calculate time and complexity adjustments based on skill level."""
        adjustments = {
            'time_multiplier': 1.0,
            'additional_tips': [],
            'simplified_steps': False,
            'extra_warnings': []
        }
        
        if skill_level == DifficultyLevel.BEGINNER:
            adjustments['time_multiplier'] = 1.5
            adjustments['additional_tips'] = [
                "Take your time with each step",
                "Don't worry about perfection - practice makes perfect",
                "Read each step completely before starting"
            ]
            adjustments['extra_warnings'] = [
                "Be careful with hot surfaces",
                "Ask for help if unsure about any step"
            ]
        
        elif skill_level == DifficultyLevel.INTERMEDIATE:
            adjustments['time_multiplier'] = 1.2
            adjustments['additional_tips'] = [
                "Focus on technique and timing",
                "Trust your instincts on seasoning"
            ]
        
        elif skill_level == DifficultyLevel.ADVANCED:
            adjustments['time_multiplier'] = 1.0
            adjustments['additional_tips'] = [
                "Experiment with flavors and techniques",
                "Consider presentation and garnishes"
            ]
        
        elif skill_level == DifficultyLevel.EXPERT:
            adjustments['time_multiplier'] = 0.8
            adjustments['additional_tips'] = [
                "Focus on precision and refinement",
                "Consider creative variations"
            ]
        
        return adjustments
    
    def _get_technique_tips(self, step: CookingStep) -> Dict:
        """Get technique-specific tips for a cooking step."""
        # Analyze step description to determine technique
        description = step.description.lower()
        
        if 'sauté' in description or 'fry' in description:
            return self.technique_guides.get('sautéing', {})
        elif 'braise' in description:
            return self.technique_guides.get('braising', {})
        elif 'roast' in description or 'oven' in description:
            return self.technique_guides.get('roasting', {})
        
        return {}
    
    def _get_skill_level_guidance(self, step: CookingStep, 
                               skill_level: DifficultyLevel) -> Dict:
        """Get skill-level specific guidance."""
        guidance = {
            'beginner_focus': [],
            'intermediate_tips': [],
            'advanced_techniques': []
        }
        
        if skill_level == DifficultyLevel.BEGINNER:
            guidance['beginner_focus'] = [
                "Focus on safety and basic techniques",
                "Don't rush through steps",
                "Ask questions if anything is unclear"
            ]
        elif skill_level == DifficultyLevel.INTERMEDIATE:
            guidance['intermediate_tips'] = [
                "Work on timing and multitasking",
                "Practice knife skills",
                "Learn to adjust seasoning by taste"
            ]
        elif skill_level in [DifficultyLevel.ADVANCED, DifficultyLevel.EXPERT]:
            guidance['advanced_techniques'] = [
                "Focus on precision and consistency",
                "Experiment with flavor combinations",
                "Consider presentation and plating"
            ]
        
        return guidance
    
    def _get_troubleshooting_tips(self, step: CookingStep) -> List[Dict]:
        """Get troubleshooting tips for current step."""
        # Analyze step to identify potential issues
        tips = []
        
        if 'meat' in step.description.lower() or 'chicken' in step.description.lower():
            tips.append(self.troubleshooting_guide['meat_tough'])
        
        if 'sauce' in step.description.lower():
            tips.append(self.troubleshooting_guide['sauce_broken'])
        
        if 'vegetable' in step.description.lower():
            tips.append(self.troubleshooting_guide['vegetables_mushy'])
        
        return tips
    
    def _calculate_session_time(self, session: CookingSession) -> Dict:
        """Calculate time spent in cooking session."""
        start_time = datetime.fromisoformat(session.start_time)
        current_time = datetime.now()
        
        total_time = current_time - start_time
        
        return {
            'total_minutes': int(total_time.total_seconds() / 60),
            'formatted_time': str(total_time).split('.')[0],
            'estimated_vs_actual': {
                'estimated': session.estimated_completion_time,
                'actual': current_time.isoformat()
            }
        }
    
    def get_session_summary(self, session_id: str) -> Dict:
        """Get complete summary of a cooking session."""
        if session_id not in self.active_sessions:
            return {'error': 'Session not found'}
        
        session = self.active_sessions[session_id]
        time_info = self._calculate_session_time(session)
        
        return {
            'session': session.to_dict(),
            'time_info': time_info,
            'completed_steps': len(session.completed_steps),
            'total_steps': session.total_steps,
            'notes': session.notes,
            'achievements': self._calculate_achievements(session)
        }
    
    def _calculate_achievements(self, session: CookingSession) -> List[str]:
        """Calculate achievements earned during cooking session."""
        achievements = []
        
        if session.progress_percentage == 100:
            achievements.append("Recipe Master - Completed full recipe")
        
        if len(session.notes) == 0:
            achievements.append("Confident Cook - No notes needed")
        
        if session.difficulty_level in [DifficultyLevel.ADVANCED, DifficultyLevel.EXPERT]:
            achievements.append("Brave Chef - Attempted advanced recipe")
        
        time_info = self._calculate_session_time(session)
        if time_info['total_minutes'] < 30:
            achievements.append("Speed Cooker - Finished in under 30 minutes")
        
        return achievements
656 lines•27.9 KB
python
app/routes.py
Raw Download
Find: Go to:
"""
Recipe Assistant Bot - Routes
Author: RSK World (https://rskworld.in)
Founder: Molla Samser
Designer & Tester: Rima Khatun
Contact: help@rskworld.in, +91 93305 39277
Year: 2026
"""

from flask import Blueprint, request, jsonify, render_template, session
from werkzeug.utils import secure_filename
from .chatbot import RecipeChatbot
from .auth import AuthManager
from .image_recognition import ImageRecognition
from .shopping_nutrition import ShoppingListManager
from .rating_review import RatingReviewManager
from .voice_assistant import VoiceAssistant
from .smart_kitchen import SmartKitchenManager
from .cooking_assistant import AICookingAssistant
from .advanced_features import AdvancedFeaturesManager, ChallengeType, IngredientInventory
import json
import os
import datetime

main_bp = Blueprint('main', __name__)
chatbot = RecipeChatbot()
auth_manager = AuthManager()
image_recognition = ImageRecognition()
shopping_manager = ShoppingListManager()
rating_manager = RatingReviewManager()
voice_assistant = VoiceAssistant()
smart_kitchen = SmartKitchenManager()
cooking_assistant = AICookingAssistant()
advanced_features = AdvancedFeaturesManager()

@main_bp.route('/')
def index():
    """Render the main chat interface."""
    return render_template('index.html')

@main_bp.route('/api/chat', methods=['POST'])
def chat():
    """Handle chat requests."""
    try:
        data = request.get_json()
        user_message = data.get('message', '').strip()
        
        if not user_message:
            return jsonify({'error': 'Message cannot be empty'}), 400
        
        # Get chatbot response
        response = chatbot.get_response(user_message)
        
        return jsonify({
            'response': response,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/recipes', methods=['GET'])
def get_recipes():
    """Get recipe suggestions based on query parameters."""
    try:
        query = request.args.get('query', '')
        dietary = request.args.get('dietary', '')
        
        recipes = chatbot.get_recipe_suggestions(query, dietary)
        
        return jsonify({
            'recipes': recipes,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/substitutions', methods=['POST'])
def get_substitutions():
    """Get ingredient substitutions."""
    try:
        data = request.get_json()
        ingredient = data.get('ingredient', '').strip()
        
        if not ingredient:
            return jsonify({'error': 'Ingredient cannot be empty'}), 400
        
        substitutions = chatbot.get_ingredient_substitutions(ingredient)
        
        return jsonify({
            'substitutions': substitutions,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/tips', methods=['GET'])
def get_cooking_tips():
    """Get cooking tips based on category."""
    try:
        category = request.args.get('category', 'general')
        tips = chatbot.get_cooking_tips(category)
        
        return jsonify({
            'tips': tips,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/meal-plan', methods=['POST'])
def create_meal_plan():
    """Create a meal plan."""
    try:
        data = request.get_json()
        days = data.get('days', 7)
        dietary = data.get('dietary', '')
        
        meal_plan = chatbot.create_meal_plan(days, dietary)
        
        return jsonify({
            'meal_plan': meal_plan,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/nutrition/<recipe_name>', methods=['GET'])
def get_nutrition(recipe_name):
    """Get nutritional information for a recipe."""
    try:
        nutrition = chatbot.calculate_nutrition(recipe_name)
        
        if 'error' in nutrition:
            return jsonify({
                'error': nutrition['error'],
                'status': 'error'
            }), 404
        
        return jsonify({
            'nutrition': nutrition,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cost/<recipe_name>', methods=['GET'])
def get_recipe_cost(recipe_name):
    """Get cost information for a recipe."""
    try:
        cost = chatbot.calculate_recipe_cost(recipe_name)
        
        if 'error' in cost:
            return jsonify({
                'error': cost['error'],
                'status': 'error'
            }), 404
        
        return jsonify({
            'cost': cost,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/shopping-list', methods=['POST'])
def generate_shopping_list():
    """Generate shopping list from recipes."""
    try:
        data = request.get_json()
        recipes = data.get('recipes', [])
        
        if not recipes:
            return jsonify({'error': 'Recipes list cannot be empty'}), 400
        
        shopping_list = chatbot.generate_shopping_list(recipes)
        
        return jsonify({
            'shopping_list': shopping_list,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/favorites', methods=['GET', 'POST'])
def manage_favorites():
    """Get or add favorite recipes."""
    try:
        if request.method == 'GET':
            favorites = chatbot.get_favorites()
            return jsonify({
                'favorites': favorites,
                'status': 'success'
            })
        
        elif request.method == 'POST':
            data = request.get_json()
            recipe_name = data.get('recipe_name', '').strip()
            
            if not recipe_name:
                return jsonify({'error': 'Recipe name cannot be empty'}), 400
            
            result = chatbot.add_to_favorites(recipe_name)
            
            return jsonify({
                'result': result,
                'status': 'success'
            })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/rate', methods=['POST'])
def rate_recipe():
    """Rate a recipe."""
    try:
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        rating = data.get('rating', 0)
        review = data.get('review', '')
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name cannot be empty'}), 400
        
        if not isinstance(rating, int) or rating < 1 or rating > 5:
            return jsonify({'error': 'Rating must be an integer between 1 and 5'}), 400
        
        result = chatbot.rate_recipe(recipe_name, rating, review)
        
        return jsonify({
            'result': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/search', methods=['POST'])
def search_recipes():
    """Search recipes with filters."""
    try:
        data = request.get_json()
        query = data.get('query', '').strip()
        filters = data.get('filters', {})
        
        if not query:
            return jsonify({'error': 'Search query cannot be empty'}), 400
        
        results = chatbot.search_recipes(query, filters)
        
        return jsonify({
            'results': results,
            'total': len(results),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/share/<recipe_name>', methods=['GET'])
def share_recipe(recipe_name):
    """Get shareable recipe information."""
    try:
        recipe = next((r for r in chatbot.recipes if r['name'].lower() == recipe_name.lower()), None)
        
        if not recipe:
            return jsonify({
                'error': 'Recipe not found',
                'status': 'error'
            }), 404
        
        # Create shareable content
        share_content = {
            'recipe': recipe,
            'share_url': f'{request.host_url}recipe/{recipe_name.lower().replace(" ", "-")}',
            'share_text': f"Check out this amazing {recipe['name']} recipe!",
            'nutrition': chatbot.calculate_nutrition(recipe_name),
            'cost': chatbot.calculate_recipe_cost(recipe_name)
        }
        
        return jsonify({
            'share_content': share_content,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'An error occurred: {str(e)}',
            'status': 'error'
        }), 500

# Authentication Routes

@main_bp.route('/api/auth/register', methods=['POST'])
def register():
    """Register a new user."""
    try:
        data = request.get_json()
        username = data.get('username', '').strip()
        email = data.get('email', '').strip()
        password = data.get('password', '')
        preferences = data.get('preferences', {})
        
        if not username or not email or not password:
            return jsonify({'error': 'Username, email, and password are required'}), 400
        
        result = auth_manager.register_user(username, email, password, preferences)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result), 201
    
    except Exception as e:
        return jsonify({
            'error': f'Registration failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/auth/login', methods=['POST'])
def login():
    """Authenticate user."""
    try:
        data = request.get_json()
        username = data.get('username', '').strip()
        password = data.get('password', '')
        
        if not username or not password:
            return jsonify({'error': 'Username and password are required'}), 400
        
        result = auth_manager.login_user(username, password)
        
        if 'error' in result:
            return jsonify(result), 401
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Login failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/auth/logout', methods=['POST'])
def logout():
    """Logout user."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        
        if not session_token:
            return jsonify({'error': 'Session token required'}), 400
        
        result = auth_manager.logout_user(session_token)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Logout failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/auth/profile', methods=['GET', 'PUT'])
def profile():
    """Get or update user profile."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        
        if not session_token:
            return jsonify({'error': 'Session token required'}), 400
        
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Invalid or expired session'}), 401
        
        if request.method == 'GET':
            return jsonify({
                'user': user.to_dict(),
                'status': 'success'
            })
        
        elif request.method == 'PUT':
            data = request.get_json()
            result = auth_manager.update_user_profile(user.id, data)
            
            if 'error' in result:
                return jsonify(result), 400
            
            return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Profile operation failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/auth/preferences', methods=['GET'])
def get_preferences():
    """Get user preferences."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        
        if not session_token:
            return jsonify({'error': 'Session token required'}), 400
        
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Invalid or expired session'}), 401
        
        preferences = auth_manager.get_user_preferences(user.id)
        
        return jsonify({
            'preferences': preferences,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get preferences: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/auth/favorites', methods=['POST', 'DELETE'])
def manage_auth_favorites():
    """Add or remove favorite recipes."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        
        if not session_token:
            return jsonify({'error': 'Session token required'}), 400
        
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Invalid or expired session'}), 401
        
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        if request.method == 'POST':
            result = auth_manager.add_favorite_recipe(user.id, recipe_name)
        else:  # DELETE
            result = auth_manager.remove_favorite_recipe(user.id, recipe_name)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Favorites operation failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/auth/stats', methods=['GET'])
def get_user_stats():
    """Get user statistics."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        
        if not session_token:
            return jsonify({'error': 'Session token required'}), 400
        
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Invalid or expired session'}), 401
        
        stats = auth_manager.get_user_stats(user.id)
        
        return jsonify({
            'stats': stats,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get user stats: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/personalized-meal-plan', methods=['POST'])
def personalized_meal_plan():
    """Generate personalized meal plan based on user preferences."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        
        user = None
        if session_token:
            user = auth_manager.get_user_by_session(session_token)
        
        data = request.get_json()
        days = data.get('days', 7)
        
        # Get user preferences if authenticated
        preferences = {}
        if user:
            preferences = {
                'dietary': user.dietary_restrictions,
                'cuisine': user.cuisine_preferences,
                'difficulty': user.skill_level,
                'allergies': user.allergies,
                'family_size': user.family_size,
                'budget': user.budget_range
            }
        
        # Override with request data if provided
        preferences.update(data.get('preferences', {}))
        
        meal_plan = chatbot.get_personalized_meal_plan(
            days=days,
            dietary=preferences.get('dietary', ''),
            preferences=preferences
        )
        
        return jsonify({
            'meal_plan': meal_plan,
            'personalized': user is not None,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to generate meal plan: {str(e)}',
            'status': 'error'
        }), 500

# Image Upload and Recognition Routes

@main_bp.route('/api/upload-image', methods=['POST'])
def upload_image():
    """Handle image upload for recipe recognition."""
    try:
        # Check if file is in request
        if 'image' not in request.files:
            return jsonify({'error': 'No image file provided'}), 400
        
        file = request.files['image']
        
        if file.filename == '':
            return jsonify({'error': 'No file selected'}), 400
        
        # Save uploaded file
        success, filepath, result = image_recognition.save_uploaded_file(file)
        
        if not success:
            return jsonify(result), 400
        
        # Analyze image for recipe recognition
        analysis = image_recognition.generate_recipe_from_image(filepath)
        
        return jsonify({
            'file_info': {
                'filename': os.path.basename(filepath),
                'size': os.path.getsize(filepath),
                'path': filepath
            },
            'analysis': analysis,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Image upload failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/analyze-image', methods=['POST'])
def analyze_image():
    """Analyze uploaded image for ingredients."""
    try:
        data = request.get_json()
        image_path = data.get('image_path', '')
        
        if not image_path or not os.path.exists(image_path):
            return jsonify({'error': 'Invalid image path'}), 400
        
        # Analyze image
        analysis = image_recognition.analyze_image(image_path)
        
        return jsonify({
            'analysis': analysis,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Image analysis failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/recognize-recipe', methods=['POST'])
def recognize_recipe():
    """Recognize recipe from detected ingredients."""
    try:
        data = request.get_json()
        ingredients = data.get('ingredients', [])
        
        if not ingredients:
            return jsonify({'error': 'No ingredients provided'}), 400
        
        # Recognize recipe
        recognition = image_recognition.recognize_recipe(ingredients)
        
        return jsonify({
            'recognition': recognition,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Recipe recognition failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/image-to-recipe', methods=['POST'])
def image_to_recipe():
    """Complete image to recipe conversion."""
    try:
        # Get user session for personalization
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = None
        if session_token:
            user = auth_manager.get_user_by_session(session_token)
        
        # Handle file upload or base64 image
        if 'image' in request.files:
            file = request.files['image']
            success, filepath, result = image_recognition.save_uploaded_file(file)
            
            if not success:
                return jsonify(result), 400
        
        elif request.is_json:
            data = request.get_json()
            image_data = data.get('image_data', '')  # Base64 encoded image
            
            if image_data:
                # Decode base64 and save
                import base64
                image_bytes = base64.b64decode(image_data.split(',')[1])
                filename = f"upload_{os.urandom(8).hex()}.jpg"
                filepath = os.path.join(image_recognition.upload_folder, filename)
                
                with open(filepath, 'wb') as f:
                    f.write(image_bytes)
            else:
                return jsonify({'error': 'No image data provided'}), 400
        else:
            return jsonify({'error': 'No image provided'}), 400
        
        # Generate complete recipe analysis
        result = image_recognition.generate_recipe_from_image(filepath)
        
        # Get user preferences for personalization
        user_preferences = {}
        if user:
            user_preferences = {
                'dietary_restrictions': user.dietary_restrictions,
                'allergies': user.allergies,
                'skill_level': user.skill_level,
                'cuisine_preferences': user.cuisine_preferences
            }
        
        # Add personalized suggestions
        if result.get('suggestions'):
            for suggestion in result['suggestions']:
                # Filter suggestions based on user preferences
                if user_preferences.get('dietary_restrictions'):
                    # Simple filtering - in real app, would be more sophisticated
                    pass
        
        return jsonify({
            'result': result,
            'user_preferences': user_preferences,
            'personalized': user is not None,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Image to recipe conversion failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/recipe-from-ingredients', methods=['POST'])
def recipe_from_ingredients():
    """Generate recipe suggestions from ingredient list."""
    try:
        data = request.get_json()
        ingredients = data.get('ingredients', [])
        preferences = data.get('preferences', {})
        
        if not ingredients:
            return jsonify({'error': 'No ingredients provided'}), 400
        
        # Get matching recipes
        matching_recipes = []
        for recipe in chatbot.recipes:
            match_score = 0
            matched_ingredients = []
            
            for ingredient in ingredients:
                for recipe_ingredient in recipe['ingredients']:
                    if ingredient.lower() in recipe_ingredient.lower():
                        match_score += 1
                        matched_ingredients.append(ingredient)
                        break
            
            if match_score > 0:
                recipe_copy = recipe.copy()
                recipe_copy['match_score'] = match_score
                recipe_copy['matched_ingredients'] = matched_ingredients
                recipe_copy['match_percentage'] = (match_score / len(recipe['ingredients'])) * 100
                matching_recipes.append(recipe_copy)
        
        # Sort by match score
        matching_recipes.sort(key=lambda x: x['match_score'], reverse=True)
        
        # Apply preferences
        if preferences.get('dietary'):
            matching_recipes = [
                r for r in matching_recipes 
                if preferences['dietary'] in r.get('dietary', [])
            ]
        
        return jsonify({
            'matching_recipes': matching_recipes[:10],  # Top 10 matches
            'total_matches': len(matching_recipes),
            'input_ingredients': ingredients,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Recipe generation failed: {str(e)}',
            'status': 'error'
        }), 500

# Shopping List and Nutrition Routes

@main_bp.route('/api/shopping-list/create', methods=['POST'])
def create_shopping_list():
    """Create shopping list from recipes."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipes = data.get('recipes', [])
        preferences = data.get('preferences', {})
        
        if not recipes:
            return jsonify({'error': 'No recipes provided'}), 400
        
        # Get user preferences
        user_preferences = {
            'dietary_restrictions': user.dietary_restrictions,
            'allergies': user.allergies,
            'budget_range': user.budget_range,
            'preferred_ingredients': user.cuisine_preferences
        }
        user_preferences.update(preferences)
        
        # Create shopping list
        shopping_list = shopping_manager.create_shopping_list(
            user.id, recipes, user_preferences
        )
        
        return jsonify({
            'shopping_list': shopping_list,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to create shopping list: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/shopping-list', methods=['GET'])
def get_shopping_list():
    """Get user's shopping list."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        shopping_list = shopping_manager.get_shopping_list(user.id)
        
        if not shopping_list:
            return jsonify({'error': 'No shopping list found'}), 404
        
        return jsonify({
            'shopping_list': shopping_list,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get shopping list: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/shopping-list/update', methods=['PUT'])
def update_shopping_list():
    """Update shopping list."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        updates = data.get('updates', {})
        
        if not updates:
            return jsonify({'error': 'No updates provided'}), 400
        
        shopping_list = shopping_manager.update_shopping_list(user.id, updates)
        
        return jsonify({
            'shopping_list': shopping_list,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to update shopping list: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/shopping-list/compare-prices', methods=['GET'])
def compare_store_prices():
    """Compare prices across stores."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        comparison = shopping_manager.compare_store_prices(user.id)
        
        return jsonify({
            'price_comparison': comparison,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to compare prices: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/nutrition/goals', methods=['POST', 'GET'])
def manage_nutrition_goals():
    """Set or get nutrition goals."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        if request.method == 'POST':
            data = request.get_json()
            goals = data.get('goals', {})
            
            if not goals:
                return jsonify({'error': 'No goals provided'}), 400
            
            result = shopping_manager.set_nutrition_goals(user.id, goals)
            
            return jsonify({
                'result': result,
                'status': 'success'
            })
        
        else:  # GET
            # Get current goals (would need to implement this in manager)
            return jsonify({
                'message': 'Nutrition goals retrieval not implemented yet',
                'status': 'pending'
            })
    
    except Exception as e:
        return jsonify({
            'error': f'Nutrition goals operation failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/nutrition/track', methods=['POST'])
def track_daily_nutrition():
    """Track daily nutrition intake."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        meals = data.get('meals', [])
        
        if not meals:
            return jsonify({'error': 'No meals provided'}), 400
        
        tracking = shopping_manager.track_daily_nutrition(user.id, meals)
        
        return jsonify({
            'nutrition_tracking': tracking,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Nutrition tracking failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/nutrition/recipe-analysis', methods=['POST'])
def analyze_recipe_nutrition():
    """Analyze nutrition for a recipe."""
    try:
        data = request.get_json()
        recipe_name = data.get('recipe_name', '')
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name required'}), 400
        
        nutrition = chatbot.calculate_nutrition(recipe_name)
        
        if 'error' in nutrition:
            return jsonify(nutrition), 404
        
        return jsonify({
            'nutrition_analysis': nutrition,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Nutrition analysis failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/nutrition/meal-plan-analysis', methods=['POST'])
def analyze_meal_plan_nutrition():
    """Analyze nutrition for a meal plan."""
    try:
        data = request.get_json()
        meal_plan = data.get('meal_plan', {})
        
        if not meal_plan:
            return jsonify({'error': 'Meal plan required'}), 400
        
        # Calculate nutrition for entire meal plan
        total_nutrition = {
            'calories': 0,
            'protein': 0,
            'carbs': 0,
            'fat': 0,
            'fiber': 0,
            'sugar': 0,
            'sodium': 0
        }
        
        daily_nutrition = {}
        
        for day, meals in meal_plan.items():
            daily_total = {
                'calories': 0,
                'protein': 0,
                'carbs': 0,
                'fat': 0,
                'fiber': 0,
                'sugar': 0,
                'sodium': 0
            }
            
            for meal_type, meal in meals.items():
                if isinstance(meal, dict):
                    nutrition = chatbot.calculate_nutrition(meal.get('name', ''))
                    if 'error' not in nutrition:
                        per_serving = nutrition.get('per_serving', {})
                        servings = meal.get('servings', 1)
                        
                        for nutrient in daily_total:
                            daily_total[nutrient] += per_serving.get(nutrient, 0) * servings
            
            daily_nutrition[day] = daily_total
            
            # Add to totals
            for nutrient in total_nutrition:
                total_nutrition[nutrient] += daily_total[nutrient]
        
        # Calculate averages
        days_count = len(meal_plan)
        average_daily = {
            nutrient: round(total / days_count, 1) 
            for nutrient, total in total_nutrition.items()
        }
        
        return jsonify({
            'total_nutrition': total_nutrition,
            'daily_nutrition': daily_nutrition,
            'average_daily': average_daily,
            'days_analyzed': days_count,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Meal plan nutrition analysis failed: {str(e)}',
            'status': 'error'
        }), 500

# Rating and Review System Routes

@main_bp.route('/api/reviews/add', methods=['POST'])
def add_review():
    """Add a new review for a recipe."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        rating = data.get('rating', 0)
        title = data.get('title', '').strip()
        comment = data.get('comment', '').strip()
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        if not (1 <= rating <= 5):
            return jsonify({'error': 'Rating must be between 1 and 5'}), 400
        
        if not title:
            return jsonify({'error': 'Review title is required'}), 400
        
        # Optional fields
        pros = data.get('pros', [])
        cons = data.get('cons', [])
        would_make_again = data.get('would_make_again', True)
        difficulty_rating = data.get('difficulty_rating', 3)
        value_rating = data.get('value_rating', 3)
        taste_rating = data.get('taste_rating', 3)
        verified_purchase = data.get('verified_purchase', False)
        
        result = rating_manager.add_review(
            recipe_name=recipe_name,
            user_id=user.id,
            username=user.username,
            rating=rating,
            title=title,
            comment=comment,
            pros=pros,
            cons=cons,
            would_make_again=would_make_again,
            difficulty_rating=difficulty_rating,
            value_rating=value_rating,
            taste_rating=taste_rating,
            verified_purchase=verified_purchase
        )
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to add review: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/update', methods=['PUT'])
def update_review():
    """Update an existing review."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        review_id = data.get('review_id', '').strip()
        updates = data.get('updates', {})
        
        if not review_id:
            return jsonify({'error': 'Review ID is required'}), 400
        
        if not updates:
            return jsonify({'error': 'No updates provided'}), 400
        
        result = rating_manager.update_review(review_id, user.id, updates)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to update review: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/delete', methods=['DELETE'])
def delete_review():
    """Delete a review."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        review_id = data.get('review_id', '').strip()
        
        if not review_id:
            return jsonify({'error': 'Review ID is required'}), 400
        
        result = rating_manager.delete_review(review_id, user.id)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to delete review: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/recipe/<recipe_name>', methods=['GET'])
def get_recipe_reviews(recipe_name: str):
    """Get reviews for a specific recipe."""
    try:
        sort_by = request.args.get('sort_by', 'newest')
        page = int(request.args.get('page', 1))
        per_page = int(request.args.get('per_page', 10))
        
        result = rating_manager.get_recipe_reviews(
            recipe_name=recipe_name,
            sort_by=sort_by,
            page=page,
            per_page=per_page
        )
        
        return jsonify({
            'reviews': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get reviews: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/user', methods=['GET'])
def get_user_reviews():
    """Get reviews by the current user."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        page = int(request.args.get('page', 1))
        per_page = int(request.args.get('per_page', 10))
        
        result = rating_manager.get_user_reviews(
            user_id=user.id,
            page=page,
            per_page=per_page
        )
        
        return jsonify({
            'reviews': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get user reviews: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/helpful', methods=['POST'])
def mark_review_helpful():
    """Mark a review as helpful."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        review_id = data.get('review_id', '').strip()
        
        if not review_id:
            return jsonify({'error': 'Review ID is required'}), 400
        
        result = rating_manager.mark_review_helpful(review_id, user.id)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to mark review helpful: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/stats/<recipe_name>', methods=['GET'])
def get_recipe_stats(recipe_name: str):
    """Get statistics for a recipe."""
    try:
        stats = rating_manager.get_recipe_stats(recipe_name)
        
        if not stats:
            return jsonify({'error': 'Recipe statistics not found'}), 404
        
        return jsonify({
            'stats': stats.to_dict(),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get recipe stats: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/top-recipes', methods=['GET'])
def get_top_recipes():
    """Get top rated recipes."""
    try:
        limit = int(request.args.get('limit', 10))
        min_reviews = int(request.args.get('min_reviews', 5))
        
        top_recipes = rating_manager.get_top_recipes(
            limit=limit,
            min_reviews=min_reviews
        )
        
        return jsonify({
            'top_recipes': top_recipes,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get top recipes: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/search', methods=['POST'])
def search_reviews():
    """Search reviews."""
    try:
        data = request.get_json()
        query = data.get('query', '').strip()
        recipe_name = data.get('recipe_name', '')
        rating_filter = data.get('rating_filter')
        page = int(data.get('page', 1))
        per_page = int(data.get('per_page', 10))
        
        if not query:
            return jsonify({'error': 'Search query is required'}), 400
        
        result = rating_manager.search_reviews(
            query=query,
            recipe_name=recipe_name,
            rating_filter=rating_filter,
            page=page,
            per_page=per_page
        )
        
        return jsonify({
            'search_results': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to search reviews: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/reviews/user-summary', methods=['GET'])
def get_user_rating_summary():
    """Get user's rating summary."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        summary = rating_manager.get_user_rating_summary(user.id)
        
        return jsonify({
            'summary': summary,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get user rating summary: {str(e)}',
            'status': 'error'
        }), 500

# Social Sharing and Community Features

@main_bp.route('/api/share/recipe', methods=['POST'])
def share_recipe():
    """Share a recipe to social platforms."""
    try:
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        platform = data.get('platform', '').strip()
        custom_message = data.get('message', '')
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        if not platform:
            return jsonify({'error': 'Platform is required'}), 400
        
        # Get recipe details
        recipe = next((r for r in chatbot.recipes if r['name'] == recipe_name), None)
        
        if not recipe:
            return jsonify({'error': 'Recipe not found'}), 404
        
        # Generate shareable content
        share_content = {
            'title': f"Check out this amazing {recipe_name} recipe!",
            'description': f"Delicious {recipe_name} - {recipe.get('prep_time', 'Quick')} to make!",
            'url': f"https://yourapp.com/recipe/{recipe_name.lower().replace(' ', '-')}",
            'image': f"https://yourapp.com/images/recipes/{recipe_name.lower().replace(' ', '-')}.jpg",
            'hashtags': ['#RecipeAssistant', '#Cooking', '#Foodie', recipe_name.replace(' ', '')],
            'platform': platform
        }
        
        # Platform-specific formatting
        if platform == 'twitter':
            share_content['text'] = f"{share_content['title']} {share_content['url']} {' '.join(share_content['hashtags'][:3])}"
        elif platform == 'facebook':
            share_content['text'] = f"{share_content['title']}\n\n{share_content['description']}\n\n{share_content['url']}"
        elif platform == 'instagram':
            share_content['text'] = f"{share_content['title']}\n\n{share_content['description']}\n\n{' '.join(share_content['hashtags'][:5])}"
        elif platform == 'pinterest':
            share_content['text'] = f"{share_content['title']}\n\n{share_content['description']}"
        
        # Add custom message if provided
        if custom_message:
            share_content['text'] = f"{custom_message}\n\n{share_content['text']}"
        
        return jsonify({
            'share_content': share_content,
            'message': 'Recipe shared successfully',
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to share recipe: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/community/trending-recipes', methods=['GET'])
def get_trending_recipes():
    """Get trending recipes in the community."""
    try:
        # Mock trending data - in real app, would analyze actual usage patterns
        trending_recipes = [
            {
                'recipe_name': 'Spaghetti Carbonara',
                'trend_score': 95,
                'recent_reviews': 12,
                'average_rating': 4.7,
                'category': 'Italian'
            },
            {
                'recipe_name': 'Chicken Stir Fry',
                'trend_score': 88,
                'recent_reviews': 8,
                'average_rating': 4.5,
                'category': 'Asian'
            },
            {
                'recipe_name': 'Greek Salad',
                'trend_score': 82,
                'recent_reviews': 6,
                'average_rating': 4.6,
                'category': 'Mediterranean'
            }
        ]
        
        return jsonify({
            'trending_recipes': trending_recipes,
            'updated_date': datetime.datetime.now().isoformat(),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get trending recipes: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/community/recent-activity', methods=['GET'])
def get_recent_activity():
    """Get recent community activity."""
    try:
        # Mock activity data - in real app, would fetch from database
        recent_activity = [
            {
                'type': 'review',
                'user': 'John Doe',
                'action': 'reviewed',
                'target': 'Spaghetti Carbonara',
                'rating': 5,
                'timestamp': datetime.datetime.now().isoformat()
            },
            {
                'type': 'favorite',
                'user': 'Jane Smith',
                'action': 'favorited',
                'target': 'Greek Salad',
                'timestamp': (datetime.datetime.now() - datetime.timedelta(hours=2)).isoformat()
            },
            {
                'type': 'share',
                'user': 'Mike Johnson',
                'action': 'shared',
                'target': 'Chicken Stir Fry',
                'platform': 'Facebook',
                'timestamp': (datetime.datetime.now() - datetime.timedelta(hours=4)).isoformat()
            }
        ]
        
        return jsonify({
            'recent_activity': recent_activity,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get recent activity: {str(e)}',
            'status': 'error'
        }), 500

# Voice Assistant Routes

@main_bp.route('/api/voice/start', methods=['POST'])
def start_voice_assistant():
    """Start voice assistant listening."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        # Start listening (mock implementation)
        return jsonify({
            'status': 'listening',
            'message': 'Voice assistant started',
            'available_commands': list(voice_assistant.command_patterns.keys())
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to start voice assistant: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/voice/stop', methods=['POST'])
def stop_voice_assistant():
    """Stop voice assistant listening."""
    try:
        voice_assistant.stop_listening()
        
        return jsonify({
            'status': 'stopped',
            'message': 'Voice assistant stopped'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to stop voice assistant: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/voice/speak', methods=['POST'])
def speak_text():
    """Convert text to speech."""
    try:
        data = request.get_json()
        text = data.get('text', '').strip()
        
        if not text:
            return jsonify({'error': 'Text is required'}), 400
        
        voice_assistant.speak(text)
        
        return jsonify({
            'message': 'Text spoken successfully',
            'text': text
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to speak text: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/voice/commands', methods=['GET'])
def get_voice_commands():
    """Get available voice commands."""
    try:
        return jsonify({
            'commands': voice_assistant.command_patterns,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get voice commands: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/voice/settings', methods=['POST'])
def update_voice_settings():
    """Update voice assistant settings."""
    try:
        data = request.get_json()
        rate = data.get('rate')
        volume = data.get('volume')
        voice = data.get('voice')
        
        voice_assistant.set_voice_settings(rate, volume, voice)
        
        return jsonify({
            'message': 'Voice settings updated',
            'settings': {
                'rate': rate,
                'volume': volume,
                'voice': voice
            }
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to update voice settings: {str(e)}',
            'status': 'error'
        }), 500

# Smart Kitchen Routes

@main_bp.route('/api/smart-kitchen/devices', methods=['GET'])
def get_smart_devices():
    """Get all smart kitchen devices."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        devices = smart_kitchen.discover_devices()
        
        return jsonify({
            'devices': [device.to_dict() for device in devices],
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get devices: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/smart-kitchen/devices', methods=['POST'])
def add_smart_device():
    """Add a new smart device."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        
        # Create device object (simplified)
        from .smart_kitchen import SmartDevice, DeviceType
        
        device = SmartDevice(
            id=data.get('id'),
            name=data.get('name'),
            type=DeviceType(data.get('type')),
            brand=data.get('brand'),
            model=data.get('model'),
            ip_address=data.get('ip_address'),
            status='offline',
            capabilities=data.get('capabilities', []),
            current_settings={},
            last_seen=datetime.datetime.now().isoformat()
        )
        
        result = smart_kitchen.add_device(device)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to add device: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/smart-kitchen/devices/<device_id>/control', methods=['POST'])
def control_smart_device(device_id: str):
    """Control a smart device."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        command = data.get('command')
        parameters = data.get('parameters', {})
        
        result = smart_kitchen.control_device(device_id, command, parameters)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to control device: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/smart-kitchen/devices/<device_id>/status', methods=['GET'])
def get_device_status(device_id: str):
    """Get device status."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        status = smart_kitchen.get_device_status(device_id)
        
        if not status:
            return jsonify({'error': 'Device not found'}), 404
        
        return jsonify({
            'device_status': status,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get device status: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/smart-kitchen/cooking-sessions', methods=['POST'])
def start_cooking_session():
    """Start cooking session on smart device."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        device_id = data.get('device_id')
        recipe_name = data.get('recipe_name')
        target_temperature = data.get('target_temperature', 350)
        estimated_time = data.get('estimated_time', 30)
        
        result = smart_kitchen.start_cooking_session(
            device_id, recipe_name, target_temperature, estimated_time
        )
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to start cooking session: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/smart-kitchen/cooking-sessions', methods=['GET'])
def get_active_sessions():
    """Get active cooking sessions."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        sessions = smart_kitchen.get_active_sessions()
        
        return jsonify({
            'active_sessions': [session.to_dict() for session in sessions],
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get active sessions: {str(e)}',
            'status': 'error'
        }), 500

# AI Cooking Assistant Routes

@main_bp.route('/api/cooking-assistant/start', methods=['POST'])
def start_ai_cooking_session():
    """Start AI cooking assistant session."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipe_name = data.get('recipe_name')
        skill_level = data.get('skill_level', 'beginner')
        dietary_preferences = data.get('dietary_preferences', [])
        
        from .cooking_assistant import DifficultyLevel
        difficulty = DifficultyLevel(skill_level)
        
        result = cooking_assistant.start_cooking_session(
            recipe_name, user.id, difficulty, dietary_preferences
        )
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to start cooking session: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cooking-assistant/<session_id>/current-step', methods=['GET'])
def get_current_cooking_step(session_id: str):
    """Get current cooking step."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        current_step = cooking_assistant.get_current_step(session_id)
        
        if not current_step:
            return jsonify({'error': 'No current step found'}), 404
        
        return jsonify({
            'current_step': current_step.to_dict(),
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get current step: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cooking-assistant/<session_id>/next-step', methods=['POST'])
def advance_to_next_step(session_id: str):
    """Advance to next cooking step."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        notes = data.get('notes', '')
        
        result = cooking_assistant.advance_to_next_step(session_id, notes)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to advance step: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cooking-assistant/<session_id>/guidance', methods=['GET'])
def get_step_guidance(session_id: str):
    """Get detailed guidance for current step."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        guidance_type = request.args.get('type', 'detailed')
        
        guidance = cooking_assistant.get_step_guidance(session_id, guidance_type)
        
        return jsonify({
            'guidance': guidance,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get step guidance: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cooking-assistant/<session_id>/pause', methods=['POST'])
def pause_cooking_session(session_id: str):
    """Pause cooking session."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        reason = data.get('reason', '')
        
        result = cooking_assistant.pause_session(session_id, reason)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to pause session: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cooking-assistant/<session_id>/resume', methods=['POST'])
def resume_cooking_session(session_id: str):
    """Resume cooking session."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        result = cooking_assistant.resume_session(session_id)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to resume session: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/cooking-assistant/<session_id>/summary', methods=['GET'])
def get_cooking_session_summary(session_id: str):
    """Get cooking session summary."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        summary = cooking_assistant.get_session_summary(session_id)
        
        return jsonify({
            'session_summary': summary,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get session summary: {str(e)}',
            'status': 'error'
        }), 500

# Advanced Features Routes - Recipe Scaling and Variations

@main_bp.route('/api/recipe/scale', methods=['POST'])
def scale_recipe():
    """Scale recipe to different serving size."""
    try:
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        servings = data.get('servings', 4)
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        if servings < 1:
            return jsonify({'error': 'Servings must be at least 1'}), 400
        
        result = chatbot.scale_recipe(recipe_name, servings)
        
        if 'error' in result:
            return jsonify(result), 404
        
        return jsonify({
            'result': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to scale recipe: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/recipe/variations', methods=['POST'])
def get_recipe_variations():
    """Get recipe variations."""
    try:
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        variation_type = data.get('variation_type', 'all')
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        result = chatbot.generate_recipe_variations(recipe_name, variation_type)
        
        if 'error' in result:
            return jsonify(result), 404
        
        return jsonify({
            'result': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to generate variations: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/recipe/leftover-suggestions', methods=['POST'])
def get_leftover_suggestions():
    """Get recipe suggestions based on leftover ingredients."""
    try:
        data = request.get_json()
        ingredients = data.get('ingredients', [])
        
        if not ingredients:
            return jsonify({'error': 'Ingredients list is required'}), 400
        
        result = chatbot.suggest_leftover_recipes(ingredients)
        
        return jsonify({
            'result': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get leftover suggestions: {str(e)}',
            'status': 'error'
        }), 500

# Advanced Features Routes - Ingredient Inventory

@main_bp.route('/api/inventory', methods=['GET'])
def get_inventory():
    """Get user's ingredient inventory."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        result = advanced_features.get_inventory(user.id)
        
        return jsonify({
            'inventory': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get inventory: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/inventory/add', methods=['POST'])
def add_to_inventory():
    """Add item to inventory."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        ingredient = IngredientInventory(
            name=data.get('name', '').strip(),
            quantity=float(data.get('quantity', 1)),
            unit=data.get('unit', 'item'),
            expiry_date=data.get('expiry_date'),
            location=data.get('location', 'pantry'),
            category=data.get('category', 'other'),
            added_date=datetime.datetime.now().isoformat(),
            notes=data.get('notes', '')
        )
        
        if not ingredient.name:
            return jsonify({'error': 'Item name is required'}), 400
        
        result = advanced_features.add_to_inventory(user.id, ingredient)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to add to inventory: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/inventory/update', methods=['PUT'])
def update_inventory_item():
    """Update inventory item."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        item_name = data.get('item_name', '').strip()
        updates = data.get('updates', {})
        
        if not item_name:
            return jsonify({'error': 'Item name is required'}), 400
        
        result = advanced_features.update_inventory_item(user.id, item_name, updates)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to update inventory: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/inventory/remove', methods=['DELETE'])
def remove_from_inventory():
    """Remove item from inventory."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        item_name = data.get('item_name', '').strip()
        
        if not item_name:
            return jsonify({'error': 'Item name is required'}), 400
        
        result = advanced_features.remove_from_inventory(user.id, item_name)
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to remove from inventory: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/inventory/recipes', methods=['GET'])
def get_recipes_from_inventory():
    """Get recipe suggestions from inventory."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        inventory_info = advanced_features.get_recipes_from_inventory(user.id)
        ingredients = inventory_info.get('available_ingredients', [])
        
        if not ingredients:
            return jsonify({
                'message': 'No ingredients in inventory',
                'suggestions': []
            })
        
        result = chatbot.suggest_leftover_recipes(ingredients)
        
        return jsonify({
            'result': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get recipes: {str(e)}',
            'status': 'error'
        }), 500

# Advanced Features Routes - Recipe Collections

@main_bp.route('/api/collections', methods=['GET', 'POST'])
def manage_collections():
    """Get or create recipe collections."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        if request.method == 'GET':
            result = advanced_features.get_collections(user.id)
            return jsonify(result)
        
        elif request.method == 'POST':
            data = request.get_json()
            name = data.get('name', '').strip()
            description = data.get('description', '')
            tags = data.get('tags', [])
            is_public = data.get('is_public', False)
            
            if not name:
                return jsonify({'error': 'Collection name is required'}), 400
            
            result = advanced_features.create_collection(
                user.id, name, description, tags, is_public
            )
            
            return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Collection operation failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/collections/<collection_id>/recipes', methods=['POST'])
def add_recipe_to_collection(collection_id: str):
    """Add recipe to collection."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        result = advanced_features.add_recipe_to_collection(
            user.id, collection_id, recipe_name
        )
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to add recipe: {str(e)}',
            'status': 'error'
        }), 500

# Advanced Features Routes - Cooking Challenges

@main_bp.route('/api/challenges', methods=['GET', 'POST'])
def manage_challenges():
    """Get or create cooking challenges."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        if request.method == 'GET':
            result = advanced_features.get_active_challenges(user.id)
            return jsonify(result)
        
        elif request.method == 'POST':
            data = request.get_json()
            challenge_type_str = data.get('challenge_type', 'weekly_recipe')
            title = data.get('title', '').strip()
            description = data.get('description', '')
            target_recipes = data.get('target_recipes', [])
            duration_days = data.get('duration_days', 7)
            difficulty = data.get('difficulty', 'medium')
            
            if not title:
                return jsonify({'error': 'Challenge title is required'}), 400
            
            try:
                challenge_type = ChallengeType(challenge_type_str)
            except ValueError:
                return jsonify({'error': 'Invalid challenge type'}), 400
            
            result = advanced_features.create_challenge(
                user.id, challenge_type, title, description,
                target_recipes, duration_days, difficulty
            )
            
            return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Challenge operation failed: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/challenges/<challenge_id>/complete', methods=['POST'])
def complete_challenge_recipe(challenge_id: str):
    """Complete a recipe in a challenge."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        result = advanced_features.complete_challenge_recipe(
            user.id, challenge_id, recipe_name
        )
        
        if 'error' in result:
            return jsonify(result), 400
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to complete recipe: {str(e)}',
            'status': 'error'
        }), 500

# Advanced Features Routes - Meal Prep Planning

@main_bp.route('/api/meal-prep/create', methods=['POST'])
def create_meal_prep_plan():
    """Create a meal prep plan."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipes = data.get('recipes', [])
        week_start = data.get('week_start', datetime.datetime.now().isoformat())
        
        if not recipes:
            return jsonify({'error': 'Recipes are required'}), 400
        
        result = advanced_features.create_meal_prep_plan(
            user.id, recipes, week_start
        )
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to create meal prep plan: {str(e)}',
            'status': 'error'
        }), 500

# Advanced Features Routes - Cooking Stats

@main_bp.route('/api/stats/cooking', methods=['GET'])
def get_cooking_stats():
    """Get user's cooking statistics."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        result = advanced_features.get_cooking_stats(user.id)
        
        return jsonify({
            'stats': result,
            'status': 'success'
        })
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to get stats: {str(e)}',
            'status': 'error'
        }), 500

@main_bp.route('/api/stats/track-recipe', methods=['POST'])
def track_recipe_cooked():
    """Track that user cooked a recipe."""
    try:
        session_token = request.headers.get('Authorization', '').replace('Bearer ', '')
        user = auth_manager.get_user_by_session(session_token)
        
        if not user:
            return jsonify({'error': 'Authentication required'}), 401
        
        data = request.get_json()
        recipe_name = data.get('recipe_name', '').strip()
        
        if not recipe_name:
            return jsonify({'error': 'Recipe name is required'}), 400
        
        result = advanced_features.track_recipe_cooked(user.id, recipe_name)
        
        return jsonify(result)
    
    except Exception as e:
        return jsonify({
            'error': f'Failed to track recipe: {str(e)}',
            'status': 'error'
        }), 500
2,424 lines•79.9 KB
python
app/voice_assistant.py
Raw Download
Find: Go to:
"""
Recipe Assistant Bot - Voice Command Integration
Author: RSK World (https://rskworld.in)
Founder: Molla Samser
Designer & Tester: Rima Khatun
Contact: help@rskworld.in, +91 93305 39277
Year: 2026
"""

import speech_recognition as sr
import pyttsx3
import json
import re
import threading
import queue
import time
from typing import Dict, List, Optional, Callable, Any
from dataclasses import dataclass
from datetime import datetime

@dataclass
class VoiceCommand:
    """Voice command model."""
    command: str
    intent: str
    entities: Dict[str, Any]
    confidence: float
    timestamp: str

class VoiceAssistant:
    """Handles voice commands and text-to-speech."""
    
    def __init__(self):
        """Initialize voice assistant."""
        self.recognizer = sr.Recognizer()
        self.microphone = sr.Microphone()
        self.tts_engine = pyttsx3.init()
        
        # Voice settings
        self.tts_engine.setProperty('rate', 150)  # Speed of speech
        self.tts_engine.setProperty('volume', 0.9)  # Volume level
        
        # Command patterns
        self.command_patterns = {
            'recipe_search': [
                r'find (?:a )?recipe (?:for|with) (.+)',
                r'search (?:for )?(.+?) recipe',
                r'look up (.+?) recipe',
                r'what can i make with (.+)'
            ],
            'ingredient_substitution': [
                r'what can i use instead of (.+)',
                r'substitute (.+)',
                r'replacement for (.+)'
            ],
            'cooking_time': [
                r'how long (?:do|i) (?:you|I) cook (.+)',
                r'cooking time for (.+)',
                r'(.+) cooking time'
            ],
            'temperature_conversion': [
                r'convert (\d+) degrees? (.+?) to (.+)',
                r'(\d+)°(.+?) to (.+)',
                r'what is (\d+) (.+?) in (.+)'
            ],
            'shopping_list': [
                r'add (.+?) to shopping list',
                r'put (.+?) on my list',
                r'shopping list add (.+)'
            ],
            'timer': [
                r'set timer for (\d+) (.+)',
                r'timer (\d+) (.+)',
                r'start timer (\d+) (.+)'
            ],
            'nutrition_info': [
                r'how many calories in (.+)',
                r'nutrition (?:info|information) (?:for|about) (.+)',
                r'(.+) nutrition'
            ],
            'step_by_step': [
                r'how to cook (.+)',
                r'step by step (.+)',
                r'guide me through (.+)'
            ],
            'meal_planning': [
                r'plan meals? for (.+)',
                r'meal plan for (.+)',
                r'what should i eat this week'
            ]
        }
        
        # Response templates
        self.responses = {
            'greeting': [
                "Hello! I'm your Recipe Assistant. How can I help you today?",
                "Hi there! Ready to help you with cooking. What would you like to make?",
                "Welcome! I'm here to assist with all your cooking needs."
            ],
            'confirmation': [
                "Got it! I'll help you with that.",
                "Understood! Let me take care of that for you.",
                "Perfect! I'm on it."
            ],
            'error': [
                "I didn't quite catch that. Could you repeat?",
                "Sorry, I'm having trouble understanding. Try again?",
                "I didn't get that. Could you say it differently?"
            ],
            'processing': [
                "Let me check that for you...",
                "Searching my recipe database...",
                "Looking that up for you..."
            ]
        }
        
        # Command queue for async processing
        self.command_queue = queue.Queue()
        self.is_listening = False
        self.current_recipe = None
        
    def start_listening(self, callback: Callable[[VoiceCommand], None]):
        """Start listening for voice commands."""
        self.is_listening = True
        self.callback = callback
        
        def listen_loop():
            with self.microphone as source:
                self.recognizer.adjust_for_ambient_noise(source, duration=1)
                
            while self.is_listening:
                try:
                    with self.microphone as source:
                        audio = self.recognizer.listen(source, timeout=5, phrase_time_limit=10)
                    
                    # Recognize speech
                    text = self.recognizer.recognize_google(audio)
                    command = self._parse_command(text)
                    
                    if command:
                        self.command_queue.put(command)
                        
                except sr.WaitTimeoutError:
                    continue
                except sr.UnknownValueError:
                    continue
                except Exception as e:
                    print(f"Voice recognition error: {e}")
                    continue
        
        # Start listening in separate thread
        self.listening_thread = threading.Thread(target=listen_loop, daemon=True)
        self.listening_thread.start()
        
        # Process commands
        self._process_commands()
    
    def stop_listening(self):
        """Stop listening for voice commands."""
        self.is_listening = False
        if hasattr(self, 'listening_thread'):
            self.listening_thread.join(timeout=1)
    
    def speak(self, text: str):
        """Convert text to speech."""
        try:
            self.tts_engine.say(text)
            self.tts_engine.runAndWait()
        except Exception as e:
            print(f"TTS error: {e}")
    
    def _parse_command(self, text: str) -> Optional[VoiceCommand]:
        """Parse voice command text."""
        text = text.lower().strip()
        
        for intent, patterns in self.command_patterns.items():
            for pattern in patterns:
                match = re.search(pattern, text)
                if match:
                    entities = {}
                    
                    # Extract entities based on groups
                    if match.groups():
                        if intent == 'recipe_search':
                            entities['ingredients'] = match.group(1).strip()
                        elif intent == 'ingredient_substitution':
                            entities['ingredient'] = match.group(1).strip()
                        elif intent == 'cooking_time':
                            entities['food_item'] = match.group(1).strip()
                        elif intent == 'temperature_conversion':
                            entities['temperature'] = int(match.group(1))
                            entities['from_unit'] = match.group(2).strip()
                            entities['to_unit'] = match.group(3).strip()
                        elif intent == 'shopping_list':
                            entities['item'] = match.group(1).strip()
                        elif intent == 'timer':
                            entities['duration'] = int(match.group(1))
                            entities['unit'] = match.group(2).strip()
                        elif intent == 'nutrition_info':
                            entities['food_item'] = match.group(1).strip()
                        elif intent == 'step_by_step':
                            entities['recipe'] = match.group(1).strip()
                        elif intent == 'meal_planning':
                            entities['timeframe'] = match.group(1).strip() if match.group(1) else 'week'
                    
                    return VoiceCommand(
                        command=text,
                        intent=intent,
                        entities=entities,
                        confidence=0.8,  # Simplified confidence
                        timestamp=datetime.now().isoformat()
                    )
        
        return None
    
    def _process_commands(self):
        """Process queued commands."""
        while self.is_listening:
            try:
                if not self.command_queue.empty():
                    command = self.command_queue.get(timeout=0.1)
                    self._handle_command(command)
                    self.callback(command)
                else:
                    time.sleep(0.1)
            except queue.Empty:
                continue
            except Exception as e:
                print(f"Command processing error: {e}")
    
    def _handle_command(self, command: VoiceCommand):
        """Handle voice command with appropriate response."""
        if command.intent == 'recipe_search':
            self.speak("Searching for recipes with those ingredients...")
            
        elif command.intent == 'ingredient_substitution':
            self.speak(f"Looking for substitutes for {command.entities['ingredient']}...")
            
        elif command.intent == 'cooking_time':
            self.speak(f"Checking cooking time for {command.entities['food_item']}...")
            
        elif command.intent == 'temperature_conversion':
            temp = command.entities['temperature']
            from_unit = command.entities['from_unit']
            to_unit = command.entities['to_unit']
            self.speak(f"Converting {temp} degrees {from_unit} to {to_unit}...")
            
        elif command.intent == 'shopping_list':
            item = command.entities['item']
            self.speak(f"Adding {item} to your shopping list...")
            
        elif command.intent == 'timer':
            duration = command.entities['duration']
            unit = command.entities['unit']
            self.speak(f"Setting timer for {duration} {unit}...")
            
        elif command.intent == 'nutrition_info':
            self.speak(f"Getting nutrition information for {command.entities['food_item']}...")
            
        elif command.intent == 'step_by_step':
            self.speak(f"Getting step-by-step instructions for {command.entities['recipe']}...")
            
        elif command.intent == 'meal_planning':
            timeframe = command.entities['timeframe']
            self.speak(f"Creating meal plan for {timeframe}...")
    
    def get_voice_commands_history(self, limit: int = 10) -> List[Dict]:
        """Get history of voice commands (mock implementation)."""
        # In real implementation, this would store commands in database
        return [
            {
                'command': 'find recipe with chicken',
                'intent': 'recipe_search',
                'timestamp': datetime.now().isoformat(),
                'processed': True
            }
        ]
    
    def set_voice_settings(self, rate: int = None, volume: float = None, voice: str = None):
        """Update voice settings."""
        if rate is not None:
            self.tts_engine.setProperty('rate', rate)
        
        if volume is not None:
            self.tts_engine.setProperty('volume', volume)
        
        if voice is not None:
            voices = self.tts_engine.getProperty('voices')
            for v in voices:
                if voice.lower() in v.name.lower():
                    self.tts_engine.setProperty('voice', v.id)
                    break
    
    def get_available_voices(self) -> List[Dict]:
        """Get list of available TTS voices."""
        voices = self.tts_engine.getProperty('voices')
        return [
            {
                'id': voice.id,
                'name': voice.name,
                'languages': voice.languages,
                'gender': voice.gender
            }
            for voice in voices
        ]
    
    def test_microphone(self) -> Dict:
        """Test microphone functionality."""
        try:
            with self.microphone as source:
                self.recognizer.adjust_for_ambient_noise(source, duration=1)
                audio = self.recognizer.listen(source, timeout=2, phrase_time_limit=3)
            
            text = self.recognizer.recognize_google(audio)
            return {
                'status': 'success',
                'recognized_text': text,
                'message': 'Microphone working correctly'
            }
        except Exception as e:
            return {
                'status': 'error',
                'error': str(e),
                'message': 'Microphone test failed'
            }
    
    def create_custom_command(self, name: str, patterns: List[str], response_template: str):
        """Create custom voice command."""
        self.command_patterns[name] = patterns
        self.responses[name] = [response_template]
    
    def get_command_suggestions(self, partial_command: str) -> List[str]:
        """Get suggestions for partial voice commands."""
        suggestions = []
        
        for intent, patterns in self.command_patterns.items():
            for pattern in patterns:
                # Simple matching - in real app would use more sophisticated NLP
                if any(word in partial_command.lower() for word in pattern.split() if word.isalpha()):
                    suggestions.append(f"Try: {pattern}")
        
        return suggestions[:5]  # Return top 5 suggestions
343 lines•13.4 KB
python
🚀 Support RSK World

Subscribe to our YouTube channel for latest tutorials & updates!



Click subscribe & support our work ❤️

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer