Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from flask import Flask, request, jsonify
|
| 2 |
from flask_cors import CORS
|
| 3 |
from google import genai
|
| 4 |
-
from google.genai import types
|
| 5 |
import os
|
| 6 |
import json
|
| 7 |
import random
|
|
@@ -29,7 +29,6 @@ FS_TOKEN_EXPIRY = 0
|
|
| 29 |
# --- HELPER FUNCTIONS ---
|
| 30 |
|
| 31 |
def clean_json_text(text):
|
| 32 |
-
"""Membersihkan markdown ```json ... ``` jika AI tidak sengaja mengirimnya"""
|
| 33 |
text = text.strip()
|
| 34 |
if text.startswith("```"):
|
| 35 |
parts = text.split("\n", 1)
|
|
@@ -153,7 +152,7 @@ def format_user_context(context_data):
|
|
| 153 |
def home():
|
| 154 |
return jsonify({
|
| 155 |
"status": "online",
|
| 156 |
-
"message": "GastroGuard AI Backend is Running (
|
| 157 |
"endpoints": ["/analyze-text", "/analyze-image", "/chat"]
|
| 158 |
})
|
| 159 |
|
|
@@ -166,13 +165,11 @@ def analyze_text():
|
|
| 166 |
if not query:
|
| 167 |
return jsonify({"error": "No query provided"}), 400
|
| 168 |
|
| 169 |
-
# 1. Try FatSecret First
|
| 170 |
if FS_CLIENT_ID and FS_CLIENT_SECRET:
|
| 171 |
fs_data = search_fatsecret(query)
|
| 172 |
if fs_data:
|
| 173 |
return jsonify(fs_data)
|
| 174 |
|
| 175 |
-
# 2. Check API Key
|
| 176 |
if not client:
|
| 177 |
return jsonify(mock_analyze_food(query))
|
| 178 |
|
|
@@ -180,18 +177,14 @@ def analyze_text():
|
|
| 180 |
user_context_str = format_user_context(user_context)
|
| 181 |
|
| 182 |
prompt_content = f"""
|
| 183 |
-
ROLE:
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
CONTEXT:
|
| 187 |
-
{user_context_str}
|
| 188 |
USER INPUT: "{query}"
|
| 189 |
|
| 190 |
INSTRUCTIONS:
|
| 191 |
1. Identify if the user mentioned a food.
|
| 192 |
2. If yes, ESTIMATE the nutrition facts accurately.
|
| 193 |
-
3.
|
| 194 |
-
4. Your 'chat_response' must be friendly but SHORT (max 2 sentences).
|
| 195 |
|
| 196 |
OUTPUT JSON FORMAT:
|
| 197 |
{{
|
|
@@ -220,7 +213,6 @@ OUTPUT JSON FORMAT:
|
|
| 220 |
|
| 221 |
result = json.loads(clean_json_text(response.text))
|
| 222 |
|
| 223 |
-
# Mapping
|
| 224 |
food_data = result.get("data_makanan", {})
|
| 225 |
nutrisi = food_data.get("nutrisi", {})
|
| 226 |
decision = result.get("keputusan_sistem", {})
|
|
@@ -271,7 +263,6 @@ def analyze_image():
|
|
| 271 |
user_context = {}
|
| 272 |
user_context_str = format_user_context(user_context)
|
| 273 |
|
| 274 |
-
# 1. System Instruction (Aturan Main)
|
| 275 |
system_instruction_text = f"""
|
| 276 |
SYSTEM OVERRIDE: YOU ARE A JSON-ONLY API.
|
| 277 |
ROLE: GastroGuard AI Vision Engine.
|
|
@@ -301,21 +292,17 @@ OUTPUT SCHEMA (STRICT):
|
|
| 301 |
}}
|
| 302 |
}}
|
| 303 |
"""
|
| 304 |
-
|
| 305 |
-
# 2. Build Content Parts
|
| 306 |
request_contents = [
|
| 307 |
-
system_instruction_text,
|
| 308 |
-
types.Part.from_bytes(
|
| 309 |
data=image_bytes,
|
| 310 |
mime_type=file.content_type or "image/jpeg"
|
| 311 |
)
|
| 312 |
]
|
| 313 |
|
| 314 |
-
# Part 3: User Query
|
| 315 |
if user_prompt:
|
| 316 |
request_contents.append(f"USER QUERY: {user_prompt}")
|
| 317 |
|
| 318 |
-
# 3. Execute with JSON Config
|
| 319 |
response_vision = client.models.generate_content(
|
| 320 |
model="gemini-2.5-flash",
|
| 321 |
contents=request_contents,
|
|
@@ -328,14 +315,12 @@ OUTPUT SCHEMA (STRICT):
|
|
| 328 |
text_res = clean_json_text(response_vision.text)
|
| 329 |
result = json.loads(text_res)
|
| 330 |
|
| 331 |
-
# 4. Mapping Result
|
| 332 |
food_data = result.get("data_makanan", {})
|
| 333 |
nutrisi = food_data.get("nutrisi", {})
|
| 334 |
decision = result.get("keputusan_sistem", {})
|
| 335 |
|
| 336 |
health_msg = f"[{decision.get('safety_score', 'Info')}] {decision.get('alasan_utama', '')}"
|
| 337 |
|
| 338 |
-
# APPEND LENGKAP MACROS KE TEKS
|
| 339 |
nutrition_text = f"\n\nπ **{food_data.get('nama_menu', 'Food')} Info:**\nπ₯ {nutrisi.get('kalori', 0)} kcal | π₯© P: {nutrisi.get('protein', 0)}g | π C: {nutrisi.get('karbohidrat', 0)}g | π₯ F: {nutrisi.get('lemak_total', 0)}g"
|
| 340 |
|
| 341 |
final_reply = result.get("chat_response", "Food detected.") + nutrition_text
|
|
@@ -377,20 +362,22 @@ def chat():
|
|
| 377 |
try:
|
| 378 |
user_context_str = format_user_context(user_context)
|
| 379 |
|
| 380 |
-
# --- PERBAIKAN
|
|
|
|
|
|
|
| 381 |
prompt_content = f"""
|
| 382 |
ROLE: GastroGuard AI (Health & Nutrition Expert).
|
| 383 |
CONTEXT: {user_context_str}
|
| 384 |
USER MESSAGE: "{message}"
|
| 385 |
|
| 386 |
-
INSTRUCTIONS:
|
| 387 |
-
1.
|
| 388 |
-
|
| 389 |
-
-
|
| 390 |
-
-
|
| 391 |
-
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
|
| 395 |
OUTPUT JSON SCHEMA:
|
| 396 |
{{
|
|
@@ -411,8 +398,7 @@ OUTPUT JSON SCHEMA:
|
|
| 411 |
contents=prompt_content,
|
| 412 |
config=types.GenerateContentConfig(
|
| 413 |
response_mime_type="application/json",
|
| 414 |
-
# Temperature
|
| 415 |
-
temperature=0.3
|
| 416 |
)
|
| 417 |
)
|
| 418 |
|
|
@@ -422,17 +408,22 @@ OUTPUT JSON SCHEMA:
|
|
| 422 |
food_data = result.get("data_makanan", {})
|
| 423 |
nutrisi = food_data.get("nutrisi", {})
|
| 424 |
|
| 425 |
-
# ---
|
| 426 |
-
#
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
final_reply += nutrition_text
|
| 431 |
|
| 432 |
mapped_result = {
|
| 433 |
"reply": final_reply,
|
| 434 |
"food_name": food_data.get("nama_menu"),
|
| 435 |
-
"calories":
|
| 436 |
"protein": nutrisi.get("protein", 0),
|
| 437 |
"carbs": nutrisi.get("karbohidrat", 0),
|
| 438 |
"fat": nutrisi.get("lemak_total", 0),
|
|
|
|
| 1 |
from flask import Flask, request, jsonify
|
| 2 |
from flask_cors import CORS
|
| 3 |
from google import genai
|
| 4 |
+
from google.genai import types
|
| 5 |
import os
|
| 6 |
import json
|
| 7 |
import random
|
|
|
|
| 29 |
# --- HELPER FUNCTIONS ---
|
| 30 |
|
| 31 |
def clean_json_text(text):
|
|
|
|
| 32 |
text = text.strip()
|
| 33 |
if text.startswith("```"):
|
| 34 |
parts = text.split("\n", 1)
|
|
|
|
| 152 |
def home():
|
| 153 |
return jsonify({
|
| 154 |
"status": "online",
|
| 155 |
+
"message": "GastroGuard AI Backend is Running (Aggressive Data Mode)!",
|
| 156 |
"endpoints": ["/analyze-text", "/analyze-image", "/chat"]
|
| 157 |
})
|
| 158 |
|
|
|
|
| 165 |
if not query:
|
| 166 |
return jsonify({"error": "No query provided"}), 400
|
| 167 |
|
|
|
|
| 168 |
if FS_CLIENT_ID and FS_CLIENT_SECRET:
|
| 169 |
fs_data = search_fatsecret(query)
|
| 170 |
if fs_data:
|
| 171 |
return jsonify(fs_data)
|
| 172 |
|
|
|
|
| 173 |
if not client:
|
| 174 |
return jsonify(mock_analyze_food(query))
|
| 175 |
|
|
|
|
| 177 |
user_context_str = format_user_context(user_context)
|
| 178 |
|
| 179 |
prompt_content = f"""
|
| 180 |
+
ROLE: You are GastroGuard AI. STRICT CALORIE LOGGING ASSISTANT.
|
| 181 |
+
CONTEXT: {user_context_str}
|
|
|
|
|
|
|
|
|
|
| 182 |
USER INPUT: "{query}"
|
| 183 |
|
| 184 |
INSTRUCTIONS:
|
| 185 |
1. Identify if the user mentioned a food.
|
| 186 |
2. If yes, ESTIMATE the nutrition facts accurately.
|
| 187 |
+
3. Your 'chat_response' must be friendly but SHORT.
|
|
|
|
| 188 |
|
| 189 |
OUTPUT JSON FORMAT:
|
| 190 |
{{
|
|
|
|
| 213 |
|
| 214 |
result = json.loads(clean_json_text(response.text))
|
| 215 |
|
|
|
|
| 216 |
food_data = result.get("data_makanan", {})
|
| 217 |
nutrisi = food_data.get("nutrisi", {})
|
| 218 |
decision = result.get("keputusan_sistem", {})
|
|
|
|
| 263 |
user_context = {}
|
| 264 |
user_context_str = format_user_context(user_context)
|
| 265 |
|
|
|
|
| 266 |
system_instruction_text = f"""
|
| 267 |
SYSTEM OVERRIDE: YOU ARE A JSON-ONLY API.
|
| 268 |
ROLE: GastroGuard AI Vision Engine.
|
|
|
|
| 292 |
}}
|
| 293 |
}}
|
| 294 |
"""
|
|
|
|
|
|
|
| 295 |
request_contents = [
|
| 296 |
+
system_instruction_text,
|
| 297 |
+
types.Part.from_bytes(
|
| 298 |
data=image_bytes,
|
| 299 |
mime_type=file.content_type or "image/jpeg"
|
| 300 |
)
|
| 301 |
]
|
| 302 |
|
|
|
|
| 303 |
if user_prompt:
|
| 304 |
request_contents.append(f"USER QUERY: {user_prompt}")
|
| 305 |
|
|
|
|
| 306 |
response_vision = client.models.generate_content(
|
| 307 |
model="gemini-2.5-flash",
|
| 308 |
contents=request_contents,
|
|
|
|
| 315 |
text_res = clean_json_text(response_vision.text)
|
| 316 |
result = json.loads(text_res)
|
| 317 |
|
|
|
|
| 318 |
food_data = result.get("data_makanan", {})
|
| 319 |
nutrisi = food_data.get("nutrisi", {})
|
| 320 |
decision = result.get("keputusan_sistem", {})
|
| 321 |
|
| 322 |
health_msg = f"[{decision.get('safety_score', 'Info')}] {decision.get('alasan_utama', '')}"
|
| 323 |
|
|
|
|
| 324 |
nutrition_text = f"\n\nπ **{food_data.get('nama_menu', 'Food')} Info:**\nπ₯ {nutrisi.get('kalori', 0)} kcal | π₯© P: {nutrisi.get('protein', 0)}g | π C: {nutrisi.get('karbohidrat', 0)}g | π₯ F: {nutrisi.get('lemak_total', 0)}g"
|
| 325 |
|
| 326 |
final_reply = result.get("chat_response", "Food detected.") + nutrition_text
|
|
|
|
| 362 |
try:
|
| 363 |
user_context_str = format_user_context(user_context)
|
| 364 |
|
| 365 |
+
# --- PERBAIKAN LOGIKA CHAT ---
|
| 366 |
+
# Prompt ini memaksa AI untuk "EXTRACT DATA" jika ada nama makanan
|
| 367 |
+
# meskipun user menggunakan kata "mau" (intent).
|
| 368 |
prompt_content = f"""
|
| 369 |
ROLE: GastroGuard AI (Health & Nutrition Expert).
|
| 370 |
CONTEXT: {user_context_str}
|
| 371 |
USER MESSAGE: "{message}"
|
| 372 |
|
| 373 |
+
MANDATORY INSTRUCTIONS:
|
| 374 |
+
1. If the user mentions ANY food name (e.g., "I want to eat X", "X calories?", "Eating X"):
|
| 375 |
+
- YOU MUST ESTIMATE THE NUTRITION FACTS in 'data_makanan'.
|
| 376 |
+
- DO NOT leave calorie values as 0.
|
| 377 |
+
- FILL 'nama_menu' with the specific food name.
|
| 378 |
+
- IGNORE verbs like "want to" or "planning to". If food is named, DATA IS REQUIRED.
|
| 379 |
+
2. If no food is mentioned (e.g., "Hello", "My stomach hurts"), keep nutrition 0.
|
| 380 |
+
3. RETURN JSON ONLY.
|
| 381 |
|
| 382 |
OUTPUT JSON SCHEMA:
|
| 383 |
{{
|
|
|
|
| 398 |
contents=prompt_content,
|
| 399 |
config=types.GenerateContentConfig(
|
| 400 |
response_mime_type="application/json",
|
| 401 |
+
temperature=0.3 # Temperature rendah = patuh instruksi
|
|
|
|
| 402 |
)
|
| 403 |
)
|
| 404 |
|
|
|
|
| 408 |
food_data = result.get("data_makanan", {})
|
| 409 |
nutrisi = food_data.get("nutrisi", {})
|
| 410 |
|
| 411 |
+
# --- PYTHON LOGIC FIX ---
|
| 412 |
+
# Pastikan kalori dianggap angka (bukan string) untuk pengecekan
|
| 413 |
+
try:
|
| 414 |
+
cal_val = float(nutrisi.get("kalori", 0))
|
| 415 |
+
except:
|
| 416 |
+
cal_val = 0
|
| 417 |
+
|
| 418 |
+
# Jika ada nama menu DAN kalori > 0, tempelkan teks nutrisi
|
| 419 |
+
if food_data.get("nama_menu") and cal_val > 0:
|
| 420 |
+
nutrition_text = f"\n\nπ **{food_data.get('nama_menu')} Info:**\nπ₯ {cal_val} kcal | π₯© P: {nutrisi.get('protein', 0)}g | π C: {nutrisi.get('karbohidrat', 0)}g | π₯ F: {nutrisi.get('lemak_total', 0)}g"
|
| 421 |
final_reply += nutrition_text
|
| 422 |
|
| 423 |
mapped_result = {
|
| 424 |
"reply": final_reply,
|
| 425 |
"food_name": food_data.get("nama_menu"),
|
| 426 |
+
"calories": cal_val,
|
| 427 |
"protein": nutrisi.get("protein", 0),
|
| 428 |
"carbs": nutrisi.get("karbohidrat", 0),
|
| 429 |
"fat": nutrisi.get("lemak_total", 0),
|