File size: 19,171 Bytes
7570b12
68261ad
 
 
 
 
 
7570b12
68261ad
 
974ba0d
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
 
 
 
 
a9804a6
 
 
 
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
11457d8
 
 
 
 
 
 
 
 
68261ad
71bf6ca
11457d8
68261ad
 
 
 
 
 
 
11457d8
68261ad
 
 
 
 
 
 
71bf6ca
68261ad
 
 
 
 
974ba0d
68261ad
 
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
a9804a6
 
68261ad
a9804a6
68261ad
 
 
a9804a6
68261ad
 
 
 
a9804a6
68261ad
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
 
 
 
a9804a6
 
68261ad
 
 
71bf6ca
68261ad
 
 
a9804a6
68261ad
 
 
71bf6ca
 
 
 
 
 
 
68261ad
 
a9804a6
68261ad
 
 
 
 
71bf6ca
68261ad
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
 
11457d8
68261ad
a9804a6
11457d8
68261ad
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
a9804a6
68261ad
 
 
 
 
 
 
 
 
 
 
 
 
 
a9804a6
68261ad
 
 
 
 
 
11457d8
 
68261ad
 
 
 
 
a9804a6
 
68261ad
 
 
 
 
7570b12
 
a9804a6
68261ad
 
a9804a6
 
 
11457d8
68261ad
 
 
a9804a6
 
68261ad
 
7570b12
68261ad
 
 
 
 
 
 
 
 
7570b12
 
68261ad
 
 
 
 
 
 
 
 
 
 
 
71bf6ca
68261ad
 
 
 
 
 
 
 
 
71bf6ca
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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
import streamlit as st
import base64
from huggingface_hub import upload_file
from openai import OpenAI
from datetime import datetime
import json
import re

# Page configuration - MUST be first Streamlit command
st.set_page_config(
    page_title="Gen AI - Menu Visual Generator",
    layout="wide",
    page_icon="🧪"
)

# API Key Input Section
st.markdown("### 🔑 OpenAI API Configuration")
api_key_input = st.text_input(
    "Enter your OpenAI API Key",
    type="password",
    placeholder="sk-...",
    help="Get your API key from https://platform.openai.com/api-keys"
)

# Initialize OpenAI client with UI input
client = None
if api_key_input:
    try:
        client = OpenAI(api_key=api_key_input)
        st.success("✅ API key provided successfully!")
    except Exception as e:
        st.error(f"❌ Invalid API key: {str(e)}")
        st.stop()

def create_menu_text_from_image(image_file):
    """Extract text from image"""
    try:
        image_bytes = image_file.read()
        base64_image = base64.b64encode(image_bytes).decode('utf-8')

        # Reset file pointer
        image_file.seek(0)

        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": """Please extract all menu items from this image and organize them in a structured format. For each item, extract:
                        1. Item name
                        2. Description (if available)
                        3. Price
                        4. Category (appetizers, mains, desserts, bevrages, etc.)
                        
                        Format your response as a JSON structure like this:
                        {
                            "restaurant_image": "Restaurant Image (if visible),
                            "cusine_type": "Indian/Italian/Mexico/British/etc. (best guess)",
                            "categories": {
                                "Appetizers": [
                                    { "name": "Item Name", "description": "Description (if available)", "price": "$x.xx" }
                                ],
                                "Main Cources": [
                                    { "name": "Item Name", "description": "Description (if available)", "price": "$x.xx" }
                                ],
                            }
                        }
                        
                        If you can't determine the category, group similar items together. Be as accurate as possible with the text extraction."""
                    },
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:image/jpeg;base64,{base64_image}"
                        }
                    }
                ]
            }],
            max_tokens=2000,
        )

        menu_text = response.choices[0].message.content

        # Clean the response and extract JSON
        json_start = menu_text.find("{")
        json_end = menu_text.rfind("}") + 1
        if json_start != -1 and json_end != -1:
            menu_data = json.loads(menu_text[json_start:json_end])
            return menu_data
        else:
            return { "error": "Unable to extract menu data from image", "raw_text": menu_text }

    except Exception as e:
        return { "error": f"Error extracting data from image: {str(e)}" }


