Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion app/api/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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}


21 changes: 21 additions & 0 deletions app/models/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
88 changes: 88 additions & 0 deletions app/services/diet_parser_service.py
Original file line number Diff line number Diff line change
@@ -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
Loading