File size: 13,499 Bytes
ba0fd1a
14da270
ba0fd1a
 
14da270
 
c0c3449
259a06b
6f497f3
 
259a06b
c0c3449
 
 
6f497f3
259a06b
 
c0c3449
259a06b
c0c3449
259a06b
 
c0c3449
 
 
 
 
259a06b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c0c3449
259a06b
7ed817f
 
 
 
 
 
 
 
 
 
 
 
259a06b
 
 
c0c3449
259a06b
 
 
 
 
 
 
 
 
 
 
c0c3449
259a06b
 
c0c3449
259a06b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c0c3449
259a06b
 
 
 
c0c3449
259a06b
 
 
 
c0c3449
259a06b
 
 
 
c0c3449
259a06b
 
 
 
 
 
 
 
 
c0c3449
259a06b
 
 
 
 
 
 
 
 
 
c0c3449
7ed817f
 
 
 
 
 
 
 
 
 
 
 
c0c3449
 
 
 
 
 
259a06b
c0c3449
259a06b
c0c3449
 
 
 
 
 
 
 
259a06b
 
 
 
7ed817f
 
 
 
259a06b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c0c3449
 
 
 
 
e8b87ec
c0c3449
 
 
e8b87ec
259a06b
c0c3449
259a06b
7ed817f
 
259a06b
e8b87ec
259a06b
c0c3449
 
 
 
 
 
 
 
 
e8b87ec
c0c3449
259a06b
e8b87ec
259a06b
e8b87ec
259a06b
e8b87ec
259a06b
6f497f3
259a06b
 
 
c0c3449
259a06b
 
c0c3449
a307e20
c0c3449
 
a307e20
14da270
259a06b
 
 
6f497f3
259a06b
 
 
 
 
 
 
 
b60ff26
c0c3449
259a06b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b60ff26
ba0fd1a
7ed817f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import streamlit as st
from transformers import pipeline
from PIL import Image
import io
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
import docx
import PyPDF2
from pptx import Presentation as PPTXPresentation
import pandas as pd
import numpy as np
from typing import List, Dict, Any, Optional, Tuple
import time
import requests
from bs4 import BeautifulSoup
import re
import tempfile
import logging
from pathlib import Path
import json

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Nutrition-specific knowledge base
NUTRITION_FIELDS = {
    "Clinical Nutrition": [
        "Medical Nutrition Therapy",
        "Disease-Specific Diets",
        "Nutritional Assessment",
        "Hospital Dietetics",
        "Nutrition Care Process"
    ],
    "Sports Nutrition": [
        "Performance Nutrition",
        "Sports Supplements",
        "Hydration Strategies",
        "Pre/Post Workout Nutrition",
        "Recovery Nutrition"
    ],
    "Community Nutrition": [
        "Public Health Nutrition",
        "Food Security",
        "Population Health",
        "School Nutrition",
        "Cultural Food Practices"
    ],
    "Therapeutic Nutrition": [
        "Diet Therapy",
        "Disease Management",
        "Metabolic Disorders",
        "Diabetes Management",
        "Cardiovascular Nutrition"
    ]
}

class ContentEnricher:
    @staticmethod
    def fetch_open_images(topic: str) -> Optional[str]:
        """Fetch free images from Unsplash's public website"""
        try:
            url = f"https://source.unsplash.com/featured/?{topic.replace(' ', ',')}"
            response = requests.get(url, stream=True)
            if response.status_code == 200:
                return url
        except Exception as e:
            logger.warning(f"Error fetching image: {e}")
        return None

    @staticmethod
    def fetch_pubmed_content(topic: str) -> str:
        """Fetch content from PubMed's public RSS feed"""
        try:
            url = f"https://pubmed.ncbi.nlm.nih.gov/rss/search/1?term={topic.replace(' ', '+')}"
            response = requests.get(url)
            if response.status_code == 200:
                soup = BeautifulSoup(response.content, 'xml')
                items = soup.find_all('item')
                content = []
                for item in items[:3]:  # Get first 3 articles
                    title = item.title.text
                    desc = item.description.text
                    content.append(f"{title}\n{desc}")
                return "\n\n".join(content)
        except Exception as e:
            logger.warning(f"Error fetching PubMed content: {e}")
        return ""

    @staticmethod
    def fetch_nutrition_gov(topic: str) -> str:
        """Fetch content from nutrition.gov public pages"""
        try:
            url = f"https://www.nutrition.gov/search/{topic.replace(' ', '+')}"
            response = requests.get(url)
            if response.status_code == 200:
                soup = BeautifulSoup(response.content, 'html.parser')
                content = []
                for p in soup.find_all('p')[:5]:
                    content.append(p.text.strip())
                return "\n".join(content)
        except Exception as e:
            logger.warning(f"Error fetching Nutrition.gov content: {e}")
        return ""

