Spaces:
Sleeping
Sleeping
| import os | |
| import re | |
| import json | |
| import asyncio | |
| import tempfile | |
| import gradio as gr | |
| from telethon import TelegramClient | |
| from dotenv import load_dotenv | |
| # ================== LOAD ENV ================== | |
| load_dotenv() | |
| # ================== CONFIG ================== | |
| BOT_USERNAME = "mealschatbot" | |
| api_id = int(os.getenv("TG_API_ID")) | |
| api_hash = os.getenv("TG_API_HASH") | |
| phone_number = os.getenv("TG_PHONE") | |
| # ================== PARSER ================== | |
| def parse_food_message(text: str): | |
| if "Calories" not in text: | |
| return None | |
| data = {} | |
| # Item name | |
| name_match = re.search(r"You added:\s*\*\*(.+?)\*\*", text) | |
| if name_match: | |
| data["item"] = name_match.group(1).strip() | |
| # Calories | |
| cal_match = re.search( | |
| r"\*\*Calories\*\*\s*\n+\s*(\d+)\s*kcal", | |
| text, | |
| re.IGNORECASE, | |
| ) | |
| if cal_match: | |
| data["calories_kcal"] = int(cal_match.group(1)) | |
| # Macros | |
| macros = {} | |
| for key, pattern in { | |
| "protein_g": r"Protein:\s*(\d+)g", | |
| "carbs_g": r"Carbs:\s*(\d+)g", | |
| "fat_g": r"Fat:\s*(\d+)g", | |
| }.items(): | |
| m = re.search(pattern, text) | |
| if m: | |
| macros[key] = int(m.group(1)) | |
| if macros: | |
| data["macros"] = macros | |
| # Ingredients | |
| ingredients = [] | |
| block = re.search( | |
| r"\*\*Likely Ingredients\*\*\s*\n+([\s\S]+?)(?:π|$)", | |
| text, | |
| ) | |
| if block: | |
| for line in block.group(1).splitlines(): | |
| line = line.strip("β’ ").strip() | |
| if not line: | |
| continue | |
| m = re.match(r"(.+?)\s*\((\d+)g,\s*(\d+)kcal\)", line) | |
| if m: | |
| ingredients.append({ | |
| "name": m.group(1), | |
| "quantity_g": int(m.group(2)), | |
| "calories_kcal": int(m.group(3)), | |
| }) | |
| if ingredients: | |
| data["likely_ingredients"] = ingredients | |
| return data | |
| # ================== TELEGRAM HANDLER ================== | |
| async def analyze_image(image_path: str): | |
| async with TelegramClient("session", api_id, api_hash) as client: | |
| await client.start(phone=phone_number) | |
| bot = await client.get_entity(BOT_USERNAME) | |
| history = await client.get_messages(bot, limit=1) | |
| last_id_before = history[0].id if history else 0 | |
| await client.send_file(bot, image_path) | |
| await asyncio.sleep(8) | |
| replies = await client.get_messages( | |
| bot, | |
| min_id=last_id_before, | |
| limit=10, | |
| ) | |
| for msg in reversed(replies): | |
| if msg.text: | |
| parsed = parse_food_message(msg.text) | |
| if parsed: | |
| return parsed | |
| return {"error": "No food data detected"} | |
| # ================== GRADIO WRAPPER ================== | |
| def gradio_handler(image): | |
| with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f: | |
| image.save(f.name) | |
| result = asyncio.run(analyze_image(f.name)) | |
| return json.dumps(result, indent=2) | |
| # ================== UI ================== | |
| with gr.Blocks(title="Meal Image β Nutrition JSON") as demo: | |
| gr.Markdown("## π½οΈ Meal Image β Nutrition JSON") | |
| gr.Markdown("Upload a food image. Output will be **pure JSON only**.") | |
| image_input = gr.Image(type="pil", label="Upload Food Image") | |
| json_output = gr.Code(label="Parsed JSON", language="json") | |
| analyze_btn = gr.Button("Analyze") | |
| analyze_btn.click( | |
| fn=gradio_handler, | |
| inputs=image_input, | |
| outputs=json_output, | |
| ) | |
| demo.launch() | |