def get_nutrinutional_info(dish_name, cuisine_type):
    """Get nutrient information from Nutritionix API"""
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{
                "role": "user",
                "content": f"""For the {cuisine_type} dish '{dish_name}', provide estimated nutritional information in JSON format:
                {{
                    "calories": 350,
                    "protein": 10g,
                    "carbs": 30g,
                    "fat": 15g,
                    "fiber": 5g
                }}
                
                Base your estimate on typical restaurant portions and ingredients for this type of dish. Be realistic with the values."""
            }],
            max_tokens=200,
        )
        nutrient_info = response.choices[0].message.content

        # Clean the response and extract JSON
        json_start = menu_text.find("{")
        json_end = menu_text.rfind("}") + 1
        if json_start != -1 and json_end != -1:
            menu_data = json.loads(menu_text[json_start:json_end])
            return menu_data
        else:
            return { "calories": 'N/A', "protein": 'N/A',  "carbs": 'N/A', "fat": 'N/A', "fiber": 'N/A' }

    except Exception as e:
        return { "calories": 'N/A', "protein": 'N/A',  "carbs": 'N/A', "fat": 'N/A', "fiber": 'N/A' }

def generate_food_image(dish_name, description, cuisine_type, image_style):
    """Generate food image with enhanced prompting"""

    # style configurations
    style_config = {
        "Professional Food Photography": {
            "lighting": "professional studio lighting with soft shadows",
            "background": "clean white or neutral background",
            "composition": "centered plating with professional garnish",
            "quality": "restaurant-quality presentation, high-end food photography"
        },
        "Rustic Homestyle": {
            "lighting": "warm, natural lighting",
            "background": "rusted wooden table or textured surface",
            "composition": "casual, homestyle presentation",
            "quality": "comforting, homemade appearance with natrual styling"
        },
        "Modern Minimalstic": {
            "lighting": "clean, bright lighting",
            "background": "minimalistic white or light gray background",
            "composition": "artistic plating with negative space",
            "quality": "contemporary, Instagram-worthy presentation"
        },
        "Vibrant Colorful": {
            "lighting": "brigt, vibrant lighting that enhances color",
            "background": "colorful or complementary background",
            "composition": "dynamic, eye-catching, presentation",
            "quality": "bold, appetizing colors that pop"
        }
    }

    selected_style = style_config.get(image_style, style_config['Professional Food Photography'])

    # Create detailed food photography prompt
    food_prompt = f"""
Professional food photography of {dish_name} ({cuisine_type} cuisine).
    
Dish details: {description}
    
Photography specifications:
- {selected_style['lighting']}
- {selected_style['background']}
- {selected_style['composition']}
- {selected_style['quality']}

Technical Requirements:
- High resolution, commercial food photography quality
- Appetizing and mouth-watering presentation
- Perfect focus and sharp details
- Colors that enhance appetite appeal
- Professional plating and garnish
- Shot from optimal angle to showcase the dish
- No text or watermark in the image

Style: Photorealistic, magazine-quality food photography that would be used in high-end restaurant menu or food advertising.
"""
    try:
        gen_params = {
            "model": "gpt-image-1",
            "prompt": food_prompt,
            "size": "1024x1024",
            "quality": "high",
            "n": 1
        }

        result = client.images.generate(**gen_params)

        if not result or not result.data or len(result.data) == 0:
            st.error(f"No image data returned for {dish_name}")
            return None, None

        # Get base64 image data directly ( it should work directly )
        image_base64 = result.data[0].b64_json

        # Check if base64 data is valid
        if not image_base64:
            st.error(f"No base64 image returned for {dish_name}")
            return None, None

        image_bytes = base64.b64decode(image_base64)

        # Create a data URL for display purpose
        image_url = f"data:image/jpeg;base64,{image_base64}"

        return image_url, image_bytes
    except Exception as e:
        st.error(f"Error generating food image for {dish_name}: {str(e)}")
        return None, None

# Main UI
st.title("GenAI - Menu Visual Generator")
st.markdown("Transforms your text menu into beautiful visual menu with food photos and nutritional Information!")