class FileProcessor:
    @staticmethod
    def process_txt(file) -> str:
        return file.read().decode('utf-8')

    @staticmethod
    def process_docx(file) -> str:
        doc = docx.Document(file)
        return "\n".join([paragraph.text for paragraph in doc.paragraphs])

    @staticmethod
    def process_pdf(file) -> str:
        pdf_reader = PyPDF2.PdfReader(file)
        return "\n".join([page.extract_text() for page in pdf_reader.pages])

    @staticmethod
    def process_pptx(file) -> str:
        prs = PPTXPresentation(file)
        text_content = []
        for slide in prs.slides:
            for shape in slide.shapes:
                if hasattr(shape, "text"):
                    text_content.append(shape.text)
        return "\n".join(text_content)

class PresentationGenerator:
    def __init__(self):
        self.text_generator = self.load_text_generator()
        self.content_enricher = ContentEnricher()
        self.file_processor = FileProcessor()

    @staticmethod
    @st.cache_resource
    def load_text_generator():
        return pipeline("text-generation", model="gpt2-medium")

    def add_image_to_slide(self, slide, image_url: str):
        try:
            response = requests.get(image_url, stream=True)
            if response.status_code == 200:
                img_path = tempfile.NamedTemporaryFile(delete=False, suffix=".jpg").name
                with open(img_path, "wb") as f:
                    f.write(response.content)
                slide.shapes.add_picture(img_path, Inches(1), Inches(1), Inches(4), Inches(3))
        except Exception as e:
            logger.warning(f"Failed to add image to slide: {e}")

    def create_slide(self, prs: Presentation, title: str, points: List[str], image_url: Optional[str], template_style: str) -> None:
        slide = prs.slides.add_slide(prs.slide_layouts[1])
        
        # Set title
        title_shape = slide.shapes.title
        title_shape.text = title
        
        # Style based on template
        if template_style == "Professional":
            title_shape.text_frame.paragraphs[0].font.size = Pt(40)
            title_shape.text_frame.paragraphs[0].font.color.rgb = RGBColor(0, 51, 102)
        
        # Add content
        content_shape = slide.shapes.placeholders[1]
        text_frame = content_shape.text_frame
        
        for point in points:
            p = text_frame.add_paragraph()
            p.text = "β€’ " + point
            p.font.size = Pt(18)
            p.font.color.rgb = RGBColor(51, 51, 51)

        # Add image if available
        if image_url:
            self.add_image_to_slide(slide, image_url)

    def generate_content(self, topic: str, base_content: str, category: str) -> Dict[str, Any]:
        """Generate enhanced content with multiple sources"""
        try:
            # Combine content from multiple sources
            pubmed_content = self.content_enricher.fetch_pubmed_content(topic)
            nutrition_gov_content = self.content_enricher.fetch_nutrition_gov(topic)
            
            combined_content = f"{base_content}\n{pubmed_content}\n{nutrition_gov_content}"
            
            # Generate with context
            prompt = f"Create educational content about {topic} in {category}:\n{combined_content}"
            generated = self.text_generator(
                prompt,
                max_length=500,
                num_return_sequences=1,
                temperature=0.7
            )[0]['generated_text']
            
            # Process into sections
            sections = self.process_content(generated)
            
            return {
                'content': generated,
                'sections': sections,
                'topic': topic,
                'category': category
            }
        except Exception as e:
            logger.error(f"Error generating content: {e}")
            st.error(f"Error generating content: {str(e)}")
            return None

    def process_content(self, content: str, max_points: int = 5) -> List[Dict]:
        """Process content into well-structured sections"""
        sections = []
        current_section = {'title': '', 'points': []}
        
        for line in content.split('\n'):
            line = line.strip()
            if not line:
                continue
                
            if line.endswith(':'):
                if current_section['points']:
                    sections.append(current_section)
                current_section = {'title': line.rstrip(':'), 'points': []}
            elif line.startswith(('β€’', '*', '-')):
                if len(current_section['points']) < max_points:
                    current_section['points'].append(line.lstrip('β€’*- '))
            else:
                sentences = line.split('. ')
                for sentence in sentences[:max_points - len(current_section['points'])]:
                    if sentence.strip():
                        current_section['points'].append(sentence.strip())
                        
        if current_section['points']:
            sections.append(current_section)
            
        return sections

    def create_presentation(self, title: str, content: Dict[str, Any], 
                          template: str) -> Optional[io.BytesIO]:
        try:
            prs = Presentation()
            
            # Title slide
            title_slide = prs.slides.add_slide(prs.slide_layouts[0])
            title_slide.shapes.title.text = title
            
            # Process sections
            progress_bar = st.progress(0)
            for idx, section in enumerate(content['sections']):
                image_url = self.content_enricher.fetch_open_images(section['title'])
                self.create_slide(prs, section['title'], section['points'], image_url, template)
                progress_bar.progress((idx + 1) / len(content['sections']))
            
            # Save
            output = io.BytesIO()
            prs.save(output)
            output.seek(0)
            return output
            
        except Exception as e:
            logger.error(f"Error creating presentation: {e}")
            st.error(f"Error creating presentation: {str(e)}")
            return None

