Ashhar commited on
Commit
726017c
·
1 Parent(s): 4a694a1

visual improvements

Browse files
Files changed (2) hide show
  1. app.py +209 -112
  2. utils.py +29 -1
app.py CHANGED
@@ -1,8 +1,11 @@
1
  import base64
 
 
2
  import streamlit as st
3
  from openai import OpenAI
4
  import os
5
- from utils import pprint
 
6
 
7
  # Load environment variables
8
  from dotenv import load_dotenv
@@ -21,43 +24,53 @@ st.set_page_config(
21
  initial_sidebar_state="expanded"
22
  )
23
  # Custom CSS for styling
24
- st.markdown("""
25
- <style>
26
- .big-font {
27
- font-size:20px !important;
28
- color: #2C3E50;
29
- }
30
- .highlight-box {
31
- background-color: #F5F5F5; /* Soft light gray */
32
- border-radius: 10px;
33
- padding: 20px;
34
- margin-bottom: 20px;
35
- color: #333333; /* Very dark gray for text */
36
- border: 1px solid #E0E0E0; /* Subtle border */
37
- }
38
- .highlight-box h1 {
39
- color: #1A5F7A; /* Deep teal for main heading */
40
- font-size: 24px;
41
- margin-bottom: 15px;
42
- }
43
- .highlight-box h2 {
44
- color: #2C7DA0; /* Slightly lighter teal for subheadings */
45
- font-size: 20px;
46
- margin-top: 15px;
47
- margin-bottom: 10px;
48
- }
49
- .highlight-box h3 {
50
- color: #468FAF; /* Even lighter teal for smaller headings */
51
- font-size: 18px;
52
- margin-top: 10px;
53
- margin-bottom: 8px;
54
- }
55
- .highlight-box p {
56
- color: #333333; /* Dark gray for paragraphs */
57
- line-height: 1.6;
58
- }
59
- </style>
60
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
61
 
62
  # Initialize session state
63
  if "ipAddress" not in st.session_state:
@@ -84,75 +97,150 @@ def google_image_search(query):
84
  return "https://via.placeholder.com/300x200.png?text=" + query.replace(" ", "+")
85
 
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  def analyze_and_generate_recipe(uploaded_image, available_equipment=None, language='English'):
88
  """Analyze food image and generate recipe in a single LLM call"""
89
- with st.spinner('🍳 Analyzing your delicious image and generating recipe...'):
90
- try:
91
- # Prepare the system and user messages
92
- messages = [
93
- {
94
- "role": "system",
95
- "content": f"""You are a professional chef and food analyst.
96
- When analyzing a food image, provide a comprehensive recipe in {language} that considers:
97
- 1. Detailed food description
98
- 2. Complete ingredient list
99
- 3. Cooking method
100
- 4. Step-by-step instructions"""
101
- },
102
- {
103
- "role": "user",
104
- "content": [
105
- {
106
- "type": "image_url",
107
- "image_url": {"url": f"data:image/jpeg;base64,{uploaded_image}"}
108
- },
109
- {
110
- "type": "text",
111
- "text": f"""Analyze this food image and generate a detailed recipe in {language}.
112
-
113
- {'Available cooking equipment: ' + ', '.join(available_equipment) if available_equipment else 'No equipment restrictions'}
114
-
115
- If specific equipment is available, prioritize cooking methods that use those tools.
116
-
117
- Provide:
118
- - Detailed food description
119
- - Ingredient list
120
- - Cooking method adapted to available equipment
121
- - Precise, step-by-step instructions
122
- - Difficulty level
123
- - Estimated cooking time
124
-
125
- Use markdown formatting for clear presentation."""
126
- }
127
- ]
128
- }
129
- ]
130
 
131
- # Log API call details
132
- pprint({
133
- "function": "analyze_and_generate_recipe",
134
- "model": model,
135
- "language": language,
136
- "available_equipment": available_equipment
137
- })
138
-
139
- # Make the LLM call
140
- response = client.chat.completions.create(
141
- model=model,
142
- messages=messages
143
- )
144
 
145
- # Log response details
146
- pprint({
147
- "function": "analyze_and_generate_recipe_response",
148
- "tokens_used": response.usage.total_tokens if response.usage else None,
149
- "response_length": len(response.choices[0].message.content)
150
- })
151
-
152
- return response.choices[0].message.content
153
- except Exception as e:
154
- st.error(f"Error analyzing image and generating recipe: {e}")
155
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
 
158
  def refine_recipe(original_recipe, user_refinement, language='English'):
@@ -241,7 +329,16 @@ img_source = st.radio("Choose image source:", ["Upload from device", "Take a pho
241
  if img_source == "Upload from device":
242
  uploaded_file = st.file_uploader("Choose an image...", type=['jpg', 'jpeg', 'png'])
243
  else:
244
- uploaded_file = st.camera_input("Take a photo of your dish")
 
 
 
 
 
 
 
 
 
245
 
246
  # Food Analysis and Recipe Generation
247
  if uploaded_file is not None:
@@ -305,12 +402,12 @@ if uploaded_file is not None:
305
  else:
306
  st.warning("Please enter refinement preferences.")
307
 
308
- # Visual References
309
- st.markdown("### 🖼️ Visual References", unsafe_allow_html=True)
310
- if st.session_state.original_recipe:
311
- food_name = st.session_state.original_recipe.split('\n')[0]
312
- image_urls = [google_image_search(food_name) for _ in range(3)]
313
 
314
- cols = st.columns(3)
315
- for i, url in enumerate(image_urls):
316
- cols[i].image(url, use_container_width=True)
 
1
  import base64
2
+ import io
3
+ import time
4
  import streamlit as st
5
  from openai import OpenAI
6
  import os
7
+ from PIL import Image
8
+ from utils import pprint, getFontsUrl
9
 
10
  # Load environment variables
11
  from dotenv import load_dotenv
 
24
  initial_sidebar_state="expanded"
25
  )
26
  # Custom CSS for styling
27
+ st.markdown(
28
+ f"""
29
+ <head>
30
+ <link href="{getFontsUrl()}" rel="stylesheet">
31
+ </head>
32
+ """ """
33
+ <style>
34
+ h1 {
35
+ font-family: 'Whisper';
36
+ }
37
+ .big-font {
38
+ font-size:20px !important;
39
+ color: #2C3E50;
40
+ }
41
+ .highlight-box {
42
+ background-color: #F5F5F5; /* Soft light gray */
43
+ border-radius: 10px;
44
+ padding: 20px;
45
+ margin-bottom: 20px;
46
+ color: #333333; /* Very dark gray for text */
47
+ border: 1px solid #E0E0E0; /* Subtle border */
48
+ }
49
+ .highlight-box h1 {
50
+ color: #1A5F7A; /* Deep teal for main heading */
51
+ font-size: 24px;
52
+ margin-bottom: 15px;
53
+ }
54
+ .highlight-box h2 {
55
+ color: #2C7DA0; /* Slightly lighter teal for subheadings */
56
+ font-size: 20px;
57
+ margin-top: 15px;
58
+ margin-bottom: 10px;
59
+ }
60
+ .highlight-box h3 {
61
+ color: #468FAF; /* Even lighter teal for smaller headings */
62
+ font-size: 18px;
63
+ margin-top: 10px;
64
+ margin-bottom: 8px;
65
+ }
66
+ .highlight-box p {
67
+ color: #333333; /* Dark gray for paragraphs */
68
+ line-height: 1.6;
69
+ }
70
+ </style>
71
+ """,
72
+ unsafe_allow_html=True
73
+ )
74
 
75
  # Initialize session state
76
  if "ipAddress" not in st.session_state:
 
97
  return "https://via.placeholder.com/300x200.png?text=" + query.replace(" ", "+")
98
 
99
 
100
+ def resize_image(image_base64, max_size=1024):
101
+ """
102
+ Resize an image from base64 to max dimension of 1024 pixels while maintaining aspect ratio
103
+
104
+ Args:
105
+ image_base64 (str): Base64 encoded image
106
+ max_size (int): Maximum dimension for the image
107
+
108
+ Returns:
109
+ str: Resized image as base64 encoded string
110
+ """
111
+ # Decode base64 image
112
+ image_bytes = base64.b64decode(image_base64)
113
+
114
+ # Open image with Pillow
115
+ img = Image.open(io.BytesIO(image_bytes))
116
+
117
+ # Calculate resize ratio
118
+ width, height = img.size
119
+ resize_ratio = min(max_size / width, max_size / height)
120
+
121
+ # If image is already smaller than max_size, return original
122
+ if resize_ratio >= 1:
123
+ return image_base64
124
+
125
+ # Calculate new dimensions
126
+ new_width = int(width * resize_ratio)
127
+ new_height = int(height * resize_ratio)
128
+
129
+ # Resize image
130
+ resized_img = img.resize((new_width, new_height), Image.LANCZOS)
131
+
132
+ # Convert back to base64
133
+ buffered = io.BytesIO()
134
+ resized_img.save(buffered, format=img.format)
135
+ return base64.b64encode(buffered.getvalue()).decode('utf-8')
136
+
137
+
138
  def analyze_and_generate_recipe(uploaded_image, available_equipment=None, language='English'):
139
  """Analyze food image and generate recipe in a single LLM call"""
140
+ progress_stages = [
141
+ {"message": "🔍 Scanning the delicious image...", "progress": 10},
142
+ {"message": "🧐 Identifying culinary ingredients...", "progress": 30},
143
+ {"message": "🍳 Consulting virtual chef's expertise...", "progress": 50},
144
+ {"message": "📝 Crafting personalized recipe...", "progress": 70},
145
+ {"message": "🌟 Finalizing gourmet instructions...", "progress": 90}
146
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
+ # Create a progress bar
149
+ progress_bar = st.progress(0)
150
+ status_text = st.empty()
151
+
152
+ try:
153
+ # Update progress stages
154
+ for stage in progress_stages:
155
+ status_text.text(stage["message"])
156
+ progress_bar.progress(stage["progress"])
157
+ time.sleep(1) # Short delay between stages
 
 
 
158
 
159
+ # Resize the image before sending to LLM
160
+ resized_image_base64 = resize_image(uploaded_image)
161
+
162
+ # Prepare the system and user messages
163
+ messages = [
164
+ {
165
+ "role": "system",
166
+ "content": f"""You are a professional chef and food analyst.
167
+ When analyzing a food image, provide a comprehensive recipe in {language} that considers:
168
+ 1. Detailed food description
169
+ 2. Complete ingredient list
170
+ 3. Cooking method
171
+ 4. Step-by-step instructions"""
172
+ },
173
+ {
174
+ "role": "user",
175
+ "content": [
176
+ {
177
+ "type": "image_url",
178
+ "image_url": {"url": f"data:image/jpeg;base64,{resized_image_base64}"}
179
+ },
180
+ {
181
+ "type": "text",
182
+ "text": f"""Analyze this food image and generate a detailed recipe in {language}.
183
+
184
+ {'Available cooking equipment: ' + ', '.join(available_equipment) if available_equipment else 'No equipment restrictions'}
185
+
186
+ If specific equipment is available, prioritize cooking methods that use those tools.
187
+
188
+ Provide:
189
+ - Detailed food description
190
+ - Ingredient list
191
+ - Cooking method adapted to available equipment
192
+ - Precise, step-by-step instructions
193
+ - Difficulty level
194
+ - Estimated cooking time
195
+
196
+ Use markdown formatting for clear presentation."""
197
+ }
198
+ ]
199
+ }
200
+ ]
201
+
202
+ # Log API call details
203
+ pprint({
204
+ "function": "analyze_and_generate_recipe",
205
+ "model": model,
206
+ "language": language,
207
+ "available_equipment": available_equipment
208
+ })
209
+
210
+ # Make the LLM call
211
+ status_text.text("🚀 Generating recipe with AI...")
212
+ progress_bar.progress(95)
213
+
214
+ response = client.chat.completions.create(
215
+ model=model,
216
+ messages=messages
217
+ )
218
+
219
+ # Final progress update
220
+ status_text.text("✅ Recipe generated successfully!")
221
+ progress_bar.progress(100)
222
+
223
+ # Clear the progress bar and status text after a short delay
224
+ time.sleep(1)
225
+ progress_bar.empty()
226
+ status_text.empty()
227
+
228
+ # Log response details
229
+ pprint({
230
+ "function": "analyze_and_generate_recipe_response",
231
+ "tokens_used": response.usage.total_tokens if response.usage else None,
232
+ "response_length": len(response.choices[0].message.content)
233
+ })
234
+
235
+ return response.choices[0].message.content
236
+
237
+ except Exception as e:
238
+ # Clear progress indicators in case of error
239
+ progress_bar.empty()
240
+ status_text.empty()
241
+
242
+ st.error(f"Error analyzing image and generating recipe: {e}")
243
+ return None
244
 
245
 
246
  def refine_recipe(original_recipe, user_refinement, language='English'):
 
329
  if img_source == "Upload from device":
330
  uploaded_file = st.file_uploader("Choose an image...", type=['jpg', 'jpeg', 'png'])
331
  else:
332
+ uploaded_file = st.camera_input(
333
+ "Take a photo of your dish",
334
+ help="Please hold your device vertically for best results",
335
+ # Set aspect ratio to portrait (3:4)
336
+ key="portrait_camera",
337
+ args={
338
+ "landscape": False, # Force portrait mode
339
+ "aspectRatio": 3 / 4 # Portrait aspect ratio
340
+ }
341
+ )
342
 
343
  # Food Analysis and Recipe Generation
344
  if uploaded_file is not None:
 
402
  else:
403
  st.warning("Please enter refinement preferences.")
404
 
405
+ # # Visual References
406
+ # st.markdown("### 🖼️ Visual References", unsafe_allow_html=True)
407
+ # if st.session_state.original_recipe:
408
+ # food_name = st.session_state.original_recipe.split('\n')[0]
409
+ # image_urls = [google_image_search(food_name) for _ in range(3)]
410
 
411
+ # cols = st.columns(3)
412
+ # for i, url in enumerate(image_urls):
413
+ # cols[i].image(url, use_container_width=True)
utils.py CHANGED
@@ -3,6 +3,25 @@ import pytz
3
  import streamlit as st
4
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  def __nowInIST() -> DT.datetime:
7
  return DT.datetime.now(pytz.timezone("Asia/Kolkata"))
8
 
@@ -10,4 +29,13 @@ def __nowInIST() -> DT.datetime:
10
  def pprint(log: str):
11
  now = __nowInIST()
12
  now = now.strftime("%Y-%m-%d %H:%M:%S")
13
- print(f"[{now}] [{st.session_state.ipAddress}] {log}")
 
 
 
 
 
 
 
 
 
 
3
  import streamlit as st
4
 
5
 
6
+ FONTS = [
7
+ # "Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900",
8
+ # "Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900",
9
+ # "Raleway:ital,wght@0,100..900;1,100..900",
10
+ # "Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900",
11
+ # "Nunito:ital,wght@0,200..1000;1,200..1000",
12
+ # "Quicksand:wght@300..700",
13
+ "Montserrat:ital,wght@0,100..900;1,100..900",
14
+ # "Edu+AU+VIC+WA+NT+Dots:wght@400..700",
15
+ "Whisper",
16
+ # "Merienda:wght@300..900",
17
+ "Playwrite+DE+Grund:wght@100..400",
18
+ # "Roboto+Slab:wght@100..900",
19
+ # "Open+Sans:ital,wght@0,300..800;1,300..800",
20
+ # "Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000",
21
+ # "Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700",
22
+ ]
23
+
24
+
25
  def __nowInIST() -> DT.datetime:
26
  return DT.datetime.now(pytz.timezone("Asia/Kolkata"))
27
 
 
29
  def pprint(log: str):
30
  now = __nowInIST()
31
  now = now.strftime("%Y-%m-%d %H:%M:%S")
32
+ print(f"[{now}] [{st.session_state.ipAddress}] {log}")
33
+
34
+
35
+ def getFontsUrl():
36
+ baseLink = "https://fonts.googleapis.com/css2"
37
+ params = "&".join([f"family={font}" for font in FONTS])
38
+ params = f"{params}&display=swap"
39
+ fontsUrl = f"{baseLink}?{params}"
40
+ # pprint(f"{fontsUrl=}")
41
+ return fontsUrl