Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from PIL import Image | |
| import os | |
| import base64 | |
| import io | |
| import textwrap | |
| from typing import Optional, Tuple | |
| from dotenv import load_dotenv | |
| from groq import Groq | |
| from reportlab.lib.pagesizes import letter | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage | |
| from reportlab.lib.styles import getSampleStyleSheet | |
| # ====================== | |
| # CONFIGURATION | |
| # ====================== | |
| st.set_page_config( | |
| page_title="Smart Diet Analyzer", | |
| page_icon="π", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| ALLOWED_FILE_TYPES = ['png', 'jpg', 'jpeg'] | |
| MODEL_NAME = "llama-3.2-11b-vision-preview" | |
| MODEL_SETTINGS = { | |
| 'temperature': 0.2, | |
| 'max_tokens': 400, | |
| 'top_p': 0.5 | |
| } | |
| LOGO_PATH = "src/logo.png" | |
| # ====================== | |
| # CACHED RESOURCES | |
| # ====================== | |
| def get_logo_base64() -> Optional[str]: | |
| """Load and cache logo as base64 string""" | |
| try: | |
| with open(LOGO_PATH, "rb") as img_file: | |
| return base64.b64encode(img_file.read()).decode("utf-8") | |
| except FileNotFoundError: | |
| st.error(f"Logo file not found at {LOGO_PATH}") | |
| return None | |
| def initialize_groq_client() -> Groq: | |
| """Initialize and cache Groq API client""" | |
| load_dotenv() | |
| if api_key := os.getenv("GROQ_API_KEY"): | |
| return Groq(api_key=api_key) | |
| st.error("GROQ_API_KEY not found in environment") | |
| st.stop() | |
| # ====================== | |
| # CORE FUNCTIONALITY | |
| # ====================== | |
| def process_image(uploaded_file: io.BytesIO) -> Optional[Tuple[str, str]]: | |
| """Process uploaded image to base64 string with format detection""" | |
| try: | |
| with Image.open(uploaded_file) as img: | |
| fmt = img.format or 'PNG' | |
| buffer = io.BytesIO() | |
| img.save(buffer, format=fmt) | |
| return base64.b64encode(buffer.getvalue()).decode('utf-8'), fmt | |
| except Exception as e: | |
| st.error(f"Image processing error: {str(e)}") | |
| return None | |
| def generate_pdf_content(report_text: str, logo_b64: Optional[str]) -> io.BytesIO: | |
| """Generate PDF report with logo and analysis content""" | |
| buffer = io.BytesIO() | |
| doc = SimpleDocTemplate(buffer, pagesize=letter) | |
| styles = getSampleStyleSheet() | |
| story = [] | |
| # Add logo if available | |
| if logo_b64: | |
| try: | |
| logo_data = base64.b64decode(logo_b64) | |
| with Image.open(io.BytesIO(logo_data)) as logo_img: | |
| aspect = logo_img.height / logo_img.width | |
| max_width = 150 | |
| img_width = min(logo_img.width, max_width) | |
| img_height = img_width * aspect | |
| story.append( | |
| ReportLabImage(io.BytesIO(logo_data), width=img_width, height=img_height) | |
| ) | |
| story.append(Spacer(1, 12)) | |
| except Exception as e: | |
| st.error(f"Logo processing error: {str(e)}") | |
| # Add report content | |
| story.extend([ | |
| Paragraph("<b>Nutrition Analysis Report</b>", styles['Title']), | |
| Spacer(1, 12), | |
| Paragraph(report_text.replace('\n', '<br/>'), styles['BodyText']) | |
| ]) | |
| try: | |
| doc.build(story) | |
| except Exception as e: | |
| st.error(f"PDF generation failed: {str(e)}") | |
| buffer.seek(0) | |
| return buffer | |
| def generate_ai_analysis(client: Groq, image_b64: str, img_format: str) -> Optional[str]: | |
| """Generate nutritional analysis using Groq's vision API""" | |
| vision_prompt = textwrap.dedent(""" | |
| As an expert nutritionist with advanced image analysis capabilities, analyze the provided food image: | |
| 1. Identify all visible food items | |
| 2. Estimate calorie content considering: | |
| - Portion size | |
| - Cooking method | |
| - Food density | |
| 3. Mark estimates as "approximate" when assumptions are needed | |
| 4. Calculate total meal calories | |
| Output format: | |
| - Food Item 1: [Name] β Estimated Calories: [value] kcal | |
| - ... | |
| - **Total Estimated Calories:** [value] kcal | |
| Include confidence levels for unclear images and specify limitations. | |
| """) | |
| try: | |
| response = client.chat.completions.create( | |
| model=MODEL_NAME, | |
| messages=[{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": vision_prompt}, | |
| {"type": "image_url", "image_url": { | |
| "url": f"data:image/{img_format.lower()};base64,{image_b64}" | |
| }} | |
| ] | |
| }], | |
| **MODEL_SETTINGS | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| st.error(f"API Error: {str(e)}") | |
| return None | |
| # ====================== | |
| # UI COMPONENTS | |
| # ====================== | |
| def render_main_content(logo_b64: Optional[str]): | |
| """Main content layout and interactions""" | |
| st.markdown(f""" | |
| <div style="text-align: center;"> | |
| {f'<img src="data:image/png;base64,{logo_b64}" width="100">' if logo_b64 else ''} | |
| <h2 style="color: #4CAF50;">Smart Diet Analyzer</h2> | |
| <p style="color: #FF6347;">AI-Powered Food & Nutrition Analysis</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("---") | |
| if analysis := st.session_state.get('analysis_result'): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| pdf_buffer = generate_pdf_content(analysis, logo_b64) | |
| st.download_button( | |
| "π Download Nutrition Report", | |
| data=pdf_buffer, | |
| file_name="nutrition_report.pdf", | |
| mime="application/pdf" | |
| ) | |
| with col2: | |
| if st.button("Clear Analysis ποΈ"): | |
| del st.session_state.analysis_result | |
| st.rerun() | |
| st.markdown("### π― Nutrition Analysis Report") | |
| st.info(analysis) | |
| def render_sidebar(client: Groq): | |
| """Sidebar upload and processing functionality""" | |
| with st.sidebar: | |
| st.subheader("Meal Image Analysis") | |
| uploaded_file = st.file_uploader( | |
| "Upload Food Image", | |
| type=ALLOWED_FILE_TYPES, | |
| help="Upload clear photo of your meal for analysis" | |
| ) | |
| if not uploaded_file: | |
| return | |
| try: | |
| st.image(Image.open(uploaded_file), caption="Uploaded Meal Image") | |
| except Exception as e: | |
| st.error(f"Invalid image file: {str(e)}") | |
| return | |
| if st.button("Analyze Meal π½οΈ", use_container_width=True): | |
| with st.spinner("Analyzing nutritional content..."): | |
| if img_data := process_image(uploaded_file): | |
| analysis = generate_ai_analysis(client, *img_data) | |
| if analysis: | |
| st.session_state.analysis_result = analysis | |
| st.rerun() | |
| # ====================== | |
| # APPLICATION ENTRYPOINT | |
| # ====================== | |
| def main(): | |
| """Main application controller""" | |
| client = initialize_groq_client() | |
| logo_b64 = get_logo_base64() | |
| render_main_content(logo_b64) | |
| render_sidebar(client) | |
| if __name__ == "__main__": | |
| main() |