Riteesh2k6 commited on
Commit
00407dc
·
verified ·
1 Parent(s): c86a973

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +787 -662
src/streamlit_app.py CHANGED
@@ -1,10 +1,26 @@
1
  import streamlit as st
2
  import pandas as pd
 
3
  import json
 
4
  import uuid
5
  from datetime import datetime
6
- from collections import Counter
7
  import base64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  # --- Page Configuration ---
10
  st.set_page_config(
@@ -13,126 +29,310 @@ st.set_page_config(
13
  layout="wide"
14
  )
15
 
16
- # --- In-Memory Storage Functions ---
17
- def generate_recipe_id():
18
- """Generate a unique ID for each recipe"""
19
- return str(uuid.uuid4())[:8]
20
-
21
- def save_image_to_memory(image_file, recipe_id):
22
- """Save uploaded image to session state as base64"""
23
- if image_file is not None:
24
- try:
25
- # Convert image to base64 for storage in memory
26
- image_bytes = image_file.getvalue()
27
- image_b64 = base64.b64encode(image_bytes).decode()
28
- file_extension = image_file.name.split('.')[-1].lower()
29
-
30
- # Store in session state
31
- if 'recipe_images' not in st.session_state:
32
- st.session_state['recipe_images'] = {}
33
-
34
- image_key = f"{recipe_id}.{file_extension}"
35
- st.session_state['recipe_images'][image_key] = {
36
- 'data': image_b64,
37
- 'type': image_file.type
38
- }
39
-
40
- return image_key
41
- except Exception as e:
42
- st.error(f"Error saving image: {e}")
43
- return None
44
- return None
45
-
46
- def load_image_from_memory(image_filename):
47
- """Load image from session state"""
48
- if image_filename and 'recipe_images' in st.session_state:
49
- try:
50
- if image_filename in st.session_state['recipe_images']:
51
- image_data = st.session_state['recipe_images'][image_filename]
52
- return base64.b64decode(image_data['data'])
53
- except Exception as e:
54
- st.error(f"Error loading image: {e}")
55
- return None
56
-
57
- def save_recipe_to_memory(recipe_data):
58
- """Save recipe to session state"""
59
  try:
60
- if 'recipes' not in st.session_state:
61
- st.session_state['recipes'] = []
 
 
 
 
 
 
 
62
 
63
- # Add to the beginning of the list (newest first)
64
- st.session_state['recipes'].insert(0, recipe_data)
65
- return True
 
 
 
 
66
  except Exception as e:
67
- st.error(f"Error saving recipe: {e}")
68
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- def load_all_recipes_from_memory():
71
- """Load all recipes from session state"""
72
- if 'recipes' not in st.session_state:
73
- st.session_state['recipes'] = []
74
- return st.session_state['recipes']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- def delete_recipe_from_memory(recipe_id):
77
- """Delete a recipe and its associated image from memory"""
 
 
 
78
  try:
79
- # Delete recipe
80
- if 'recipes' in st.session_state:
81
- st.session_state['recipes'] = [
82
- r for r in st.session_state['recipes']
83
- if r.get('id') != recipe_id
84
- ]
 
 
 
 
 
 
 
 
85
 
86
- # Delete associated image
87
- if 'recipe_images' in st.session_state:
88
- keys_to_delete = [
89
- key for key in st.session_state['recipe_images'].keys()
90
- if key.startswith(recipe_id)
91
- ]
92
- for key in keys_to_delete:
93
- del st.session_state['recipe_images'][key]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
 
 
95
  return True
96
  except Exception as e:
97
- st.error(f"Error deleting recipe: {e}")
98
  return False
99
 
100
- def get_recipe_stats():
101
- """Get statistics about stored recipes"""
 
102
  try:
103
- recipes = load_all_recipes_from_memory()
104
- images = st.session_state.get('recipe_images', {})
105
 
106
- # Calculate approximate storage size
107
- storage_size = 0
108
- for recipe in recipes:
109
- storage_size += len(json.dumps(recipe, ensure_ascii=False).encode('utf-8'))
 
 
 
110
 
111
- for image_data in images.values():
112
- storage_size += len(image_data['data']) * 3 // 4 # Approximate decoded size
113
 
114
- return {
115
- 'total_recipes': len(recipes),
116
- 'total_images': len(images),
117
- 'storage_size': storage_size
118
- }
119
- except:
120
- return {'total_recipes': 0, 'total_images': 0, 'storage_size': 0}
121
 
122
  # --- Language and Text ---
123
  TEXT = {
124
  "en": {
125
  "title": "Ruchi Chudu",
126
- "subtitle": "A Community Cookbook to Preserve Telugu Cuisine",
127
  "language_select": "Choose Language",
128
  "sidebar_header": "Contribute Your Recipe!",
129
  "tab_submit": "📝 Submit Your Recipe",
130
- "tab_gallery": "🍲 Community Gallery",
 
131
  "tab_analytics": "📊 Recipe Analytics",
132
- "tab_manage": "⚙️ Manage Recipes",
133
  "form_header": "Tell us about your dish",
134
  "dish_name": "Dish Name",
135
- "your_name": "Your Name",
136
  "district": "Your District",
137
  "select_district": "--- Select a District ---",
138
  "recipe_type": "Recipe Type (e.g., Curry, Pickle, Snack)",
@@ -143,89 +343,41 @@ TEXT = {
143
  "submit_button": "Submit Recipe",
144
  "success_message": "Thank you! Your recipe has been submitted successfully.",
145
  "gallery_header": "Explore Recipes from the Community",
146
- "gallery_empty": "No recipes submitted yet. Be the first to share!",
147
- "submitted_by": "Submitted by",
148
- "from_district": "from",
149
- "submitted_on": "Submitted on",
150
- "recipe_id": "Recipe ID",
151
- "story_title": "📖 The Story",
152
- "ingredients_title": "🌶️ Ingredients",
153
- "instructions_title": "✍️ Instructions",
154
- "tags_title": "AI-Generated Tags",
155
- "error_dish_name": "Please enter a dish name before submitting.",
156
- "search_placeholder": "Search recipes...",
157
- "filter_by_district": "Filter by District",
158
- "filter_by_type": "Filter by Recipe Type",
159
- "all_districts": "All Districts",
160
- "all_types": "All Recipe Types",
161
- "total_recipes": "Total Recipes",
162
- "total_images": "Total Images",
163
- "storage_size": "Storage Used",
164
- "top_districts": "Top Contributing Districts",
165
- "popular_types": "Popular Recipe Types",
166
- "recent_recipes": "Recent Submissions",
167
- "clear_filters": "Clear All Filters",
168
- "export_recipes": "Export All Recipes",
169
- "manage_header": "Recipe Management",
170
- "delete_recipe": "Delete Recipe",
171
- "confirm_delete": "Are you sure you want to delete this recipe?",
172
- "recipe_deleted": "Recipe deleted successfully!",
173
- "backup_data": "Download All Data",
174
- "backup_created": "Data downloaded successfully!",
175
- "memory_storage_note": "Note: Data is stored in memory and will be lost when the session ends."
176
  },
177
  "te": {
178
- "title": "రుచి చూడు",
179
- "subtitle": "మన తెలుగు వంటల సంస్కృతిని కాపాడుదాం",
180
  "language_select": "భాషను ఎంచుకోండి",
181
  "sidebar_header": "మీ వంటకాన్ని పంపండి!",
182
  "tab_submit": "📝 మీ వంటకాన్ని పంపండి",
183
  "tab_gallery": "🍲 కమ్యూనిటీ గ్యాలరీ",
 
184
  "tab_analytics": "📊 వంటకాల విశ్లేషణ",
185
- "tab_manage": "⚙️ ��ంటకాలను నిర్వహించండి",
186
  "form_header": "మీ వంటకం గురించి చెప్పండి",
187
  "dish_name": "వంటకం పేరు",
188
  "your_name": "మీ పేరు",
189
- "district": "మీ జిల్లా",
190
  "select_district": "--- జిల్లాను ఎంచుకోండి ---",
191
  "recipe_type": "వంటకం రకం (ఉదా. కూర, పచ్చడి, స్నాక్)",
192
  "ingredients": "కావలసినవి (ఒకదానికి ఒకటి)",
193
  "instructions": "తయారీ విధానం",
194
  "story": "ఈ వంటతో మీ కథ (మీ జ్ఞాపకాలు, కుటుంబ సంప్రదాయాలు మొదలైనవి)",
195
  "image_upload": "వంటకం ఫోటోను అప్‌లోడ్ చేయండి (ఐచ్ఛికం)",
196
- "submit_button": "వంటకాన్ని పంపండి",
197
  "success_message": "ధన్యవాదాలు! మీ వంటకం విజయవంతంగా సమర్పించబడింది.",
198
- "gallery_header": "కమ్యూనిటీ నుంి వంటకాలను అన్వేషించండి",
199
- "gallery_empty": "ఇంకా వంటకాలు సమర్పించబడలేదు. పంచుకున్న మొదటి ్యక్తి మీరే అవవండి!",
200
- "submitted_by": "సమర్పించిన వాు",
201
- "from_district": "ని",
202
- "submitted_on": "సమర్పిచిన తేదీ",
203
- "recipe_id": "వంటకం ID",
204
- "story_title": "📖",
205
- "ingredients_title": "🌶️ కావలసినవి",
206
- "instructions_title": "✍️ తయారీ విధానం",
207
- "tags_title": "AI ద్వారా రూపొందించబడిన ట్యాగ్‌లు",
208
- "error_dish_name": "సమర్పించే ముందు దయచేసి వంటకం పేరును నమోదు చేయండి.",
209
- "search_placeholder": "వంటకాలను వెతకండి...",
210
- "filter_by_district": "జిల్లా ద్వారా ఫిల్టర్ చేయండి",
211
- "filter_by_type": "వంటకం రకం ద్వారా ఫిల్టర్ చేయండి",
212
- "all_districts": "అన్ని జిల్లాలు",
213
- "all_types": "అన్ని వంటకాల రకాలు",
214
- "total_recipes": "మొత్తం వంటకాలు",
215
- "total_images": "మొత్తం చిత్రాలు",
216
- "storage_size": "వాడిన స్టోరేజ్",
217
- "top_districts": "అధిక సహకారం అందించిన జిల్లాలు",
218
- "popular_types": "ప్రాచుర్యం పొందిన వంటకాల రకాలు",
219
- "recent_recipes": "ఇటీవలి సమర్పణలు",
220
- "clear_filters": "అన్ని ఫిల్టర్లను క్లియర్ చేయండి",
221
- "export_recipes": "అన్ని వంటకాలను ఎగుమతి చేయండి",
222
- "manage_header": "వంటకాల నిర్వహణ",
223
- "delete_recipe": "వంటకాన్ని తొలగించండి",
224
- "confirm_delete": "మీరు ఖచ్చితంగా ఈ వంటకాన్ని తొలగించాలనుకుంటున్నారా?",
225
- "recipe_deleted": "వంటకం విజయవంతంగా తొలగించబడింది!",
226
- "backup_data": "అన్ని డేటాను డౌన్‌లోడ్ చేయండి",
227
- "backup_created": "డేటా విజయవంతంగా డౌన్‌లోడ్ చేయబడింది!",
228
- "memory_storage_note": "గమనిక: డేటా మెమరీలో నిల్వ చేయబడుతుంది మరియు సెషన్ ముగిసినప్పుడు పోతుంది."
229
  }
230
  }
231
 
@@ -250,94 +402,36 @@ RECIPE_TYPES = ["Curry (కూర)", "Fry (వేపుడు)", "Pickle (పచ
250
  if 'language' not in st.session_state:
251
  st.session_state['language'] = 'en'
252
  if 'recipes' not in st.session_state:
253
- st.session_state['recipes'] = []
254
- if 'recipe_images' not in st.session_state:
255
- st.session_state['recipe_images'] = {}
256
 
257
  # --- Helper Functions ---
258
  def get_text(key):
259
  """Fetches text from the dictionary based on the selected language."""
260
  return TEXT[st.session_state['language']][key]
261
 
262
- def simulate_ai_tagging(story, ingredients):
263
- """Simulate AI tagging based on content analysis"""
264
- tags = set()
265
- text_to_scan = (story + " " + ingredients).lower()
266
-
267
- keyword_map = {
268
- "festival": ["sankranti", "ugadi", "dasara", "diwali", "vinayaka chaviti"],
269
- "spicy": ["chilli", "karam", "mirapa", "pepper", "guntur"],
270
- "healthy": ["millet", "ragi", "jowar", "vegetable", "leafy", "greens"],
271
- "quick": ["15 min", "20 min", "quick", "easy", "tvaraga"],
272
- "traditional": ["grandma", "ammamma", "nanamma", "traditional", "sampradayam"],
273
- "summer": ["mango", "summer", "cool", "curd", "perugu"],
274
- "winter": ["winter", "warm", "hot", "sheeta"],
275
- "vegetarian": ["vegetable", "veg", "sabzi", "kura"],
276
- "non-vegetarian": ["chicken", "mutton", "fish", "egg", "kodi", "meka"]
277
- }
278
-
279
- for tag, keywords in keyword_map.items():
280
- if any(keyword in text_to_scan for keyword in keywords):
281
- tags.add(tag.capitalize())
282
-
283
- if not tags:
284
- tags.add("Community Recipe")
285
-
286
- return list(tags)
287
-
288
- def filter_recipes(recipes, search_term="", district_filter="", type_filter=""):
289
- """Filter recipes based on search and filter criteria"""
290
- filtered = recipes
291
-
292
- if search_term:
293
- filtered = [r for r in filtered if
294
- search_term.lower() in r['dish_name'].lower() or
295
- search_term.lower() in r.get('your_name', '').lower() or
296
- search_term.lower() in r.get('ingredients', '').lower()]
297
-
298
- if district_filter and district_filter != get_text('all_districts'):
299
- filtered = [r for r in filtered if r.get('district') == district_filter]
300
-
301
- if type_filter and type_filter != get_text('all_types'):
302
- filtered = [r for r in filtered if r.get('recipe_type') == type_filter]
303
-
304
- return filtered
305
-
306
- def export_recipes_to_csv(recipes):
307
- """Export recipes to CSV format"""
308
- if not recipes:
309
- return None
310
-
311
- export_data = []
312
- for recipe in recipes:
313
- export_data.append({
314
- 'Recipe ID': recipe.get('id', ''),
315
- 'Dish Name': recipe['dish_name'],
316
- 'Submitted By': recipe.get('your_name', ''),
317
- 'District': recipe.get('district', ''),
318
- 'Recipe Type': recipe.get('recipe_type', ''),
319
- 'Ingredients': recipe.get('ingredients', ''),
320
- 'Instructions': recipe.get('instructions', ''),
321
- 'Story': recipe.get('story', ''),
322
- 'Tags': ', '.join(recipe.get('tags', [])),
323
- 'Submitted Date': recipe.get('submitted_date', ''),
324
- 'Has Image': 'Yes' if recipe.get('image_filename') else 'No'
325
- })
326
-
327
- return pd.DataFrame(export_data)
328
-
329
- def format_storage_size(size_bytes):
330
- """Convert bytes to human readable format"""
331
- for unit in ['B', 'KB', 'MB', 'GB']:
332
- if size_bytes < 1024.0:
333
- return f"{size_bytes:.1f} {unit}"
334
- size_bytes /= 1024.0
335
- return f"{size_bytes:.1f} TB"
336
 
337
  # --- Sidebar ---
338
  with st.sidebar:
339
  st.title(f"🍲 {get_text('title')}")
340
  st.markdown(get_text('subtitle'))
 
 
 
 
 
 
341
  st.markdown("---")
342
 
343
  # Language selector
@@ -350,27 +444,19 @@ with st.sidebar:
350
  st.session_state['language'] = 'te' if lang_choice == 'తెలుగు' else 'en'
351
 
352
  st.info(get_text('sidebar_header'))
353
-
354
- # Storage stats
355
- stats = get_recipe_stats()
356
- st.metric(get_text('total_recipes'), stats['total_recipes'])
357
- st.metric(get_text('total_images'), stats['total_images'])
358
- st.metric(get_text('storage_size'), format_storage_size(stats['storage_size']))
359
-
360
- # Memory storage note
361
- st.warning(get_text('memory_storage_note'))
362
 
363
  # --- Main App Layout ---
364
  st.title(get_text('title'))
365
 
366
- tab_submit, tab_gallery, tab_analytics, tab_manage = st.tabs([
 
367
  get_text('tab_submit'),
368
  get_text('tab_gallery'),
369
- get_text('tab_analytics'),
370
- get_text('tab_manage')
371
  ])
372
 
373
- # --- Submission Form Tab ---
374
  with tab_submit:
375
  st.header(get_text('form_header'))
376
 
@@ -397,19 +483,12 @@ with tab_submit:
397
 
398
  if submitted:
399
  if not dish_name:
400
- st.error(get_text('error_dish_name'))
401
  else:
402
- # Generate unique ID for this recipe
403
  recipe_id = generate_recipe_id()
404
 
405
- # Save image if provided
406
- image_filename = save_image_to_memory(image_file, recipe_id)
407
-
408
- # Generate AI tags
409
- ai_tags = simulate_ai_tagging(story, ingredients)
410
-
411
- # Create new recipe with unique ID
412
- new_recipe = {
413
  "id": recipe_id,
414
  "dish_name": dish_name,
415
  "your_name": your_name,
@@ -418,444 +497,490 @@ with tab_submit:
418
  "ingredients": ingredients,
419
  "instructions": instructions,
420
  "story": story,
421
- "image_filename": image_filename,
422
- "tags": ai_tags,
423
  "submitted_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
424
  }
425
 
426
- # Save recipe to memory
427
- if save_recipe_to_memory(new_recipe):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  st.success(f"{get_text('success_message')} (ID: {recipe_id})")
429
  st.balloons()
430
-
431
- # Show recipe details
432
- with st.expander("Recipe Details", expanded=True):
433
- st.write(f"**{get_text('recipe_id')}:** `{recipe_id}`")
434
- st.write(f"**Storage:** In-memory (session-based)")
435
- if image_filename:
436
- st.write(f"**Image:** Stored as {image_filename}")
437
  else:
438
  st.error("Failed to save recipe. Please try again.")
439
 
440
- # --- Community Gallery Tab ---
441
  with tab_gallery:
442
  st.header(get_text('gallery_header'))
443
 
444
- # Filters and search
445
- col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
 
447
  with col1:
448
- search_term = st.text_input(
449
- label="",
450
- placeholder=get_text('search_placeholder'),
451
- key="search_recipes"
452
- )
453
 
454
  with col2:
455
- district_options = [get_text('all_districts')] + DISTRICTS
456
- district_filter = st.selectbox(
457
- get_text('filter_by_district'),
458
- district_options,
459
- key="district_filter"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  )
461
 
462
- with col3:
463
- type_options = [get_text('all_types')] + RECIPE_TYPES
464
- type_filter = st.selectbox(
465
- get_text('filter_by_type'),
466
- type_options,
467
- key="type_filter"
468
  )
469
 
470
- with col4:
471
- if st.button(get_text('clear_filters')):
472
- st.session_state.search_recipes = ""
473
- st.session_state.district_filter = get_text('all_districts')
474
- st.session_state.type_filter = get_text('all_types')
475
- st.rerun()
476
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  st.markdown("---")
478
 
479
- # Filter recipes
480
- recipes = load_all_recipes_from_memory()
481
- filtered_recipes = filter_recipes(
482
- recipes,
483
- search_term,
484
- district_filter,
485
- type_filter
486
- )
487
 
488
- if not filtered_recipes:
489
- if not recipes:
490
- st.info(get_text('gallery_empty'))
491
- else:
492
- st.info("No recipes match your search criteria. Try adjusting your filters.")
493
- else:
494
- st.write(f"**{len(filtered_recipes)}** recipes found")
495
 
496
- # Display recipes
497
- for recipe in filtered_recipes:
498
- with st.container():
499
- st.markdown("""
500
- <style>
501
- .recipe-card {
502
- background-color: #f8f9fa;
503
- border-radius: 15px;
504
- padding: 25px;
505
- margin-bottom: 25px;
506
- border-left: 5px solid #ff6b6b;
507
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
508
- }
509
- </style>
510
- """, unsafe_allow_html=True)
511
 
512
- with st.container():
513
- st.markdown('<div class="recipe-card">', unsafe_allow_html=True)
514
-
515
- # Header with recipe ID
516
- col_header1, col_header2, col_header3 = st.columns([2, 1, 1])
517
- with col_header1:
518
- st.subheader(f"🍽️ {recipe['dish_name']}")
519
- st.code(f"ID: {recipe.get('id', 'N/A')}", language=None)
520
-
521
- with col_header2:
522
- if recipe.get('recipe_type'):
523
- st.info(recipe['recipe_type'])
524
-
525
- with col_header3:
526
- if recipe.get('submitted_date'):
527
- st.caption(f"{get_text('submitted_on')}: {recipe['submitted_date'][:10]}")
528
-
529
- # Metadata
530
- metadata_parts = []
531
- if recipe.get('your_name'):
532
- metadata_parts.append(f"**{recipe['your_name']}**")
533
- if recipe.get('district'):
534
- metadata_parts.append(f"{get_text('from_district')} **{recipe['district']}**")
535
-
536
- if metadata_parts:
537
- st.caption(f"{get_text('submitted_by')} " + " • ".join(metadata_parts))
538
-
539
- # Tags
540
- if recipe.get('tags'):
541
- tag_display = " ".join([f"`{tag}`" for tag in recipe['tags']])
542
- st.markdown(tag_display)
543
-
544
- # Content
545
- left_col, right_col = st.columns([1, 1.2])
546
-
547
- with left_col:
548
- if recipe.get('image_filename'):
549
- try:
550
- image_data = load_image_from_memory(recipe['image_filename'])
551
- if image_data:
552
- st.image(image_data, caption=recipe['dish_name'], use_column_width=True)
553
- else:
554
- st.image("https://placehold.co/400x300/E8F4FD/31343C?text=Image+Not+Found")
555
- except:
556
- st.image("https://placehold.co/400x300/E8F4FD/31343C?text=Error+Loading+Image")
557
- else:
558
- st.image("https://placehold.co/400x300/E8F4FD/31343C?text=No+Image+Available")
559
-
560
- with right_col:
561
- if recipe.get('story'):
562
- with st.expander(get_text('story_title')):
563
- st.write(recipe['story'])
564
-
565
- with st.expander(get_text('ingredients_title')):
566
- if recipe.get('ingredients'):
567
- ingredients_list = recipe['ingredients'].split('\n')
568
- for ingredient in ingredients_list:
569
- if ingredient.strip():
570
- st.write(f"• {ingredient.strip()}")
571
- else:
572
- st.write("No ingredients listed.")
573
-
574
- with st.expander(get_text('instructions_title'), expanded=True):
575
- st.write(recipe.get('instructions', 'No instructions provided.'))
576
-
577
- st.markdown('</div>', unsafe_allow_html=True)
578
 
579
- st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
 
581
  # --- Analytics Tab ---
582
  with tab_analytics:
583
- st.header("📊 Recipe Analytics")
584
 
585
- recipes = load_all_recipes_from_memory()
 
 
586
 
587
- if not recipes:
588
- st.info("No data available yet. Submit some recipes to see analytics!")
589
- else:
590
- # Overview metrics
591
- col1, col2, col3, col4 = st.columns(4)
592
-
593
- stats = get_recipe_stats()
594
-
595
- with col1:
596
- st.metric(get_text('total_recipes'), len(recipes))
597
-
598
- with col2:
599
- unique_contributors = len(set(r.get('your_name', 'Anonymous') for r in recipes if r.get('your_name')))
600
- st.metric("Contributors", unique_contributors)
601
-
602
- with col3:
603
- unique_districts = len(set(r.get('district', '') for r in recipes if r.get('district')))
604
- st.metric("Districts Represented", unique_districts)
605
-
606
- with col4:
607
- recipes_with_images = len([r for r in recipes if r.get('image_filename')])
608
- st.metric("Recipes with Photos", recipes_with_images)
609
-
610
- st.markdown("---")
611
-
612
- # Storage information
613
- st.subheader("💾 Memory Storage Information")
614
- col1, col2, col3 = st.columns(3)
615
-
616
- with col1:
617
- st.info(f"**Recipe Data:** {stats['total_recipes']} items")
618
- st.caption("Stored in session state")
619
-
620
- with col2:
621
- st.info(f"**Image Data:** {stats['total_images']} items")
622
- st.caption("Stored as base64 in memory")
623
-
624
- with col3:
625
- st.info(f"**Memory Usage:** {format_storage_size(stats['storage_size'])}")
626
- st.caption("Approximate size in memory")
627
-
628
- st.markdown("---")
629
-
630
- # Charts
631
- col1, col2 = st.columns(2)
632
-
633
- with col1:
634
- st.subheader(get_text('top_districts'))
635
- district_counts = Counter(r.get('district', 'Unknown') for r in recipes if r.get('district'))
636
- if district_counts:
637
- district_df = pd.DataFrame(list(district_counts.items()), columns=['District', 'Count'])
638
- district_df = district_df.sort_values('Count', ascending=True).tail(10)
639
- st.bar_chart(district_df.set_index('District'))
640
- else:
641
- st.info("No district data available")
642
-
643
- with col2:
644
- st.subheader(get_text('popular_types'))
645
- type_counts = Counter(r.get('recipe_type', 'Unknown') for r in recipes)
646
- if type_counts:
647
- type_df = pd.DataFrame(list(type_counts.items()), columns=['Type', 'Count'])
648
- st.bar_chart(type_df.set_index('Type'))
649
 
650
- # Recent activity
651
- st.subheader(get_text('recent_recipes'))
652
- recent_recipes = sorted(recipes,
653
- key=lambda x: x.get('submitted_date', ''),
654
- reverse=True)[:10]
 
 
655
 
656
- for recipe in recent_recipes:
657
- col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
 
 
 
 
 
 
 
 
 
658
  with col1:
659
  st.write(f"**{recipe['dish_name']}**")
 
 
660
  with col2:
661
- st.write(recipe.get('your_name', 'Anonymous'))
 
 
662
  with col3:
663
- st.write(recipe.get('submitted_date', 'Unknown')[:10] if recipe.get('submitted_date') else 'Unknown')
664
- with col4:
665
- st.code(recipe.get('id', 'N/A'), language=None)
666
-
667
- # Export functionality
668
- st.markdown("---")
669
- col1, col2 = st.columns(2)
670
-
671
- with col1:
672
- if st.button(get_text('export_recipes')):
673
- csv_data = export_recipes_to_csv(recipes)
674
- if csv_data is not None:
675
- csv_string = csv_data.to_csv(index=False)
676
- st.download_button(
677
- label="Download CSV",
678
- data=csv_string,
679
- file_name=f"ruchi_chudu_recipes_{datetime.now().strftime('%Y%m%d')}.csv",
680
- mime="text/csv"
681
- )
682
- else:
683
- st.error("No recipes to export")
684
-
685
- with col2:
686
- if st.button(get_text('backup_data')):
687
- try:
688
- # Create a comprehensive JSON backup
689
- backup_data = {
690
- "recipes": recipes,
691
- "images": st.session_state.get('recipe_images', {}),
692
- "exported_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
693
- "total_recipes": len(recipes),
694
- "total_images": len(st.session_state.get('recipe_images', {}))
695
- }
696
-
697
- backup_json = json.dumps(backup_data, ensure_ascii=False, indent=2)
698
-
699
- st.download_button(
700
- label="Download JSON Backup",
701
- data=backup_json,
702
- file_name=f"ruchi_chudu_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
703
- mime="application/json"
704
- )
705
- st.success(get_text('backup_created'))
706
- except Exception as e:
707
- st.error(f"Error creating backup: {e}")
708
-
709
- # --- Management Tab ---
710
- with tab_manage:
711
- st.header(get_text('manage_header'))
712
-
713
- recipes = load_all_recipes_from_memory()
714
-
715
- if not recipes:
716
- st.info("No recipes to manage yet.")
717
- else:
718
- st.write(f"Managing **{len(recipes)}** recipes")
719
-
720
- # Search for specific recipe to manage
721
- search_manage = st.text_input("Search for recipe to manage", placeholder="Enter recipe name or ID...")
722
-
723
- # Filter recipes for management
724
- manage_recipes = recipes
725
- if search_manage:
726
- manage_recipes = [r for r in recipes if
727
- search_manage.lower() in r['dish_name'].lower() or
728
- search_manage.lower() in r.get('id', '').lower()]
729
-
730
- if not manage_recipes and search_manage:
731
- st.warning("No recipes found matching your search.")
732
-
733
- # Display recipes for management
734
- for recipe in manage_recipes[:20]: # Limit to 20 for performance
735
- with st.expander(f"🍽️ {recipe['dish_name']} (ID: {recipe.get('id', 'N/A')})"):
736
- col1, col2, col3 = st.columns([2, 1, 1])
737
-
738
- with col1:
739
- st.write(f"**Submitted by:** {recipe.get('your_name', 'Anonymous')}")
740
- st.write(f"**District:** {recipe.get('district', 'Not specified')}")
741
- st.write(f"**Type:** {recipe.get('recipe_type', 'Not specified')}")
742
- st.write(f"**Submitted:** {recipe.get('submitted_date', 'Unknown')}")
743
-
744
- with col2:
745
- st.write(f"**Storage Location:**")
746
- st.code("Session Memory")
747
-
748
- if recipe.get('image_filename'):
749
- st.write(f"**Image:**")
750
- st.code(f"Memory: {recipe['image_filename']}")
751
-
752
- # Show approximate size
753
- recipe_size = len(json.dumps(recipe, ensure_ascii=False).encode('utf-8'))
754
- st.caption(f"Size: {format_storage_size(recipe_size)}")
755
-
756
- with col3:
757
- # Delete button with confirmation
758
- delete_key = f"delete_{recipe.get('id', 'unknown')}"
759
- confirm_key = f"confirm_delete_{recipe.get('id')}"
760
-
761
- if st.button(f"🗑️ {get_text('delete_recipe')}", key=delete_key):
762
- if st.session_state.get(confirm_key, False):
763
- if delete_recipe_from_memory(recipe.get('id')):
764
- st.success(get_text('recipe_deleted'))
765
- st.rerun()
766
- else:
767
- st.error("Failed to delete recipe.")
768
- else:
769
- st.session_state[confirm_key] = True
770
- st.warning(get_text('confirm_delete'))
771
-
772
- if st.session_state.get(confirm_key, False):
773
- if st.button(f"✅ Confirm Delete", key=f"confirm_{recipe.get('id')}"):
774
- if delete_recipe_from_memory(recipe.get('id')):
775
- st.success(get_text('recipe_deleted'))
776
- # Clear the confirmation state
777
- st.session_state[confirm_key] = False
778
- st.rerun()
779
- else:
780
- st.error("Failed to delete recipe.")
781
-
782
- if len(manage_recipes) > 20:
783
- st.info(f"Showing first 20 recipes. Use search to find specific recipes. Total: {len(manage_recipes)}")
784
-
785
- # Bulk operations
786
- st.markdown("---")
787
- st.subheader("🔧 Memory Management")
788
-
789
- col1, col2, col3 = st.columns(3)
790
-
791
- with col1:
792
- if st.button("📊 Refresh Stats"):
793
- stats = get_recipe_stats()
794
- st.success("Stats refreshed!")
795
- st.write(f"Total recipes: {stats['total_recipes']}")
796
- st.write(f"Total images: {stats['total_images']}")
797
- st.write(f"Memory usage: {format_storage_size(stats['storage_size'])}")
798
-
799
- with col2:
800
- if st.button("🔄 Reload Data"):
801
- st.success("Data reloaded from session state!")
802
- st.rerun()
803
-
804
- with col3:
805
- if st.button("🧹 Clear All Data", type="secondary"):
806
- if st.button("⚠️ Confirm Clear All", type="secondary"):
807
- st.session_state['recipes'] = []
808
- st.session_state['recipe_images'] = {}
809
- st.success("All data cleared!")
810
- st.rerun()
811
-
812
- # Session info
813
- st.markdown("---")
814
- st.subheader("📋 Session Information")
815
-
816
- col1, col2 = st.columns(2)
817
-
818
- with col1:
819
- st.info("**Storage Type:** Session Memory")
820
- st.caption("Data persists only during this browser session")
821
 
822
- st.info("**Data Persistence:** Temporary")
823
- st.caption("Data will be lost when you close the browser or refresh")
824
-
825
- with col2:
826
- st.info("**Session State Keys:**")
827
- session_keys = [key for key in st.session_state.keys() if key in ['recipes', 'recipe_images']]
828
- for key in session_keys:
829
- if key == 'recipes':
830
- st.caption(f"• {key}: {len(st.session_state[key])} items")
831
- elif key == 'recipe_images':
832
- st.caption(f"• {key}: {len(st.session_state[key])} items")
833
-
834
- # --- Footer Information ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
  st.markdown("---")
836
  st.markdown("""
837
- ### 💡 Important Notes for Hugging Face Spaces:
838
-
839
- - **Data Storage**: All recipes and images are stored in memory (session state) only
840
- - **Persistence**: Data will be lost when the browser session ends or the space restarts
841
- - **File System**: Hugging Face Spaces has read-only file system, so we use in-memory storage
842
- - **Images**: Uploaded images are converted to base64 and stored in memory
843
- - **Export**: Use the export/backup features to save your data locally
844
- - **Session Sharing**: Each user has their own independent session and data
845
-
846
- **For Production Use**: Consider integrating with a database service or cloud storage for persistent data storage.
847
- """)
848
-
849
- # --- Debug Information (Optional) ---
850
- if st.sidebar.checkbox("Show Debug Info"):
851
- st.sidebar.markdown("---")
852
- st.sidebar.subheader("🔧 Debug Info")
853
- st.sidebar.write(f"Session recipes: {len(st.session_state.get('recipes', []))}")
854
- st.sidebar.write(f"Session images: {len(st.session_state.get('recipe_images', {}))}")
855
- st.sidebar.write(f"Current language: {st.session_state.get('language', 'en')}")
856
-
857
- # Show session state keys
858
- if st.sidebar.checkbox("Show all session keys"):
859
- st.sidebar.write("Session state keys:")
860
- for key in st.session_state.keys():
861
- st.sidebar.caption(f"• {key}")
 
1
  import streamlit as st
2
  import pandas as pd
3
+ from collections import Counter
4
  import json
5
+ import os
6
  import uuid
7
  from datetime import datetime
 
8
  import base64
9
+ import glob
10
+ import re
11
+ from typing import List, Dict, Tuple
12
+
13
+ # AI/ML imports - lightweight transformers
14
+ try:
15
+ from sentence_transformers import SentenceTransformer
16
+ from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
17
+ import torch
18
+ import numpy as np
19
+ from sklearn.metrics.pairwise import cosine_similarity
20
+ AI_AVAILABLE = True
21
+ except ImportError:
22
+ AI_AVAILABLE = False
23
+ st.warning("⚠️ AI features require additional packages. Install with: pip install sentence-transformers transformers torch scikit-learn")
24
 
25
  # --- Page Configuration ---
26
  st.set_page_config(
 
29
  layout="wide"
30
  )
31
 
32
+ # --- AI Model Management ---
33
+ @st.cache_resource
34
+ def load_ai_models():
35
+ """Load AI models with caching to prevent reloading"""
36
+ if not AI_AVAILABLE:
37
+ return None, None, None
38
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  try:
40
+ # Lightweight sentence transformer for recipe similarity
41
+ similarity_model = SentenceTransformer('all-MiniLM-L6-v2')
42
+
43
+ # Sentiment analysis for recipe stories
44
+ sentiment_analyzer = pipeline(
45
+ "sentiment-analysis",
46
+ model="cardiffnlp/twitter-roberta-base-sentiment-latest",
47
+ return_all_scores=True
48
+ )
49
 
50
+ # Text classification for cuisine type - using a lighter model
51
+ cuisine_classifier = pipeline(
52
+ "text-classification",
53
+ model="cardiffnlp/twitter-roberta-base-sentiment-latest" # Reuse sentiment model
54
+ )
55
+
56
+ return similarity_model, sentiment_analyzer, cuisine_classifier
57
  except Exception as e:
58
+ st.error(f"Error loading AI models: {e}")
59
+ return None, None, None
60
+
61
+ # --- Enhanced AI Functions ---
62
+ def analyze_recipe_sentiment(story: str, sentiment_analyzer) -> Dict:
63
+ """Analyze the emotional tone of recipe stories"""
64
+ if not story or not sentiment_analyzer:
65
+ return {"sentiment": "neutral", "confidence": 0.5, "emotion": "nostalgic"}
66
+
67
+ try:
68
+ results = sentiment_analyzer(story[:500]) # Limit text length
69
+
70
+ # Map sentiment labels to readable format
71
+ sentiment_map = {
72
+ 'LABEL_0': 'negative',
73
+ 'LABEL_1': 'neutral',
74
+ 'LABEL_2': 'positive',
75
+ 'NEGATIVE': 'negative',
76
+ 'NEUTRAL': 'neutral',
77
+ 'POSITIVE': 'positive'
78
+ }
79
+
80
+ top_result = max(results[0], key=lambda x: x['score'])
81
+ sentiment = sentiment_map.get(top_result['label'], 'neutral')
82
+ confidence = top_result['score']
83
+
84
+ # Add emotion context for Telugu recipes
85
+ emotion_keywords = {
86
+ 'joyful': ['celebration', 'festival', 'happy', 'joy', 'smile', 'laugh'],
87
+ 'nostalgic': ['grandmother', 'childhood', 'memory', 'tradition', 'old', 'ammamma'],
88
+ 'proud': ['special', 'unique', 'best', 'famous', 'perfect', 'excellent'],
89
+ 'loving': ['family', 'mother', 'love', 'care', 'warmth', 'together']
90
+ }
91
+
92
+ story_lower = story.lower()
93
+ emotion = 'nostalgic' # default for recipe stories
94
+ for emotion_type, keywords in emotion_keywords.items():
95
+ if any(keyword in story_lower for keyword in keywords):
96
+ emotion = emotion_type
97
+ break
98
+
99
+ return {
100
+ "sentiment": sentiment,
101
+ "confidence": confidence,
102
+ "emotion": emotion
103
+ }
104
+ except Exception as e:
105
+ return {"sentiment": "neutral", "confidence": 0.5, "emotion": "nostalgic"}
106
 
107
+ def extract_smart_tags(recipe_data: Dict, similarity_model) -> List[str]:
108
+ """Extract intelligent tags using NLP and pattern matching"""
109
+ tags = set()
110
+
111
+ # Combine text for analysis
112
+ text_content = f"{recipe_data.get('dish_name', '')} {recipe_data.get('ingredients', '')} {recipe_data.get('instructions', '')} {recipe_data.get('story', '')}"
113
+
114
+ # Traditional keyword-based tagging (enhanced)
115
+ keyword_categories = {
116
+ 'spice_level': {
117
+ 'mild': ['mild', 'less spicy', 'children', 'sweet'],
118
+ 'medium': ['medium', 'moderate', 'balanced'],
119
+ 'hot': ['spicy', 'hot', 'chili', 'pepper', 'karam', 'guntur'],
120
+ 'very_hot': ['very spicy', 'extra hot', 'burning', 'fire']
121
+ },
122
+ 'cooking_time': {
123
+ 'quick': ['quick', 'fast', 'minutes', '15 min', '20 min', 'instant'],
124
+ 'medium': ['30 min', '45 min', '1 hour', 'moderate time'],
125
+ 'slow': ['hours', 'slow cook', 'overnight', 'patience']
126
+ },
127
+ 'meal_type': {
128
+ 'breakfast': ['breakfast', 'morning', 'tiffin', 'idli', 'dosa'],
129
+ 'lunch': ['lunch', 'meal', 'rice', 'curry', 'sambar'],
130
+ 'dinner': ['dinner', 'night', 'heavy'],
131
+ 'snack': ['snack', 'evening', 'tea time', 'biscuit']
132
+ },
133
+ 'health': {
134
+ 'healthy': ['healthy', 'nutritious', 'vitamin', 'protein', 'fiber'],
135
+ 'diabetic_friendly': ['sugar free', 'diabetic', 'low sugar', 'jaggery'],
136
+ 'high_protein': ['protein', 'dal', 'lentils', 'sprouts'],
137
+ 'low_fat': ['oil free', 'steamed', 'boiled', 'grilled']
138
+ },
139
+ 'occasion': {
140
+ 'festival': ['festival', 'celebration', 'ugadi', 'sankranti', 'diwali'],
141
+ 'wedding': ['wedding', 'marriage', 'function', 'ceremony'],
142
+ 'everyday': ['daily', 'regular', 'simple', 'routine'],
143
+ 'special': ['special', 'guest', 'party', 'occasion']
144
+ }
145
+ }
146
+
147
+ text_lower = text_content.lower()
148
+
149
+ for category, subcategories in keyword_categories.items():
150
+ for tag, keywords in subcategories.items():
151
+ if any(keyword in text_lower for keyword in keywords):
152
+ tags.add(tag.replace('_', ' ').title())
153
+
154
+ # Telugu-specific patterns
155
+ telugu_patterns = {
156
+ 'Regional': ['Andhra', 'Telangana', 'Hyderabad', 'Vijayawada', 'Guntur'],
157
+ 'Traditional': ['traditional', 'authentic', 'original', 'sampradayam'],
158
+ 'Modern': ['fusion', 'modern', 'new style', 'innovative'],
159
+ 'Street Food': ['street', 'vendor', 'roadside', 'chat']
160
+ }
161
+
162
+ for tag, patterns in telugu_patterns.items():
163
+ if any(pattern.lower() in text_lower for pattern in patterns):
164
+ tags.add(tag)
165
+
166
+ # Ingredient-based classification
167
+ ingredient_text = recipe_data.get('ingredients', '').lower()
168
+
169
+ # Detect vegetarian/non-vegetarian
170
+ non_veg_ingredients = ['chicken', 'mutton', 'fish', 'egg', 'meat', 'prawn', 'crab']
171
+ if any(ingredient in ingredient_text for ingredient in non_veg_ingredients):
172
+ tags.add('Non-Vegetarian')
173
+ else:
174
+ tags.add('Vegetarian')
175
+
176
+ # Detect main ingredients
177
+ main_ingredients = {
178
+ 'Rice Based': ['rice', 'biryani', 'pulao', 'annam'],
179
+ 'Dal Based': ['dal', 'lentil', 'pappu', 'sambar'],
180
+ 'Vegetable': ['vegetable', 'curry', 'fry', 'kura'],
181
+ 'Sweet': ['sweet', 'sugar', 'jaggery', 'dessert', 'halwa'],
182
+ 'Pickle': ['pickle', 'achar', 'pachadi']
183
+ }
184
+
185
+ for tag, ingredients in main_ingredients.items():
186
+ if any(ingredient in ingredient_text for ingredient in ingredients):
187
+ tags.add(tag)
188
+
189
+ return list(tags)
190
 
191
+ def find_similar_recipes(current_recipe: Dict, all_recipes: List[Dict], similarity_model, top_k=3) -> List[Tuple[Dict, float]]:
192
+ """Find similar recipes using semantic similarity"""
193
+ if not similarity_model or len(all_recipes) < 2:
194
+ return []
195
+
196
  try:
197
+ # Create text representations
198
+ current_text = f"{current_recipe.get('dish_name', '')} {current_recipe.get('ingredients', '')} {current_recipe.get('recipe_type', '')}"
199
+
200
+ recipe_texts = []
201
+ valid_recipes = []
202
+
203
+ for recipe in all_recipes:
204
+ if recipe.get('id') != current_recipe.get('id'): # Exclude current recipe
205
+ recipe_text = f"{recipe.get('dish_name', '')} {recipe.get('ingredients', '')} {recipe.get('recipe_type', '')}"
206
+ recipe_texts.append(recipe_text)
207
+ valid_recipes.append(recipe)
208
+
209
+ if not recipe_texts:
210
+ return []
211
 
212
+ # Generate embeddings
213
+ current_embedding = similarity_model.encode([current_text])
214
+ recipe_embeddings = similarity_model.encode(recipe_texts)
215
+
216
+ # Calculate similarities
217
+ similarities = cosine_similarity(current_embedding, recipe_embeddings)[0]
218
+
219
+ # Get top similar recipes
220
+ similar_indices = np.argsort(similarities)[::-1][:top_k]
221
+
222
+ similar_recipes = []
223
+ for idx in similar_indices:
224
+ if similarities[idx] > 0.3: # Minimum similarity threshold
225
+ similar_recipes.append((valid_recipes[idx], similarities[idx]))
226
+
227
+ return similar_recipes
228
+ except Exception as e:
229
+ st.error(f"Error finding similar recipes: {e}")
230
+ return []
231
+
232
+ def generate_cooking_tips(recipe_data: Dict) -> List[str]:
233
+ """Generate contextual cooking tips based on recipe content"""
234
+ tips = []
235
+
236
+ ingredients = recipe_data.get('ingredients', '').lower()
237
+ instructions = recipe_data.get('instructions', '').lower()
238
+ dish_name = recipe_data.get('dish_name', '').lower()
239
+
240
+ # Ingredient-specific tips
241
+ if 'oil' in ingredients:
242
+ tips.append("🔥 Tip: Heat oil until it shimmers but doesn't smoke for best results")
243
+
244
+ if 'onion' in ingredients:
245
+ tips.append("🧅 Tip: Soak sliced onions in cold water for 10 minutes to reduce tears while cutting")
246
+
247
+ if any(spice in ingredients for spice in ['turmeric', 'chili', 'coriander']):
248
+ tips.append("🌶️ Tip: Dry roast spices lightly before grinding for enhanced flavor")
249
+
250
+ if 'rice' in ingredients:
251
+ tips.append("🍚 Tip: Soak rice for 20-30 minutes before cooking for better texture")
252
+
253
+ # Cooking method tips
254
+ if 'fry' in instructions or 'deep fry' in instructions:
255
+ tips.append("🍳 Tip: Maintain oil temperature at 350°F (175°C) for perfect frying")
256
+
257
+ if 'boil' in instructions:
258
+ tips.append("💧 Tip: Add salt to boiling water for vegetables to retain color and nutrients")
259
+
260
+ # Dish-specific tips
261
+ if 'curry' in dish_name:
262
+ tips.append("🍛 Tip: Let curry rest for 10 minutes after cooking to allow flavors to meld")
263
+
264
+ if 'pickle' in dish_name:
265
+ tips.append("🥒 Tip: Store pickle in airtight containers and use dry spoons to prevent spoilage")
266
+
267
+ # Regional tips
268
+ tips.append("🏠 Traditional Tip: Taste and adjust seasoning as family preferences vary by region")
269
+
270
+ return tips[:3] # Return top 3 tips
271
+
272
+ # --- Data Storage Functions ---
273
+ RECIPES_FOLDER = "recipes"
274
+ IMAGES_FOLDER = "recipe_images"
275
+
276
+ def ensure_folders_exist():
277
+ """Create necessary folders if they don't exist"""
278
+ os.makedirs(RECIPES_FOLDER, exist_ok=True)
279
+ os.makedirs(IMAGES_FOLDER, exist_ok=True)
280
+
281
+ def generate_recipe_id():
282
+ """Generate a unique ID for each recipe"""
283
+ return str(uuid.uuid4())[:8]
284
+
285
+ def save_recipe(recipe_data):
286
+ """Save individual recipe to its own JSON file"""
287
+ try:
288
+ ensure_folders_exist()
289
+ recipe_id = recipe_data['id']
290
+ filename = f"recipe_{recipe_id}.json"
291
+ filepath = os.path.join(RECIPES_FOLDER, filename)
292
 
293
+ with open(filepath, 'w', encoding='utf-8') as f:
294
+ json.dump(recipe_data, f, ensure_ascii=False, indent=2)
295
  return True
296
  except Exception as e:
297
+ st.error(f"Error saving recipe: {e}")
298
  return False
299
 
300
+ def load_all_recipes():
301
+ """Load all recipes from individual JSON files"""
302
+ recipes = []
303
  try:
304
+ ensure_folders_exist()
305
+ recipe_files = glob.glob(os.path.join(RECIPES_FOLDER, "recipe_*.json"))
306
 
307
+ for filepath in recipe_files:
308
+ try:
309
+ with open(filepath, 'r', encoding='utf-8') as f:
310
+ recipe = json.load(f)
311
+ recipes.append(recipe)
312
+ except Exception as e:
313
+ continue
314
 
315
+ recipes.sort(key=lambda x: x.get('submitted_date', ''), reverse=True)
 
316
 
317
+ except Exception as e:
318
+ st.error(f"Error loading recipes: {e}")
319
+
320
+ return recipes
 
 
 
321
 
322
  # --- Language and Text ---
323
  TEXT = {
324
  "en": {
325
  "title": "Ruchi Chudu",
326
+ "subtitle": "AI-Powered Community Cookbook to Preserve Telugu Cuisine",
327
  "language_select": "Choose Language",
328
  "sidebar_header": "Contribute Your Recipe!",
329
  "tab_submit": "📝 Submit Your Recipe",
330
+ "tab_gallery": "🍲 Community Gallery",
331
+ "tab_ai_insights": "🤖 AI Recipe Insights",
332
  "tab_analytics": "📊 Recipe Analytics",
 
333
  "form_header": "Tell us about your dish",
334
  "dish_name": "Dish Name",
335
+ "your_name": "Your Name",
336
  "district": "Your District",
337
  "select_district": "--- Select a District ---",
338
  "recipe_type": "Recipe Type (e.g., Curry, Pickle, Snack)",
 
343
  "submit_button": "Submit Recipe",
344
  "success_message": "Thank you! Your recipe has been submitted successfully.",
345
  "gallery_header": "Explore Recipes from the Community",
346
+ "ai_analysis": "🤖 AI Analysis",
347
+ "similar_recipes": "👥 Similar Recipes",
348
+ "cooking_tips": "💡 Smart Cooking Tips",
349
+ "recipe_sentiment": "😊 Recipe Story Mood",
350
+ "search_placeholder": "Search recipes or ask AI...",
351
+ "ai_search_help": "Try: 'spicy vegetarian curry' or 'quick breakfast recipe'",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  },
353
  "te": {
354
+ "title": "రుచి చూడు",
355
+ "subtitle": "AI శక్తితో మన తెలుగు వంటల సంస్కృతిని కాపాడం",
356
  "language_select": "భాషను ఎంచుకోండి",
357
  "sidebar_header": "మీ వంటకాన్ని పంపండి!",
358
  "tab_submit": "📝 మీ వంటకాన్ని పంపండి",
359
  "tab_gallery": "🍲 కమ్యూనిటీ గ్యాలరీ",
360
+ "tab_ai_insights": "🤖 AI వంటకాల విశ్లేషణ",
361
  "tab_analytics": "📊 వంటకాల విశ్లేషణ",
 
362
  "form_header": "మీ వంటకం గురించి చెప్పండి",
363
  "dish_name": "వంటకం పేరు",
364
  "your_name": "మీ పేరు",
365
+ "district": "మీ జిల్లా",
366
  "select_district": "--- జిల్లాను ఎంచుకోండి ---",
367
  "recipe_type": "వంటకం రకం (ఉదా. కూర, పచ్చడి, స్నాక్)",
368
  "ingredients": "కావలసినవి (ఒకదానికి ఒకటి)",
369
  "instructions": "తయారీ విధానం",
370
  "story": "ఈ వంటతో మీ కథ (మీ జ్ఞాపకాలు, కుటుంబ సంప్రదాయాలు మొదలైనవి)",
371
  "image_upload": "వంటకం ఫోటోను అప్‌లోడ్ చేయండి (ఐచ్ఛికం)",
372
+ "submit_button": "వంటకాన్ని పంపండి",
373
  "success_message": "ధన్యవాదాలు! మీ వంటకం విజయవంతంగా సమర్పించబడింది.",
374
+ "gallery_header": "కమ్యూనిటీ నుంి వంటకాలను అన్వేషించండి",
375
+ "ai_analysis": "🤖 AI విలేషణ",
376
+ "similar_recipes": "👥 సమన వంటకు",
377
+ "cooking_tips": "💡 తెలివైట చిట్కాలు",
378
+ "recipe_sentiment": "😊 వటకం కథ భావం",
379
+ "search_placeholder": "వంటకాలను వెతకడి లేదా AI ని అడగండి...",
380
+ "ai_search_help": "ప్రయత్నించండ���: 'ారంగా వెజిటేరియన్ కూర' లేదా 'తొందర అల్పాహారం'",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  }
382
  }
383
 
 
402
  if 'language' not in st.session_state:
403
  st.session_state['language'] = 'en'
404
  if 'recipes' not in st.session_state:
405
+ st.session_state['recipes'] = load_all_recipes()
406
+ if 'ai_models_loaded' not in st.session_state:
407
+ st.session_state['ai_models_loaded'] = False
408
 
409
  # --- Helper Functions ---
410
  def get_text(key):
411
  """Fetches text from the dictionary based on the selected language."""
412
  return TEXT[st.session_state['language']][key]
413
 
414
+ # --- Load AI Models ---
415
+ similarity_model, sentiment_analyzer, cuisine_classifier = None, None, None
416
+ if AI_AVAILABLE and not st.session_state.get('ai_models_loaded', False):
417
+ with st.spinner("🤖 Loading AI models for the first time... This may take a moment."):
418
+ similarity_model, sentiment_analyzer, cuisine_classifier = load_ai_models()
419
+ if similarity_model:
420
+ st.session_state['ai_models_loaded'] = True
421
+ st.success(" AI models loaded successfully!")
422
+ else:
423
+ similarity_model, sentiment_analyzer, cuisine_classifier = load_ai_models()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
  # --- Sidebar ---
426
  with st.sidebar:
427
  st.title(f"🍲 {get_text('title')}")
428
  st.markdown(get_text('subtitle'))
429
+
430
+ if AI_AVAILABLE and similarity_model:
431
+ st.success("🤖 AI Features: Active")
432
+ else:
433
+ st.warning("🤖 AI Features: Limited")
434
+
435
  st.markdown("---")
436
 
437
  # Language selector
 
444
  st.session_state['language'] = 'te' if lang_choice == 'తెలుగు' else 'en'
445
 
446
  st.info(get_text('sidebar_header'))
 
 
 
 
 
 
 
 
 
447
 
448
  # --- Main App Layout ---
449
  st.title(get_text('title'))
450
 
451
+ # Create tabs including the new AI insights tab
452
+ tab_submit, tab_gallery, tab_ai, tab_analytics = st.tabs([
453
  get_text('tab_submit'),
454
  get_text('tab_gallery'),
455
+ get_text('tab_ai_insights'),
456
+ get_text('tab_analytics')
457
  ])
458
 
459
+ # --- Submission Form Tab (Enhanced with AI) ---
460
  with tab_submit:
461
  st.header(get_text('form_header'))
462
 
 
483
 
484
  if submitted:
485
  if not dish_name:
486
+ st.error("Please enter a dish name before submitting.")
487
  else:
 
488
  recipe_id = generate_recipe_id()
489
 
490
+ # Create recipe data for AI analysis
491
+ recipe_data = {
 
 
 
 
 
 
492
  "id": recipe_id,
493
  "dish_name": dish_name,
494
  "your_name": your_name,
 
497
  "ingredients": ingredients,
498
  "instructions": instructions,
499
  "story": story,
 
 
500
  "submitted_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
501
  }
502
 
503
+ # AI Analysis
504
+ if AI_AVAILABLE and sentiment_analyzer:
505
+ with st.spinner("🤖 AI is analyzing your recipe..."):
506
+ # Sentiment analysis
507
+ sentiment_data = analyze_recipe_sentiment(story, sentiment_analyzer)
508
+
509
+ # Smart tag extraction
510
+ ai_tags = extract_smart_tags(recipe_data, similarity_model)
511
+
512
+ # Add AI analysis to recipe data
513
+ recipe_data.update({
514
+ "ai_tags": ai_tags,
515
+ "sentiment_analysis": sentiment_data,
516
+ "cooking_tips": generate_cooking_tips(recipe_data)
517
+ })
518
+
519
+ # Show AI insights
520
+ st.subheader("🤖 AI Analysis Results")
521
+
522
+ col1, col2 = st.columns(2)
523
+ with col1:
524
+ st.metric("Recipe Mood", sentiment_data['emotion'].title(), f"{sentiment_data['confidence']:.0%} confidence")
525
+ st.write("**AI Tags:**", ", ".join([f"`{tag}`" for tag in ai_tags]))
526
+
527
+ with col2:
528
+ st.write("**Smart Cooking Tips:**")
529
+ for tip in recipe_data['cooking_tips']:
530
+ st.write(f"• {tip}")
531
+ else:
532
+ # Fallback basic tagging
533
+ recipe_data["ai_tags"] = ["Community Recipe"]
534
+ recipe_data["sentiment_analysis"] = {"sentiment": "positive", "emotion": "nostalgic"}
535
+ recipe_data["cooking_tips"] = ["Follow the recipe step by step for best results."]
536
+
537
+ # Save recipe
538
+ if save_recipe(recipe_data):
539
+ st.session_state.recipes.insert(0, recipe_data)
540
  st.success(f"{get_text('success_message')} (ID: {recipe_id})")
541
  st.balloons()
 
 
 
 
 
 
 
542
  else:
543
  st.error("Failed to save recipe. Please try again.")
544
 
545
+ # --- Community Gallery Tab (Enhanced with AI) ---
546
  with tab_gallery:
547
  st.header(get_text('gallery_header'))
548
 
549
+ # AI-powered search
550
+ if AI_AVAILABLE and similarity_model:
551
+ st.subheader("🔍 AI-Powered Recipe Search")
552
+ search_query = st.text_input(
553
+ "Search recipes with natural language:",
554
+ placeholder=get_text('ai_search_help'),
555
+ help="Ask in natural language like 'show me spicy vegetarian curries' or 'quick breakfast ideas'"
556
+ )
557
+
558
+ if search_query and st.session_state.recipes:
559
+ with st.spinner("🤖 AI is searching..."):
560
+ # Use semantic search
561
+ query_embedding = similarity_model.encode([search_query])
562
+ recipe_texts = []
563
+ valid_recipes = []
564
+
565
+ for recipe in st.session_state.recipes:
566
+ recipe_text = f"{recipe.get('dish_name', '')} {recipe.get('ingredients', '')} {recipe.get('recipe_type', '')} {' '.join(recipe.get('ai_tags', []))}"
567
+ recipe_texts.append(recipe_text)
568
+ valid_recipes.append(recipe)
569
+
570
+ if recipe_texts:
571
+ recipe_embeddings = similarity_model.encode(recipe_texts)
572
+ similarities = cosine_similarity(query_embedding, recipe_embeddings)[0]
573
+
574
+ # Get top matches
575
+ top_indices = np.argsort(similarities)[::-1][:10]
576
+ matching_recipes = []
577
+
578
+ for idx in top_indices:
579
+ if similarities[idx] > 0.2: # Minimum similarity
580
+ matching_recipes.append((valid_recipes[idx], similarities[idx]))
581
+
582
+ if matching_recipes:
583
+ st.success(f"🎯 Found {len(matching_recipes)} recipes matching your search")
584
+ display_recipes = [recipe for recipe, _ in matching_recipes]
585
+ else:
586
+ st.info("No recipes found matching your search. Try different keywords.")
587
+ display_recipes = st.session_state.recipes[:5]
588
+ else:
589
+ display_recipes = st.session_state.recipes
590
+ else:
591
+ display_recipes = st.session_state.recipes
592
+ else:
593
+ # Fallback to basic search
594
+ search_term = st.text_input("Search recipes:", placeholder=get_text('search_placeholder'))
595
+ if search_term:
596
+ display_recipes = [r for r in st.session_state.recipes if
597
+ search_term.lower() in r['dish_name'].lower() or
598
+ search_term.lower() in r.get('ingredients', '').lower()]
599
+ else:
600
+ display_recipes = st.session_state.recipes
601
+
602
+ # Display recipes with AI insights
603
+ for recipe in display_recipes[:10]: # Show top 10
604
+ with st.expander(f"🍽️ {recipe['dish_name']} (ID: {recipe.get('id', 'N/A')})"):
605
+
606
+ # Basic recipe info
607
+ col1, col2, col3 = st.columns(3)
608
+ with col1:
609
+ st.write(f"**By:** {recipe.get('your_name', 'Anonymous')}")
610
+ st.write(f"**District:** {recipe.get('district', 'Not specified')}")
611
+ st.write(f"**Type:** {recipe.get('recipe_type', 'Not specified')}")
612
+
613
+ with col2:
614
+ # AI Tags
615
+ if recipe.get('ai_tags'):
616
+ st.write("**AI Tags:**")
617
+ for tag in recipe['ai_tags'][:3]: # Show top 3 tags
618
+ st.badge(tag)
619
+
620
+ # Sentiment analysis
621
+ if recipe.get('sentiment_analysis'):
622
+ sentiment = recipe['sentiment_analysis']
623
+ st.write(f"**Story Mood:** {sentiment['emotion'].title()}")
624
+
625
+ with col3:
626
+ st.write(f"**Submitted:** {recipe.get('submitted_date', 'Unknown')}")
627
+ if recipe.get('cooking_tips'):
628
+ with st.popover("💡 Cooking Tips"):
629
+ for tip in recipe['cooking_tips']:
630
+ st.write(f"• {tip}")
631
+
632
+ # Recipe content
633
+ st.markdown("### Ingredients:")
634
+ st.text(recipe.get('ingredients', 'No ingredients provided'))
635
+
636
+ st.markdown("### Instructions:")
637
+ st.text(recipe.get('instructions', 'No instructions provided'))
638
+
639
+ if recipe.get('story'):
640
+ st.markdown("### Story:")
641
+ st.text(recipe['story'])
642
+
643
+ # AI-powered similar recipes
644
+ if AI_AVAILABLE and similarity_model and len(st.session_state.recipes) > 1:
645
+ similar_recipes = find_similar_recipes(recipe, st.session_state.recipes, similarity_model)
646
+ if similar_recipes:
647
+ st.markdown("### 👥 Similar Recipes:")
648
+ sim_cols = st.columns(len(similar_recipes))
649
+ for idx, (sim_recipe, similarity) in enumerate(similar_recipes):
650
+ with sim_cols[idx]:
651
+ st.write(f"**{sim_recipe['dish_name']}**")
652
+ st.write(f"Similarity: {similarity:.0%}")
653
+ st.caption(f"By {sim_recipe.get('your_name', 'Anonymous')}")
654
+
655
+ # --- AI Insights Tab ---
656
+ with tab_ai:
657
+ st.header("🤖 AI Recipe Insights")
658
+
659
+ if not AI_AVAILABLE or not similarity_model:
660
+ st.warning("AI features are not available. Please install required packages to enable AI insights.")
661
+ st.code("pip install sentence-transformers transformers torch scikit-learn")
662
+ st.stop()
663
+
664
+ if not st.session_state.recipes:
665
+ st.info("No recipes available for analysis. Submit some recipes first!")
666
+ st.stop()
667
+
668
+ # AI Analysis Dashboard
669
+ st.subheader("📊 Community Recipe Analysis")
670
+
671
+ # Sentiment distribution
672
+ sentiments = [recipe.get('sentiment_analysis', {}).get('emotion', 'nostalgic')
673
+ for recipe in st.session_state.recipes]
674
+ sentiment_counts = Counter(sentiments)
675
+
676
+ col1, col2 = st.columns(2)
677
 
678
  with col1:
679
+ st.markdown("**Recipe Story Moods:**")
680
+ for emotion, count in sentiment_counts.most_common():
681
+ percentage = (count / len(st.session_state.recipes)) * 100
682
+ st.write(f"😊 {emotion.title()}: {count} recipes ({percentage:.1f}%)")
 
683
 
684
  with col2:
685
+ # Most common AI tags
686
+ all_tags = []
687
+ for recipe in st.session_state.recipes:
688
+ all_tags.extend(recipe.get('ai_tags', []))
689
+
690
+ if all_tags:
691
+ tag_counts = Counter(all_tags)
692
+ st.markdown("**Most Popular Recipe Types:**")
693
+ for tag, count in tag_counts.most_common(5):
694
+ st.write(f"🏷️ {tag}: {count} recipes")
695
+
696
+ st.markdown("---")
697
+
698
+ # Recipe Recommendation Engine
699
+ st.subheader("🎯 AI Recipe Recommendations")
700
+
701
+ col1, col2 = st.columns(2)
702
+
703
+ with col1:
704
+ preference = st.selectbox(
705
+ "What are you in the mood for?",
706
+ ["Spicy dishes", "Sweet treats", "Quick meals", "Traditional recipes",
707
+ "Healthy options", "Festival specials", "Comfort food"]
708
  )
709
 
710
+ with col2:
711
+ dietary_pref = st.selectbox(
712
+ "Dietary preference:",
713
+ ["Any", "Vegetarian only", "Non-vegetarian", "Healthy options"]
 
 
714
  )
715
 
716
+ if st.button("🤖 Get AI Recommendations"):
717
+ with st.spinner("🤖 AI is finding perfect recipes for you..."):
718
+ # Create recommendation query based on preferences
719
+ query_map = {
720
+ "Spicy dishes": "spicy hot chili pepper karam",
721
+ "Sweet treats": "sweet jaggery sugar dessert halwa",
722
+ "Quick meals": "quick fast instant easy",
723
+ "Traditional recipes": "traditional authentic grandmother ammamma",
724
+ "Healthy options": "healthy nutritious protein vitamin",
725
+ "Festival specials": "festival celebration ugadi sankranti",
726
+ "Comfort food": "comfort home family love"
727
+ }
728
+
729
+ search_query = query_map.get(preference, preference)
730
+
731
+ # Filter by dietary preference
732
+ filtered_recipes = st.session_state.recipes
733
+ if dietary_pref == "Vegetarian only":
734
+ filtered_recipes = [r for r in filtered_recipes if 'Vegetarian' in r.get('ai_tags', [])]
735
+ elif dietary_pref == "Non-vegetarian":
736
+ filtered_recipes = [r for r in filtered_recipes if 'Non-Vegetarian' in r.get('ai_tags', [])]
737
+ elif dietary_pref == "Healthy options":
738
+ filtered_recipes = [r for r in filtered_recipes if any(tag in ['Healthy', 'High Protein', 'Low Fat'] for tag in r.get('ai_tags', []))]
739
+
740
+ if filtered_recipes:
741
+ # Use AI to find best matches
742
+ query_embedding = similarity_model.encode([search_query])
743
+ recipe_texts = []
744
+
745
+ for recipe in filtered_recipes:
746
+ recipe_text = f"{recipe.get('dish_name', '')} {recipe.get('ingredients', '')} {' '.join(recipe.get('ai_tags', []))}"
747
+ recipe_texts.append(recipe_text)
748
+
749
+ if recipe_texts:
750
+ recipe_embeddings = similarity_model.encode(recipe_texts)
751
+ similarities = cosine_similarity(query_embedding, recipe_embeddings)[0]
752
+
753
+ # Get top recommendations
754
+ top_indices = np.argsort(similarities)[::-1][:3]
755
+
756
+ st.success(f"🎯 Here are your personalized recommendations:")
757
+
758
+ for idx in top_indices:
759
+ if similarities[idx] > 0.1: # Minimum relevance
760
+ recipe = filtered_recipes[idx]
761
+ match_score = similarities[idx] * 100
762
+
763
+ with st.container():
764
+ st.markdown(f"### 🍽️ {recipe['dish_name']}")
765
+ col1, col2, col3 = st.columns(3)
766
+
767
+ with col1:
768
+ st.write(f"**Match Score:** {match_score:.0f}%")
769
+ st.write(f"**By:** {recipe.get('your_name', 'Anonymous')}")
770
+
771
+ with col2:
772
+ st.write(f"**Type:** {recipe.get('recipe_type', 'N/A')}")
773
+ st.write(f"**District:** {recipe.get('district', 'N/A')}")
774
+
775
+ with col3:
776
+ if recipe.get('ai_tags'):
777
+ st.write("**Tags:**")
778
+ for tag in recipe['ai_tags'][:2]:
779
+ st.badge(tag)
780
+
781
+ if recipe.get('story'):
782
+ st.write(f"*{recipe['story'][:150]}...*")
783
+
784
+ st.markdown("---")
785
+ else:
786
+ st.info("No recipes found matching your preferences. Try different criteria!")
787
+
788
  st.markdown("---")
789
 
790
+ # Recipe Insights
791
+ st.subheader("🔍 Deep Recipe Analysis")
 
 
 
 
 
 
792
 
793
+ if st.session_state.recipes:
794
+ selected_recipe = st.selectbox(
795
+ "Select a recipe to analyze:",
796
+ options=[f"{r['dish_name']} (by {r.get('your_name', 'Anonymous')})" for r in st.session_state.recipes],
797
+ index=0
798
+ )
 
799
 
800
+ if selected_recipe:
801
+ # Find the selected recipe
802
+ recipe_idx = next(i for i, r in enumerate(st.session_state.recipes)
803
+ if f"{r['dish_name']} (by {r.get('your_name', 'Anonymous')})" == selected_recipe)
804
+ recipe = st.session_state.recipes[recipe_idx]
805
+
806
+ col1, col2 = st.columns(2)
807
+
808
+ with col1:
809
+ st.markdown("**🤖 AI Analysis:**")
 
 
 
 
 
810
 
811
+ # Sentiment analysis details
812
+ if recipe.get('sentiment_analysis'):
813
+ sentiment = recipe['sentiment_analysis']
814
+ st.metric(
815
+ "Story Emotion",
816
+ sentiment['emotion'].title(),
817
+ f"{sentiment['confidence']:.0%} confidence"
818
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
 
820
+ # AI tags
821
+ if recipe.get('ai_tags'):
822
+ st.write("**AI-Detected Categories:**")
823
+ for tag in recipe['ai_tags']:
824
+ st.badge(tag)
825
+
826
+ with col2:
827
+ st.markdown("**💡 Smart Cooking Tips:**")
828
+ if recipe.get('cooking_tips'):
829
+ for tip in recipe['cooking_tips']:
830
+ st.info(tip)
831
+ else:
832
+ # Generate tips on demand
833
+ tips = generate_cooking_tips(recipe)
834
+ for tip in tips:
835
+ st.info(tip)
836
+
837
+ # Similar recipes
838
+ st.markdown("**👥 Similar Recipes:**")
839
+ similar_recipes = find_similar_recipes(recipe, st.session_state.recipes, similarity_model, top_k=5)
840
+
841
+ if similar_recipes:
842
+ for sim_recipe, similarity in similar_recipes:
843
+ with st.expander(f"{sim_recipe['dish_name']} - {similarity:.0%} similar"):
844
+ st.write(f"**By:** {sim_recipe.get('your_name', 'Anonymous')}")
845
+ st.write(f"**Type:** {sim_recipe.get('recipe_type', 'N/A')}")
846
+ if sim_recipe.get('story'):
847
+ st.write(f"**Story:** {sim_recipe['story'][:200]}...")
848
+ else:
849
+ st.info("No similar recipes found in the current database.")
850
 
851
  # --- Analytics Tab ---
852
  with tab_analytics:
853
+ st.header("📊 Recipe Analytics Dashboard")
854
 
855
+ if not st.session_state.recipes:
856
+ st.info("No recipes available for analysis. Submit some recipes first!")
857
+ st.stop()
858
 
859
+ # Basic Statistics
860
+ total_recipes = len(st.session_state.recipes)
861
+ st.metric("Total Recipes", total_recipes)
862
+
863
+ # Create dataframe for analysis
864
+ df_data = []
865
+ for recipe in st.session_state.recipes:
866
+ df_data.append({
867
+ 'dish_name': recipe.get('dish_name', ''),
868
+ 'your_name': recipe.get('your_name', 'Anonymous'),
869
+ 'district': recipe.get('district', 'Unknown'),
870
+ 'recipe_type': recipe.get('recipe_type', 'Other'),
871
+ 'submitted_date': recipe.get('submitted_date', ''),
872
+ 'ai_tags': recipe.get('ai_tags', []),
873
+ 'emotion': recipe.get('sentiment_analysis', {}).get('emotion', 'neutral')
874
+ })
875
+
876
+ df = pd.DataFrame(df_data)
877
+
878
+ # District-wise distribution
879
+ col1, col2 = st.columns(2)
880
+
881
+ with col1:
882
+ st.subheader("📍 Recipes by District")
883
+ district_counts = df['district'].value_counts().head(10)
884
+ if not district_counts.empty:
885
+ st.bar_chart(district_counts)
886
+ else:
887
+ st.info("No district data available")
888
+
889
+ with col2:
890
+ st.subheader("🍽️ Recipe Types Distribution")
891
+ type_counts = df['recipe_type'].value_counts()
892
+ if not type_counts.empty:
893
+ st.bar_chart(type_counts)
894
+ else:
895
+ st.info("No recipe type data available")
896
+
897
+ # Contributors
898
+ st.subheader("👥 Top Contributors")
899
+ contributor_counts = df['your_name'].value_counts().head(5)
900
+ for name, count in contributor_counts.items():
901
+ st.write(f"🏆 {name}: {count} recipe(s)")
902
+
903
+ # Emotional analysis
904
+ if AI_AVAILABLE:
905
+ st.subheader("😊 Recipe Story Emotions")
906
+ emotion_counts = df['emotion'].value_counts()
907
+
908
+ emotion_colors = {
909
+ 'joyful': '#FFD700',
910
+ 'nostalgic': '#DDA0DD',
911
+ 'proud': '#FF6347',
912
+ 'loving': '#FF69B4',
913
+ 'neutral': '#87CEEB'
914
+ }
 
 
 
 
 
 
915
 
916
+ emotion_data = []
917
+ for emotion, count in emotion_counts.items():
918
+ emotion_data.append({
919
+ 'Emotion': emotion.title(),
920
+ 'Count': count,
921
+ 'Percentage': f"{(count/total_recipes)*100:.1f}%"
922
+ })
923
 
924
+ emotion_df = pd.DataFrame(emotion_data)
925
+ st.dataframe(emotion_df, use_container_width=True)
926
+
927
+ # Recent activity
928
+ st.subheader("📅 Recent Recipe Submissions")
929
+ recent_recipes = st.session_state.recipes[:5] # Show 5 most recent
930
+
931
+ for recipe in recent_recipes:
932
+ with st.container():
933
+ col1, col2, col3 = st.columns([3, 2, 1])
934
+
935
  with col1:
936
  st.write(f"**{recipe['dish_name']}**")
937
+ st.caption(f"by {recipe.get('your_name', 'Anonymous')}")
938
+
939
  with col2:
940
+ st.write(f"{recipe.get('recipe_type', 'N/A')}")
941
+ st.caption(f"{recipe.get('district', 'Unknown')}")
942
+
943
  with col3:
944
+ st.write(recipe.get('submitted_date', 'Unknown')[:10]) # Show date only
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
945
 
946
+ st.markdown("---")
947
+
948
+ # Export functionality
949
+ st.subheader("📤 Export Data")
950
+ if st.button("Download Recipe Database as CSV"):
951
+ # Flatten the data for CSV export
952
+ export_data = []
953
+ for recipe in st.session_state.recipes:
954
+ export_data.append({
955
+ 'ID': recipe.get('id', ''),
956
+ 'Dish Name': recipe.get('dish_name', ''),
957
+ 'Contributor': recipe.get('your_name', ''),
958
+ 'District': recipe.get('district', ''),
959
+ 'Recipe Type': recipe.get('recipe_type', ''),
960
+ 'Ingredients': recipe.get('ingredients', ''),
961
+ 'Instructions': recipe.get('instructions', ''),
962
+ 'Story': recipe.get('story', ''),
963
+ 'AI Tags': ', '.join(recipe.get('ai_tags', [])),
964
+ 'Emotion': recipe.get('sentiment_analysis', {}).get('emotion', ''),
965
+ 'Submitted Date': recipe.get('submitted_date', '')
966
+ })
967
+
968
+ export_df = pd.DataFrame(export_data)
969
+ csv_data = export_df.to_csv(index=False)
970
+
971
+ st.download_button(
972
+ label="📥 Download CSV",
973
+ data=csv_data,
974
+ file_name=f"ruchi_chudu_recipes_{datetime.now().strftime('%Y%m%d')}.csv",
975
+ mime="text/csv"
976
+ )
977
+
978
+ # --- Footer ---
979
  st.markdown("---")
980
  st.markdown("""
981
+ <div style='text-align: center; color: #666; padding: 20px;'>
982
+ <p>🍲 రుచి చూడు (Ruchi Chudu) - Preserving Telugu Cuisine Through Community & AI</p>
983
+ <p>Made with ❤️ for Telugu food lovers everywhere</p>
984
+ <p><small>AI Features powered by Hugging Face Transformers | Data stored locally</small></p>
985
+ </div>
986
+ """, unsafe_allow_html=True)