if client:
    st.markdown("### Upload your Menu Image")
    st.markdown("Upload a photo of your text-only menu and we'll extract all items automatically!")

    uploaded_file = st.file_uploader(
        "Choose menu image",
        type=['jpg', "jpeg", "png"],
        help="Upload a clear image of your menu with readable text"
    )

    if uploaded_file:
        st.image(uploaded_file, caption="Uploaded Menu Image", width=400)

        if st.button("Extract menu Items", type="primary"):
            with st.spinner("Analyzing menu image and extracting items...."):
                menu_data = create_menu_text_from_image(uploaded_file)

                if "error" in menu_data:
                    st.error(f"Error extracting menu data: {menu_data['error']}")
                    if "raw_text" in menu_data:
                        st.text_area("Raw extracted Text", menu_data['raw_text'], height=200)

                else:
                    st.success("Menu items extracted successfully!")
                    st.session_state.menu_data = menu_data

                    # Display extracted menu structure
                    st.markdown("### Extracted Menu Structure")

                    col1, col2 = st.columns([2, 1])

                    with col1:
                        st.markdown(f"**Restaurant** {menu_data.get('restaurant_image', 'Not detected')}")
                        st.markdown(f"**Cuisine Type** {menu_data.get('cuisine_type', 'Not detected')}")

                        for category, items in menu_data.get('categories', {}).items():
                            st.markdown(f"**{category}**")
                            for item in items:
                                st.markdown(f"- {item['name']} - {item.get('price', 'N/A')}")
                                if item.get('description'):
                                    st.markdown(f"  *{item['description']}*")

                    with col2:
                        st.markdown("**Menu Statistics**")
                        total_items = sum(len(items) for items in menu_data.get('categories', {}).values())
                        st.metric("Total Items", total_items)
                        st.metric("Categories", len(menu_data.get('categories', {})))

    if "menu_data" in st.session_state:
        st.markdown("---")
        st.markdown("## Generate Visual Menu!")

        #configuration options
        cols = st.columns(3)
        col1, col2, col3 = cols[0], cols[1], cols[2]

        with col1:
            image_style = st.selectbox(
                "Food Photo Style",
                [
                    "Professional Food Photography",
                    "Rustic Homestyle",
                    "Modern Minimalstic",
                    "Vibrant Colorful"
                ],
                help="Choose the style of food photography"
            )

        with col2:
            layout_style = st.selectbox(
                "Food Description Display",
                [
                    "Show Descriptions",
                    "Hide Descriptions",
                    "Short Descriptions only"
                ],
                help="Choose how to display food descriptions"
            )

        with col3:
            include_nutrition = st.checkbox(
                "Include Nutritional Information",
                value=True,
                help="Add calories and nutritional information to each dish"
            )


    if st.button("Generate Complete Visual Menu", type="primary", use_container_width=True):
        menu_data = st.session_state.menu_data

        with st.spinner("Generating your visual menu... This may take several minutes as we generate food photos for each item."):

            # progress tracking
            progress_bar = st.progress(0, text="Generating food photos for each item")
            status_text = st.empty()

            dish_images = {}
            nutritional_data = {}

            # get all dishes
            all_dishes = []
            for category, items in menu_data.get('categories', {}).items():
                for item in items:
                    all_dishes.append((category, item))

            total_dishes = len(all_dishes)

            # Generate images and nutrition for each dish
            for i, (category, item) in enumerate(all_dishes):
                dish_name = item['name']
                description = item.get('description', "")

                status_text.text(f"Generating food photo for {dish_name} ({i+1}/{total_dishes})")

                # When you generate the image
                image_bytes, image_url = generate_food_image(
                    dish_name,
                    description,
                    menu_data.get('cuisine_type', "Fine Dining"),
                    image_style
                )

                # Store the image data in the dish_images dictionary
                if image_bytes and image_url:
                    dish_images[dish_name] = {
                        'bytes': image_bytes,
                        'url': image_url
                    }

                # Get nutritional information
                if include_nutrition:
                    nutritional_data[dish_name] = get_nutrinutional_info(dish_name, menu_data.get('cuisine_type', "Fine Dining"))

                # update progress
                progress_bar.progress((i+1)/total_dishes)

            progress_bar.progress(1.0)
            status_text.text("Visual menu generation complete!")

            # Display results
            st.markdown("### Your Visual Menu Results")

            # Show individual food photo
            st.markdown("### Generated Food Photos with Nutritional Information")

            for category, items in menu_data.get('categories', {}).items():
                st.markdown(f"**{category}**")
                cols = st.columns(min(len(items), 3))

                for i, item in enumerate(items):
                    dish_name = item['name']
                    col_index = i % 3

                    with cols[col_index]:
                        if dish_name in dish_images:
                            st.image(
                                dish_images[dish_name]['url'],
                                caption=f"{dish_name} - {item.get('price', '')}",
                                use_column_width=True
                            )

                        if include_nutrition and dish_name in nutritional_data:
                            nutrition = nutritional_data[dish_name]
                            st.markdown(f"**Nutrition** {nutrition.get('calories', 'N/A')} cal, {nutrition.get('protein', 'N/A')} protein")

                        if item.get('description'):
                            st.markdown(f"*{item['description']}*")


        # Download Options
        st.markdown("### Download Individual Food Photos")

        # Create download options for individual images
        download_cols = st.columns(3)
        col_count = 0

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        for category, items in menu_data.get('categories', {}).items():
            st.markdown(f"**Download {category} Photos:**")
            for item in items:
                dish_name = item['name']
                if dish_name in dish_images:
                    clean_name = re.sub(r'[^a-zA-Z0-9]', '', dish_name)

                    with download_cols[col_count % 3]:
                        st.download_button(
                            f"Download {dish_name} Photo",
                            dish_images[dish_name]['bytes'],
                            file_name=f"{clean_name}_{timestamp}.png",
                            mime="image/png",
                            help=f"Download {dish_name} food photo",
                            use_container_width=True
                        )
                    col_count += 1

        restaurant_name_clean = re.sub(r'[^a-zA-Z0-9]', '', menu_data.get('restaurant_name', "menu"))

        st.markdown("### Download Menu Report")

        col_download1, col_download2 = st.columns(2)

        with col_download1:
            st.markdown(f"**Bulk Download**")
            st.info(f"Individual photos available above, or use menu report for compelete details")

        with col_download2:
            menu_summary = f"""VISUAL MENU GENERATED REPORT
Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}

Restaurant: {menu_data.get('restaurant_name', 'N/A')}
Cuisine Type: {menu_data.get('cuisine_type', 'N/A')}
Image Style: {image_style}
Food Description Display: {layout_style}
Nutritional Information: {'Included' if include_nutrition else 'Excluded'}

Menu Items Generated: {total_dishes}
"""

            for category, items in menu_data.get('categories', {}).items():
                menu_summary += f"\n{category}:\n"
                for item in items:
                    menu_summary += f"- {item['name']} - {item.get('price', 'N/A')}\n"
                    if include_nutrition and item['name'] in nutritional_data:
                        nutrition = nutritional_data[item['name']]
                        menu_summary += f"Nutrition: {nutrition.get('calories', 'N/A')} cal, {nutrition.get('protein', 'N/A')} protein\n"

            menu_summary += f"""
Generation Statistics:
- Total Items: {sum(len(items) for items in menu_data.get('categories', {}).values())}
- Categories: {len(menu_data.get('categories', {}))}
- Food Photos Generated: {len(dish_images)}
- Processing Time: Several minutes

File Included:
- Individual food photography for each menu item
- Nutritional information (if selected)

Usage Recommendations:
- Use individual food photos for online menus and delivery apps
- Share on social media to showcase specific dishes
- Use for promotional materials and advertisements
- Update photos seasonally or when menu changes
"""

            st.download_button(
                "Download Menu Report",
                menu_summary.encode("utf-8"),
                file_name=f"menu_report_{restaurant_name_clean}_{timestamp}.txt",
                mime="text/plain",
                help="Download detailed generation report",
                use_container_width=True
            )
else:
    st.info("Please enter OpenAI key to get started!.")

    st.markdown("""
    ### Trasform you Menu with AI
    
    ### **Upload Any Menu Photo**
    - Take a picture of your menu and we'll extract all items automatically!
    
    ### **Visualize Your Menu**
    - Generate beautiful visual menu with food photos and nutritional Information!
    
    ### **Download Your Menu Report**
    - Download detailed report of your menu with all details!    
    """)