def main():
    st.set_page_config(page_title="DietitianSlide AI", layout="wide", page_icon="πŸ₯—")
    
    generator = PresentationGenerator()
    
    st.title("πŸ₯— DietitianSlide AI - Professional Nutrition Presentations")
    
    # Sidebar settings
    with st.sidebar:
        category = st.selectbox("Nutrition Field", list(NUTRITION_FIELDS.keys()))
        topic = st.selectbox("Specific Topic", NUTRITION_FIELDS[category])
        
        template = st.selectbox("Template Style", 
                              ["Professional", "Academic", "Modern", "Clinical"])
        
        max_points = st.slider("Points per Slide", 3, 8, 5)
    
    # Main content area
    col1, col2 = st.columns([2, 1])
    
    with col1:
        st.subheader("Content Input")
        content_method = st.radio("Input Method", 
                                ["Write Content", "Upload Files", "Both"])
        
        user_content = ""
        if content_method in ["Write Content", "Both"]:
            user_content = st.text_area("Enter your content", height=150)
        
        if content_method in ["Upload Files", "Both"]:
            uploaded_file = st.file_uploader(
                "Upload File",
                type=["txt", "docx", "pdf", "pptx"]
            )
            
            if uploaded_file:
                file_content = ""
                if uploaded_file.type == "text/plain":
                    file_content = generator.file_processor.process_txt(uploaded_file)
                elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
                    file_content = generator.file_processor.process_docx(uploaded_file)
                elif uploaded_file.type == "application/pdf":
                    file_content = generator.file_processor.process_pdf(uploaded_file)
                elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.presentationml.presentation":
                    file_content = generator.file_processor.process_pptx(uploaded_file)
                
                if file_content:
                    st.text_area("Extracted Content", file_content, height=100)
                    user_content = f"{user_content}\n{file_content}" if user_content else file_content
    
    with col2:
        st.subheader("Preview & Generate")
        if st.button("Generate Presentation", type="primary"):
            if not user_content:
                st.error("Please provide some content!")
                return
                
            with st.spinner("Generating your presentation..."):
                content = generator.generate_content(topic, user_content, category)
                
                if content:
                    output = generator.create_presentation(
                        f"{category}: {topic}",
                        content,
                        template
                    )
                    
                    if output:
                        st.success("Presentation generated successfully!")
                        st.download_button(
                            "πŸ“₯ Download Presentation",
                            output,
                            f"nutrition_{topic.lower().replace(' ', '_')}.pptx",
                            "application/vnd.openxmlformats-officedocument.presentationml.presentation"
                        )
                        
                        # Preview content
                        st.subheader("Content Preview")
                        for section in content['sections']:
                            st.write(f"**{section['title']}**")
                            for point in section['points']:
                                st.write(f"β€’ {point}")

if __name__ == "__main__":
    main()