diff --git a/app/api/endpoints.py b/app/api/endpoints.py index 95bf97a..115faaa 100644 --- a/app/api/endpoints.py +++ b/app/api/endpoints.py @@ -22,7 +22,9 @@ map_gemini_to_macros, GEMINI_API_KEY ) -from app.models.schemas import ComplianceRequest, ComplianceResponse +from app.models.schemas import ComplianceRequest, ComplianceResponse, DietParseRequest, DietPlanParsedResponse +from app.services.diet_parser_service import parse_diet_plan +import base64 logger = logging.getLogger(__name__) @@ -418,3 +420,30 @@ async def analyze_compliance(request: ComplianceRequest): logger.error(f"Error checking compliance: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") +@router.post("/parse-diet") +async def parse_diet(request: DietParseRequest): + try: + image_bytes = None + if request.image_base64: + # Remove header if exists, like 'data:image/jpeg;base64,' + base64_data = request.image_base64 + if "," in base64_data: + base64_data = base64_data.split(",")[1] + try: + image_bytes = base64.b64decode(base64_data) + except Exception as decode_err: + logger.error(f"Failed to decode base64 image: {decode_err}") + return {"success": False, "message": "Geçersiz görsel formatı", "data": None} + + parsed_data = await parse_diet_plan(raw_text=request.raw_text, image_data=image_bytes) + + if not parsed_data: + return {"success": False, "message": "Diyet listesi analiz edilemedi", "data": None} + + return {"success": True, "message": "Analiz başarılı", "data": parsed_data} + + except Exception as e: + logger.error(f"Error in parse_diet: {str(e)}", exc_info=True) + return {"success": False, "message": str(e), "data": None} + + diff --git a/app/models/schemas.py b/app/models/schemas.py index 029a203..a07a28f 100644 --- a/app/models/schemas.py +++ b/app/models/schemas.py @@ -106,3 +106,24 @@ class ComplianceRequest(BaseModel): class ComplianceResponse(BaseModel): success: bool = Field(..., description="Status") feedback: str = Field(..., description="AI feedback string") + +class DietMealSchema(BaseModel): + meal_type: str = Field(..., description="BREAKFAST, LUNCH, DINNER, or SNACK") + food_name: str + target_calories: int = 0 + target_protein: float = 0.0 + target_carbs: float = 0.0 + target_fat: float = 0.0 + +class DietDaySchema(BaseModel): + day_number: int + total_target_calories: int = 0 + meals: List[DietMealSchema] + +class DietPlanParsedResponse(BaseModel): + plan_name: str + days: List[DietDaySchema] + +class DietParseRequest(BaseModel): + raw_text: Optional[str] = None + image_base64: Optional[str] = None diff --git a/app/services/diet_parser_service.py b/app/services/diet_parser_service.py new file mode 100644 index 0000000..8a0a46e --- /dev/null +++ b/app/services/diet_parser_service.py @@ -0,0 +1,88 @@ +import base64 +import json +import logging +from typing import Optional + +from google import genai +from google.genai import types + +from app.core.config import GEMINI_API_KEY, GEMINI_MODEL +from app.models.schemas import DietPlanParsedResponse + +logger = logging.getLogger(__name__) + +def get_gemini_client(): + if not GEMINI_API_KEY: + return None + return genai.Client(api_key=GEMINI_API_KEY, http_options={'api_version': 'v1beta'}) + + +async def parse_diet_plan(raw_text: Optional[str] = None, image_data: Optional[bytes] = None) -> Optional[dict]: + client = get_gemini_client() + if not client: + logger.error("Gemini client not initialized.") + return None + + prompt = ( + "Sen profesyonel bir diyetisyensin. Görseldeki veya metindeki 7 günlük diyet listesini analiz et. " + "Her öğün için protein, karbonhidrat ve yağ değerlerini profesyonel olarak TAHMİN ET ve JSON formatında döndür. " + "Görseldeki veya metindeki diyet planının kaç günlük olduğunu saptayarak her bir günü ayrı ayrı analiz et. Her günün öğünlerini (BREAKFAST, LUNCH, DINNER, SNACK) belirle ve her besin için protein, karbonhidrat ve yağ değerlerini profesyonelce TAHMİN ET. Eğer listede belirli bir gün sayısı belirtilmemişse, mevcut tüm veriyi işle." + "Dönecek JSON içerisinde Türkçe karakterleri (ş, ğ, ç, ö, ü, ı) doğrudan kullan ve unicode escape sequence kullanma. " + "JSON çıktısı tam olarak aşağıdaki yapıya uygun olmalıdır:\n" + "{\n" + ' "plan_name": "Plan Name",\n' + ' "days": [\n' + " {\n" + ' "day_number": 1,\n' + ' "total_target_calories": 2000,\n' + ' "meals": [\n' + " {\n" + ' "meal_type": "BREAKFAST",\n' + ' "food_name": "Food Name",\n' + ' "target_calories": 500,\n' + ' "target_protein": 30.0,\n' + ' "target_carbs": 40.0,\n' + ' "target_fat": 15.0\n' + " }\n" + " ]\n" + " }\n" + " ]\n" + "}\n" + ) + + contents = [prompt] + if raw_text: + contents.append(raw_text) + if image_data: + contents.append( + types.Part.from_bytes(data=image_data, mime_type="image/jpeg",) + ) + + try: + response = await client.aio.models.generate_content( + model=GEMINI_MODEL, + contents=contents, + config=types.GenerateContentConfig(response_mime_type="application/json") + ) + + raw_response = response.text.strip() + if raw_response.startswith("```json"): + raw_response = raw_response[7:] + elif raw_response.startswith("```"): + raw_response = raw_response[3:] + + if raw_response.endswith("```"): + raw_response = raw_response[:-3] + + raw_response = raw_response.strip() + + try: + parsed_result = json.loads(raw_response) + return parsed_result + except Exception as json_err: + logger.error(f"Error parsing JSON from Gemini: {json_err} - Raw Output:\n{response.text}") + return None + + except Exception as e: + logger.error(f"Error in parse_diet_plan: {e}") + return None